Teensy APA102 POV Poi / Pixel Poi Build. Tutorial

reporting my progress here, since 2015Sept,
this is one of the post I first referenced, to start building my own POV (the other's octows2812 proj)

using teensy 3.6 and features below
SDio & apa102 & FastLED CRGB->DMASPI

reading from SDio ontime, can get a 1,666 FPS with 24 bit BMP (256 color bmp even gives more FPS) on 90'pixel apa102 strips.

Finally, I've complete a POV that can possibly compete with those luxury products :)
thanks to T3.6 and SDIO interface, otherwise I'll stuck at speed.
thanks to Paul and all supporters online~

still moving on for my custom LED strips with 3528, which promisingly will grant much denser POV effects.

edit:add attachments, sorry for the camera settings,
IMG_1749.jpgIMG_1767.jpgIMG_1757.jpgIMG_1758.jpg
 
Last edited:
That's looking very nice.

And yes, the high frame rate is really needed for this kind of thing, so I'm glad you've got it going hard out.
 
@happyinmotion thank you,

in my tests high frame rate above 1000 squeezes and ruins many pictures , overkills
300 is kind lowest limit for POV,
500-800 works fine depends on pictures
have to manually slow down
i got my code so that ,all pic FPS are individually set and reachable for all users.
 
@Po Ting, this is great, finally displaying decent image rate from SD card. Really good work. Are you using delay() to throttle back the refresh rate to remove the squashed lines?

Looks like I have to build a new set of Poi now!
 
the theory refresh rate with 16Mhz SPI, 90LED of apa102 is approx 5000 FPS,
while my approach with DMASPI reaches 1800 at maximum ,the speed still locks to SDio,
so if i'm making more pixels, like 160HD, still needs some more speed

I have my POV functions with FastLED working random CRGB palettes while SD card's absent or failure.
tested with max and works, though its all white in human sight. so I got them set on 550FPS and its ok.

@mortonkopf : I guess apa102 just dont flicker when communicating, "squashed lines?"
I guess only exist in ws2812, and btw, not in the same I2C protocol SK6812 strips?
and since the speed peek is limited to SDIO, the DMASPI clock could set at lower rate, maybe 8~4Mhz is enough, not sure if the clock effects the "squashed lines?"

here's my function that determines FPS : no
if (FPSchecker) {
while (micros() < dur3) {
//
delayMicroseconds(1);
}
dur2 = micros();
//FastLED.show();
dur3 = dur2 + screen;
}
bool FPSchecker decides if I limits the FPS, if false it ran afap.
all ints are unsigned long, with "screen = (1000000 / setfps) - 1;"
so they don't need to be calculated each frame, saves cycles without DMA (fastled.show()), but not matters much since i use DMASPI with Teensy3.6

the FPS can be held at +-1, or at least under 1% error.

Yes, the SDIO grants the origin project with SD a powerful buff. (I remember the project is yours, with ws2812)
I don't need to mess with flash chips and stuff, they are mostly unreachable to non-makers
and my POV is fully-understandable and customizable for my teenage friends!! that's far out of my expectation.
 
Yes, I very quickly used apa102 and not the ws2811 . But it was the sd card read speed that was the problem. by "squashed line" I meant that the data was read too fast to the leds, and so the image was displayed too quickly, so I had to slow the teensy down using delay() to make the image look correct.
 
yes, delay is needed, and by setting FPS directly,
you can imagine your effects while editing picture and determine pic height more easily
 
The FastLED library has a bunch of macros for non-blocking delays, which let your code update frames when needed while allowing your code to do all the other things it may want to do.

So you can write something as simple as:
EVERY_N_MILLISECONDS( 3 ) { FastLED.show(); }

I'd like to have the display speed linked to the rotation speed so that images can be presented without distortion, no matter how fast I am spinning. That's on my list of extras to implement, but I keep getting distracted by all the other fun things to make...
 
@happyinmotion :Thanks for telling
EVERY_N_MILLISECONDS( 3 ) { FastLED.show(); }
I don't know these functions be4, do they belong to FASTLED or its include in the teensyduino IDE? guess it's using timer interrupt .
for me its fine with my own function above, currently DMASPI. And still needs a check to see if transfer's complete.
there's not much waiting since it's the DMA waiting for DATA read from SDio.

but I'll surely need the timer interrupt if I add RF or I2C 6-DOF, ( and they might all need extra DMA channels to recieve data)
 
For people going to use T3.6 in POV, here's some bad result, as warnings.

after weeks of testing , there are some cons of using T3.6 on POV:
the BGA soldering seems to fail after many impact , my other POV with TQFP teensy didn't show this problem, or less.
(yeah, the POV staff/poi is something that hits a lot, taking damage from impact is totally normal)

there are signs of failure, I believe should be bad connection to the MK chip (apply force to bend the board slightly, and the devices works temporary)
the signs are observed over three T3.6s

and I'm going to try hot air gun rework, and see if it fixes the chip

about my juggling design: PC staff with LED devices on two sides,
each teensy is located at the end of PC tubes, and held with a M3 screw.
which might be more vulnerable to impact

I'm still waiting to test more futher, maybe with some foam and rubber to absorb some impact.
or maybe apply some rubber band on the chip to hold?

for all other project uses T3.6, its fine, but don't drop you device too often.
 
Thanks for finding that out for us. I'm another of the people who wants to use a T3.6 for an LED staff, so it's good to know in advance what the failure modes will be. I've too had a surface-mount component come off a board in an LED staff when I dropped it, so yeah, this is a worry.

Stopping the chip breaking off the board needs the chip to be held in place by something that is at least as stiff as the solder balls. Rubber won't do it, as it's too flexible.

For shock and vibration resistance, NASA recommends epoxy. Seriously, there's a NASA Workmanship Standard on exactly our problem, which details exactly how to do this (in stunningly tedious detail). They recommend epoxy fillets at the corners of the chip. Smothering the whole chip in epoxy could cause overheating and running beads along all four sides would be a problem when the chip heats up.

So check out https://nepp.nasa.gov/files/27618/NSTD87391B.pdf, especially Figure 9-4 for a picture of what NASA recommends for our circumstance.

(Or you can pot the entire thing in epoxy, but then it becomes unrepairable, uninspectable, and a bit heavy.)

I prefer four LED strips in the staff, which means putting my Teensy on a very long and narrow PCB. This too can flex - my current model had the PCB flexing enough that the reset button on the Teensy would bump against neighbouring parts and trigger a reset. I think the solution there is just more supports from PCB to neighbouring structure - if everything flexes the same amount, then there's no bumping or point stresses. Reducing the overall flex requires the addition of either weight or width and I'm trying to reduce both of those already.
 
Epoxy, that's the hard path of securing ,
I'm planning on the soft,isolate the T3.6 from the PCB, and cover the chip with rubber/foam
connect the power/data line with strong but soft jumpers, so the T3.6 itself is impact-isolated and absorbing from the rest.
it is also important to protect the battery from shock, I already do that on battery (is
there's plenty of room in my staff for extra foams,

hope this works

btw, last night my rework failed, can desolder the chip, but not-repaired after putting back.
 
btw, last night my rework failed, can desolder the chip, but not-repaired after putting back.

If you can get your hands on SAC101 solder balls (SnAg1Cu0.1) that would be worth a retry. SAC101 is optimized for mobile devices, where shock resistance is of more importance than temperature cycling related fatigue. In general, low copper and silver content improves shock resistance in solders

Ben
 
this is some what simular to what I want to do with my t3.6 and a strip of 351 ws2812b leds.. but for photography aka light painting. could this be adapted to read a image from the built in sd card and output to the leds after a trigger button push? I'm still learning and having fun with my teensy 3.6
 
I am not familiar with the speed requirements for light painting. I know that for persistence of vision (POV) type projects, the WS2812B leds are too slow, and instead the APA102 (Adafruit dotstar) are more recommended. The reason is the WS2812B have a fixed timing window, while the APA102 have a separate clock signal that you can change the cycle time.
 
@happyinmotion
this is cool, I didn't look into it until i switch to chrome, fox and ie blocks its unsecure url.
Looks like nasa uses epoxy to save the world of PCB,
I use hot glue to save mine often, until I found that hot glue sometimes pulls signal up or down.... halts SPI , and temperature dependent makes it happens minutes after boot


If you can get your hands on SAC101 solder balls (SnAg1Cu0.1) that would be worth a retry. SAC101 is optimized for mobile devices, where shock resistance is of more importance than temperature cycling related fatigue. In general, low copper and silver content improves shock resistance in solders
Ben
nice, thank you, I'll try to find some paste for BGAs

but I need to improve my hot air soldering skills, did some LED and QFN,
applying paste manually and overapply most of the time, need to clean excess solder after hot airing, but still works with 3528&QFN32
needs practice, and its expensive with BGA
especially with impacted chips, some pads were even off....

@firehopper
yes, avoid ws2812, only if prototyping.

I also heard the new CHINA version of apa102, the sk9812,
Is made by china manufacturer , they bought the apa102 FPGA/whatever IC design, and built their own. for the scarce supply of apa102c, from Taiwan.
its slightly cheaper, but it provides only 1.2Khz PWM rate, from their datasheet, instead of 20Khz from apa102c.
thats the only different I spotted from datasheets.

but my POV's still fun, at least I'm not noticing it with my eyes.

@MichaelMeissner
it depends on the picture, and the detail of I called a Brain-Recognizing-Part (BRP?) and need to predict the angle-transformation while spinning.
a unit of BRP is letters, or words, or a meaningful picture.
Its proper to catch for 6-50BRP per second.
For pictures (human faces, flags,watermelons etc)with more details 6~12 is okay.
For letters/words, 25~50 is okay.

geography beautiful picture doesn't matter much that much with speed.

I've played some day and had those conclusions.

recent:
And I'm trying to make high-density POV with apa102c - 2020 package,
Making new part in fritzing is tough job, I should learn autodesk(?) eagle ASAP.
and trying to build an UI with processing,
I wonder why the interface of processing look so close to arduino's.
 
this is some what simular to what I want to do with my t3.6 and a strip of 351 ws2812b leds.. but for photography aka light painting. could this be adapted to read a image from the built in sd card and output to the leds after a trigger button push? I'm still learning and having fun with my teensy 3.6
The ws2811/12 is fine for light painting, and as the speed required to push data to pixels is low for this type of use, even running images from an sd card on the 3.1 teensy was more than fast enough. the images further down the post on this thread shows right painting with this setup:
https://forum.pjrc.com/threads/24535-OctoWS2811-POV-reading-BMP-from-SD-card
 
Last edited:
cool morton. I tried to compile your ino file, and it kept spitting out errors. I want to use it with a teensy 3.6 with the built in sd card, but I'm not sure what the errors mean as to why its not working.
 
Hi, yes, that old ino will require updating. I have a sketch for using the T3.6 onboard sd card, but not to hand. I will post it up this evening, but it has a couple of small issues that need ironing out, but it certainly is enough to get BMP from SD onto ws2811 strips for light painting
 
@firehopper, below is a sketch that draws BMP files from the T3.6 onboard sd card to a light painting wand with WS2811 led strip using FastLed:
lightpainting_test.jpg

EDIT- The code below has an error in reversing green and blue. This happens in the compiling from constituent RGB. the line povbuffer[povidx++] =(b<<16) + (r<<8) +g; should be replaced with
povbuffer[povidx++] =(b<<16) + (g<<8) +r;

Code:
/*
This code uses the T3.6 onboard sd card slot, BMP stored on the card are light painted using a cheap strip of ws2811 leds using FastLed
 code derived from lightpainting sketch:
 https://forum.pjrc.com/threads/24535-OctoWS2811-POV-reading-BMP-from-SD-card
 and this post:
  ------> https://forum.pjrc.com/threads/40871-Teensy-6-5-SDFat-BMP-file-read-fail
*/

#include <SPI.h>
#include <SD.h>
#include <FastLED.h>

File bmpFile;
const int chipSelect = BUILTIN_SDCARD;

// you can remove all Serial.print when you have your paint staff 
// set up, this is just for debug

int bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;
#define BUFFPIXEL 512

unsigned int Color(byte b, byte r, byte g); //placed here to avoid compiler error

// How many leds in your strip?
#define NUM_LEDS 147

#define DATA_PIN 2 //the pin that Led strip is attached to
CRGB leds[NUM_LEDS];
int paintSpeed = 15; //adjust this to vary image refresh rate


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

  FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);

//test our led strip - you can remove this to the comment line "// if you dont get ..."
for(int x=0;x<NUM_LEDS;x++){
leds[x] = CRGB::Green;}
  FastLED.show();
  delay(500);
for(int x=0;x<NUM_LEDS;x++){
leds[x] = CRGB::Red;}
  FastLED.show();
  delay(500);
for(int x=0;x<NUM_LEDS;x++){
leds[x] = CRGB::Blue;}
  FastLED.show();
  delay(500);
  for(int x=0;x<NUM_LEDS;x++){
  leds[x] = CRGB::Black;}
  FastLED.show();
  delay(500);
// if you dont get all leds lighting red then going off, check your wiring

  Serial.println("init");
  delay(500);
  Serial.print("Initializing SD card...");
 
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("SD OK!");

}
void loop() {

bmpDraw("JEM.bmp");//example filename
bmpDraw("DWARFS.bmp");
bmpDraw("ISLA.bmp");
}

