In a program, I have a lot of arrays of different length strings, and each array is declared as an array of pointers to those strings, like:
static const char * num_tab[] = {"First", "Second", "Third"};
static const char * day_tab[] = {"Sunday", "Monday", "Tuesday"};
static const char * random_tab[] = {"Strings and arrays can have", "diferent", "lenghts"};
The (pointer to) strings are returned from simple functions such as:
const char * dayName(int index) {
return day_tab[index];
}
Under the AVR architecture, those strings need to be stored in program memory. I understand that the functions need to be changed in order to work also on AVR's, (they need to copy the string from program memory to a buffer in ram, and return a pointer to that instead).
How can I change the arrays' initialization to use PROGMEM, without the need to name each individual string?
The only way I found is to define each string with a name (and PROGMEM), and define an array of pointers initialized with pointers to those strings:
static const char d1[] PROGMEM = "First";
static const char d2[] PROGMEM = "Second";
static const char d3[] PROGMEM = "Third";
const char * const day_tab[] = {d1, d2, d3}; // only needs PROGMEM for large arrays
This works, but for large arrays of different sizes, it changes the code from a few lines to hundreds, which makes maintenaince practically imposible. Also, adding or removing a value from an array, will need a renumbering of all of the following items.
As your question is tagged "C", GNU-C and named address-spaces as of ISO/IEC DTR 18037 may be a way to go. Compile with -std=gnu99
or higher:
#define F(X) ((const __flash char[]) { X })
static const __flash char *const __flash nums[] =
{
F("first"), F("second"), F("third")
};
#include <stdio.h>
void print_num (int id)
{
printf ("num = %S\n", nums[id]);
}
The generated code for the nums[]
array is:
.section .progmem.data,"a",@progbits
.type nums, @object
.size nums, 6
nums:
.word __compound_literal.0
.word __compound_literal.1
.word __compound_literal.2
.type __compound_literal.2, @object
.size __compound_literal.2, 6
__compound_literal.2:
.string "third"
.type __compound_literal.1, @object
.size __compound_literal.1, 7
__compound_literal.1:
.string "second"
.type __compound_literal.0, @object
.size __compound_literal.0, 6
__compound_literal.0:
.string "first"
The code to print the string uses LPM to read nums[id]
, which is again in progmem and printed using the %S
format specifier:
print_num:
lsl r24 ; 33 [c=8 l=2] *ashlhi3_const/1
rol r25
movw r30,r24 ; 26 [c=4 l=1] *movhi/0
subi r30,lo8(-(nums)) ; 8 [c=8 l=2] *addhi3/1
sbci r31,hi8(-(nums))
lpm r24,Z+ ; 27 [c=8 l=2] *movhi/2
lpm r25,Z+
push r25 ; 11 [c=4 l=1] pushqi1/0
push r24 ; 13 [c=4 l=1] pushqi1/0
ldi r24,lo8(.LC0) ; 14 [c=4 l=2] *movhi/4
ldi r25,hi8(.LC0)
push r25 ; 16 [c=4 l=1] pushqi1/0
push r24 ; 19 [c=4 l=1] pushqi1/0
call printf ; 20 [c=16 l=2] call_value_insn/1
; SP += 4 ; 21 [c=4 l=4] *addhi3_sp
pop __tmp_reg__
pop __tmp_reg__
pop __tmp_reg__
pop __tmp_reg__
ret ; 31 [c=0 l=1] return
Note: You can port this to archs without __flash
by means of:
#ifndef __FLASH
#define __flash /* empty */
#endif
The macro __FLASH
is a builtin macro in avr-gcc and only defined when address-space __flash
is present.
Print modifier %S
prints a string in progmem / flash. (On compliant platforms it stands for a wide string.) So you have to use %s
instead.