serial.read multiple 3-digit values

hey folks,

i am new to programming so bare with me.

I want to control (blink) 2 LEDs through the serial port and a python script running on the computer.

here is the python:

import serial
import time

arduino = serial.Serial('COM9', 9600, timeout=1)
print("initialising...")
print("")
time.sleep(2)


while True:
    arduino.write("a1")
    arduino.write("b0")
    print("LED ON")
    time.sleep(.5) # waits for .5s

    arduino.write("a0")
    arduino.write("b1")
    print("LED OFF")
    time.sleep(.5) # waits for .5s

arduino.close()

and the arduino code

int LEDa =13;
int LEDb =12;
int statea;
int stateb;

void setup () {

  pinMode (LEDa, OUTPUT);
  pinMode (LEDb, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  while (Serial.available() == 0);

  if (Serial.peek() == 'a') {
    Serial.read();
    statea = Serial.parseInt();
    digitalWrite(LEDa,statea);
  }
  if (Serial.peek() == 'b') {
    Serial.read();
    stateb = Serial.parseInt();
    digitalWrite(LEDb,stateb);
  }
}

The problem i have right now is, that i can't read more than one value over serial, so i can only control the state of one LED. As soon as I add the code for the second LED, the first one stops blinking as well.

In addition to the current script i want to adjust the brightness of multiple LEDs over serial.
I thought of doing it like this:

ledA127ledB0ledC255... and so on
would that work with the serial.peek() function?

How can i read multi-digit values over serial?

thank you so much!

i am new to programming so bare with me.

You can take your clothes off by yourself.

    arduino.write("a1")
    arduino.write("b0")

which will result in "a1b0" arriving at the Arduino. Make is easy on yourself on the Arduino end by using a delimiter between the values and start and end of packet markers:

    arduino.write("<a1,")
    arduino.write("b0>")

You can then read and store that data using code like:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(57600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

Where it says "Process the packet", use strtok() to parse the tokens from the stored data ("a1,b0" will yield tokens 'a1" and "b0").

If each token is a single letter and a multiple character value, like "d128" might be, then you can make a copy of the letter, and then change it to a space (" 128"). The resulting string can be passed to atoi() to get a numeric value (128) back.

haha :slight_smile:

thank you so much! i will try this out asap and report back

i added the following code to read out the first token and prints it to the serial monitor.

char* v1;
v1 = strtok(inData, ",");    
Serial.println(v1);

which gives me the 'a100' from following input:

<a100,b200,c300>

how can i skip one delimiter to read out the next value? It always starts at the very beginning

How do I implement the atoi() to get rid of the letter and change the string to a numeric value?

thank you!!

how can i skip one delimiter to read out the next value? It always starts at the very beginning

Where did you learn to use strtok()? Did you miss the part that said "to continue parsing the same string, pass NULL as the first argument"?

This Nick Gammon blog shows different approaches to text input. Be sure to check out the State Machine part:

Nick is a master of simple, common sense and to the point explanations.

Another way of processing incoming data, without blocking, is to set up a "state machine". Effectively this means looking at each byte in the input stream, and handling it depending on the current state.

As an example, say you had this coming into the serial port:

R4500S80G3

Where Rnnn is RPM, Snnnn is speed, and Gnnnn is the gear setting.

The state machine below switches state when it gets a letter "R", "S" or "G". Otherwise it processes incoming digits by multiplying the previous result by 10, and adding in the new one.

When switching states, if first handles the previous state. So for example, after getting R4500 when the "S" arrives, we call the ProcessRPM function, passing it 4500.

This has the advantage of handling long messages without even needing any buffer, thus saving RAM. You can also process message as soon as the state changes, rather than waiting for end-of-line.

But also check out the last sketch on that page:

How to send and receive numbers

To send a number (greater than 0 to 9) from one Arduino to another reliably you need to do a couple of things.

Send some sort of "start number" delimiter (in this example, the "<" character).
"Print" the number, which turns it into a string of ASCII characters.
Send some sort of "end number" delimiter (in this example, the ">" character).

Example sketch, sending 10 random numbers out the serial port:

Note that instead of buffering all the input and only then evaluating it, this way the evaluation is done and ready when the delimiter/end-of-number character is received, and there is no need to use string manipulation and commands at all. Your code starts out on top and stays on top, only 1 char after-the-fact at any time.

These sketches can have other tasks added without any need to weave the new code into the existing code. The new is put in loop() before or after the existing part. The existing code won't block the new code and the new code should not block execution either. You can in practice have multiple programs "run at the same time".

More on that from the Maestro himself:

When you combine this lesson with the ones above, you pretty much have the key to multitasking Arduino without wasting RAM.

Let's look at an analogy. Say you want to cook breakfast. You need to cook:

Coffee - takes 1 minute
Bacon - takes 2 minutes
Eggs - takes 3 minutes

Now a seasoned cook would NOT do this:

Put coffee on. Stare at watch until 1 minute has elapsed. Pour coffee.
Cook bacon. Stare at watch until 2 minutes have elapsed. Serve bacon.
Fry eggs. Stare at watch until 3 minutes have elapsed. Serve eggs.

The flaw in this is that whichever way you do it, something is going to be cooked too early (and get cold).

In computer terminology this is blocking. That is, you don't do anything else until the one task at hand is over.

What you are likely to do is this:

Start frying eggs. Look at watch and note the time.
Glance at watch from time to time. When one minute is up then ...
Start cooking bacon. Look at watch and note the time.
Glance at watch from time to time. When another minute is up then ...
Put coffee on. Look at watch and note the time.
When 3 minutes are up, everything is cooked. Serve it all up.

In computer terminology this is non-blocking. That is, keep doing other things while you wait for time to be up.

thank you so much!
I already got a step closer using following code

  if(started && ended)
  {
    char* val;
    int value;
    val = strtok(inData, ",");  

    while (val != NULL) {
      value = atoi(val);
      Serial.println(value);
      val = strtok (NULL, ",");
    }

the input of <100, 200, 300> yields following output which are already int numbers:

100
200
300

so far so good, I still can't get rid of the letters, nor have every token assigned to a separate variable.
I would like to assign every single value to a specific variable, ie.

int first = 100;
int second = 200;
int third = 300;
etc.

thank you!!

Store them in an array at index 0, 1, 2, etc.

could you explain this a bit more detailed? sorry, but i just searched the web and couldn't find a hint :frowning:

From the Arduino Reference page:
http://arduino.cc/en/Reference/Array

And here is the Example, your IDE has the code under File->Examples->05.Control-?Array
http://arduino.cc/en/Tutorial/Array

If you think of a variable as a cup that holds values, you might think of an array as an ice cube tray.

Instead of multiple ints with different names, an array lets you use 1 name for all, as shown above.

thank you!

I tried to call the array

 while (val != NULL) {
    value = atoi(val);
     val = strtok (NULL, ",");
     char x = inData[2];
     Serial.println(x);

input <100,200,300>
i should get 300 as output, but I get:
0
0
0

Did you declare the array? I don't see your whole code so I have to ask and wonder.

char x = inData[2]; // char can hold -128 to 127. how will you fit 300 into x?

Serial.println( inData[ 2 ] ); // hopefully inData[] is at least an array of variable type int.

You show a lack of knowing many fundamental C code basics. You would benefit by working through examples to get exposure and explanations before trying to write your own sketches.

thank you!

here is my complete code i currently use:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
  Serial.begin(57600);
  // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
      index = 0;
      inData[index] = '\0';
      started = true;
      ended = false;
    }
    else if(inChar == EOP)
    {
      ended = true;
      break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }
 
  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    char* val;
    int value;
    val = strtok(inData, ",");  

    while (val != NULL) {
      value = atoi(val);
      Serial.println(val);
      val = strtok (NULL, ",");
    }
     Serial.println( inData[ 2 ] )
    
    // The end of packet marker arrived. Process the packet

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';

  }
}

I just have a problem in extracting the value from the array. if i print the whole input (variable val in my sketch) i get the correct output, but i just want one value at a time.

I just have a problem in extracting the value from the array

You'll need to be clearer on what the problem is.

if i print the whole input (variable val in my sketch) i get the correct output,

The "whole input" is in inData, not val.

    while (val != NULL) {
      value = atoi(val);
      Serial.println(val);
      val = strtok (NULL, ",");
    }

A couple of things, here. First, it is value, not val, that you should be printing. Second, anonymous output is stupid. Think how much more information is conveyed by

value = 128

than is conveyed by

37

     Serial.println( inData[ 2 ] )

This won't even compile. I can't image what printing the 3rd character from the array is going to tell you.

Maybe you should be reading text chars and use bigger variables to hold and work with the values?

Is this missive directed at the latest code? OP is reading text chars and using ints to hold the result of converting a string to a value.

Fixed.

I just have a problem in extracting the value from the array. if i print the whole input (variable val in my sketch) i get the correct output, but i just want one value at a time.

The below code for servos might be similar in function for what you are wanting to do.

//zoomkat 11-22-12 simple delimited ',' string parse 
//from serial port input (via serial monitor)
//and print result out serial port
//multi servos added 

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
    }
  }
}

thanks for all your help!

after a bit of research i got it to work with one value.

 if(started && ended)
  {
    char* val = strtok(inData, ",");  

    while (val != NULL) {
      int value = atoi(val);
      analogWrite(led, value);
      val = strtok (NULL, ",");
      
    }

this writes the serial input to pwm to drive an led (i know it should be restricted to 0-255,...)

the problem I still have is how to use the tokens individually.
for example:

input:
<50, 60, 200,>

result:
led_one: pwm to 50
led_two: pwm to 60
led_three: pwm to 200
... and so on...

Is there a 'simple' way to use a single token?

thank you!

the problem I still have is how to use the tokens individually.

Identify the LEDs as a, b, and c. Then send the commands like <50a, 60b, 200c,>

But I can't search within char* using the .indexOf function. What other function should be used to search for the characters a,b,c,... and delete them so that the numbers can be converted to int via atoi().