Neopixels with SPI transactions on prop shield working with ST7735/SSD1351 displays

Status
Not open for further replies.

MichaelMeissner

Senior Member+
I'm doing a Halloween project (evolution of my shutter release programs and the uncanny eyes display), and I want to combine several things on the same Teensy 3.2. I can use two teensys (one to do sound, neopixels, & shutter release) and the second to do the uncanny eyes program (this is a program that does two eyes on 128x128 SPI OLED/LCD displays, and does so mostly without the library except for setup).

The hardware is:

I want to run the uncannyEyes program (https://learn.adafruit.com/animated-electronic-eyes-using-teensy-3-1) along with the other things. The program uses the DMA engine to update the eye display, but it does it on its own, and not through the library. In the past, I've had problems using neopixels (using the Adafruit Neopixel library) via the prop shield level shifter and smart displays using SPI transactions.

Does somebody have a program that they can share which uses Adafruit Neopixels (using the Adafruit library, not fastLed) and a smart display to show how to:
  • Wait for the current DMA actions to finish
  • Add the proper SPI begin transaction before doing a neopixel call
  • Set pin 7 high to enable the LED level shifter
  • Call Neopixel play
  • Set pin 7 low
  • Do SPI end transaction so that the display can be used

Now, I do have 3 alternatives, but at some point I would like to do it with one Teensy:
  • Use a second Teensy 3.2 to run the eyes, and do everything else on the first 3.2
  • Use an external level shifter (74AHCT125) and put the neopixels on a separate pin
  • Don't do neopixels :)
 
Last edited:
Adafruit_Neopixel doesn't use the SPI port at all, so there shouldn't be a conflict with SPI-based libraries, nor any need to use SPI transactions during the LED update.

But it will disable interrupts for 30 us per LED. Two of those rings out to add up to about 1 ms. I'm not familiar with Phil's uncanny eyes code, so I can't really say if it sets up any DMA transfers that last more than 1 ms. If it does, that would be the perfect time to let Adafruit_Neopixel hog the CPU to update the LEDs. If uncanny eyes does only much smaller transfers, then going with 2 Teensy boards would probably be the much easier path.

Another alternative would be to use the APA102 "Dotstar" LEDs, and one of the slower libs that drives them with non-SPI pins. With only 32 LEDs, doing them slowly and allowing the rest of the code to steal CPU time as needed should be fine.
 
Adafruit_Neopixel doesn't use the SPI port at all, so there shouldn't be a conflict with SPI-based libraries, nor any need to use SPI transactions during the LED update.

But it will disable interrupts for 30 us per LED. Two of those rings out to add up to about 1 ms. I'm not familiar with Phil's uncanny eyes code, so I can't really say if it sets up any DMA transfers that last more than 1 ms. If it does, that would be the perfect time to let Adafruit_Neopixel hog the CPU to update the LEDs. If uncanny eyes does only much smaller transfers, then going with 2 Teensy boards would probably be the much easier path.
I put the neopixel code after the display is complete, so there is nothing going on. I only have 32 neopixels (2 16-LED rings).

In the function rendering the eye, the code looks like:

Code:
  SPI.beginTransaction(settings);
  // all of the code to draw the frame
  KINETISK_SPI0.SR |= SPI_SR_TCF;         // Clear transfer flag
  while((KINETISK_SPI0.SR & 0xF000) ||    // Wait for SPI FIFO to drain
       !(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
  digitalWrite(eye[e].cs, HIGH);          // Deselect
  SPI.endTransaction();

Note, due to the slowness of the Adafruit OLED displays, I actually have to run the 3.2/3.5/3.6 at 24Khz. Anything higher will glitch the particular OLED displays that I have (and this is with the neopixel code #ifdef'ed out).

I suspect just using a level shifter is the way to go. Unfortunately, space was a little tight, but I think I have room for it.

Another alternative would be to use the APA102 "Dotstar" LEDs, and one of the slower libs that drives them with non-SPI pins. With only 32 LEDs, doing them slowly and allowing the rest of the code to steal CPU time as needed should be fine.

Unfortunately APA102's only come in strips. I am using Neopixel rings (the idea being to have a ring around the eye display).

However, even without the uncannyEyes, if I read a sound clip from the prop shield, it seems to turn off the LED level shifters, even though I explicitly enable the level shifter just before doing the neopixel code and turn it off after doing the neopixels.

This program should demonstrate the problem. Put a few RAW files in the flash memory of the prop shield, and put a neopixel/ws2812 connected to the level shifters of the prop shield, connecting the neopixel data pin to the pin that translates pin 11:

Code:
// Converted from the WavFilePlay from the Teensy release:
// hardware/teensy/avr/libraries/Audio/examples/WavFilePlayer/WavFilePlayer.ino
//
// Simple RAW file player example for the prop shield to use the Analog DAC
// and prop shield amplifier to play mono sounds.
//         http://www.pjrc.com/teensy/gui/?info=AudioOutputAnalog
//
// On the prop shield, pin 6 selects the serial flash memory controller,
// and pin 5 enables the amplifier.
//
// This example code is in the public domain.
// After the sound plays do a round of neopixels

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>

// GUItool: begin automatically generated code
AudioPlaySerialflashRaw  playFlashRaw1;  //xy=149,388
AudioMixer4              mixer1;         //xy=445,386
AudioOutputAnalog        dac1;           //xy=591,379
AudioConnection          patchCord1(playFlashRaw1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, dac1);
// GUItool: end automatically generated code

#define PROP_AMP_ENABLE		 5
#define FLASH_CHIP_SELECT	 6
#define PIN_PROP_LED_ENABLE	 7			// Pin to enable 11/13 as outputs
#define PIN_PROP_LED_DATA	11			// Pin #1 for 5v LEDs (data for APA102/dotstar leds) 
#define PIN_PROP_LED_CLK	13			// Pin #2 for 5v LEDs (clock for APA102/dotstar leds) 

Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, PIN_PROP_LED_DATA, NEO_GRB + NEO_KHZ800);
int cur_color;

void do_ring () {
  int red, green, blue;

  if (cur_color == 0)
    {
      pinMode (PIN_PROP_LED_ENABLE, OUTPUT);
      digitalWrite (PIN_PROP_LED_ENABLE, HIGH);
      strip.begin ();
      strip.show ();				// all pixels off
      cur_color = 0x1;
      digitalWrite (PIN_PROP_LED_ENABLE, LOW);
    }

  red   = (cur_color & 0x4) ? 5 : 0;
  green = (cur_color & 0x2) ? 5 : 0;
  blue  = (cur_color & 0x1) ? 5 : 0;
  Serial.printf ("Neopixel, red = %d, green = %d, blue = %d\n", red, green, blue);

  // Enable LEDs
  digitalWrite (PIN_PROP_LED_ENABLE, HIGH);

  // Set the ring to a given color
  for (int i = 0; i < 16; i++)
    strip.setPixelColor (i, red, green, blue);

  strip.show ();

  Serial.println ("Sleep 5 seconds");
  delay (5000);
  Serial.println ("Done sleep");

  cur_color = (cur_color == 0x4) ? 0x1 : cur_color<<1;

  // Allow the SPI port to be used for something else
  digitalWrite (PIN_PROP_LED_ENABLE, LOW);
}

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

  // wait up to 3 seconds for the Serial device to become available
  long unsigned debug_start = millis ();
  while (!Serial && ((millis () - debug_start) <= 3000))
    ;

  Serial.println ("Start");

  // Enable the amplifier on the prop shield
  pinMode (PROP_AMP_ENABLE, OUTPUT);
  digitalWrite (PROP_AMP_ENABLE, HIGH);

  // Do all color variations of the ring before enabling the sound
  for (int i = 0; i < 3; i++)
    do_ring ();

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(8);

  // Set initial volume
  mixer1.gain(0, 0.5f);

  // Start SerialFlash
  if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
    while (1)
      {
	Serial.println ("Cannot access SPI Flash chip");
	delay (1000);
      }
  }
}

void playFile(const char *filename)
{
  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playFlashRaw1.play(filename);

  // A brief delay for the library read RAW info
  delay(5);

  // Simply wait for the file to finish playing.
  while (playFlashRaw1.isPlaying()) {
    ;
  }

  Serial.println ("Done playing");
}


void loop() {
  char filename[12];
  uint32_t filesize;

  SerialFlash.opendir ();
  while (SerialFlash.readdir (filename, sizeof (filename), filesize))
    {
      const char *dot	= strchr (filename, '.');
      bool raw_p	= (dot && strcmp (dot, ".RAW") == 0);

      Serial.printf ("%s %-12s%7lu bytes\n",
		     (raw_p ? "Playing " : "Skipping"),
		     filename,
		     (unsigned long) filesize);

      if (raw_p)
	{
	  playFile (filename);
	  do_ring ();
	}
    }

  Serial.println ("Sleep for 30 seconds");
  delay (30000);
}
 
Last edited:
Oh, I see, you've got the Neopixel data on pin 11.

Maybe put a SPI.beginTransaction() and SPI.endTransaction() around strip.show(). That will keep the audio library from trying to access those pins. It also locks out the audio for 1 ms of the 2.9 ms audio update period, but that should still be ok.
 
Oh, I see, you've got the Neopixel data on pin 11.
Yes, wanted to use the level shifter in the prop shield.

Maybe put a SPI.beginTransaction() and SPI.endTransaction() around strip.show(). That will keep the audio library from trying to access those pins. It also locks out the audio for 1 ms of the 2.9 ms audio update period, but that should still be ok.

Yes, it is getting the exact sequence for the beginTransaction/endTransaction that I was essentially asking about. When I last was hacking on the code, I couldn't get it to work in the full sketch, where the neopixel stuff is buried done in several layers, but I'll try again in the smaller example.

Sorry for being unclear.

I've tried this quick hack, and I think I just need to incorporate the external level shifter, unless somebody has settings that works:

Code:
// Converted from the WavFilePlay from the Teensy release:
// hardware/teensy/avr/libraries/Audio/examples/WavFilePlayer/WavFilePlayer.ino
//
// Simple RAW file player example for the prop shield to use the Analog DAC
// and prop shield amplifier to play mono sounds.
//         http://www.pjrc.com/teensy/gui/?info=AudioOutputAnalog
//
// On the prop shield, pin 6 selects the serial flash memory controller,
// and pin 5 enables the amplifier.
//
// This example code is in the public domain.
// After the sound plays do a round of neopixels

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Adafruit_NeoPixel.h>

// GUItool: begin automatically generated code
AudioPlaySerialflashRaw  playFlashRaw1;  //xy=149,388
AudioMixer4              mixer1;         //xy=445,386
AudioOutputAnalog        dac1;           //xy=591,379
AudioConnection          patchCord1(playFlashRaw1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, dac1);
// GUItool: end automatically generated code

#define PROP_AMP_ENABLE		 5
#define FLASH_CHIP_SELECT	 6
#define PIN_PROP_LED_ENABLE	 7			// Pin to enable 11/13 as outputs
#define PIN_PROP_LED_DATA	11			// Pin #1 for 5v LEDs (data for APA102/dotstar leds) 
#define PIN_PROP_LED_CLK	13			// Pin #2 for 5v LEDs (clock for APA102/dotstar leds) 

#define PIN_NEOPIXEL		PIN_PROP_LED_DATA	// PIN_PROP_LED_DATA to use prop shield level shifter

Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
int cur_color;
SPISettings neopixel_spi (20000000, MSBFIRST, SPI_MODE0);

void do_ring () {
  int red, green, blue;

  red   = (cur_color & 0x4) ? 5 : 0;
  green = (cur_color & 0x2) ? 5 : 0;
  blue  = (cur_color & 0x1) ? 5 : 0;
  Serial.printf ("Neopixel, red = %d, green = %d, blue = %d\n", red, green, blue);

  // Enable LEDs
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.beginTransaction (neopixel_spi);
      digitalWrite (PIN_PROP_LED_ENABLE, HIGH);
    }

  // Set the ring to a given color
  for (int i = 0; i < 16; i++)
    strip.setPixelColor (i, red, green, blue);

  strip.show ();

  Serial.println ("Sleep 5 seconds");
  delay (5000);
  Serial.println ("Done sleep");

  cur_color = (cur_color == 0x4) ? 0x1 : cur_color<<1;

  // Allow the SPI port to be used for something else
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.endTransaction ();
      digitalWrite (PIN_PROP_LED_ENABLE, LOW);
    }
}

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

  // wait up to 3 seconds for the Serial device to become available
  long unsigned debug_start = millis ();
  while (!Serial && ((millis () - debug_start) <= 3000))
    ;

  Serial.println ("Start");

  // Enable the neopixels
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.begin ();
      pinMode (PIN_PROP_LED_ENABLE, OUTPUT);
      digitalWrite (PIN_PROP_LED_ENABLE, LOW);
    }
  strip.begin ();
  cur_color = 0x1;

      // Enable the amplifier on the prop shield
  pinMode (PROP_AMP_ENABLE, OUTPUT);
  digitalWrite (PROP_AMP_ENABLE, HIGH);

  // Do all color variations of the ring before enabling the sound
  for (int i = 0; i < 3; i++)
    do_ring ();

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory (8);

  // Set initial volume
  mixer1.gain (0, 0.5f);

  // Start SerialFlash
  if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
    while (1)
      {
	Serial.println ("Cannot access SPI Flash chip");
	delay (1000);
      }
  }
}

