NeoPixel (using Adafruit_NeoPixel) Affects Stepper Motor (using AccelStepper)

I'm working on consolidating stepper motor control to the same board I'm using to blink NeoPixels randomly (colour & timing) but see a BIG problem when the Neos come on they affect the constant speed rotation of the stepper. Looking at the two libraries (Adafruit_NeoPixel.h & AccelStepper.h) I can't see why that would be. The power is supplied by a 12V/600mA wallwart and the board has a 5V/500mA voltage regulator to power the Neos (and the TMC2208 SilentStepstick stepper motor board, a drop-in replacement for A4988).

In the video, you can see the stepper drop in speed at roughly the 6.5 second mark: https://youtu.be/JG9Y_WNESVo

The code for the blinking Neos is from post #6 here: https://forum.arduino.cc/t/im-want-to-make-pixels-on-a-string-blink-independently-and-randomly/497172/5

#define DEBUG_MODE

#include <AccelStepper.h>       // https://github.com/waspinator/AccelStepper
#include <Adafruit_NeoPixel.h>  // https://github.com/adafruit/Adafruit_NeoPixel


// Define pins for stepper motor control, and others
#define DIR_PIN   8   // Req'd for AccelStepper
#define STEP_PIN  12  // Step on rising edge
#define EN_PIN    18  // LOW: Driver enabled. HIGH: Driver disabled
#define MS1_PIN   11  // To set step mode
#define MS2_PIN   10  // To set step mode

// Set up accelStepper intance
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN); 

// Set up stepper speed details 

// For NMB stepper with gear box reduction ratio of 1:41.66666:
//   Motor step angle 15 deg
//   Output shaft step angle 0.36 deg
//   1000 pulse output = 360 deg

float motorRPM  = 660.0;            // default # of steps per second (1/2 step mode) [first power on, before saved to EEPROM]
float maxRPM    = motorRPM*2.0;
float minRPM    = motorRPM/3.0;     // previous: 2.0
float adjFactor = 15.0;

// Set up variables with for ramping up in loop()
bool motorStartup = true;
unsigned long timeStamp1;
unsigned long previousAccel = 0;
int interval = 10;                  // # of milliseconds between speed increases (1/2 step)

bool motorRPMAdj  = false;

int eeAddress = 0;                  // EEPROM address to start reading


// 4) Blinking NEOPixel LEDs
// =========================

/*

    The NeoPixel data pin does ** NOT *** need to be a PWM-enabled pin 
    as there is no realistic way to fade RGB LEDs. Hence, any digital
    pin can be used.

    For optimum randomness between the port & starboard lights the
    DATA_OUT pin from one couild connect to the DATA_IN on the other
    with NEO_COUNT = 20.

*/


#define NEO_PIN 4               // NeoPixel pin
#define NEO_COUNT 10            // Number of NePixels connected in a string (could be 10 or 20)
uint8_t NEO_BRIGHTNESS  = 3;    // NeoPixel brightness
uint32_t MIN_RANDOM_NUM = 250;  // lower random blink time
uint32_t MAX_RANDOM_NUM = 1000; // upper random blink time

// State variables to determine when to start showing NecPixel blinkies
bool waitForAmberLEDStartup = true;
bool showNeoPixelBlinkies   = false;

uint32_t colors[] = {
  0x00990000,   // Darkest red
  0x00CC0000,   // Dark red
  0x00FF0000,   // Red
  0x00FF6666,   // Lt. Red
  0x0000FF00,   // Green
  0x0066FF66,   // Lt. Green
  0x000000FF,   // Blue
  0x0099CCFF,   // Lt. Blue
  0x00FFFFFF,   // White
  0x00FFFF00,   // Yellow
  0x00FFFF99,   // Lt. Yellow
  0x00FF66FF,   // Pink
  0x00FFCCFF    // Lt. Pink 
};

class BlinkyPixel : public Adafruit_NeoPixel {
  public:
    BlinkyPixel(int numLeds, int pin, int type) : (numLeds, pin, type)
    {
      timer = new Timer[numLeds];
    };
    void update(void);
    void init();
  private:
    struct Timer{
      uint32_t nextUpdateMillis;
      bool state;
    };
    Timer* timer;
};

