Adafruit_NeoPixel library corrupts readings from Arduino MIDI library

I am working on a project to read MIDI input and set LEDs in an Adafruit LED strip. (Mainly, MIDI Note On message will turn on an LED, and MIDI Note Off message will turn off an LED).

I started with Arduino Uno with USB Host Shield (reading MIDI data from USB) and finished a working demo. Now I am migrating the project to read MIDI data from MIDI port (with optocoupler circuit). The goal is to do away with an Uno-sized shield and go smaller, such as with Arduino Nano or Trinket, if possible.

The MIDI-port version works, but often the wrong LEDs are turned on/off. Sometimes, mashing one piano MIDI note can cause multiple different LEDs to be stuck in the on state. After trying multiple hardware pieces and stripping the code down, I have isolated the problem to be some kind of conflict between the 2 libraries:

Please see this minimal example to illustrate the problem:

#include <Adafruit_NeoPixel.h>
#include <MIDI.h>

#define LED 13    // Arduino Board LED is on Pin 13

#define LED_COUNT 144
#define PIN_LED_STRIP 6

Adafruit_NeoPixel pixels(LED_COUNT, PIN_LED_STRIP, NEO_GRB + NEO_KHZ800);

//Create an instance of the library with default name, serial port and settings
MIDI_CREATE_DEFAULT_INSTANCE(); // this macro will set Serial.begin(31250);

void setup() {
  pinMode (LED, OUTPUT);

  pixels.begin();
  pixels.clear();
  pixels.show();
  
  MIDI.begin(MIDI_CHANNEL_OMNI); // listen to all MIDI channels
  MIDI.turnThruOff();
    
  MIDI.setHandleNoteOn(myHandleNoteOn);
  MIDI.setHandleNoteOff(myHandleNoteOff);
}

void loop() {
  MIDI.read();
  pixels.show(); // This line corrupts the arguments to myHandleNoteOn and myHandleNoteOff
}


void myHandleNoteOn(byte channel, byte pitch, byte velocity) { 
  digitalWrite(LED,HIGH);
  myPrint("NOTE ON", pitch, velocity);
}


void myHandleNoteOff(byte channel, byte pitch, byte velocity) { 
  digitalWrite(LED,LOW);
  myPrint("    NOTE OFF", pitch, velocity);
}

void myPrint(char* message, byte pitch, byte velocity) {
  Serial.print(message);
  Serial.print(": ");
  Serial.print(pitch);
  Serial.println();
}

Calling MIDI.read() triggers the 2 callback functions, which should abstract away handling MIDI Running Status edge cases where a MIDI message omits the status byte. However, calling pixels.show() corrupts the arguments to the 2 callbacks. (If I remove pixels.show(), then the corruption problem goes away.)

Note: Arduino IDE cannot open serial monitor with baud 31250 (used by MIDI library to read MIDI input in Rx pin). However, we can still read the debug statements with a python script that reads from the same serial port, after uploading the program in Arduino IDE.

import serial # pip install pyserial

SERIAL_PORT = 'COM3'

def main():
    ser = serial.Serial(SERIAL_PORT, 31250)              #enable for non-command line argument version
    ser.timeout = 10                                  #Set timeout so readline() cannot block forever 
    ser.setRTS(True)
    ser.setRTS(False)
    ser.close()                                     #be safe, close the port before opening (rids Windows errors...)
    ser.open()                                      #Open the port between arduino serial and python

    print(SERIAL_PORT, 'log:')

    while True:
        d1 = ser.readline();                        #d1 will be a BYTE class
        data = d1.decode('utf-8','ignore')                   #convert BYTE class into str
        print(data)

main()

Here is some sample debug log when I pressed and released notes 36 and 38 simultaneously a few times. This shows the incorrect pitch values are sent to the 2 callbacks.

NOTE ON: 36
NOTE ON: 38
NOTE ON: 38
NOTE ON: 52
NOTE ON: 36
    NOTE OFF: 36
    NOTE OFF: 38
    NOTE OFF: 38
NOTE ON: 36
NOTE ON: 41
NOTE ON: 38
NOTE ON: 64
NOTE ON: 38
    NOTE OFF: 36
    NOTE OFF: 36
    NOTE OFF: 38

In conclusion, I realized Adafruit_NeoPixel::show() causes Arduino MIDI Library to yield incorrect behavior, but I do not understand why it happens, or how to work around it. If anyone has any idea, please let me know.

I expect you're running out of memory. IIRC, the Neopixel library allocates memory for the LED strip at runtime, so you will have rather less than the compiler suggests.

The above program, compiled for Arduino Uno:

Sketch uses 6008 bytes (18%) of program storage space. Maximum is 32256 bytes.
Global variables use 454 bytes (22%) of dynamic memory, leaving 1594 bytes for local variables. Maximum is 2048 bytes.