void playFile(const char *filename)
{
  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playFlashRaw1.play(filename);

  // A brief delay for the library read RAW info
  delay(5);

  // Simply wait for the file to finish playing.
  while (playFlashRaw1.isPlaying()) {
    ;
  }

  Serial.println ("Done playing");
}


void loop() {
  char filename[12];
  uint32_t filesize;

  SerialFlash.opendir ();
  while (SerialFlash.readdir (filename, sizeof (filename), filesize))
    {
      const char *dot	= strchr (filename, '.');
      bool raw_p	= (dot && strcmp (dot, ".RAW") == 0);

      Serial.printf ("%s %-12s%7lu bytes\n",
		     (raw_p ? "Playing " : "Skipping"),
		     filename,
		     (unsigned long) filesize);

      if (raw_p)
	{
	  playFile (filename);
	  do_ring ();
	}
    }

  Serial.println ("Sleep for 30 seconds");
  delay (30000);
}
 
Last edited:
Just throwing out some darts here...
Code:
void do_ring () {
  int red, green, blue;

  red   = (cur_color & 0x4) ? 5 : 0;
  green = (cur_color & 0x2) ? 5 : 0;
  blue  = (cur_color & 0x1) ? 5 : 0;
  Serial.printf ("Neopixel, red = %d, green = %d, blue = %d\n", red, green, blue);

  // Enable LEDs
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.beginTransaction (neopixel_spi);
      digitalWrite (PIN_PROP_LED_ENABLE, HIGH);
    }

  // Set the ring to a given color
  for (int i = 0; i < 16; i++)
    strip.setPixelColor (i, red, green, blue);

  strip.show ();
  Serial.println ("Sleep 5 seconds");
  delay (5000);
  Serial.println ("Done sleep");

  cur_color = (cur_color == 0x4) ? 0x1 : cur_color<<1;

  // Allow the SPI port to be used for something else
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.endTransaction ();
      digitalWrite (PIN_PROP_LED_ENABLE, LOW);
    }
}
But wondering how this would work... If I remember correctly the neopixel code works by using something like digitalWrite...
Pin 11 would still I believe be in SPI mode (mode 2).
What happens if in your code you add: a pinMode(PIN_NEOPIXEL, OUTPUT);
into your if(PIN_NEOPIXEL...) code?

