Using a hex code from a char array with a function expecting an unsigned int

Hi everyone. I'm working on an LED matrix that will have a number of pixel animations. Since the animations will take up a lot of memory, i'm storing them on an sd card in text files. Ive written a program that reads the txt files just fine and stores the hex values into a char array on the esp32. When i serial write the array it all shows up correctly in the serial monitor and appears as i would expect.

The issue i'm having is when i try to write the array hex value to the FastLED library in order to display the animation, it throws an error.....

"Compilation error: invalid conversion from 'char*' to 'uint32_t' {aka 'unsigned int'} [-fpermissive]"

So, if i write a hex value to FastLED directly ie

leds[i] = 0xffff52;

it works correctly. But if i pluck the hex value out of my char array, it throws the error.....

leds[i] = frameBuffer[rowCounter+8][(i / 8)-32];

As the error suggests that im trying to supply a char value to a function expecting an unsigned int, how do convert my char hex array in to a value the FastLED function expects?

Thanks

SD_Card_Test.ino (2.8 KB)
Axel.txt (5.0 KB)

Use strtoul() to convert text representing a hex number to the actual number; strtol(3) - Linux manual page has an example, just replace the strtol by strtoul.

It's the best way (my opinion) because it checks the input as well.

Alternatively, manually check if it's a valid hexadecimal representation and next use atol.

Thanks for your quick reply.

Ill give that a try in the morning.

Cheers

alternatively, instead of storing ASCII in your animation file, store binary information. The file will be smaller, faster to read, no parsing needed and thus you can load it directly into memory and there is no conversion needed.

Thank you also for a great suggestion.

Ill try that in the morning as well.

Cheers

to read the hexadecimal data you could do this (typed here so not really tested)

#include <SD.h>
#define CS_PIN 5
const uint8_t colCount = 32;
const uint8_t lineCount = 16;

uint32_t ledBuffer[colCount * lineCount];

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

  Serial.print("Initializing SD card... ");

  if (!SD.begin(CS_PIN)) {
    Serial.println("Card initialization failed!");
    while (true);
  }

  Serial.println("initialization done.");

  File textFile = SD.open("/Animations/Axel.txt");
  if (textFile) {
    size_t pos = 0;
    char buffer[15];
    while (textFile.available()) {
      if (textFile.readBytesUntil(',', buffer, sizeof buffer) > 0) {
        if (pos >= colCount * lineCount) {
          Serial.println("too much data input file");
          break;
        } else {
          char * endptr;
          ledBuffer[pos++] = strtoul(buffer, &endptr, 16); // could use endptr to detect format error
        }
      }
    }
    textFile.close();
    if (pos != colCount * lineCount) {
      Serial.println("wrong format for input file");
    } else {
      Serial.println("I read the file.");
      for (byte line = 0; line < lineCount; line++) {
        for (byte col = 0; col < colCount; col++) {
          Serial.printf("0x%06X ", ledBuffer[line * colCount + col], HEX);
        }
        Serial.println();
      }
    }
  } else {
    Serial.println("error opening Axel.txt!");
  }

}

void loop() {}

