Split string to drive NeoPixel Ring - Help needed

I am working on an Intel Edison project but I found out the Edison are not able to give the NeoPixel commands so I am using an Arduino Pro Mini 328 - 3.3V/8 MHz to drive the NeoPixel Ring.The Pro gets the commands true Serial port from the Edison.

I can read the Serial port, but how do I splitt it?

String inputString = ""; 
boolean stringComplete = false;

int GreenLED = 0;   // 0-255
int RedLED = 0;      // 0-255
int BlueLED = 0;     // 0-255
int BlinkOn = 0;      // How long the RGB LED stays on
int BlinkOff = 0;     // How long the RGB LED stays off

void setup() {
  
  // initialize serial:
  Serial.begin(9600);
  // reserve 200 bytes for the inputString:
  inputString.reserve(200);
}

void loop() {

   serialEvent();

   if (stringComplete) {

/*
Split Serial data code here
*/

     inputString = "";
     stringComplete = false;
      }
}

void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

Serial data demo -- $LED,170,50,0,100,5000

$LED = Start of code, not used
170 = Red code 0-255
50 = Green code 0-255
0 = Blue code 0-255
100 = 100ms the RGB light is on
5000 = 5000ms the RGB light is off

   serialEvent();

It is absolutely pointless to define a serialEvent() function and then call it yourself. Just read the damned data, if there is any.

Since you seem to know how much data you are receiving, since you call reserve, it is pointless to piss away resources on the String class. Use a char array, adding a NULL terminator after adding each character, and then you can use strtok() to parse the string.

I don't know if there's another way if you're using Strings. I've never had the luxury of using that memory hogging monstrosity - I usually work with smaller chips than the 328p, so I can't even think about using Strings without running out of memory.

I always do that with C strings and strtok(), and you should to. strtok is weird, read the description a few times. It builds character, and uses less flash and ram.

I tryed using strtok(), but did not get it to work. Got any good webpages that an low skilled person Like me can read?

My current code does work, but still needs a bit of work.

The Serial command from my Edison to my board is R170\nG50\nB0\nH100\nL200
needed to add \n or the Serial.find() did not work. I added millis and not delay to get it to work faster.

R170 = 170 of 255 at the Neopixel Red led
G50 = 50 of 255
B0 = 0 of 255
H100 = 100ms the RGB Neopixel stays ON
L200 = 200ms the RGB Neopixel stays OFF by the command pixels.setPixelColor(3, pixels.Color(0,0,0));

I can easy edit these commands and the RGB Neopixel LED starts with the new command then the loop starts from the top again.

#include <Adafruit_NeoPixel.h>

#define PIN            8
#define NUMPIXELS      4
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int Red = 170;
int Green = 50;
int Blue = 0;


long LEDON = 100;
long LEDOFF = 200;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);
  pixels.begin();
}