//////////////////Function to read BMP and send to Led strip a row at a time/////////////////////
void bmpDraw(char* filename){

  File   bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must report 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel) 
  uint32_t povbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)//////mg/////this needs to be 24bit per pixel////////
  uint32_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  int  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  povidx = 0;
  boolean  first = true;
 
 
  // Open requested file on SD card
  bmpFile = SD.open(filename);
Serial.println(filename);
  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); 
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); 
    Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("Header size: "); 
    Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
 
        goodBmp = true; // Supported BMP format -- proceed!
    Serial.print("Image size: ");
    Serial.print(bmpWidth);
    Serial.print('x');
    Serial.println(bmpHeight);
    
        // BMP rows are padded (if needed) to 4-byte boundary
       rowSize = (bmpWidth * 3 + 3) & ~3;
 
        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }
 
        w = bmpWidth;
        h = bmpHeight;
 
     for (row=0; row<h; row++) {       
         if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }
          
          for (col=0; col<w; col++) { // For each column...
            // read more pixel data
           if (buffidx >= sizeof(sdbuffer)) { 
             povidx = 0;         
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
           }
            // set pixel
            r = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            b = sdbuffer[buffidx++];
          Serial.print(r);Serial.print(" ");Serial.print(g);Serial.print(" ");Serial.println(b);
          //we need to output BRG 24bit colour//
            povbuffer[povidx++] =(b<<16) + (r<<8) +g;
          }
          
        for(int i=0;i<NUM_LEDS;i++){
          leds[i]=povbuffer[i];}
           FastLED.show();
            delay(paintSpeed);// change the delay time depending effect required
          } // end scanline
 
      } // end goodBmp
    }
  }//end of IF BMP
  Serial.println();
   
  bmpFile.close();
}

//*************Support Funcitons****************//
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t read16(File& f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}
uint32_t read32(File& f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
 
Last edited:
okay it is working, how ever I cant seem to dim the leds with the setbrightness(BRIGHTNESS) thing and the colors are wrong for some reason. if you can offer some tips it would help
 
Back
Top