Teensy APA102 POV Poi / Pixel Poi Build. Tutorial

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
okay the color problem is understood, but not how to fix as of yet. I think I might be able to fix that. the blue and green are reversed in the image part, the test code at the start is correct. green red blue but the code at the end has blue and green swapped somehow.
the brightness thing I dont know anything about.

for brightness I'm trying to do the following.

#define BRIGHTNESS 64


it doesnt seem to affect how bright the strip is during the bmp output. it does seem to affect the green red blue test at the start.

next post will have the modified code I have. including the hopefull green blue swap fix.
Last edited:
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:
 and this post:
  ------> [url]https://forum.pjrc.com/threads/40871-Teensy-6-5-SDFat-BMP-file-read-fail[/url]

#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 351
#define BRIGHTNESS 64
#define DATA_PIN 2 //the pin that Led strip is attached to
int paintSpeed = 15; //adjust this to vary image refresh rate

void setup(void) {
  FastLED.addLeds<WS2812B, 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;}
for(int x=0;x<NUM_LEDS;x++){
leds[x] = CRGB::Red;}
for(int x=0;x<NUM_LEDS;x++){
leds[x] = CRGB::Blue;}
  for(int x=0;x<NUM_LEDS;x++){
  leds[x] = CRGB::Black;}
// if you dont get all leds lighting red then going off, check your wiring

  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
  Serial.println("SD OK!");

void loop() {
 if (digitalRead(7) == HIGH) {
    Serial.println("Button is not pressed...");
  } else {
      Serial.println("Button pressed!!!");

bmpDraw("penguin.bmp");//example filename
  for(int x=0;x<NUM_LEDS;x++){
  leds[x] = CRGB::Black;}

//////////////////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);
  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); 
    (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: "); 
    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: ");
        // 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?
            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 GRB 24bit colour//
            povbuffer[povidx++] =(g<<16) + (r<<8) +b; //original code is b r g, should be g r b? 
        for(int i=0;i<NUM_LEDS;i++){
            delay(paintSpeed);// change the delay time depending effect required
          } // end scanline
      } // end goodBmp
  }//end of IF BMP

//*************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;
the brightness of Fastled library, is calculated while FastLED.show();
if I remember correct, like some (buf*brightness>>8) before the byte was pushed,

btw from the code above, you set the both FastLED.setBrightness(BRIGHTNESS); to a fixed defined number, 64?
one at the setup, one under the loop.
try two obvious different number, like 10 to 255, to see significant difference,
you should also look into Gamma correction function in FastLED

or declare like
"FastLED.addLeds<APA102, 3, 4, BGR, DATA_RATE_MHZ(8)>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );"

the colorcorrection, brightness, and colortemperature, are all non-destructive settings,
they don't affect the stored bits in leds[] array, only affect when show();
Hi, sorry, in my haste to change the sketch, that line should have been: povbuffer[povidx++] =(b<<16) + (g<<8) +r;
That should put the green and blue in the correct place. I used to have a set of Masks there, but this was wasn't needed.

also, i am using the set brightness without any issue, it works as the original had it. I am currently using it for daytime light painting like this:
FastLED.setBrightness(25);//for daytime reduced intensity
Last edited:
okay, lets see, that change fixed the colors so yay.. I had to make a second change due to the fact I'm running 351 leds,

uint8_t povidx = 0; had to be changed to uint16_t povidx = 0; otherwise it cuts out before all my leds are updated

16 brightness level is too bright had to stop my camera down to f32 and its still almost too bright.. so perhaps 8 will work
will try and attach some images
Last edited:

How To Build your own giant POV swinging poi ... Learning Center detailing how to build your own giant 36 pixel LED poi. ... These poi use Dotstar APA102 LEDs which have a really fast chipset, so the ... They run on the Teensy 3.2 -- it's pretty easy to use, there's lots of support ...The PWM is built into each LED-chip so once you set the color you can stop ... to set the 24-bit color data for each pixel LED once, and then the LED+built-in-chip ... We have a tutorial showing wiring, power usage calculations, example code ... Supernova Poi ... Adafruit DotStar LED Strip - APA102 Warm White - 144 LED/m.
I hope someone can help, i build a "led poi", all running fine, but if i viewing converted bitmap, some colors are bounded in big areas. If i view simple geometric figures or fastled standard pattern all fine.
i tryed with parameter changing like brightness, set Correction, set Temperature and I try with 5v support but without success.