you see you have to deal with reading the text into a buffer and calling strtoul() and dealing with the comma etc... it's a pain. with a binary file (which you could generate from this program since you have the data in memory now) , you could test the file size to see if it's exactly 32x16x4 bytes long (thus you know it's likely well formed) and then just readBytes in one go into your memory buffer.

even better, you could write in binary the bytes of the CRGB struct (3 bytes) and read the file (if it has the right size) into your array CRGB leds[NUM_LEDS]; then you would just have to issue a show to get the file mapped into your neopixel matrix. that would be faster.

Thanks again,

I havent tried the binary option yet but strtoul() definitely fixed my initial problem. Ill try binary at some point as well.

The new issue ive got is when i try to display 3 frames one after the other. When i do, it panics and reboots with this error........

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1344
load:0x40078000,len:13964
load:0x40080400,len:3600
entry 0x400805f0
SD Card Type: SDHC
Reading file: /Animations/Axel/1.txt
Read from file: 

Reading file: /Animations/Axel/2.txt
Read from file: 

Guru Meditation Error: Core  0 panic'ed (IllegalInstruction). Exception was unhandled.

Core  0 register dump:
PC      : 0x63613535  PS      : 0x00060031  A0      : 0x8008b223  A1      : 0x3ffbea9c  
A2      : 0x3ffc3a20  A3      : 0x00000000  A4      : 0x00060520  A5      : 0x3ffbc6a0  
A6      : 0x007bdc98  A7      : 0x003fffff  A8      : 0x80083794  A9      : 0x3ffbea8c  
A10     : 0x00000003  A11     : 0x00060023  A12     : 0x00060021  A13     : 0x00000007  
A14     : 0x00000005  A15     : 0x00000001  SAR     : 0x0000001d  EXCCAUSE: 0x00000000  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  


Backtrace: 0x63613532:0x3ffbea9c |<-CORRUPTED




ELF file SHA256: 3c9d0ce79a0890d4

rebooting....

It seems to read the first and second file ok then freaks out when it gets to the 3rd.

Any ideas whats happening here?

Thanks
SD_Card_Test.ino (3.6 KB)

I’m reading from my iPhone, can’t easily read attachments

Post the code here directly within code tags.

Thanks J-M-L

If I reference a single frame from the frame buffer and display it by itself it works fine. But if i try to display one after the other in a loop, it reboots.
Reading suggests the esp32 watch timer is being triggered but im not sure how or why.


#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "FastLED.h"

int rowIndex = 0;
int columnIndex = 0;
int next;

#define NUM_LEDS 512
#define DATA_PIN 13
CRGB leds[NUM_LEDS];
int led;

const byte row = 16;
const byte column = 32;
const byte maxChars = 9;

char frameBuffer[row][column][maxChars];

char buffer[maxChars];

int bufferIndex;

unsigned int readTimer;

void setup() {


  Serial.begin(115200);

  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
  FastLED.clear();
  FastLED.show();

  delay(2000);

  if (!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }

  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }

  //readFile(SD, "/Animations/Axel/1.txt");
}



void readFile(fs::FS &fs, const char *path) {
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  readTimer = millis();
  while ((next = file.read()) != -1) {

    char nextChar = (char)next;

    if (nextChar != 32 && nextChar != '\,' && nextChar != '\n' && nextChar != '\r') {

      frameBuffer[rowIndex][columnIndex][bufferIndex] = nextChar;
      bufferIndex++;

    } else if (nextChar == '\n') {

      rowIndex++;
      columnIndex = 0;
      bufferIndex = 0;

    } else if (nextChar == 32) {

      columnIndex++;
      bufferIndex = 0;
    }
  }
  file.close();
  //displayFrame();
}


byte rowCounter = 0;

void displayFrame() {

  for (int i = 0; i < 256; i++) {
    if (i % 16 > 0 && i % 16 <= 7) {
      rowCounter++;
    } else if (i % 16 > 8 && i % 16 < 16) {
      rowCounter--;
    }
    char *endptr;
    leds[i] = strtoul(frameBuffer[rowCounter][(i / 8)], &endptr, 16);

    /*Serial.print(rowCounter);
    Serial.print("  ");
    Serial.print(i / 8);
    Serial.print("  ");
    Serial.print(i);
    Serial.print("  ");
    Serial.println(frameBuffer[rowCounter][(i / 8)]);*/
  }

  for (int i = 256; i < 512; i++) {
    if (i % 16 > 0 && i % 16 <= 7) {
      rowCounter++;
    } else if (i % 16 > 8 && i % 16 < 16) {
      rowCounter--;
    }
    char *endptr;
    leds[i] = strtoul(frameBuffer[rowCounter + 8][i / 8 - 32], &endptr, 16);

    /*Serial.print(rowCounter + 8);
    Serial.print("  ");
    Serial.print(i / 8 - 32);
    Serial.print("  ");
    Serial.print(i);
    Serial.print("  ");
    Serial.println(frameBuffer[rowCounter][i / 8 - 32]);*/
  }
  FastLED.show();

  /*readTimer = millis() - readTimer;

  Serial.print("Reading the SD card took ");
  Serial.print(readTimer);
  Serial.println("ms");
  Serial.println();

  for (int i = 0; i < row; i++) {
    for (int k = 0; k < column; k++) {
      Serial.print(frameBuffer[i][k]);
      Serial.print(", ");
    }
    Serial.println();
  }*/
}