void BlinkyPixel::init()
{
  begin();

  setBrightness(NEO_BRIGHTNESS);


  for (size_t i = 0; i < numPixels(); i++)
  {
    timer[i].state = 0;
    setPixelColor(i, 0, 0, 0);

    timer[i].nextUpdateMillis = millis() + random(MIN_RANDOM_NUM, MAX_RANDOM_NUM);
  }

  show();
}

void BlinkyPixel::update()
{
  for (size_t i = 0; i < numPixels(); i++)
  {
    if (millis() >= timer[i].nextUpdateMillis)
    {
      
      if (timer[i].state)
      {
        setPixelColor(i, 0, 0, 0); // off
      }
      else 
      {
        setPixelColor(i, colors[random(sizeof(colors)/sizeof(uint32_t))]); // random colour
        //setPixelColor(i, random(0xFF), random(0xFF), random(0xFF));
      }
      timer[i].state = !timer[i].state;
      timer[i].nextUpdateMillis = millis() + random(MIN_RANDOM_NUM, MAX_RANDOM_NUM);
    }
    show();
  }
}

BlinkyPixel strip(NEO_COUNT, NEO_PIN, NEO_RGB + NEO_KHZ800);

// ===================================================================================================

void setup()
{
#ifdef DEBUG_MODE
  Serial.begin(9600);
  delay(100);
#endif

#ifdef DEBUG_MODE
  Serial.println("-----------");
  Serial.println("DEBUG_MODE ON");
  Serial.println("-----------");
#endif

//  // Get the saved motor RPM float data from the EEPROM at position 'eeAddress'
//  float f = 0.00f;   // Variable to store data read from EEPROM.
//  EEPROM.get( eeAddress, f );
//
//#ifdef DEBUG_MODE
//  Serial.print("EEPROM f val: ");
//  Serial.println( f, 3 );  // This may print 'ovf, nan' if the data inside the EEPROM is not a valid float.
//#endif
//
//  if (!isnan(f)) {
//    // EEPROM read yields numerical value, so set motorRPM to this if it's within min/max
//    if ((f >= minRPM) && (f <= maxRPM)) {
//      motorRPM = f;
//    }  
//  }

#ifdef DEBUG_MODE
  Serial.print("motorRPM: ");
  Serial.println(motorRPM);  // This may print 'ovf, nan' if the data inside the EEPROM is not a valid float.
#endif

  // Initialize TMC2208 SilentStepstick
  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, HIGH);   // Disable driver in hardware until remaining config is done

  pinMode(MS1_PIN, OUTPUT);
  pinMode(MS2_PIN, OUTPUT);

  // Set up SilentStepStick in 1/2 step mode for this particular stepper motor
  digitalWrite(MS1_PIN, HIGH);
  digitalWrite(MS2_PIN, LOW);

  digitalWrite(EN_PIN, LOW);    // Enable stepper driver 

  stepper.setMaxSpeed(motorRPM);// This is the speed which code in loop() will ramp up to
  stepper.setSpeed(10);         // Set to a really low value in anticipation of ramping up to
                                // motorRPM

  // if analog input pin 1 is unconnected, random analog
  // noise will cause the call to randomSeed() to generate
  // different seed numbers each time the sketch runs.
  // randomSeed() will then shuffle the random function.
  randomSeed(analogRead(A1));

  // Initialize NEOPixel LEDs
  strip.init();
  
} 

// ===================================================================================================

void loop()
{

  // 6) Stepper motor
  // ================

  if ( motorStartup == true ) {
    // Ramp up motor speed to set value, then continue at that speed continuously

#ifdef DEBUG_MODE
    Serial.print("motorStartup: ");
    Serial.println(motorStartup);
    Serial.print("stepper.speed(): ");
    Serial.println(stepper.speed());
    Serial.print("motorRPM: ");
    Serial.println(motorRPM);
#endif

    if ( stepper.speed() < motorRPM ) {
      timeStamp1 = millis();
      if (timeStamp1 > previousAccel + interval) {
        previousAccel = timeStamp1;
        stepper.setSpeed(stepper.speed() + 2);
      }
    } else {
      motorStartup = false;
    }
  }
  stepper.runSpeed();



  unsigned long currentTimeMS = millis();


  // 4) NEOPixel warp engine blinkies
  // ================================
  if ( (currentTimeMS >= (8000)) && (waitForAmberLEDStartup == true) ) {
    waitForAmberLEDStartup  = false; 
    showNeoPixelBlinkies    = true;                
  }

  if ( showNeoPixelBlinkies == true ) {
    strip.update();
  }
  
}

