The project I'm working on requires both integers and strings to be received via serial communications. I'm making a PC fan and LED controller with an OLED display and a PC software interface, which will allow the user to, among other things, set the names of the connected fans.
My problem is sending a string along with integers to the Arduino. I have seen numerous examples on how to parse integers from a serial message (which I am currently using and is working well), and tutorials on how to read strings from serial, but none on how to combine the both. Ideally I would like to be able to send the Arduino a serial message like this hypothetical one: 3,255,"Fan 1" The 3 selecting Fan 3, the 255 setting the fan's PWM amount to max and the "Fan 1" string setting the fans' name to be displayed on the screen.
Is it possible to send a mixture of integers and strings (or just 1 string) to the Arduino?
Here's the code I'm working with currently, which allows setting either LED or Fan Control variables. It works great for setting integer variables, but I have no idea on how to add string parsing into it. (A Serial.parseString(); would be great haha)
while (Serial.available() > 0 ) //Incoming serial commands
{
//Set each integer in the serial message to its own variable
int b0 = Serial.parseInt(); //Addressed module (-1=Send Values, 0=LED, 1=Fan Control) //Send a -1 for Fan vars to leave them unchanged
int b1 = Serial.parseInt(); //[LED]Red Value 1 [FAN]Target fan
int b2 = Serial.parseInt(); //[LED]Green Value 1 [FAN]Assigned sensor
int b3 = Serial.parseInt(); //[LED]Blue Value 1 [FAN]PWM Amount (Power)
int b4 = Serial.parseInt(); //[LED]Pattern [FAN]Fan mode (0=Auto, 1=Manual)
int b5 = Serial.parseInt(); //[LED]Pattern Mode [FAN]Sensor polling rate
int b6 = Serial.parseInt(); //[LED]Pattern Speed [FAN]UI Fan Select
int b7 = Serial.parseInt(); //[LED]Red Value 2 [FAN] No use currently
int b8 = Serial.parseInt(); //[LED]Green Value 2 [FAN] No use currently
int b9 = Serial.parseInt(); //[LED]Blue Value 2 [FAN] No use currently
if (Serial.read() == '\n') //If there's a newline character (signals end of msg), set the variables
{
if (b0 == -1) //If the command was to send variables
{
Serial.println("Send variables!");
}
else if (b0 == 0) //If this command was for the LED section (defined by first int in serial msg)
{
led_redVal = b1;
led_greenVal = b2;
led_blueVal = b3;
led_pattern = b4;
led_patMode = b5;
led_speedMod = b6;
}
else if (b0 == 1) //If this command was for the Fan section (defined by first int in serial msg)
{
if (b2!=-1) {fan_sens[b1] = b2;}
if (b3!=-1) {fan_power[b1] = b3;}
if (b4!=-1)
{
if (b4 == 0) {fan_mode = 'A';}
else if (b4 == 1) {fan_mode = 'M';}
}
if (b5!=-1) {pollrate = b5;}
if (b6!=-1) {ui_fanselect = b6;}
}
}
}
How many fans are there and does it control others things too?
Why not just send [3,255,0] // 0 = fan 1, 1 = fan 2 ... N = fan N+1
With this you can just have an array of possible messages to display. Or you could keep it as [3,255,"fan 1"], in which you don't need to have an array of messages, and just print what you got in.
Strtok() or sscanf() can split this up with no problem, Or you can be clever and make your own function.
There are plenty of example sketches that show how to split this up [3,255,0] easily without Strtok or sscanf.
Is it possible to send a mixture of integers and strings (or just 1 string) to the Arduino?
Sure. The data is sent as one string of characters. Decide on a parsing scheme that is easy for you to use, then send your data in that format. Below is simple parsing test code.
//zoomkat 11-12-13 String capture and parsing
//from serial port input (via serial monitor)
//and print result out serial port
//copy test strings and use ctrl/v to paste in
//serial monitor if desired
// * is used as the data string delimiter
// , is used to delimit individual data
String readString; //main captured String
String angle; //data String
String fuel;
String speed1;
String altidude;
int ind1; // , locations
int ind2;
int ind3;
int ind4;
void setup() {
Serial.begin(9600);
Serial.println("serial delimit test 11-12-13"); // so I can keep track of what is loaded
}
void loop() {
//expect a string like 90,low,15.6,125*
//or 130,hi,7.2,389*
if (Serial.available()) {
char c = Serial.read(); //gets one byte from serial buffer
if (c == '*') {
//do stuff
Serial.println();
Serial.print("captured String is : ");
Serial.println(readString); //prints string to serial port out
ind1 = readString.indexOf(','); //finds location of first ,
angle = readString.substring(0, ind1); //captures first data String
ind2 = readString.indexOf(',', ind1+1 ); //finds location of second ,
fuel = readString.substring(ind1+1, ind2+1); //captures second data String
ind3 = readString.indexOf(',', ind2+1 );
speed1 = readString.substring(ind2+1, ind3+1);
ind4 = readString.indexOf(',', ind3+1 );
altidude = readString.substring(ind3+1); //captures remain part of data after last ,
Serial.print("angle = ");
Serial.println(angle);
Serial.print("fuel = ");
Serial.println(fuel);
Serial.print("speed = ");
Serial.println(speed1);
Serial.print("altidude = ");
Serial.println(altidude);
Serial.println();
Serial.println();
readString=""; //clears variable for new input
angle="";
fuel="";
speed1="";
altidude="";
}
else {
readString += c; //makes the string readString
}
}
}
As has been mentioned above I don't see why you can't just send three bytes. The sending of stuff like "Fan 1" is a common beginner thing to do because they want the data to be human-readable, but computers don't care and it just makes life harder.
a very simple way is to simply use a starting Unique Identifier and ending delimiter for example, transmit your serial data like this:
F255x
Where the device is F, its value is 255 and the trailing delimiter is x. This will allow you to have 52 devices to control just using the alphabet's capital and small letters. You could also use any of the other non-numeric characters (e.g. #,$) further expanding your options.
using this method, you can even stack the values in a transmission for example:
Servo test code that uses a command structure that starts with the numeric value, followed by the device identifier, followed by the packet delimiter.
//zoomkat 11-22-12 simple delimited ',' string parse
//from serial port input (via serial monitor)
//and print result out serial port
//multi servos added
// Powering a servo from the arduino usually *DOES NOT WORK*.
String readString;
#include <Servo.h>
Servo myservoa, myservob, myservoc, myservod; // create servo object to control a servo
void setup() {
Serial.begin(9600);
//myservoa.writeMicroseconds(1500); //set initial servo position if desired
myservoa.attach(6); //the pin for the servoa control
myservob.attach(7); //the pin for the servob control
myservoc.attach(8); //the pin for the servoc control
myservod.attach(9); //the pin for the servod control
Serial.println("multi-servo-delimit-test-dual-input-11-22-12"); // so I can keep track of what is loaded
}
void loop() {
//expect single strings like 700a, or 1500c, or 2000d,
//or like 30c, or 90a, or 180d,
//or combined like 30c,180b,70a,120d,
if (Serial.available()) {
char c = Serial.read(); //gets one byte from serial buffer
if (c == ',') {
if (readString.length() >1) {
Serial.println(readString); //prints string to serial port out
int n = readString.toInt(); //convert readString into a number
// auto select appropriate value, copied from someone elses code.
if(n >= 500)
{
Serial.print("writing Microseconds: ");
Serial.println(n);
if(readString.indexOf('a') >0) myservoa.writeMicroseconds(n);
if(readString.indexOf('b') >0) myservob.writeMicroseconds(n);
if(readString.indexOf('c') >0) myservoc.writeMicroseconds(n);
if(readString.indexOf('d') >0) myservod.writeMicroseconds(n);
}
else
{
Serial.print("writing Angle: ");
Serial.println(n);
if(readString.indexOf('a') >0) myservoa.write(n);
if(readString.indexOf('b') >0) myservob.write(n);
if(readString.indexOf('c') >0) myservoc.write(n);
if(readString.indexOf('d') >0) myservod.write(n);
}
readString=""; //clears variable for new input
}
}
else {
readString += c; //makes the string readString
}
}
}
while (Serial.available() > 0 ) //Incoming serial commands
{
//Set each integer in the serial message to its own variable
int b0 = Serial.parseInt(); //Addressed module (-1=Send Values, 0=LED, 1=Fan Control) //Send a -1 for Fan vars to leave them unchanged
int b1 = Serial.parseInt(); //[LED]Red Value 1 [FAN]Target fan
int b2 = Serial.parseInt(); //[LED]Green Value 1 [FAN]Assigned sensor
int b3 = Serial.parseInt(); //[LED]Blue Value 1 [FAN]PWM Amount (Power)
int b4 = Serial.parseInt(); //[LED]Pattern [FAN]Fan mode (0=Auto, 1=Manual)
int b5 = Serial.parseInt(); //[LED]Pattern Mode [FAN]Sensor polling rate
int b6 = Serial.parseInt(); //[LED]Pattern Speed [FAN]UI Fan Select
int b7 = Serial.parseInt(); //[LED]Red Value 2 [FAN] No use currently
int b8 = Serial.parseInt(); //[LED]Green Value 2 [FAN] No use currently
int b9 = Serial.parseInt(); //[LED]Blue Value 2 [FAN] No use currently
if (Serial.read() == '\n') //If there's a newline character (signals end of msg), set the variables
You seem to have assumed that the whole packet is going to have arrived before you start reading. That is fatally flawed assumption.
That readCSV() function seems to be exactly what I'm looking for! I'll give it a go and report back!
To the people saying "why not just send a number or 'f'", the string is a user-defined piece of text to be displayed on the OLED screen. "Fan 1" was just an example, it could be set to "Motherboard Fan" or "Front Bay Fan" or "foobar", it allows the user to set the name of each fan for easy identification on the screen and is not a constant. It isn't used as a way to identify a fan in the serial message, it's what is being printed to the screen.
You seem to have assumed that the whole packet is going to have arrived before you start reading. That is fatally flawed assumption.
The code works fine as it is, the " if (Serial.read() == '\n')" makes sure that the entire serial message has arrived before deciding what to do with it. This code is taken from the built-in serial communication tutorial.
PaulS:
And, if it hasn't? You don't continue where you left off reading. You start over.
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?
liljohn360:
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?
liljohn360:
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean?
Whether it's Windows, Mac, Linux or a whole lot of monkeys typing on the keyboard, they have to obey the laws of physics.
One of those is that at 9600 baud (and you don't say what your baud rate is) each character takes 1/960 of a second (1.04 mS). Your hypothetical message (3,255,"Fan 1") therefore must take 13 * 1.04 mS (13.5 mS) at least, if not longer if the monkeys get tired. So if you read in a very fast loop "just hoping" it will all arrive, you will probably miss most of those.
PaulS:
And, if it hasn't? You don't continue where you left off reading. You start over.
The serial messages are being sent from a Windows program, not a user manually entering it into a console, so it will always be fully formed if that's what you mean? Are you saying there's a better way to do this? If so, how?