1
\$\begingroup\$

I try to use a ATTiny10 to drive some APA102 LEDs. The LEDs are arranged in a 5x5 array. To draw something I stored some 'images' in PROGMEM. Program looks like this:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>

unsigned char brightness = 12;

const unsigned char i[]  PROGMEM = {
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
};

const unsigned char o[]  PROGMEM = {
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00
};
*/
const unsigned char u[]  PROGMEM = {
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
    0xFF,0xFF,0xFF,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0x00,0x00,0x00,
    0xFF,0xFF,0xFF,
};

void delay (int millis) {
  for (volatile unsigned int i = 34*millis; i>0; i--);
}

// send clock on PB0 and data on PB1
void sendByte(const unsigned char byte){
   //bit banging stuff here
}

void show(const unsigned char *data){
  // send header
  sendByte(0x00);
  sendByte(0x00);
  sendByte(0x00);
  sendByte(0x00);

  // send data
  for (unsigned char led = 0; led < 25; led = ++led){
    sendByte(0xE0 + brightness);            // brightness
    sendByte(pgm_read_byte(data[led]));     // blue   
    sendByte(pgm_read_byte(data[led+1]));   // green
    sendByte(pgm_read_byte(data[led+2]));   // red
  }

  // send foooter
  sendByte(0xFF);
  sendByte(0xFF);
  sendByte(0xFF);
  sendByte(0xFF);
}

void setup(){
  DDRB = 3;                       // PB0 and PB1 as an output
}


int main (void) {
  setup();

  while (1){
    show(i);
    delay(100);
    show(o);
    delay(100);
    show(u);
    delay(100);
  }
}

What confuses me is the output of the compiler.
No matter how many character arrays I add always get the result 186 bytes used.

DATA:    [          ]   0.0% (used 0 bytes from 32 bytes)
PROGRAM: [==        ]  18.2% (used 186 bytes from 1024 bytes)

What I would expect is something > 225 byte (3 characters * 3 byte per color * 25 LEDS)

As it seems the the PROGMEM arrays are optimized out by the compiler.

when I change the main to following (ignore the nonsense of the operation):

int main (void) {
  setup();

  char d = pgm_read_byte(i);
  char e = pgm_read_byte(o);
  char f = pgm_read_byte(u);

  while (d>e && e < f){
    show(i);
    delay(100);
    show(o);
    delay(100);
    show(u);
    delay(100);
 }
}

this results in the expected size:

DATA:    [          ]   0.0% (used 0 bytes from 32 bytes)
PROGRAM: [====      ]  44.9% (used 460 bytes from 1024 bytes)

This means compiler looses track of the usage of i,o and u by passing them as pointer to show? Have I missed something by passing the data arrays?

I use VS Code + PlatformIO with latest AVR environment (avr-g++ 5.4.0)

UPDATE:

I don't know why this made the difference but when passing it as PGM_VOID_P aka const void* and casting it inside the function it works!