Looks like a case of the straws that broke the camel's back.

@anon85221860 seems so. More googling shows that the NeoPixel library temporarily disables interrupts while outputting data, which may be affecting the stepper. But, this also means I would have to do something else with the rotary encoder (controls stepper motor speed) as that uses interrupts.

The Issue | Using NeoPixels and Servos Together | Adafruit Learning System might be of help.

I'm looking into that but it won't solve the problem with the rotary encoder that uses interrupts, or the I2C communication from a main board that can send commands to this one.

In my experience you need to absolutely minimize calls to show(). Only update the LED strip if there have been changes to the colors that are being displayed, or slow down the color transition timing (ie, change less frequently). If that does not fix it then you may have hit a processing limit.

That's the problem, then - I need to flash the NeoPixels quite quickly to simulate the Christmas tree lights that were used in the 11' Enterprise miniature back in the 60s.

TOS E warp engine lighting effect with 5mm NeoPixel RGB LEDs

They did that without neopixels (or LEDs of any sort) you know.

On the original model built in 1965, that's true. But when the Smithsonian restored the model in 2015/16 they used addressable RGB LEDs to ensure lowest possible heat emission.

Believe me, I'm very well versed in the model and went to great pains to build a model to look as much like the original as possible.

My Polar Lights 1/350 Scale TOS Enterprise model

Can the adafruit neopixel library be used with ATtiny85 ?
Hand-off that operation to it.
(Haven't tried.)

Or another board. But yes, I think it can.

"The limitation here is that all the pixels are of the same color."
Scratch that one.

Use an esp8266 with I2S DMA. No need to disable interrupts.

Directly drive the neopixels without a neopixel library...

If the goal is to have one PCB that can do both stepper motor control using a rotary encoder/interrupts to change the speed AND my NeoPixel quick blinking, could I use a separate chip for one of them? I don't want to add a whole other Arduino board, but could I program a chip like the ATTiny85? For the NeoPixel code, I really need only 1 output pin.

Depending on the number of Neopixels. I have not tried to find the maximum for ATtiny85 without a library, but Uno/Nano is about 620 pixels with a library.

Since the ATiny85 has 1/4 the ram it would be less than about 155, depending on how much the code needed.

Using a library (in the Nano/Uno) takes space from the Neopixel total. The ATtiny maxNeoPix sounds like a fun project... now to find 600+ neopixels not in use.

AH, but if it is a stand-alone ATtiny85, the library can be used (for simplicity of programming). I understand, now.

I only need to run a maximum of 20 NeoPixels, so I think this is the way.

My IDE says, with FastLED, the number of neopixels is 133, but in the simulator, I could only get 62 to work (not out of memory, but not lighting the pixels).

From the IDE with 133 neopixels:

ketch uses 3770 bytes (57%) of program storage space. Maximum is 6586 bytes.
Global variables use 511 bytes (99%) of dynamic memory, leaving 1 bytes for local variables. Maximum is 512 bytes.

This works in the simulator with 62 neopixels.

#include <FastLED.h> // https://github.com/FastLED/FastLED

#define PIN 0   // ATtiny85 Neopixel pin
#define led 62  // Number of Neopixels
#define MAX 255 // Maximum brightness
#define MIN 0   // Minimum brightness

CRGB pix[led];

void setup() {
  FastLED.addLeds<WS2812, PIN, GRB>(pix, led);
  FastLED.clear();
  FastLED.setBrightness(255);
  FastLED.show();
}

void loop() {
  // blink();
  fill();
}

void blink() { // Hello, Blink!
  pix[0] = CRGB::White; FastLED.show(); delay(500);
  pix[0] = CRGB::Black; FastLED.show(); delay(500);
}

void fill() {
  for (int i = 0; i < led; i++) {
    pix[i] = CRGB::White;
  } 
  FastLED.show();
  delay(500);
  FastLED.clear();
  FastLED.show();
  delay(500);
}