简体   繁体   中英

Accessing individual bytes in PROGMEM on Arduino/AVR

I've read up on accessing PROGMEM for days now, and combed through several other questions, but I still can't get my code working. Any help would be appreciated.

I've included a full test sketch for Arduino below. The majority of it works, but when I loop through each byte of an "alpha" character, as pointed to by "alphabytes", I'm just getting garbage out so I'm obviously not accessing the correct memory location. The problem is, I can't figure out how to access that memory location.

I've seen several other examples of this working, but none that have different sizes of data arrays in the pointer array.

Please see line beginning with ">>>> Question is..."

// Include PROGMEM library
#include <avr/pgmspace.h>

// Variable to hold an alphabet character row
char column_byte;

// Used to hold LED pixel value during display
char led_val;

// Used to hold the screen buffer for drawing the screen
char matrix_screen[64];

/*
  Define Alphabet characters. This should allow for characters of varying byte lengths.
*/
const char alpha_A[] PROGMEM = {0x06, 0x38, 0x48, 0x38, 0x06};
const char alpha_B[] PROGMEM = {0x7E, 0x52, 0x52, 0x2C};
const char alpha_C[] PROGMEM = {0x3C, 0x42, 0x42, 0x24};

/*
  The "alphabytes" contains an array of references (pointers) to each character array.
  Read right-to-left, alphabytes is a 3-element constant array of pointers,
  which points to constant characters

*/
const char* const alphabytes[3] PROGMEM = {
  alpha_A, alpha_B, alpha_C
};

/*
  This array is necessary to list the number of pixel-columns used by each character.
  The "sizeof" function cannot be used on the inner dimension of alphabytes directly
  because it will always return the value "2". The "size_t" data
  type is used because is a type suitable for representing the amount of memory
  a data object requires, expressed in units of 'char'.
*/
const char alphabytes_sizes[3] PROGMEM = {
  sizeof(alpha_A), sizeof(alpha_B), sizeof(alpha_C)
};

/**
 * Code Setup. This runs once at the start of operation. Mandatory Arduino function
 **/
void setup(){

  // Include serial for debugging
  Serial.begin(9600);
}

/**
 * Code Loop. This runs continually after setup. Mandatory Arduino function
 **/
void loop(){

  // Loop through all alphabet characters
  for( int a = 0; a < 3; a++) {

    // Reset screen
    for (int r = 0; r < 64; r++) {
      matrix_screen[r] = 0;
    }

    // This line works to read the length of the selected "alphabyte"
    int num_char_bytes = pgm_read_byte(alphabytes_sizes + a);

    for (int b = 0; b < num_char_bytes; b++){

      // Based on alphabytes definition,
      // Examples of desired value for column_byte would be:
      //
      // When a=0, b=0 -> column_byte = 0x06
      // When a=0, b=1 -> column_byte = 0x38
      // When a=0, b=2 -> column_byte = 0x48
      // When a=0, b=3 -> column_byte = 0x38
      // When a=0, b=4 -> column_byte = 0x06
      // When a=1, b=0 -> column_byte = 0x7E
      // When a=1, b=1 -> column_byte = 0x52
      // When a=1, b=2 -> column_byte = 0x52
      // When a=1, b=3 -> column_byte = 0x2C
      // When a=2, b=0 -> column_byte = 0x3C
      // When a=2, b=1 -> column_byte = 0x42
      // When a=2, b=2 -> column_byte = 0x42
      // When a=2, b=3 -> column_byte = 0x24

      // >>>>> Question is... how to I get that? <<<<<<<
      // column_byte = pgm_read_byte(&(alphabytes[a][b])); // This doesn't work

      // Thought: calculate offset each time
      // int offset = 0;
      // for(int c = 0; c < a; c++){
      //   offset += pgm_read_byte(alphabytes_sizes + c);
      // }
      // column_byte = pgm_read_byte(&(alphabytes[offset])); // This doesn't work

      // column_byte = (char*)pgm_read_word(&alphabytes[a][b]); // Doesn't compile
      column_byte = pgm_read_word(&alphabytes[a][b]); // Doesn't work

      // Read each bit of column byte and save to screen buffer
      for (int j = 0; j < 8; j++) {
        led_val = bitRead(column_byte, 7 - j);
        matrix_screen[b * 8 + j] = led_val;
      }

    }

    // Render buffer to screen
    draw_screen();

    // Delay between frames
    delay(5000);

  }

}

/**
 * Draw the screen. This doesn't have the correct orientation, but
 * that's fine for the purposes of this test.
 **/
void draw_screen(){
  for (int a = 0; a < 8; a++) {
    for (int b = 0; b < 8; b++) {
      Serial.print((int) matrix_screen[a * 8 + b]);
      Serial.print(" ");
    }
    Serial.println();
  }
  Serial.println();
}

After much research (and quite frankly a lot of trial and error), I have come across a solution which is working. I don't know if this the the most correct or most elegant solution, but it works.

column_byte = pgm_read_byte(pgm_read_byte(&alphabytes[a]) + b);

The inner call to pgm_read_byte():

pgm_read_byte(&alphabytes[a])

returns a value, which is the address of the character being evaluated (notice the leading "address-of" operator "&").

The outer pgm_read_byte reads that memory at an offset of "b".

This solution can also be broken down into two parts:

int memory_loc = pgm_read_byte(&alphabytes[a]);
column_byte = pgm_read_byte(memory_loc + b);

My C skills are not good enough to really explain if "memory_loc" needs to be an int (I tried it as a "char" and it also worked).

Note that alphabytes it is array, which each element contains a REFERENCE (ie address) where corresponding characters are stored. So, you should access it in two steps.

First step is to know address in the progmem of the required item. Addresses are 16bits wide (unless you are using 128+k device).

PGM_VOID_P ptr = (PGM_VOID_P) pgm_read_word(&alphabytes[a]); 

and, if you want to access it byte by byte, you can just read using this pointer, and then increment it:

for (int b = 0; b < num_char_bytes; b++) {
    uint8_t column_byte = pgm_read_byte(ptr++);
    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM