Bool array memory usage

Hi,

I'm using a 2d bool array as a mask for turning an addressable LED strip into a seven segment display. It's currently only storing masks for 0-9 (I'd like to have masks for other letters and symbols tbd in the future) but it is already using up ~33% of my available dynamic memory on a Nano 328P with ~35% used elsewhere.

I've noticed it's using a byte for every bool, is there an alternate data type that is similarly easy to iterate over and perform operations on?

Alternately, what other low-cost micro-controllers would have better dynamic memory? I am considering integrating a web-server into the project in the future, (not sure if wired or wireless but wireless seems more accessible these days) and I imagine that would require a different board anyway.

Thanks!

#include <FastLED.h>

#define NUM_LEDS 194
#define DATA_PIN 2

CRGB leds[NUM_LEDS];
bool mask[10][64] = { {0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                      {1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                      {1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0},
                      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0},
                      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} };

unsigned long startTime;
byte currentTime;
byte currHue = 0;
byte currSat = 255;
byte currBri = 150;

void setup() {
  delay(2000);
  //Serial.begin(9600);
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  startTime = millis();
}



void loop() {
  currentTime = 180 - ( ( (millis()-startTime) / 1000 ) % 180 );

  if ( (millis()/500) % 2) {
    leds[0] = CHSV(currHue, currSat, currBri);
    leds[1] = CHSV(currHue, currSat, currBri);
  } else {
    leds[0] = CHSV(currHue, currSat, 0);
    leds[1] = CHSV(currHue, currSat, 0);
  }

  int curDigit = currentTime % 10;
  for (int i = 0; i<64; i++) {
    leds[i+2] = CHSV(currHue, currSat, mask[curDigit][i]*currBri);
  }

  curDigit = (currentTime/10) % 6;
  for (int i = 0; i<64; i++) {
    leds[i+66] = CHSV(currHue, currSat, mask[curDigit][i]*currBri);
  }

  curDigit = (currentTime/60) % 10;
  for (int i = 0; i<64; i++) {
    leds[i+130] = CHSV(currHue, currSat, mask[curDigit][i]*currBri);
  }

  FastLED.show();

  delay(10);
}

I believe @robtillaart made a library to store bool arrays elements in bool size... I believe it was in the Showcase part of the forum.

https://docs.arduino.cc/libraries/boolarray/

If you want to display just letters, numbers and similar symbols, you can use its ascii code as pointer into FLASH array serving as font.

I am using one (an tamega2560) to store 320x480 pixel array as 40x60 character array, so 2400 bytes of RAM are used.

The code resides here: memxFORTH-asm/SW2 at master · githubgilhad/memxFORTH-asm · GitHub , but the main trick is taking character as offset into FLASH based font array and take the picture from there one line after another.

Consider this:

uint8_t lampTest [] = [
0x01111111,
0x01001001,
0x01001001,
0x01111111,
0x01000000.
}

Takes 5 bytes of storage instead of 40.

  • An unsigned long has 32 bits, you can scan thru the bits one at a time.

Sounds good, but I can't tell from the documentation how to actually implement it. Have you used it before?

I have not. It is above my head. Perhaps @robtillaart will stop in for a how-to.

Use one array to store the patterns for the 7-segment characters (a byte will hold the data for 7 segments), and a 2nd array to map the segment to the LEDs. Easier if each segment takes the same number of LEDs, and the LEDs are sequential.

How would you actually access this data after storing it? I'm aware of bit-shift operations but I've never tried actually using them.
Also, if you check my code the masks are currently each 64 bools long. From what I can tell Arduino support of 64bit data-types is lacking. Would this method still work?

I was thinking I might have to do it this way. How would you access the data if stored in bytes? I assume that is a bit-shifting operation but I haven't messed around with that.
The segments don't take the same number of LEDs and some of them I treat as shared between segments in the current implementation (only on if both segments are on). They are sequential, however.


Generally it would be a bit-shifting operation.

That does complicate things, but would not be too much trouble to handle in code. Check to see if both segments are ON, and if so light the shared LEDs.

Arrays like this are generally stored in the program memory on an atmega328p based Nano, search for how to use PROGMEM to do that.

  • You can pack your bools.

  • You can use a Macro to pack 8 bits into a byte.


#define B8(b7,b6,b5,b4,b3,b2,b1,b0) \
  ((b7<<7)|(b6<<6)|(b5<<5)|(b4<<4)|(b3<<3)|(b2<<2)|(b1<<1)|(b0))


#define UNPACK8(x) \
  BIT_GET(x,7), BIT_GET(x,6), BIT_GET(x,5), BIT_GET(x,4), \
  BIT_GET(x,3), BIT_GET(x,2), BIT_GET(x,1), BIT_GET(x,0)

Have you used it before?

Yes, there are some performance figures in the examples.

There are recent downloads, see graph from GitHub.
Although a download is not a proof of usage I assume that people download to use it.

Furthermore there are no issues reports on this library the last 5 years, so code can be considered stable (or it is not used ;).

On my (private) todo list for this repo is adding an index operator so you can access the internal storage as a boolean array. Something like

ba[455] = true;
if (ba[23]) doSomething...;

might be easier to code / read / maintain than get() and set()

considering that a value for 7 segments easily fits into a byte, why not just use a single byte for each pattern.

i've used the following to display patterns right side and up side down

//    a      d
//   f b    c e
//    g      g
//   e c    b f
//    d h    a
//
//   h g f e d c b a

const byte SEGMENT_MAP_DIGIT[] = {
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0X80, 0X90
};

const byte SEGMENT_MAP_DIGIT_F [] = {
    0xC0, 0xCF, 0xA4, 0x86, 0x8B, 0x92, 0x90, 0xC7, 0x80, 0x82
};

and the following code to output the bits to drive the displays via shift registers

// shift 16-bits from data into the shift register
void output (
    uint16_t  data)
{
    digitalWrite (Latch, LOW);

    for (unsigned i = 0; i < 16; i++, data <<= 1)  {
        digitalWrite (Data, 0 != (data & 0x8000));

        digitalWrite (Clock, HIGH);
        digitalWrite (Clock, LOW);
    }

    digitalWrite (Latch, HIGH);
}

It is not lacking
See this for reading a bit from a 64 bit variable.
https://docs.arduino.cc/language-reference/en/functions/bits-and-bytes/bitRead/

Very simple, just one line of code!

It is not that difficult to convert your current code to use a byte array for each digit.
Extracting the correct bit is just a matter of selecting the correct byte, then the correct bit within that byte.
Personally I would make this into a function, with arguments of the digit to display, and the starting LED number, and I would store the mask array in PROGMEM.

byte mask[10][8] = {
  {0b00000011, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111},
  {0b00000000, 0b00000000, 0b00000000, 0b00011111, 0b11111111, 0b11110000, 0b00000000, 0b00000000},
  {0b11111100, 0b00000000, 0b11111111, 0b11111111, 0b11100000, 0b00001111, 0b11111111, 0b11111111},
  {0b11111100, 0b00000000, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11000000},
  {0b11111111, 0b11111110, 0b00000000, 0b00011111, 0b11111111, 0b11110000, 0b00000000, 0b00000000},
  {0b11111111, 0b11111111, 0b11111111, 0b11100000, 0b00011111, 0b11111111, 0b11111111, 0b11000000},
  {0b11111111, 0b11111110, 0b00000000, 0b00000000, 0b00011111, 0b11111111, 0b11111111, 0b11111111},
  {0b00000000, 0b00000000, 0b11111111, 0b11111111, 0b11111111, 0b11110000, 0b00000000, 0b00000000},
  {0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111},
  {0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11110000, 0b00000000, 0b00000000},
};
  for (int i = 0; i < 64; i++) {
    byte brightness;
    if (((mask[curDigit][i / 8] >> (7 - (i % 8))) & 0x01) == 1) {
      brightness = currBri;
    } else {
      brightness = 0;
    }
    leds[i + 2] = CHSV(currHue, currSat, brightness);
  }

Nice. The isolated bit is a one or zero, already truth, so you can give the key expression a bit of a haircut, viz:

    if ((mask[curDigit][i / 8] >> (7 - (i % 8))) & 0x01) {

I think you can go a step further at the risk of making it harder once again

    if (mask[curDigit][i / 8] >> (7 - (i % 8)) & 0x01) {

For total clarity, though, that ought to be a few lines of code. No, I would not. :expressionless:

I think this is a case where making a table[8] of bits would not speed anything up, as much as it might feel like it should. I've wasted time going up against modern optimising compilers…

a7

Arduino supports uint64_t and int64_t however you cannot print them directly.
Use my printHelpers library to print the 64 bit variables
(and more like scientific notation, Roman numbers etc).

You can use an array of std::bitset. The initialization would be very similar to your original code as long as each mask has 64 bits or fewer. Also, because of the way std::bitset is indexed you need to reverse the initialization order to preserve your original indexing.

#include <bitset>

constexpr std::bitset<64> mask[10] = {
  {0b1111111111111111111111111111111111111111111111111111111111000000},
  {0b0000000000000000000011111111111111111000000000000000000000000000},
  {0b1111111111111111111100000000011111111111111111110000000000111111},
  {0b0000001111111111111111111111111111111111111111110000000000111111},
  {0b0000000000000000000011111111111111111000000000000111111111111111},
  {0b0000001111111111111111111111100000000111111111111111111111111111},
  {0b1111111111111111111111111111100000000000000000000111111111111111},
  {0b0000000000000000000011111111111111111111111111110000000000000000},
  {0b1111111111111111111111111111111111111111111111111111111111111111},
  {0b0000000000000000000011111111111111111111111111111111111111111111}
};

void setup() {
  Serial.begin(115200);
  delay(1000);

  for (size_t row = 0; row < 10; row++) {
    for (size_t col = 0; col < 64; col++) {
      Serial.print(mask[row][col]);
      Serial.print(" ");
    }
    Serial.println();
  }
}

void loop() {
}

Serial Port Output:

0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

That's new to me. Definitely only used to work up to 32 bits. But that page was updated Apr '25, after I last looked into it. Good news! :+1: