NeoPixel+Serial project: Dropped characters on Serial.read(), random resets

Hi, I'm using an Arduino UNO R3 with a Adafruit NeoPixel LED Side Light Strip (122 LEDs, despite it being advertised as 120). Adafruit NeoPixel LED Side Light Strip - Black 120 LED | The Pi Hut

I've adapted the demo Neopixel strip example and added some code of my own to perform Serial comms with a host computer that I want to control it.

I've followed the Neopixel guidance. I have a 1000uF 16V capacitor to even out the power to the strip, and a 470ohm resistor to the data in on the strip. I'm not running at 100% brightness nor using pure white so I don't try drawing 7.3amps over my USB power lol. It seems to work just fine. My code animates the light strip and there's no apparent problem. Currently I'm using a breadboard for the resistor/capacitor and I could shorten the leads, but it's not the NeoPixels I'm having a problem with.

The problem comes when I try to send Serial data (even at 9600 baud) to my Arduino. Characters are randomly dropping, and the Arduino sometimes appears to reset. I can't figure out where my bug would be as I think I've caught most eventualities (obviously I'm wrong on that). My guess is that my code is leaking or I've done something really dumb.

Here's a typical session from my Serial Monitor. Even though I definitely sent the right commands each time, sometimes a random character is dropped causing the code to think I've mistyped. Not every time, just sometimes. I can't figure out the behaviour. Note - the first command "hello" was sent on purpose to demonstrate it responds to commands it doesn't recognise, but every other time I definitely typed "value ".

INIT
hello
UNKNOWN COMMAND <hello>
valu 0.5
UNKNOWN COMMAND <valu 0.5>
value 
LIGHT VALUE 0.00
value 0
LIGHT VALUE 0.00
vaue 0.5
UNKNOWN COMMAND <vaue 0.5>
value 0.7
LIGHT VALUE 0.70
value 0.5
LIGHT VALUE 0.50
valu 0.5
UNKNOWN COMMAND <valu 0.5>

Sketch below - any suggestions or advice would be warmly welcomed!

#include <Adafruit_NeoPixel.h>

#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN        5
#define NUMPIXELS 122
#define BRIGHTNESS 128

// NEOPIXEL BEST PRACTICES for most reliable operation:
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
//   connect GROUND (-) first, then +, then data.
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
//   a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
// (Skipping these may work OK on your workbench but can fail in the field)

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

String input;

float value = 0.0;
float valueSmooth = 0.0;
float valueDelta = 0.002;

void setup() {
  Serial.begin(9600);
  //if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) // Not needed
  // clock_prescale_set(clock_div_1);
  //endif

  pixels.begin();

  Serial.println(F("INIT"));
  
}


void loop() {

  // Serial input processing
  while (Serial.available()) {
    char c = Serial.read(); // Read a character from the serial input
    Serial.print(c); // Echo it back
    if ((c == '\n') || (c == '\r')) {
      // Complete line received
      processInput(input); // Process the complete line of input
      input = ""; // Clear the input buffer for the next line
    } else {
      // Add character to the input buffer
      if (c != 0)
        input += c;
    }
  }

  // Neopixel lighting processing
  // Smoothly bounce value between 0..1
  value = value + valueDelta;
  if (value >= 1) { 
    value = 1;
    valueDelta = -valueDelta;
  }
  if (value < 0) {
    value = 0;
    valueDelta = -valueDelta;
  }

  // De-zip value
  valueSmooth = (valueSmooth * 0.9) + (value * 0.1);

  // Work out how many neopixels should be lit
  int val = valueSmooth * NUMPIXELS;

  // Create a nice blue..red gradient across all neopixels, only lighting those that are under val (so when val=1, all neopixels are lit)
  for (int i = 0; i < NUMPIXELS; i++) {
    if (i < val) {
      pixels.setPixelColor(i, BRIGHTNESS * ((float)i/(float)NUMPIXELS),0,BRIGHTNESS * (1-((float)i/(float)NUMPIXELS)));
    } else {
      pixels.setPixelColor(i, 0,0,0);
    }
  }

  // Boom
  pixels.show();

}


void processInput(const String& input) {

  // Process the received input - just respond to commands like "value 0.5" for now
  if (input.startsWith(F("value "))) {
    value = input.substring(5).toFloat();
    Serial.print(F("LIGHT VALUE "));
    Serial.println(value);
  } else {
    Serial.print(F("UNKNOWN COMMAND <"));
    Serial.print(input);
    Serial.println(F(">"));
  }
  
}

I did not see mention of a power supply, so verifying: Are you using an external power supply for the Neopixels? You should never use the Arduino as a power supply.

ideas
what does process input do with an empty string? Because with the default setting of the Serial Monitor, you'll get both \n and \r, and the string is nulled after the first one is received and the line is processed.

Other idea - IIRC, Neopixel processing blocks interrupts- is your code busy with Neopixel long enough to miss characters(because Serial reception is interrupt based, and several characters in a row coming in while busy with Neopixel might result in an overrun in the serial buffer). Clue - is the missing character(s) always at the end of a line being sent, such that several characters were received in a row very quickly, and the last one(s) missed? That would indicate to me that there's an issue with neopixel blocking interrupts. If this is the case, you might test it by LOWERING the serial baud rate ( try 2400) for the serial monitor, so less data is sent within any time period. This is not our usually recommended course of action, but may provide some hints. Your application has no great need for high baud rates, anyway, it's operator-limited.

Others might know this topic better than I, I'm just working from what I remember of what I've read regarding Neopixel.

Apologies, I did not specify. I am indeed taking 5V from the Arduino pin, and using a 9v 2A external power brick connected to the barrel jack of the Arduino, although I do note that the NeoPixels are more flakey when run like this versus just drawing on the USB power supply. Im using an iMac which appears to be able to easily power the full brightness of the entire strip without any issues over USB, but I’m aware that isn’t an advised approach.

122 pixels consume (at full white) 122 x 60 mA = 7A; the onboard 5V regulator will not handle that and overheat / shutdown. The onboard 5V regulator is not involved when using USB but there should be a polyfuse on your board that should blow (temporarily) with that current.

Updating 122 pixels takes 122 x 24 x 1.25 microseconds (3.6 milliseconds). During that time interrupts are disabled and incoming serial data can get corrupted. Your best bet is to send one character to the Arduino, let the Arduino echo that back to the sender and only send the next character once the echo is received.

1 Like

So slowing serial to 2400 baud should actually allow serial transmission to not miss characters. Remember, there is a multi-character buffer within the hardware serial implementation of the processor of the Uno(not a reference to the in-software serial buffer), so it's not critical that every character be read before reception of the next is complete, just that that buffer be read out before overflow. What I don't know, and won't pursue right now, is how deep that buffer is, and how well the Arduino implementation handles emptying that buffer in use. That's for someone else to dig into. Dropping to 2400 SHOULD demonstrate that this is the source of the problem, or not, and is free from implications, as the OP's design simply needs commands to be sent from the Serial Monitor, infrequently one would presume.
I've looked at this before, in the same context - the usual serial panacea(set baud to 115200) can worsen the problem, because then entire messages can be missed during a Neopixel update. The best solution is either, as you suggest, to verify each message character(tedious), or to have the Uno request messages, and hold off updates until a message cycle is complete. Sometimes that's feasible, sometimes not, depending upon the application.

As far as I understand it, the 328P has a shift register to shift the data in and a register (UDR) that stores that byte once it is complete. So the maximum would be two bytes.

You forced me to dig deeper! Actually, from figure 20-1 and associated text in the datasheet, the UDR seems to be two characters deep. So, for the purposes of preventing character overrun, the maximum time one might block the serial interrupt for would be close to three character times, so over 30 clock periods at the baud rate. Greater than 3 mS at 9600 baud, if everything works as advertised. But the OP is clearly pushing that, so the missed characters are to be expected, and 2400, or even 4800, should demonstrate better performance without missed characters.
Pity, the OP hasn't checked back to let us know if this is the case.
As I see it, the OP has two problems. Power(lack thereof), and Serial dropouts. Conflating the two issues makes for a difficult-to-solve problem, because fixing either won't completely address the complaint.

Hi @camsysca,

I second your post!

pixels.show() takes between 0.6 and 1.6 msec for its execution. At other places one may find that the show() function disables interrupts and influences even hardware serial:

Source: https://forums.adafruit.com/viewtopic.php?p=668710

And also

https://stackoverflow.com/questions/35596005/adafruit-neopixel-and-serial-communication

Here is a proof that changing the baudrate to 2400 (like you correctly assumed :wink: ) leads to a quite stable communication:

1 Like

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