Adafruit Neopixal library interrupts Serial processing

Hello!

I am working with the Adafruit NeoPixel library, and I just read that the number of LED's used has an influence on the clock of the Arduino, due to the interrupts being used. I am also experiencing problems with the increase in the LED count in my project.

I am currently making a LED controller using an Arduino Nano Every for a WS2813B-RGBW led strip with 300 LED's. I want to control this strip with my phone over a Wi-Fi module (Ai-Thinker ESP8266 WiFi Module ESP-01S), but I am currently still using the Arduino IDE for all the serial communication. (The Wi-Fi module is not yet connected)

The problem that I (think I) am having is that the Serial-in is not processed correctly due to all the interrupts from the NeoPixel library. Is there a way to call those functions as an interrupt when Serial-in is detected? Or prevent an interrupt happening when those functions are called? Or maybe something else?

The code included is the serial receive code, as you can see, I already tried the noInterrupts() function, but this did not gave any improvements. I am also currently reading: Gammon Forum : Electronics : Microprocessors : Interrupts but it is a bit complex for me at the moment.

In advance: thanks!

void loop() {
  //noInterrupts();
  recvWithStartEndMarkers();

  if (newData == true) {
    strcpy(tempChars, receivedChars);
    parseData();
    newData = false;
  }
 // interrupts();

//The_other_stuff();

}

void parseData() {  // split the data into its parts
  Serial.println("Serial Input Registered:");
  char* strtokIndx;  // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, ",");  // get the first part - the string
  float bgOrfx = atof(strtokIndx);      // convert this part to a float

  strtokIndx = strtok(NULL, ",");
  bgOn = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  inFX = *strtokIndx;

  strtokIndx = strtok(NULL, ",");
  float intensity = atof(strtokIndx);  // convert this part to a float

  strtokIndx = strtok(NULL, ",");  // this continues where the previous call left off
  float red = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  float green = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  float blue = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  float white = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  fxSpeed = atof(strtokIndx);

  strtokIndx = strtok(NULL, ",");
  fxSize = atof(strtokIndx);

  stripColors(intensity, red, green, blue, white, bgOrfx);
}

//The code for reading the Serial input. This is not made by me!
void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      } else {
        receivedChars[ndx] = '\0';  // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    } else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

You have it backwards. The problem is that the NeoPixel library disables interrupts while sending data to the LEDs, which prevents the interrupts from the serial hardware from being processed.

Like @david_2018 says, when the show() function is executing, all interrupts are turned off so any incoming serial data can be come lost or corrupted.

Request data from the sender between calls to show(). That means a "handshake" between the 2 where the master (with the LEDs) asks the sender for data.

Updating 300 LEDs take roughly 300 x 24 x 1.25 microseconds equals 9 milliseconds.

At 9600 baud, transmission of one byte take approximately 1 millisecond.

If you bring the baud rate down to 1200 baud it will probably work, else bring it down to 600.

My preferred way, as mentioned by @groundFungus, is a handshake. The sender sends one character and waits for a reply from the Arduino before sending the next character.

Easy to implement in Robin's code; below simply echoes the received character back to the sender.

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();
    Serial.write(rc);
    ...
    ...

Note
Be aware that with a baud rate of 1200 in combination with boards with native USB, opening and closing the serial port with that baud rate will reset the Arduino.

Sorry for the late response, its been a while since I worked on this project

In some quick testing, it looked like changing the baud did not work, for some reason it did not at all then, no matter how many led's I used.

I have a few questions:

  • Why does the while function not block the Adafruit library? I even tried putting the parseData() in a while loop, but this did not change anything
  • I've also been advised to use an ESP instead of an Arduino, since they can handle multi-functions. Does anyone have any experience with that?

Asuming that you are referring to while (Serial.available() > 0 && newData == false), it does actually block as long as the combination of conditions evaluates to true. If you would manage to send an endless stream of characters from your application, you will see that your neopixels will no longer smoothly update. You can test by using another Arduino (whose sole purpose is to stream data as fast as possible) instead of your application.

Some ESPs have multiple cores; you can use one core for the LED strips and one core for the serial communication. I have no experience with ESPs from that perspective.

I'm trying to understand the problem a bit more.

So when the while (Serial.available() > 0 && newData == false), a part of the serial input is then already gone? Since an interrupt did not happen on the Serial port. I've don't seem to have mentioned it before, but the String I am sending is <0,1,0,250,0,250,250,0,50,1>, but it could be that the recvWithStartEndMarkers() function only sees 0,250,250,0,50,1>?

I also use the <Encoder.h> library and have 2 encoder wheels, until now, they do seem unaffected by the amounts of led's I use, so why is this unaffected but the Serial port is affected?

EDIT:
Sending <1,1,2,250,0,0,250,0,5,5> results in the following, using 100 led's

<1,1,2,250,,0,250,0,5,5>
<1,1,2,250,0,0250,0,5,5>
<1,1,2,25,0,0,250,0,5,5
<1,1,250,0,0,250,0,5>
<1,1,2,250,0,0,0,0,5,5>
<1,1,,250,0,0,250,05,5>
<1,1,2,50,0,0,250,0,55>
<1,1,2,25,0,0,250,0,5,5
<11,2,250,0,0,25,0,5,5>
<1,1,2,250,0,0,20,0,5,5>
<1,1,2,2,0,0,250,0,5,
<11,2,250,0,0,2,0,5,5>
<1,1,2,25,0,0,250,0,5,5>

Regarding serial.

A byte is transmitted as bits. Those bits end up in a shift register. Once the shift register is full (8 bits received) it is copied to a data register and a flag is set that a byte is available. The hardware can now receive a new byte in the shift register; once the shift register is full, it again gets copied to the mentioned data register. If the data register was not read before the contents of the shift register is copied, the previous byte is lost.

Up till now this is all hardware. Now the software comes in.

The mentioned flag can trigger an interrupt (which is the case when you use the Arduino core). The core handles the interrupt and in the interrupt service routine the byte on the data register is copied to a free location in a buffer (in RAM); the size depends on the processor, for an Uno it is 64 bytes, Mega might be bigger (can't check now). If that buffer us full, you will also loose data.

So the software needs to (1) read the data register before it is overwritten and it needs to (2) read the buffer in time before it gets full.

(1) is basically outside your control and (2) is handled by a properly written sketch which is what your code is from that perspective.

When interrupts are disabled (by the neopixel library that you use) for too long, you can see that the byte in the data register will not be copied to the buffer and hence the next byte that is received will overwrite the previously received one in the data register.

I hope that this clarifies it.

Thank you for your clarification! This gives me a better idea of what the actual problem is.

I am going to try and implement the handshake method, not using the Neopixel library when the handshake is initiated. But I will do this on another day. The first version of the handshake code is below.

In the meantime, thanks for the help!

char rc;
bool waitfor = true;

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

void loop() {
  while (Serial.available() > 0) {
    //Read initial incomming
    rc = Serial.read();

    //Check if incoming char is 'a' -> will become handshake initiation call
    if (rc == 'a') {

      //Stop Neopixel library

      //Show status update + send message back
      Serial.write("Handshake received");

      //Wait for the rest of the message
      while (waitfor) {
        //Read next Char
        rc = Serial.read();

        //Check if incoming char is 'b' -> will become recvWithStartEndMarkers()
        if (rc == 'b') {
          waitfor = false;
        }

        //TODO Add function that goes back to normal code back to normal after x seconds in case of no follow up
      }
      
      //Show status update
      Serial.write("Data recieved");
      //Reset boolean (and other possible values)
      waitfor = true;
    }
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.