How are lookup tables implemented in 8052 assembler?
Submitted By: Jan Waclawek FAQ Last Modified: 07/16/06
- (The following FAQ is combined from contributions of Andy Neil and Phillip M Gallo)
Lookup table is a general programming technique - not restricted to microcontrollers (even Excel has lookup tables!) In fact, the answer is all in the name; it's just a table (or "array" in programmer speak) in which you look-up a value to find the answer to some question.
In 'C', we could implement a lookup table (array):
y = f[x]where f is an array in which each element contains the value of y corresponding to the index x.
One application is in implementing trigonometric functions; e.g. sinus:
int sin_table[] = { 0, // 1000 * sin(0) 17, // 1000 * sin(1) 35, // 1000 * sin(2) : 500, // 1000 * sin(30) : 707, // 1000 * sin(45) : 1000 // 1000 * sin(90) };Note the factor of 1000 - to give useful integer values (sometimes known as "fixed point")
For example, given the task of converting a nibble into an ASCII Hex Digit, the following look-up table approach can be used:
Use the nibble in the accumulator as an index into a table of ASCII characters constructed to correlate to the nibbles Hex value.
The Conversion Table:
Hex_table: db '0','1','2','3' db '4','5','6','7' db '8','9','A','B' db 'C','D','E','F'Input: Least significant nibble of accumulator has the 4 bit value to convert.
mov dptr,#hex_table ;Point to table base movc a,@a+dptr ;Acc nibble = Table indexThe accumulator now has the ASCII char corresponding to the nibble hex value. If you enter with the accumulator = 00001000b=08H you will exit with the ASCII Value for '8'(38H). If you enter with the accumulator = 00001110b=0EH you will exit with the ASCII Value for 'E' (45H).
Easy wasn't it? Well probably too easy as we did not validate that the Acc had value greater than 0FH. Good practice dictates that we validate the input or our index could end up invalid. In the routine above you could just "ANL A,#0FH" and force the value to validity, but, if you are having problems, this could mask it. It's best to "bound" the value, erroring out to a handler upon invalid input.
Another Look Up routine that is very common in 8051 code is that where canned messages are output to a terminal or display based upon a value in the accumulator. Message size is fixed to fill a display field.
Task: Display one of 4 messages on a display device based upon the value in the accumulator.
Method:Multiply accepted values by the message length.
The message table struct:
msg_table: db 'Begin ' db 'More ' db 'Enter >' db 'Quit ? 'Note there are only 4 entries so we must bound the input to values = 0,1,2 or 3. Also note each message is 7 characters long (spaces are appended to shorter message to make them 7 characters long).
Input: Acc = Message Entry Number. Check input value, reject any value greater than 3.
mov dptr,#msg_table ;Init the base of the Message Table ; ************************ ; *** Input Validation *** ; ************************ cjne a,#3,eval_acc ;value is other than 3? sjmp msg_index ;if equal, accept eval_acc: jnc input_err ;If we didn't carry the value exceeds that allowable. ; ***************************** ; *** Compute Message Index *** ; ***************************** msg_index: mov b,#7 ;Init msg length multiplier mul ab ;Multiply Index (Element Number) by Element size. add a,dpl ;Add the Index Value to the low byte of dptr mov dpl,a mov a,dph ;Correct for any carry (cross page boundry) addc a,b mov dph,a ; ******************************************* ; *** We have an index into the Msg Table *** ; *** Output the Selected Message *** ; ******************************************* mov r2,#7 ;Init a msg counter - msg length msg_loop: clr a ;Index already in dptr so clear acc movc a,@a+dptr inc dptr ;Point to next char call txo ;Output to display djnz r2,msg_loop ;Down count through available chars. ret input_err: (error handling) txo: (your display routine. (Must not contaminate DPTR or R2)
For further tweaking on the above code, see the followup to this FAQ.
Can the lookup table routines be further tweaked?
Submitted By: Jan Waclawek FAQ Last Modified: 07/16/06
- The above two examples can be further tweaked, for code simplicity, for flexibility in modification, or simply for "programming beauty".
In the first example, a sanity check is proposed for input values higher than 0Fh. Here it is:
NibbleToAscii: cjne a,#10h,NibToAscX1 ;compare and jump nowhere, just to set carry NibToAscX1: jc NibToAscX2 ;if <10h, proceed mov a,#'?' ;otherwise return a question mark to indicate error ret NibToAscX1: mov dptr,#Hex_table ;this is the old lookup routine movc a,@a+dptr ret
Also, in this example, the lookup can be accomplished using the other move-from-code-memory instruction of '51, which uses the program counter (PC) as the base address rather than DPTR. For that, the displacement from the current value of PC. Advantage is, that the value of DPTR remains preserved, but there are limitations: the table should be less than 256 bytes long and placed closely after the lookup routine - this all is fulfilled for this example (the above sanity check omitted now for clarity):
NibbleToAscii: add a,#Hex_table-NibToAscX1 ;add offset movc a,@a+pc NibToAscX1: ret Hex_table: db '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
The second routine - message lookup - works for all data values. More typically ASCII Messages are tabled with a trailing 0 (null terminator) this would change the message routine in following way:
- The message multiplied would be 8 as we have added a "00H" to the end of each table element.
- We would not need a loop counter.
Our look up could now follow as:
;******************************************* ;*** We have an index into the Msg Table *** ;*** Output the Selected Message *** ;******************************************* msg_loop: clr a ;Index already in dptr so clear acc movc a,@a+dptr ;Fetch a tabled char jz msg_loop_exit ;If it's a Null we are done inc dptr ;Point to next char call txo ;Output to display jmp msg_loop ;Loop until Terminator is found msg_loop_exit: retThe routine above is typical of ASCII handling but it's sensitivity to the null value eliminates it's suitability for variable data.
Some other ways to make a lookup table (for the messages/text)?
Submitted By: Jan Waclawek FAQ Last Modified: 07/16/06
- Certainly.
The first would use a lookup table to figure out, where the messages start. This is to make the messages variable length - more flexible to change and also spares code memory.
msg_table: dw msg1,msg2,msg3,msg4,msg_end msg1: db 'Begin' msg2: db 'More' msg3: db 'Enter >' msg4: db 'Quit ?' msg_end:
Now our "print the A-th message" routine (omitting the "sanity check") will look like:
rlc a ;calculate pointer - 2 bytes per entry mov dptr,#msg_table ;get the base address of the "pointers' table" add a,dpl ;and add the offset mov dpl,a clr a ;higher byte assumed to be 0 addc a,dph ;(-> max. 128 entries in the table) mov dph,a clr a movc a,@a+dptr ;get the upper address mov r2,a ;store it temporarily inc dptr clr a movc a,@a+dptr ;get the lower address mov r3,a ;store also this inc dptr inc dptr ;now get the lower address clr a ;of the following message movc a,@a+dptr ;to calculate length of this message clr c ;- as the max.length of message is 256 chars subb a,r3 ;this is correct even if boundary cross mov dpl,r3 mov dph,r2 ;now store the pointer mov r2,a ;and message length msg_loop: clr a ;Index already in dptr so clear acc movc a,@a+dptr inc dptr ;Point to next char call txo ;Output to display djnz r2,msg_loop ;Down count through available chars. ret
The caveat to this method is, that one should first observe, in which order one's assembler stores the address bytes when using "dw". However, 8051 assemblers tend to obey an unwritten convention, according to which words are stored in "big endian" order, i.e. high byte first.
Of course, the message length calculation can be replaced by the "zero-terminated string" method, as discussed above. Note however, that if the length calculation is used, the "pointers' table" should contain one more entry, msg_end, to be able to calculate the last message's length.
The last presented method is in fact not a table lookup, rather it is table (index) search. It assumes zero-terminated, random-length messages:
msg_table: db 'Begin',0 db 'More',0 db 'Enter >',0 db 'Quit ?',0 db 0
Note, that the whole table is terminated by a double-zero now.
Our routine can now be like:
mov r2,a ;store the index mov dptr,#msg_table ;and start looking up at the tables' beginning MessageLoop: clr a movc a,@a+dptr ;check, if it is not the table end (double zero) jz error_msg ;if so, report "index out of bounds" cjne r2,#0,CharacterLoop ;if valid message, check index sjmp MessageFound ;if index is zero, this is our message CharacterLoop: clr a ;find end of message (terminating zero) movc a,@a+dptr inc dptr jnz CharacterLoop dec r2 ;decrement index sjmp MessageLoop ;and loop until message found MessageFound: ;now transmit msg_loop: clr a ;Index already in dptr so clear acc movc a,@a+dptr jz msg_end ;if terminating zero found, stop inc dptr ;Point to next char call txo ;Output to display sjmp msg_loop msg_end: ret
Note, that now we don't need any sanity check on the input value: a simple trick with an added zero at the end of table will do. So there is now no need to update a constant when adding a message to the table. Further advantage is, that now there is absolutely no limit on the message length. Disadvantages are the need for the terminating zero (i.e. message itselft can't contain zero), and the execution time, which even depends on the input index and the table content itself.
Add Information to this FAQ: If you have additional information or alternative solutions to this question, you may add to this FAQ by clicking here.