This looks nowhere close to running out of memory though. Did you mean additional dynamic memory may be allocated in each call to Adafruit_NeoPixel::show()?

Not show, the constructor allocates memory for the strip - three or four bytes for each pixel, so another 576 bytes worst case.

Grab a copy of the FreeMemory function from Adafruit so you can see how much you have left.

Just use numPixels() to see if there was enough memory to allocate for the LED strip. If so, it will return the number of pixels you requested. Otherwise it will return 0.

From Adafruit_NeoPixel.h:

uint16_t numPixels(void) const { return numLEDs; }

In each call to myPrint triggered with each MIDI Note On or Note Off message, I used this freeMemory function, and I also checked with pixels.numPixels(). I get these consistent results every time:

freeMemory: 1149
numPixels: 144

I think this might be exactly the problem, though I didn't find success with FastLED. I quickly tried this FastLED_NeoPixel wrapper library to quickly change the above code to use FastLED under the hood, but the same corrupt results still happen.

Still, I don't understand why this wasn't a problem at all when I used to read MIDI data from a USB connection to a USB Host Shield (instead of reading from Rx pin). Does the USB Host Shield have a bigger buffer or something?

Did you

#define FASTLED_ALLOW_INTERRUPTS 0

and maybe a few other details noted?

Just to be sure.

a7

That line is to disable interrupts (in case you decided to use Teensy or Due, where interrupts are possible), if your interrupts are taking too long that FastLED can't finish flushing to LEDs. I have the opposite problem, where my flush routine takes so long that the interrupts afterwards can't keep up to move data out of serial buffer.

The USB host shield likely had its own internal buffer, so it could save the data received while you were updating the LED strip. At 31250 baud it takes 0.32mS per character, so you do not have much time in which interrupts can be disabled.
Dividing the LED strip up into smaller segments, each driven from a different pin on the nano, might work, depending on how many unused pins you have and how much extra ram the neopixel library will need. If I recall correctly, the hardware serial in the atmega328 has at least a one character buffer, so you could have interrupts disabled for just short of two character times (about 0.64mS), giving enough time to update about 20 LEDs.

I modified the above program to one strip with 20 LEDs, and the problem went away, confirming 20 LEDs seem to be a good size.

In my project, I need to have one physically continuous LED strip, so I cannot cut it into separate strips driven by different pins. Is it possible to use Adafruit_NeoPixel or FastLED to control multiple "virtual" strips (that are really just one physical strip) using the same pin? Example pseudocode:

#define PIN_LED_STRIP 6

// pixels_0 controls LEDs 0-19, pixels_1 controls LEDs 20-39, pixels_2 controls LEDs 40-59
// of one physical strip
Adafruit_NeoPixel pixels_0(20, PIN_LED_STRIP, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels_1(20, PIN_LED_STRIP, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixels_2(20, PIN_LED_STRIP, NEO_GRB + NEO_KHZ800);

void loop() {
  pixels_0.show();
  // interrupts may happen here
  pixels_1.show();
  // interrupts may happen here
  pixels_2.show();
}

You can divide the strip up into segments in the code, but the data has to be sent to the entire strip - there is no way to have the LEDs at the beginning of the strip "ignore" the data going through them and just pass it along to the LEDs farther down the strip.

Explain why that cannot be done.

If it were my project, I would address this issue in one of two ways:

  1. Switch to a SPI-based LED strip such as APA102 (aka DotStar). These don't need interrupts to be disabled as timing is controlled by the SPI Clock rather than the data line timing approach used by NeoPixels.

  2. Switch to a PJRC Teensy Board and use the WS2812Serial Library. This pushes the data out to NeoPixel-type strips over a UART port using DMA. Timing is maintained without disabling interrupts.

Or any ESP8266 and neopixelbus UART method (or even just a bit-banged method, with the bigger FIFO on the RX, you will be able to support more LEDS) Which is similar to Paul Stoffelregen' s method, but using 3.2Mbps and 6N1.

I've never tried that. Unless I need the WiFi capability, I tend to stick with the Teensy family for the extra GPIO, excellent support forum, and an Audio library that's second to none.

A very nice thing about the PJRC WS2812Serial Library is that it includes a LED type to hook into FastLED. So, you only need to change the addLeds() call to use them. All your pixel manipulation code remains unchanged.

If i would use fastLED or if see anybody using that, i would recommend creating a setPixelColor() function that includes error checking on the parameters. Just modifying that function then would do the trick.

The easiest and most flexible way to create a UI, and cheapest.

Meh. I prefer accessing the CRGB and CHSV data structures directly. That's what allows very efficient operations. The only "error checking" required is watching array bounds. Good thing I've been doing this for 30+ years and know what I'm doing.

If needed.

Oh it's not you that would be in danger of making that mistake.

It's not hard to get the pointer to that buffer in Neopixel.h either of course.