3

I am trying to send a lot of serial commands really fast. I basically need to change color of my LED strip many times per second (10 times per second should really be enough). If I do this over WiFi, it will eventually clutter up and make the ESP8266 unresponsive. I need to send the colors as hex, then convert them into usable values (HEX to RGB). Something tells me that's going to cost a lot of resources and the MCU is therefore spending a lot of time just converting the colors into RGB values. Here's a pseudo code example of what I am trying to achieve:

void loop() {
    if(Serial.available()) {
        if(Serial.read().first(8) == "0xABCDEF") {
            //0xABCDEF is basically my indicator saying "this is for individual LEDs"
            //Serial.read() can now be something like 0xABCDEF FF0000 00FF00 0000FF FFFFFF 000000
            char* colors[] = Serial.read().split(" ");
            colorLED(colors);

        }
    }
}

void colorLED(colors) {
    for(i = 1; i < colors.length; i++) {
        strip.SetPixelColor(i, color[i]);
    }
}

I hope this example makes sense. I can send a serial command like 0xABCDEF FF0000 00FF00 0000FF FFFFFF 000000. 5 colors in HEX.

My real question is: How do I take a lot of inputs like above (or another way) and turn them into strings, so I can convert them to RGB? Is there a better way to send commands (like pure RGB values? but remember I need to send many)?. Here is how I convert my colors into RGB:

//hexColor can be "FF0000"
int number = (int)strtol(&hexColor[0], NULL, 16);

int r = number >> 16;
int g = number >> 8 & 0xFF;
int b = number & 0xFF;
2

Simple answer: don't.

Don't even consider strings in any form. Instead you want a state machine.

In pseudocode:

  1. Start with an invalid count number (-1)
  2. Read a character.
  3. If that character is x then set your counter to 0.
  4. Otherwise, if counter is 0 or more, convert the character to a number and place it into the right byte depending on the value of your counter
  5. Increment counter
  6. If counter is 6 then set your LED colours and set the counter to "invalid".
  7. Return to start.

Or, in untested C:

static int8_t counter = -1;
static uint8_t r = 0;
static uint8_t g = 0;
static uint8_t b = 0;

if (Serial.available()) {
    char c = Serial.read();
    if (c == 'x') {
        counter = 0;
    } else {
        if (c > 0x39) c -= 7;
        uint8_t v = c & 0x0f;
        switch (counter) {
            case 0: r = v << 4; break;
            case 1: r |= v;
            case 2: g = v << 4; break;
            case 3: g |= v;
            case 4: b = v << 4; break;
            case 5: b |= v;
        }
        counter++;
        if (counter == 6) {
            counter = -1;
            analogWrite(3, r);
            analogWrite(5, g);
            analogWrite(6, b);
        }
    }
}

The "clever" bit that may confuse you is:

if (c > 0x39) c -= 7;
uint8_t v = c & 0x0f;

That is, if the character is greater than a "9" (character 0x39) then it should be a-f. Subtracting 7 from it converts ASCII HEX from 0123456789ABCDEF to 0123456789:;<=>?. If you then mask it with 0x0F you end up with a number between 0 and 15, or 0-F in hex.

The rest just takes each nibble and places it into the right half of the right colour variable.

  • Damn, I see that is pretty freaking fast! Immediately when I send the command, it's being sent to the serial monitor (even using Serial.println() which I assume is somewhat "slow"). That works pretty damn well! Thanks a lot for the help! – MortenMoulder Mar 30 '17 at 21:03
  • Sorry for hijacking this, but while I have an experienced person here: I use Makuna's NeoPixel library. This requires me to set my LED strip to the RX on the ESP8266. I currently use the NeoEsp8266Dma800KbpsMethod method, which you can read more about here, and this means Serial.read() will be unavailable (as far as I understand). Do you happen to know if there is an alternative? I did try all the other ones, but unfortunately none of them worked. I think I need to hook it up to another pin, so RX/TX is free (at least RX is) – MortenMoulder Mar 30 '17 at 21:29
  • I don't know what is available for the ESP8266. There is probably a software serial implementation, but I don't like them much, they tend to mess with interrupts and block things from happening. Better would be if you could use some other communication protocol, such as I2C, to get the data in. – Majenko Mar 30 '17 at 21:47
  • I probably could, but it all depends on the NeoPixel library I'm using. Can you skim that page really quick and see if I can use any of the other ones, and if it's only a question of specifying the right pin and using another method/bus (and also hook up the LED strip data pin to that pin). – MortenMoulder Mar 30 '17 at 22:32
  • 1
    @EdgarBonet Indeed. Another method, if you do want separate RGB values is to shift the nibbles into a union with a 32-bit integer overlay divided into a 4 byte struct. – Majenko Mar 31 '17 at 10:24
0

Since Majenko is convinced that using binary is far too complicated here's how to do it in binary sending 32 bits per color.
The start of strip marker is any 4 non-zero bytes in a row e.g 0x01010101
Each LED is then sent as four binary bytes: 0x00, 0xRR, 0xGG, 0xBB

In the unlikely event that a byte is lost in transmission then it will re-sync at the start of the next update, depending upon the pattern there is a fair chance that lost data will be noticed and the incorrect colors not get displayed on the strip.

loop() {    
  static color_t color; 
  static int8_t counter = -1;
  static int ledIndex = 0;
  static bool aligned= false;

  if (Serial.available()) {
    if (aligned) {
    switch (counter) {
      case 0: color.r=Serial.read(); break;
      case 1: color.g=Serial.read(); break;
      case 2: color.b=Serial.read(); 
              strip.SetPixelColor(ledIndex, color);
              ledIndex++;
              break;
      case 3: // first byte of next data, should always be 0
              // only not 0 if we've lost a byte or want to restart the strip
              if (Serial.read() != 0) {
                aligned=false;
                counter = 0;
              } else 
                counter = -1;
              break;
      default: break;
    }
    counter++;
  } else { // not aligned
    ledIndex = 0;
    if (Serial.read() != 0) // count non-zeros
      counter++;
    else {                  
      if (counter >= 4)     // had a start marker, next byte is r
        aligned = true;
      counter = 0;          
    }
  }
}

Is it worth doing it in binary? It gives a 50% decrease in RS232 data which if serial data rate is an issue will be a big help. It also gives a decrease in required calculations, if you look at the code there are virtually no mathematical operations beyond increasing counters.
Personally I prefer this method because it just seems more efficient and cleaner to transfer numbers as numbers rather than converting them to text and back but ultimately if you don't need every last bit of performance then it comes down to what you feel most comfortable with.

  • So far what I have counted using a high FPS camera, I can easily track 20+ updates a second using Majenko's code. I think that's very, very good considering I won't ever need to do that. – MortenMoulder Mar 31 '17 at 21:21
  • I would hope so, twenty updates a second isn't that fast unless you have a very long chain of LEDs. If you code it right the RS 232 should be the slowest part of the system and you should easily be able to get 20 FPS on chains of several hundred LEDs. As I indicated, binary gives you that little bit more if you need it, if you don't need it then it's simply an alternative approach. – Andrew Apr 2 '17 at 6:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.