void loop() {

  unsigned long currentMillis = millis();
  //Serial.println(currentMillis - previousMillis);

  if (currentMillis - previousMillis < LEDON)
  {
    pixels.setPixelColor(3, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(2, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(1, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(0, pixels.Color(Red,Green,Blue));
    pixels.show();
  }

  if (currentMillis - previousMillis >= LEDON)
  {
    pixels.setPixelColor(3, pixels.Color(0,0,0));
    pixels.setPixelColor(2, pixels.Color(0,0,0));
    pixels.setPixelColor(1, pixels.Color(0,0,0));
    pixels.setPixelColor(0, pixels.Color(0,0,0));
    pixels.show();
  }

  if (currentMillis - previousMillis >= LEDON+LEDOFF)
  {
    previousMillis = currentMillis;
  }

 while (Serial.available())
 {
  if (Serial.find("R"))
  {
    Red = Serial.parseInt();
    if (Red > 255)
    {
      //Serial.println(" Over 255 limit, edit to 255");
      Red = 255;
    }
  }
  else break;
  if (Serial.find("G"))
  {
    Green = Serial.parseInt();
    if (Green > 255)
    {
      //Serial.println(" Over 255 limit, edit to 255");
      Green = 255;
    }
  }
  else break;
  if (Serial.find("B"))
  {
    Blue = Serial.parseInt();
    if (Blue > 255)
    {
      //Serial.println(" Over 255 limit, edit to 255");
      Blue = 255;
    }
  }
  else break;
  if (Serial.find("H"))
  {
    LEDON = Serial.parseInt();
  }
  else break;
  if (Serial.find("L"))
  {
    LEDOFF = Serial.parseInt();
  }
  else break;
 }
}

Kimlorentz:
My current code does work, but still needs a bit of work.

There's a lot to be said for working code.

I learned a new trick today which you might find useful.

Instead of:

if (Blue > 255)
    {
      //Serial.println(" Over 255 limit, edit to 255");
      Blue = 255;
    }

You could use:

  Blue = constrain(Blue, 0, 255);

The constrain command greatly reduced the size of my code.

It seems like you've got things under control. Is there something about your program in particular you want to change?

In compute and 3D shader code constrain is known as ‘clamp’ and is often implemented using ternary operators along the line of this template version.

I post this simply to take the magic and awe out of the ‘constrain’ (on Arduino) macro.

template<typename T>
T constrain(T value, T value_min, T value_max)
{
	return ((value < value_min) ? value_min : ((value > value_max) ? value_max : value));
}

This gives you an idea of how strtok() works. Once you have broken the substrings out, you’ll need to convert them to integer numbers using atoi().

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  char *results[6];
  char *ptr;
  int index = 0;
  int conversionsMade = 0;
  
  char test[] = "$LED,170,50,0,100,5000";
  
  ptr = strtok(test, ",");    // Values of interest separated by commas
  while (ptr) {               // If we find a comma...
    conversionsMade++;        // ...count the conversions made...
    results[index++] = ptr;   // ...save a pointer to the substring...
    ptr = strtok('\0',",");   //...see if there are any more.
  }
  for (index = 0 ; index < conversionsMade; index++) {
    Serial.println(results[index]);
  }
}

void loop() {
}

DuaneDegn

Not a loot I want to change. Only to make it more flexible for more advanced commands at a later point maby. but for now I wil try some of the new hints I got :slight_smile:

I was able to make a working code with using char,strstr,atoi,switch case and pin state.

Everytime digitalpin 6 goes low the switch case goes to Serial.read mode.
When the data from Serial.read is complete the strstr looks for $LED and if not found return to normal mode.
If $LED found the new data will be used for the RGB lights.

Demo code: $LED,0,100,0,100,200

Red = 0 ,Green = 100 ,Blue = 0 ,LedOn = 100ms and LedOFF = 200ms

more advanced functions are coming soon.

#include <string.h>
#include <Adafruit_NeoPixel.h>

#define PIN            8
#define NUMPIXELS      16
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int Red = 170;
int Green = 50;
int Blue = 0;
int LedON = 100;
int LedOFF = 200;
int mode = 0;
int moderunState = 0;

const int moderun = 6;
unsigned long previousMillis = 0;

char inData[80];
char users[80];
byte index = 0;

void setup()
{
  pinMode(moderun, INPUT_PULLUP);
  Serial.begin(115200);
  pixels.begin();
}  // end of setup

void serialdata()
{
  while(Serial.available() > 0)
  {
    char aChar = Serial.read();


    if(aChar == '\n'){
      //Parse here
      char *token = NULL;
      token = inData;

      if (strstr(inData, "$LED"))
      {
        //Serial.print("Found LED data: "); Serial.println(inData);
        char *p = inData;

            p = strchr(p, ',')+1;
            Red = atoi(p);
            Red = constrain(Red, 0, 255);
            p = strchr(p, ',')+1;
            Green = atoi(p);
            Green = constrain(Green, 0, 255);
            p = strchr(p, ',')+1;
            Blue = atoi(p);
            Blue = constrain(Blue, 0, 255);
            p = strchr(p, ',')+1;
            LedON = atoi(p);
            p = strchr(p, ',')+1;
            LedOFF = atoi(p);
            mode = 0;     
      }
      else
      {
        Serial.println("Wrong command");
        mode = 0;
      }
      //End Parse
      index = 0;
      inData[index] = NULL;
    }
    else
    {
      inData[index] = aChar;
      index++;
      inData[index] = '\0'; //Keep NULL Terminated
    }
  }
}

void RGBblink()
{
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis < LedON)
  {
    pixels.setPixelColor(17, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(16, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(15, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(14, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(13, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(12, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(11, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(10, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(9, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(8, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(7, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(6, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(5, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(4, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(3, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(2, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(1, pixels.Color(Red,Green,Blue));
    pixels.setPixelColor(0, pixels.Color(Red,Green,Blue));
    pixels.show();
  }

  if (currentMillis - previousMillis >= LedON)
  {
    pixels.setPixelColor(17, pixels.Color(0,0,0));
    pixels.setPixelColor(16, pixels.Color(0,0,0));
    pixels.setPixelColor(15, pixels.Color(0,0,0));
    pixels.setPixelColor(14, pixels.Color(0,0,0));
    pixels.setPixelColor(13, pixels.Color(0,0,0));
    pixels.setPixelColor(12, pixels.Color(0,0,0));
    pixels.setPixelColor(11, pixels.Color(0,0,0));
    pixels.setPixelColor(10, pixels.Color(0,0,0));
    pixels.setPixelColor(9, pixels.Color(0,0,0));
    pixels.setPixelColor(8, pixels.Color(0,0,0));
    pixels.setPixelColor(7, pixels.Color(0,0,0));
    pixels.setPixelColor(6, pixels.Color(0,0,0)); 
    pixels.setPixelColor(5, pixels.Color(0,0,0));
    pixels.setPixelColor(4, pixels.Color(0,0,0));
    pixels.setPixelColor(3, pixels.Color(0,0,0));
    pixels.setPixelColor(2, pixels.Color(0,0,0));
    pixels.setPixelColor(1, pixels.Color(0,0,0));
    pixels.setPixelColor(0, pixels.Color(0,0,0));
    pixels.show();
  }

  if (currentMillis - previousMillis >= LedON+LedOFF)
  {
    previousMillis = currentMillis;
  }
}

void loop()
{
  moderunState = digitalRead(moderun);
  if (moderunState == LOW)
  {
    mode = 1;
  }
  
  switch (mode)
  {
    case 0:
    RGBblink();
    break;
    case 1:
    serialdata();
    break;
  }
}

$LED,170,50,0,100,5000

Does this string have an end of data marker such as a carriage return or line feed byte (or can you add a marker?)? If so it would be very easy to parse using the String functions.

Edit: below is some String capture and parsing code that might do close what you want to do.

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

Nice, I Will try this later today:) Tnx

Just because I can’t help myself,

using the StaticString library, you could do something like this

etk::StaticString<80> led_str;
led_str = "$LED,170,50,0,100,5000";
  
enum led_codes
{
    RED,
    GREEN,
    BLUE,
    ON,
    OFF
  };
  
  
int codes[5];

if(led_str.compare("$LED,",5))
{
    etk::Tokeniser< etk::StaticString<80> > tok(led_str, ',');
    int token_n = 0;
    etk::StaticString<20> token;
    
    tok.next(token, 20); //the first token is '$LED', so call next() to get that out of the way
    while(tok.next(token, 20) && (token_n < 5))
      codes[token_n++] = token.atoi();
}
//your numbers are now in the codes array. codes[RED] will return red intensity and so on

if you’d rather use C strings, you can. You’ll still need the StaticString library to use the tokeniser though.

enum led_codes
{
	RED,
	GREEN,
	BLUE,
	ON,
	OFF
};
	
char* led_str = "$LED,170,50,0,100,5000";

int codes[5];
if(strncmp(led_str,"$LED,",5) == 0)
{
	etk::Tokeniser<char*> tok(led_str, ',');
	int token_n = 0;
	char token[20];
		
	tok.next(token, 20); //the first token is '$LED', so call next() to get that out of the way
	while(tok.next(token, 20) && (token_n < 5))
		codes[token_n++] = atoi(token);
}