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.



