I'm writing the firmware for a simple testing device that would need to store a very large lookup table (I need 13500 entries with values 0..100 decimal). The target PIC MCU is PIC16F1829 (8K words of program memory), and the compiler is XC8.
Obviously, this won't work:
static const uint8_t table[13500] = { 0x01, 0x23, ... };
as it will be stored as a sequence of RETLW instructions, each taking one program word, and we only have ~8K of these.
However, I can pack two 7-bit values into one 14-bit word, so if I had the capability of doing
static const uint14_t table[6750] = { 0x1181, ... };
I can imagine that reading table[0] should translate to using the flash memory read functionality to read the progmem word there. I can do the unpacking afterwards.
The actual setup/work code on the MCU would be quite minimal, about 300 words, so I'm within bounds.
Is there a way to define such arrays cleanly (using C constructs only), or one has to resort to assembly, as discussed here?
Edit:
The intended use of this lookup table is to replay a specific waveform using filtered PWM. The setup/work code is very minimal, it only initializes the PWM module and writes the waveform to the PWM period register at regular intervals. I don't think the unpacking would add a lot of code, it's just a few shifts.
Edit #2:
Since some of answers/comments suggest or assume properties of the data and suggest not storing it in full, but using interpolation, computation or compression:
The data comes from measured physical quantity which was recorded via a separate device. Reproducing it verbatim is important (so interpolation isn't recommended). It is also reasonably random, since gzip only achieves 35% compression when the data is stored in binary, in bytes. Since each byte only uses 101 different values instead of 255, by Huffman encoding alone you'd expect ~17% compression. Also the cost of adding the inflate algorithm for this PIC is probably more than the compression saves...
Edit #3:
The RETLW trick the XC8 compiler uses seems to not be a common knowledge, so let's explain why I'm talking about RETLW all the time and dismissing answers because of it.
The 8-bit PIC micros are examples of a Harvard architecture, with separated code and data address spaces. So if you wanted an initialized global array like
char mytable[5] = { 1, 2, 3, 4, 5 };
storing these 5 bytes is not as simple as putting them somewhere in the flash inside the PIC and later mapping them into the data segment, You just cannot say "these 5 bytes are part of the data segment".
Since this code is valid C, XC8 has to implement it by running some initialization code that literally does something akin to mytable[0] = 1; mytable[1] = 2; ...
. This is very expensive in terms of code space required to do this initialization and is to be avoided by any seasoned PIC dev.
However, you can forego some of this penalty by making your table const:
const char mytable[5] = { 1, 2, 3, 4, 5 };
Since the compiler knows you cannot change those values, you might get away with not actually keeping them in the data address space. They may reside in the code address space only. So if you wanted mytable[i]
, the compiler internally replaces it with a function call (something like fetch_mytable(i)
), and implements this function using an array of instructions in the code address space, something like this
... prologue: address computation, jump to address ...
retlw 1
retlw 2
retlw 3
retlw 4
retlw 5
This is the body of the function fetch_mytable(i)
, and in the prologue you do a computed goto based on i
, and the respective RETLW instruction is jumped to; it stores the desired value into the W register and returns from the function.
This method is simple and quick, and is a first class citizen in the XC8 compiler environment - it does this kind of trick quietly under the hood for static/global const arrays.
My gripe with this method is that it takes 5 program words (70 bits = 5*14) to store just 40 bits worth of data (5*8). Each RETLW instruction "wastes" 6 bits for the RETLW opcode. Each PIC instruction is 14 bits, the program memory is organized into words of 14 bits, but this is not exposed in C.
Another approach for implementing a lookup table is similar, but instead of the RETLW array, up to 14 bit values can be stored in the program memory directly. And instead of doing a computed goto, use the program memory readout mechanism (EEADR/EECON1 et al) to read the program word of a particular address. This approach is slower, as it requires more boilerplate readout code, but doesn't waste any bits, and for a large array it really makes a difference.
So my question is whether this second approach can also be accessed from C, as a first class citizen, just like the other method. I've tried making an array of uint16_t's, but still the first method is used, with each uint16 being represented by a pair of RETLWs.
uint16_t
. \$\endgroup\$