The first way to access ROM data from program is to embed that data in the code itself, exactly like the immediate operands are hard-coded in instructions. This goal can be achieved by locating the data at the return address of the subroutine.
Since the return address can be found in the TOSL/TOSH/TOSU registers, the subroutine is able to access the ROM data. Although it seems freestyle, this way is very handy from the end-user point of view. For example, the LCD library provides the void lcd_RIprint_() ; routine that uses this type of parameter passing. As a «first step» example, the following code sends a message to a LCD display:
lcd_RIprint_(); ROM_TXT("Hello !\0") ;
A better way to do that is to use a macro that hides the real nature of the message:
#define lcd_RIprint(txt) { lcd_RIprint_() ; ROM_TXT(txt) ; }
Finally, the following code will send the message:
lcd_RIprint("Hello !\0") ;
In this example, the 'R' stands for ROM and the 'I' stands for immediate.
Writing a function such as lcd_RIprint_()
is not easy because it needs a clear understanding of the PIC-18 instruction set and how the program is compiled.
People interested by this point can read the code in the file lcd.slb, that is written in assembly language.
However, it is perfectly possible to write such a function in C.
For this purpose, the rom.h header provides several very handy macros:
This macro mainly copy the TOSx registers to the TBLPTRx registers, and set bits needed to access ROM.
Reads one byte of data from ROM. The fetched data is stored in the prodl C variable, that is just an alias for the PRODL register. Consecutive invocations of READ_ROMBYTE will read consecutive data from ROM.
Reads one word of data from ROM. The fetched data is stored in the prodhl C variable, that is just an alias for the PRODL/PRODH pair of registers. Consecutive invocations of READ_ROMWORD will read consecutive data from ROM.
Ends the transaction with ROM. The TBLPTRx registers are copied back to TOSx registers. The macro takes care of alignment, so the address stored in TOSx, is always even. Obviously, the use of this macro is mandatory in this context.
Here is an example of how to uses the proposed macros. This example implements a ROM version of the following puts() routine.
void puts(char *p) { for( ; *p ; ++p) putchar(*p) ; }
The first step is to define a macro for convenience.
#define RIputs(str) { RIputs_() ; ROM_TXT(str) ;}The second step is to write a function that reads from ROM every char to be printed. The number of char can be odd or even because the FINISH_ROM_ACCESS macro restores a correct parity.
void RIputs_() { PREPARE_ROM_ACCESS ; READ_ROMBYTE ; while( prodl ) { putchar( prodl ) ; READ_ROMBYTE ; } FINISH_ROM_ACCESS ; }
Please note an important point: all the data stored in ROM must be read. Violating this rule will lead the processor to execute data instead of machine code and will crash the program. |
Here is another example: this routine fetches 16 bit data from a ROM table, and displays it. In this example, the data is preceded by a word indicating the size of the table.
#define RIputwords(list) { RIputwords_() ; ROM_WORDS(list) ; } void RIputwords_() { int k ; PREPARE_ROM_ACCESS ; READ_ROMWORD ; for( k = prodl ; k ; --k) { READ_ROMWORD ; outdec(prodhl) ; putchar(' ' ) ; } FINISH_ROM_ACCESS ; }
This function is very simple to use:
RIputwords("3, 1000, 2000, 3000") ;
Notice that despite the parameter of RIputwords() is a literal, the data really stored in ROM is a sequence of words (not a string of chars). Obviously, it is forbidden to nest functions that access ROM memory.
Alain Gibaud 2015-07-09