byte i = 0;
unsigned long frameTimer;
int frameLength = 500;

char *animations[3] = { "/Animations/Axel/1.txt", "/Animations/Axel/2.txt", "/Animations/Axel/3.txt" };

void loop() {

  if (millis() - frameTimer > frameLength) {

    if (i == 0) {
      readFile(SD, animations[0]);
      delay(50);
      displayFrame();
    }
    if (i == 1) {
      readFile(SD, animations[1]);
      delay(50);
      displayFrame();
    }
    if (i == 2) {
      readFile(SD, animations[2]);
      delay(50);
      displayFrame();
      i == 0;
    }
    i++;

    frameTimer = millis();
  }
}

Can your data be loaded into PROGMEM entirely, then read from PROGMEM?

A recent user had ?library? issues with 15 pages of 4096 bytes that he dumped to PROGMEM, then read-out when needed.

I dont see why not.

Ill give that a try

Hey, give this a try:

  for (int i = 0; i < 256; i++) {
    int j = i & 0xf;
    if (j > 8) {
      rowCounter--;
    } else if (j) {
      rowCounter++;
    }
    char *endptr;
    leds[i] = strtoul(frameBuffer[rowCounter][(i / 8)], &endptr, 16);

    /*Serial.print(rowCounter);
    Serial.print("  ");
    Serial.print(i / 8);
    Serial.print("  ");
    Serial.print(i);
    Serial.print("  ");
    Serial.println(frameBuffer[rowCounter][(i / 8)]);*/
  }

a7

Ok, so here is where im at.

Ive tried declaring frameBuffer[] using PROGMEM.....

char PROGMEM frameBuffer[row][column][maxChars];

The frameBuffer is written to using this line.....

frameBuffer[rowIndex][columnIndex][bufferIndex] = nextChar;

and i'm accessing the PROGMEM frameBuffer array like this.

strcpy_P(buffer, (char*)pgm_read_word(&(frameBuffer[rowCounter][(i / 8)])));
leds[i] = strtoul(buffer, &endptr, 16);

It compiles, but the display is blank. Am i using PROGMEM correctly? Ive seen PROGMEM used before but never used it myself.

Thanks

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "FastLED.h"

int rowIndex = 0;
int columnIndex = 0;
int next;

#define NUM_LEDS 512
#define DATA_PIN 13
CRGB leds[NUM_LEDS];
int led;

const byte row = 16;
const byte column = 32;
const byte maxChars = 9;

char PROGMEM frameBuffer[row][column][maxChars];

char buffer[maxChars];

int bufferIndex;

unsigned int readTimer;

void setup() {


  Serial.begin(115200);

  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
  FastLED.clear();
  FastLED.show();

  delay(2000);

  if (!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }

  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }

  //readFile(SD, "/Animations/Axel/1.txt");
}