At the end in it's if you probably want to set the pin back into SPI mode: PORT_PCR_MUX(2) Maybe something like:
Code:
	volatile uint32_t *reg;
	reg = portConfigRegister(11);
	*reg =PORT_PCR_MUX(2);
You might also just try: SPI.end(), SPI.begin() and see what that does...
Something like:
Code:
void do_ring () {
  int red, green, blue;

  red   = (cur_color & 0x4) ? 5 : 0;
  green = (cur_color & 0x2) ? 5 : 0;
  blue  = (cur_color & 0x1) ? 5 : 0;
  Serial.printf ("Neopixel, red = %d, green = %d, blue = %d\n", red, green, blue);

  // Enable LEDs
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.end();
      pinMode(PIN_NEOPIXEL, OUTPUT);
      digitalWrite (PIN_PROP_LED_ENABLE, HIGH);
    }

  // Set the ring to a given color
  for (int i = 0; i < 16; i++)
    strip.setPixelColor (i, red, green, blue);

  strip.show ();
  Serial.println ("Sleep 5 seconds");
  delay (5000);
  Serial.println ("Done sleep");

  cur_color = (cur_color == 0x4) ? 0x1 : cur_color<<1;

  // Allow the SPI port to be used for something else
  if (PIN_NEOPIXEL == PIN_PROP_LED_DATA || PIN_NEOPIXEL == PIN_PROP_LED_CLK)
    {
      SPI.begin ();
      digitalWrite (PIN_PROP_LED_ENABLE, LOW);
    }
}

