Help with global variable for POV stick project

Hello! :grinning:

This is my first arduino project and i’m trying to build a pov stick that displays bitmaps stored in a micro sd card.

I got stucked when i tried to store the bitmap into a global variable, maybe its a memory issue, but i don’t really know, i come from C# web development :sweat_smile: :sweat_smile:

If i read the bitmap in every blink the pov effect is not achieved because of the delay on reading the file, that’s why i need to store the data in a memory for a faster displaying.

This is the code (the commented lines are my try with the pointer thing :exploding_head:
I'm using arduino nano:

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

Adafruit_NeoPixel _strip;

const uint8_t STRIP_PIN = 2;
const uint8_t STRIP_LEDS = 32;
const uint8_t SD_CHIP_PIN = 4;
const uint8_t BLINK_DELAY = 1;

int16_t _currentImageMapWidth = -1;
int16_t _currentImageMapHeight = -1;
int16_t _currentImageMapColumnIndex = -1;
bool _loadNextImage = true;

//uint32_t *_currentImageData;

void setup() {  
  Serial.begin(9600);
  while (!Serial) {}

  loadSDCard();
  loadLedStrip();
}

void loop() {
  if(_loadNextImage){    
    loadNextImage();
  } else {
    loadNextImage();
    //processCurrentImage();
  }  
}

void loadSDCard(){
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CHIP_PIN)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done."); 
}

void loadLedStrip(){
  _strip = Adafruit_NeoPixel(STRIP_LEDS, STRIP_PIN, NEO_GRB + NEO_KHZ800);
  _strip.begin();
  _strip.setBrightness(200);
  _strip.show();
}

void loadNextImage(){

  File bmpImage = SD.open("test.bmp");
  if(bmpImage)
  {    
    byte dataStartingOffset = readNbytesInt(&bmpImage, 0x0A, 4);
    int16_t width = readNbytesInt(&bmpImage, 0x12, 4);
    int16_t height = readNbytesInt(&bmpImage, 0x16, 4);    
    byte pixelsize = readNbytesInt(&bmpImage, 0x1C, 2);

    _currentImageMapWidth = height;
    _currentImageMapHeight = width;
    
    _strip.clear();
    _strip.show();
    
    if(_loadNextImage){
      _currentImageMapColumnIndex = 0;  
    } else {
      _currentImageMapColumnIndex++;
      if(_currentImageMapColumnIndex >= _currentImageMapWidth){
        _currentImageMapColumnIndex = 0;
      }
    }
    
    if(pixelsize == 24){// three bytes per pixel
      bmpImage.seek(dataStartingOffset);//skip bitmap header
      byte R, G, B;      
      //_currentImageData = new uint8_t[width * height];
      //if(_currentImageData){
        int byteIndex = 0;      
        for(int y = 0; y < height; y++) {
          for (int x = 0; x < width; x++) {
            B = bmpImage.read();
            G = bmpImage.read();
            R = bmpImage.read();
            //_currentImageData[byteIndex] = _strip.Color(R, G, B);          
            byteIndex++;
            if(_currentImageMapColumnIndex == x){
              setPixel(y, _strip.Color(R, G, B));                      
            }          
          }        
        }
        _strip.show();
      //} else {
      //  Serial.println("Not enough memory??????");
      //}
      
      delay(BLINK_DELAY);
      _loadNextImage = false;
    }
    bmpImage.close();    
  } 
  else 
  {
    Serial.println("unable to open image"); 
  }
}

void processCurrentImage(){
  Serial.println("reading current Image");        
  _currentImageMapColumnIndex++;
  if(_currentImageMapColumnIndex >= _currentImageMapWidth){
    _currentImageMapColumnIndex = 0;
  }
  _strip.clear();
  _strip.show();

//  for(int y = 0; y < sizeof(_currentImageData); y++){
//    for(int x = 0 ; x < _currentImageMapWidth ; x++){
//      if(_currentImageMapColumnIndex == x){
//        setPixel(y, _currentImageData[y * x]);  
//      }
//    }
//  }
  
  _strip.show();
  delay(BLINK_DELAY);
}

void setPixel(uint16_t pixel, uint32_t color) {
  _strip.setPixelColor(pixel, color);
}

int32_t readNbytesInt(File *p_file, int position, byte nBytes){
    if (nBytes > 4)
        return 0;

    p_file->seek(position);

    int32_t weight = 1;
    int32_t result = 0;
    for (; nBytes; nBytes--)
    {
        result += weight * p_file->read();
        weight <<= 8;
    }
    return result;
}