void readFile(fs::FS &fs, const char *path) {
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  readTimer = millis();
  while ((next = file.read()) != -1) {

    char nextChar = (char)next;

    if (nextChar != 32 && nextChar != '\,' && nextChar != '\n' && nextChar != '\r') {

      frameBuffer[rowIndex][columnIndex][bufferIndex] = nextChar;
      bufferIndex++;

    } else if (nextChar == '\n') {

      rowIndex++;
      columnIndex = 0;
      bufferIndex = 0;

    } else if (nextChar == 32) {

      columnIndex++;
      bufferIndex = 0;
    }
  }
  file.close();
  //displayFrame();
}


byte rowCounter = 0;

void displayFrame() {

  for (int i = 0; i < 256; i++) {
    if (i % 16 > 0 && i % 16 <= 7) {
      rowCounter++;
    } else if (i % 16 > 8 && i % 16 < 16) {
      rowCounter--;
    }
    char *endptr;
    strcpy_P(buffer, (char*)pgm_read_word(&(frameBuffer[rowCounter][(i / 8)])));
    leds[i] = strtoul(buffer, &endptr, 16);
  }

  for (int i = 256; i < 512; i++) {
    if (i % 16 > 0 && i % 16 <= 7) {
      rowCounter++;
    } else if (i % 16 > 8 && i % 16 < 16) {
      rowCounter--;
    }
    char *endptr;
    strcpy_P(buffer, (char*)pgm_read_word(&(frameBuffer[rowCounter + 8][i / 8 - 32])));
    leds[i] = strtoul(buffer, &endptr, 16);
  }
  FastLED.show();

  for (int i = 0; i < row; i++) {
    for (int k = 0; k < column; k++) {
      Serial.print(frameBuffer[i][k]);
      Serial.print(", ");
    }
    Serial.println();
  }
}

byte i = 0;
unsigned long frameTimer;
int frameLength = 500;

char *animations[3] = { "/Animations/Axel/1.txt", "/Animations/Axel/2.txt", "/Animations/Axel/3.txt" };

void loop() {

  if (millis() - frameTimer > frameLength) {

    if (i == 0) {
      readFile(SD, animations[0]);
      delay(50);
      displayFrame();
    }
    if (i == 1) {
      readFile(SD, animations[1]);
      delay(50);
      displayFrame();
    }
    if (i == 2) {
      readFile(SD, animations[2]);
      delay(50);
      displayFrame();
      i == 0;
    }
    i++;

    frameTimer = millis();
  }
}

You have

char PROGMEM frameBuffer[row][column][maxChars];

The PROGMEM is read-only , as far as your sketch is concerned. It is filled with zeroes.

So

      frameBuffer[rowIndex][columnIndex][bufferIndex] = nextChar;

isn't going to do what you think.

I don't know what you've seen, but when accessing PROGMEM you have to go a bit out of your way, viz:

  char myChar = pgm_read_byte_near(signMessage + k);

for example gets the byte that is at address k off of signMessage.

a7

Hmmmm ok. So theres no way to write data from the sd card to PROGMEM using a function? The values stored in PROGMEM need to be declared during start up. Do i have that right?
Thats a shame. My plan to read directly off of the sd card may not work if it causes the esp32 to reboot.

For those who are interested, i have the code working great now. The crashing/error code was because i had forgotten to zero the rowIndex counter between frames. Rooky mistake but hey, i got there in the end.
I then had an issue where it was going very slowly regardless of what time i set in the frameLength timer. This turned out to be a simple fix by turning off the Serial Monitor.

So the code now reads the hex array form SD card into a buffer. The buffer is then read to the LED's and displayed. Its doing it in approx 20ms which is more than fast enough.

Happy days.

A big thank you to all those who chimed in with help and suggestions. Very much appreciated.

Cheers

2 Likes

You could probably save a few microseconds if you can skip the intermediary buffer.

That's actually how i found the "not re-setting the rowIndex to zero" problem. While trying to figure out why the esp32 was crashing, i re-wrote it without the buffer but reusing some of the variables including "rowIndex".

I have both versions working well now and will likely go without the buffer.

1 Like

nice - well done!
have fun

Nice. Please post your now it works sketch for the record.

a7