Again not sure if it would work, but maybe worth a try.

Edit: If this works, I don't know how often you use SPI in your code versus neopixel... I might be tempted to cache the state of this PIN (Am I in SPI mode now or Neopixel mode), and have each function that controls this check and switch if necessary...
 
Last edited:
Thanks KurtE. Yeah, I really don't know the details of SPI. I'll try it tonight when I get home from work.

Right now, the code is fairly simple. Essentially loop will call the function to update the eye display, and then it will call the neopixel display, which has a non-blocking timeout, and it checks whether it is time to update the display.

So it will alternate between doing SPI and doing normal digitalWrites to pin 11. I would have to switch back and forth.

The other alternative might be to switch to a neopixel display (FastLed) that uses SPI for the neopixels. When I was last working on it, a year or so ago, you had to use a special version of FastLed, as there was breakage.

I was trying to avoid going to a larger PCB than I was using, as I had some issues with physical space of the PCB. The whole thing is meant to fit in a pair of goggles, and the goggle I was using was fairly small. I have a larger set of goggles, that I will probably use that does give me some more room and I could add the external level shifter.
 
Just throwing out some darts here...
[
But wondering how this would work... If I remember correctly the neopixel code works by using something like digitalWrite...
Pin 11 would still I believe be in SPI mode (mode 2).
What happens if in your code you add: a pinMode(PIN_NEOPIXEL, OUTPUT);
into your if(PIN_NEOPIXEL...) code?

At the end in it's if you probably want to set the pin back into SPI mode: PORT_PCR_MUX(2) Maybe something like:
Code:
	volatile uint32_t *reg;
	reg = portConfigRegister(11);
	*reg =PORT_PCR_MUX(2);

These two changes make it work. Thank you.

You might also just try: SPI.end(), SPI.begin() and see what that does...
Unfortunately that does not work. It seems to stop doing the LEDs after the first interation.
 
In case anybody else is wanting to do the same thing, I've discovered that when I do the full program (using two ST773 screens to do the uncanny eyes, and doing neopixels in between the screen updates), that the first LED would often glitch. If I add a 1ms delay after switching from SPI mode to digital mode before doing the call to the neopixel show function, the glitch goes away.
 
Hi,
A tangent to topic, but this website has some interesting material on Neopixels. He builds an alternate library using SPI, taking you thoroughly through the thinking. On casual inspection, it looks to me like it has the potential to reduce that 1 ms blocking time, expecially using a Teensy and a more recent Arduino version with transactions set faster.

http://www.gammon.com.au/forum/?id=13357

best,
Michael
 
Hi,
A tangent to topic, but this website has some interesting material on Neopixels. He builds an alternate library using SPI, taking you thoroughly through the thinking. On casual inspection, it looks to me like it has the potential to reduce that 1 ms blocking time, expecially using a Teensy and a more recent Arduino version with transactions set faster.

http://www.gammon.com.au/forum/?id=13357

best,
Michael
Note, while Bill's library uses SPI, it does not use SPI transactions which are used in the Teensy. The FastLED and Octows2811 libraries do use both SPI transactions and DMA to be able to output 8 strands in parallel. I think they also don't disable interrupts while the strand is being written.

One note about FastLED and Octows2811, is eventually I want to switch over to using the neopixel rings that have 4 LEDs in them (red, green, blue, and white). At the present time, neither FastLED nor Octows2811 support this variant. If I use the Adafruit libraries, I just have to change a few lines of code, and it is done.

But for me, that is overkill. I'm not doing a video wall that has thousands of LEDs. I'm doing something that just illuminates 2 16-LED rings. At human reaction speed, I really can't notice that the Teensy is updating two TFT screens with the next image, and then stopping doing the TFT display and illuminating 32 neopixels before going back to the display. I was about ready to just add a 74hct125, when I remembered a few other places that fixed things by having a hard delay. It was easy to test, and it worked so I'm happy.

So it works. Yeah, it would be nice to dig in and 'do it right', but at the end of the day practicality wins out. it is good enough for now. Over the years, I have become wary of things that require me to dive deep down and grok everything and fix the annoying bug. I do that in my work setting, but for a hobby that I have limited amount of time to play, I don't need to solve all of the problems.

However, if you want to dig down, and understand how Neopixels work and improve the library, go ahead. I just wish that APA102's came in ring form, so I could get away from the strict timing requirements of the WS2812/neopixel LEDs. But they don't, and I like rings, so I'm stuck with them.
 
Last edited:
Totally understand. That's why I said tangent to topic. Your project is up and running to your satisfaction, and "if it ain't broke..." there are other projects waiting.
I posted the link because it was relevant to the discussion and I thought it was interesting in its own right and worth sharing for those with the curiosity.
BTW, he does disable interrupts in that code. And one of the revelations I found interesting was that there appears to be more flexibility in timing requirements than is generally believed.

best,
Michael W
 
Status
Not open for further replies.
Back
Top