void show(PGM_VOID_P data_p){
  const uint8_t* data = (const uint8_t*)data_p;
\$\endgroup\$
  • 1
    \$\begingroup\$ Does the code work? What does the assembly code look like? It may be that some of your constants are optimized away. \$\endgroup\$ – Elliot Alderson Mar 26 '19 at 16:44
  • \$\begingroup\$ No it does not work otherwise I wouldn't ask ;) I had a look at both hex outputs and in the short one the arrays are missing \$\endgroup\$ – Steve Mar 26 '19 at 16:57
  • 1
    \$\begingroup\$ How does it not work? What result did you expect and what actually happened when you ran the code? \$\endgroup\$ – The Photon Mar 26 '19 at 17:03
  • \$\begingroup\$ Also, where is pgm_read_byte defined? \$\endgroup\$ – The Photon Mar 26 '19 at 17:06
  • 2
    \$\begingroup\$ Your function declaration void show(const unsigned char *data) strips the PROGMEM attribute from the parameter, so inside show you're accessing some other memory location. It should probably be something like void show(const unsigned char * PROGMEM data). \$\endgroup\$ – brhans Mar 26 '19 at 17:13
2
\$\begingroup\$

I tried compiling your code with avr-gcc 4.9.2 and 5.4.0, and in both cases the array data was put into flash program memory. However the function sendByte does nothing, so reading the data was optimized out.

So I added some minimal code to 'send' the data, and the compiler generated this:-

000000ac <i>:
 ac:    00 00 00 ff ff ff ff ff ff ff ff ff 00 00 00 00     ................
 bc:    00 00 00 00 00 ff ff ff 00 00 00 00 00 00 00 00     ................
 cc:    00 00 00 00 ff ff ff 00 00 00 00 00 00 00 00 00     ................
 dc:    00 00 00 ff ff ff 00 00 00 00 00 00 00 00 00 ff     ................
 ec:    ff ff ff ff ff ff ff ff 00 00 00 00                 ............
...
    show(i);
174:    8c ea           ldi r24, 0xAC       
176:    90 e4           ldi r25, 0x40       
...
    sendByte(pgm_read_byte(data[led]));     // blue
12c:    a8 2f           mov     r26, r24
12e:    b9 2f           mov     r27, r25
130:    ec 91           ld      r30, X
132:    f0 e0           ldi     r31, 0x00   
134:    f0 5c           subi    r31, 0xC0   
136:    e0 81           ld      r30, Z

    void sendByte(const unsigned char byte){
      PORTB = byte;
138:    e2 b9           out     0x02, r30   

We see that registers r24 and r25 are loaded with the address 0x40AC, which is the data memory address corresponding to flash program memory address 0x00AC (our array data). Further down we see r24/r25 copied into r26/r27 (the 'X' address pointer) and r30 loaded from that memory location (your LED data byte). However that value is then used as the low byte of another address, loading r30 again via r30/r31 (the Z address pointer)!

This is wrong. Why?

pgm_read_byte() takes as its argument a program memory address, so you must give it the address of your array element (not its value) using the '&' operator; ie.

sendByte(pgm_read_byte(&data[led]));

With this change the generated code becomes:-

    sendByte(pgm_read_byte(&data[led]));     // blue   
 12e:   e8 2f           mov r30, r24
 130:   f9 2f           mov r31, r25
 132:   60 81           ld  r22, Z

void sendByte(const unsigned char byte){
   PORTB = byte;
 134:   62 b9           out 0x02, r22   

Now the LED data byte is simply read from the array into r22, then output via sendByte().

Hopefully this will work for you (I don't have an ATTiny10 or a simulator for it, so I can't verify that the compiler is generating the correct code). Later versions of gcc can use the attribute __flash instead of PROGMEM, which may be easier to work with.

\$\endgroup\$
  • \$\begingroup\$ I change the call to an address as sugested but did not change the result :( Which options / flags did you use? \$\endgroup\$ – Steve Mar 27 '19 at 12:02
  • \$\begingroup\$ Compiler flags:- "-Wall -gdwarf-2 -std=gnu99 -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums". Are your arrays still being optimized out? \$\endgroup\$ – Bruce Abbott Mar 27 '19 at 23:50
  • \$\begingroup\$ It's still optimized out. Even with your flags :( also tried __flash but nothing changed \$\endgroup\$ – Steve Mar 28 '19 at 15:05
  • \$\begingroup\$ I compiled the exact same code as you posted here (except for replacing //bit banging stuff here with PORTB = byte;) using the same compiler (avr_gcc v5.4.0) and - as expected - it did not optimize out the arrays. The usual reason for code being optimized out is that it has no effect on the final output, so make sure your sendByte() function is actually using the array data! __flash eliminates the need for pgm_read_byte() when referencing data in ROM (read compiler documentation on how to use it). \$\endgroup\$ – Bruce Abbott Mar 28 '19 at 18:48
  • \$\begingroup\$ I also tested the sendByte with sendByte(pgm_read_byte(&i[0]); in main which prevented i to be optimized out \$\endgroup\$ – Steve Mar 29 '19 at 16:28

Not the answer you're looking for? Browse other questions tagged or ask your own question.