Thanks in advance

Jonathan

How big are the images ?

But that is not really what you are doing though.

void setPixel(uint16_t pixel, uint32_t color) {
  _strip.setPixelColor(pixel, color);
}

This is not needed. The neopixel library has the same function as an overloaded version of setPixelColor() which takes individual R, G & B values.

what's your Arduino ? a bmp image can be pretty large and memory is usually scarce.

The images are 32x32 to fix on the leds.

Maybe i should ask first, Is it possible to make a 32 neopixel leds pov stick that reads a 32x32 bitmap from a sd card with arduino nano? If yes, how can i achieve it? :sweat_smile:

Thank you!

Arduino nano :upside_down_face:

Let’s do the maths
32 x 32 = 1024 pixels
One pixel = 4 bytes (technically 3 but stored as uint32_t)
So you need 4 KB of SRAM for one bitmap (or at least 3)

Q: how much SRAM do you have in a Nano ?

I don't see from your code how you're timing the LED updates to rotation of the stick. Adafruit seems to do it based on interrupts from an IR sensor.

My POV Project was more constrained as I used a box fan for rotation rather than a human. Timing was derived via interrupts from a Hall Sensor.

Note also that Adafruit does not recommend using NeoPixels for POV due to their slow PWM refresh rate.

Does a 32x32 image really need 24-bit color? Maybe the image can fit into RAM if it is reduced to 16-bit color (5 bits red, 6 bits green, 5 bits blue per pixel).

If the images are graphics with large areas of solid color, maybe Run Length Encoding (RLE) the image to compress it would allow it to fit.

How many different images? You can fit a few in the 32k of FLASH/PROGMEM. Or an external EEPROM.

If you double-buffer the display you might be able to use a timer to switch buffers at regular intervals even when reading from a file sometimes takes too long. You would read lines into two buffers. Display the first line and fill the first buffer with the third line while displaying the first and second buffers. When you get the second buffer displayed, load the fourth line into the second buffer...

It seems that 8kb.

"Flash memory of Arduino Nano is 32Kb. It has preinstalled bootloader on it, which takes a flash memory of 2kb. SRAM memory of this Microcontroller board is 8kb. It has an EEPROM memory of 1kb"

How can i write on that SRAM?

Hum… where did you get that?

See Arduino Nano — Arduino Official Store

Memory
The ATmega328 has 32 KB, (also with 2 KB used for the bootloader. The ATmega328 has 2 KB of SRAM and 1 KB of EEPROM.

If it is possible than that would be the way, the problem is actually that the neopixel library (and FastLED and any other library you may think to use) need the complete output buffer 'ready to go' that is still 3KB.
There are a ways around this, but those are not straight forward.

  • You can split the matrix up into smaller sections for which you create and prepare the buffer one at a time and you control from different output pins.
  • You can send oen (or a few) pixels at a time from a small buffer, and make sure that you do not exceed the 'break-time' of 50uS until you have sent the whole thing like this guy has done on an ATtiny13 (this really is such a creative solution that i did a small dance after i read that)

Even when you use 5-6-5 resolution a full 32 X 32 matrix needs 2KB of RAM, which is all there is on a Nano. Reading the data from an SD-card is to slow to attempt the 2nd option without first loading it into RAM (if the image is in PROGMEM it can work, as shown) And the first option may work, but i suggest you get a Board with more RAM like an ESP.

1 Like

You are thinking of a 32x32 matrix of LEDs. The OP has a 32 LEDs. The LEDs are switched among 32 patterns and, as the strip moves, persistence of vision makes a 32x32 image appear.

it's a virtual display of 32x32 but if performance needs to be good, you would still want the 32 pixels x 32 "pixels' patterns" in SRAM. With 3 or 4 bytes of data per pixel, that won't fit in a Nano

You could use a color lookup table. Say each pixel is represented by 4 bits. Each of those 16 possible values would index into an array of 16 unit_32s to specify the color. That would require:

512 bytes (pixels) + 64 bytes (color table) = 576 bytes.

you go down to 16 colors... that's a big limitation

Maybe use 256 colors and keep the CLUT in PROGMEM. That would be 1024 bytes per picture. Dithering could be used to get in-between colors.

It's plenty for a POV display.

it's better than nothing :wink:

32x32, 5 bits per pixel (32 colors)
image

1 Like

yes if you pick the 16 colors in a suitable way then you get OK results I suppose. I you have a fixed set of 16 colors then the result will depend on the input image