picture with the boundering

The led showing loop are same like page one, see https://forum.pjrc.com/threads/30020-Teensy-APA102-POV-Poi-Pixel-Poi-Build-Tutorial

My led poi hardware:
APA102 2x72led (front and backside)
teensy 3.2 with prob shield
bluetooth adapter
ir receiver
lipo 3,7v 2200mA

regards TheHidden
Not sure what you mean boundering?

Could you show us the original pic ?
As Pictures with bigger width than 72 is bound to 72 for showing
Hi there. Can you explain a bit more about what is wrong and what you expected to be displayed on the poi, perhaps show the original image bitmap and then what is displayed and we should be able to help.
This could be an artefact of the image conversion to rgb values in the code. Have you tried with a different image, one that has clearly demarcated patterns and colour blocks?
Sure, i tryed a lot of pictures but if the bitmap have a lot of color i get this effect.

a simple color example

Ok the deforming of the figure is not so good, but the color is fine.
One possibility is that the image you show has only 72 pixels high to show colours. Your original multi colour image has many colours, but the poi can only display the colours that the original image was converted to when the 72 pixel high bitmap was made. In converting from an image with many colour changes in to a small number pixels, it might lead to this issue. What does you converted bitmap look like, rather than the original image, it might help to see that.
This was my first idea and i compared the rgb data byte wise, but the translation was ok.

And i tested be reading the translated data back to pc and check how it look.

read back data:
Looks to me like the displayed image has far fewer colours than the original. This can happen if you are not displaying at full brightness. Is this the case?

If you have an image with 256 levels, and you display that at 1/8 brightness, then the image will only have 32 levels.

You can avoid this using the temporal dithering in FastLED. I've found good results down to brightnesses of 3/256 for stationary LEDs. With moving LEDs, well, you'll just have to try it.


You can turn it on with:

If you are using the dithering already, then apologies, I have no idea what's going on.
@dzuljo nice pictures.

Picture with simple geometric and less colors it is ok

and i maked first test with FastLED.setDither( 1 or 0 ) but i can't see differents between the setting at the picture.
brightness 0-255
setDither 0/1
@TekJunky. I am using my own software, a java app that outputs an .ino file for upload to Teensy. It takes images and converts them to the hex value array for each consecutive image.
View attachment 5157

Its my first Java app, and has some problems with running very slowly, a lot of lag. Memory leaks and 'surviving generations' seem to be the culprit.

There are a number of online converters which work, you just copy and paste the output into the sketch posted above,

RE the SD card, I am not progressing with this option, I am going to use a flash memory expansion. Paul is currently working on something I believe that will allow true SPI to work with apa102 and flash add on.

dear @mortonkopf ,
I've been finding a way for writing an UI for some time,
looked into Progressing language but details are not enough to continue yet
my POV settings use a .txt and bmps in SD cards so I don't really a UI
but looking forward to it anyway,

can you please show how to write/find examples? thank you
I wish to know how to accomplish follow features/functions

-load/save "project" through dialog selection to a folder, exports a series of resized bmp and a textbook of POV settings
-maybe loading a music timeline for refence
-import picture
-multiple players/picture banks
Hi @Po Ting. Yes, I can put my Java App on Github and you can have a look at it. I stopped using it as it was not necessary anymore, and it was never really satisfactory. The app would allow you to place all images into a timeline and then give each image a certain time to run for. Once the timeline / performance was constructed, you clicked a button to output a file. This file was output as an .ino sketch and this could be opened directly by Arduino IDE and uploaded to the Teensy. This method, however, used const arrays and not bmp files to store the image data, so I'm not how much use it will be to you. I will recover it from archive and post a link in the next day or two.
Thank you very much

good thing I actually wrote some JAVA app, but more than ten years ago when little
hope I can still dig into it. :confused:
Hello! is here my first contribution. I have finished with APA102 first Pixelpoi-Functioned. just got information about APA102-2020 LED Strip with 400mm length and 128LED price 22USD / piece plus shipping. Will try this time. Less power more LED higher resolution sound interesting. Has anyone interest > Sammelbestellung?
Best regards from Vienna
for LED strips, they had to leave some room for connections/solder point for random customers
so it's not the densest form you can get theoretically with apa102-2020 (3.4 LED / cm )

I prefer making my own PCB to reach highest density possible (e.g. 4 LED/cm)
though still in prototype stage.