Today I'm going to discuss how to read data from a table that crosses a 256 byte boundary.
The easiest and fastest way to do this is to use special "table read" instructions. See table lookups for details. [FIXME: should I move that information from there to here ?]
But some PIC processors don't have those special "table read" instructions. Are we out of luck ? No. We can extend the 'computed GOTO/RETW-style' table so it can handle tables that cross a 256 byte boundary.
Tony Nixon says:
Table can access 1 to 8178 bytes (assuming max ROM of 8K)fcall Table ; will be returned here with data in W. ; Tony Nixon ; plus tweak from Dmitry Kiryashov [zews at AHA.RU] Table movlw High(TStart) addwf OffsetH,W movwf PCLATH movlw Low(TStart) addwf OffsetL,W skpnc incf PCLATH,F movwf PCL ;computed goto with right PCLATH ; end of Table subroutine. ; ... ; org 0x???? TStart Retlw d'0' Retlw d'255' Retlw d'9' .... etcAs an example of how to use it, say you want to output speech data at 5K samples per second. A simple R2R ladder is on PortB. The speech table has 3000 data points
; Tony Nixon ; modified by David Cary ;(macros go here) ; the fcall macro ; by Roger Froud of Amytech Ltd. fcall macro subroutine_name local here lcall subroutine_name ; set PCLATH correctly pagesel here ; set PCLATH correctly here: endm ;(execution starts here) Start clrf OffsetH clrf OffsetL SoundLoop ; 200uS loop time = 5000 samples per second fcall Table movwf PORTB ;[FIXME: wait the remaining 200 microseconds] incf OffsetL ; add 1 to data pointer skpnz incf OffsetH NoUp movlw Low(d'3000') xorwf OffsetL,W skpnz goto SoundLoop movlw High(d'3000') xorwf OffsetH,W skpnz goto SoundLoop goto Start ; ; SOUND DATA TABLE 3000 ELEMENTS ; Table movlw High(TStart) addwf OffsetH,W movwf PCLATH movlw Low(TStart) addwf OffsetL,W skpnc incf PCLATH,F movwf PCL ;computed goto with right PCLATH ; end Table subroutine TStart DT "Hello. I'm Mr Ed" ; plus another 2984 data points endPS: the text won't sound like that coming from PORTB ;-)
PPS :if you want an easy way to create a 3000 point data table ready for MPASM see
Scott Dattalo says:
;******************************************************************* ;write_string ; ; The purpose of this routine is to display a string on the LCD module. ;On entry, W contains the string number to be displayed. The current cursor ;location is the destination of the output. ; This routine can be located anywhere in the code space and may be ;larger than 256 bytes. ; ; psuedo code: : ; char *string0 = "foo"; ; char *string1 = "bar"; ; ; char *strings[] = { string0, string1}; ; char num_strings = sizeof(strings)/sizeof(char *); ; ; void write_string(char string_num) ; { ; char *str; ; ; str = strings[string_num % num_strings]; ; ; for( ; *str; str++) ; LCD_WRITE_DATA(*str); ; ; } ; ; Memory used ; buffer2, buffer3 ; Calls ; LCD_WRITE_DATA ; Inputs ; W = String Number ; write_string ; Make sure the input string_num is in range. andlw WS_TABLE_MASK ; get pointer into the ws_table array: ; [w,buffer3] = wstable + 2*string_num movwf buffer3 addwf buffer3,w ; [FIXME: the carry from doubling w is lost, ; limiting us to a maximum of 128 strings -- ; add code here to handle up to 256 strings] addlw LOW(ws_table) ; carry set ... movwf buffer3 movlw HIGH(ws_table) skpnc ; ... carry used. addlw 1 ; now [w,buffer3] points to the lo byte of the address of the string. movwf PCLATH ; now [PCLATH,buffer3] points to the lo byte of the address of the string. ; get lo byte of the address of the string movf buffer3,w call ws2 ; buffer2 = lo byte of the address of the string movwf buffer2 ; get hi byte of the address of the string ; (handle special case of ws_table starting on odd address AND ; ws_table crossing a 256 byte page boundary) incf PCLATH,f incfsz buffer3,w decf PCLATH,f call ws2 ;get the high word (of the offset) ; now we have address of the start of the string in ; [w, buffer2] movwf PCLATH ; ws1: ;Now loop through the string movf buffer2,w call ws2 andlw 0xff skpnz ;If the returned byte is zero, return ;we've reached the end of the string ; exit point of write_string subroutine ; WARNING: we're holding the hi part address of the start of string ; in PCLATH right now -- ; -- so the LCD_WRITE_DATA subroutine, ; any subroutines it calls, and the actual string data ; must be on the same 2048 byte page. ; FIXME: remove this silly limitation. call LCD_WRITE_DATA incf PCLATH,f ;Point to the next character in the string incfsz buffer2,f decf PCLATH,f goto ws1 ; end of write_string subroutine ws2 movwf PCL ; WS_TABLE_MASK must be one less than the number of entries in the ws_table. #define WS_TABLE_MASK 3 ; #define WS_TABLE_MASK 127 ; use this when you have 128 strings ; The first part of the table contains pointers to the start of the ; strings. Each string has a two word pointer for the low ; and high bytes. ; no restrictions on table start location ; the number of entries in the ws_table must be a power of 2 ... ; 2, 4, 8, 16 ... up to 256 strings. ; If you only need, say, 5 strings, put 8 entries in the ws_table. ; It's OK for several entries in the ws_table ; to point to the same string. ws_table: retlw LOW(string0) retlw HIGH(string0) retlw LOW(string1) retlw HIGH(string1) retlw LOW(hello_string) retlw HIGH(hello_string) retlw LOW(hello_string) retlw HIGH(hello_string) ; no restrictions on string start location string0: dt "GPSIM WROTE THIS",0 hello_string: dt "Hello world." string1: dt "A STRING ON ROW 2",0
also:
David A Cary of Motorguide Pinpoint Says:
Someone mentioned
SoundLoop: ... incf OffsetL ; add 1 to data pointer btfsc STATUS,C incf OffsetHbut my little cheat sheet claims that `incf' only affects the Z bit, not the C bit. Is my cheat sheet leaving something out ? Or is there a bug in this code ? Or am I missing something ?
SoundLoop: ... incf OffsetL ; add 1 to data pointer btfsc STATUS,Z ; also known as skpnz incf OffsetH The heavy use of
btfsc STATUS,C ; also known as skpnc
elsewhere on this page looks OK, because they follow the ``addwf'' or ``addlw'' instructions, which do affect the carry bit. -- David Cary
[FIXME: Is there a page that lists "skpnz" "skpnc" etc. other than compcon.htm ? ]
Comments:
I think "David A Cary of Motorguide Pinpoint" has right: There has to be a bug in the first code on this page (from Tony Nixon). I used this code to convert a value from my ADC to another value. It works fine, until the code crosses a page... But the code from Dmitry Kiryashov works fine for me, even after page crossings! Thanks!
See pages.htm for more information about PCLATH.
Interested:
See:
Guy Griffin Says:
PCLATH is only modified explicitly, i.e. the PIC never changes it. Multiple computed GOTOs must set PCLATH appropriately before each PCL write, otherwise you'll get a jump to an unexpected address.
For example, a moving-message display: a small table lookup uses ADDWF PCL,f to returns the next ASCII code from a series. Then a large page-crossing table returns a column of pixels for display, probably modifying PCLATH in the process. This works OK the first iteration only - as the little table doesn't setup PCLATH, it gets implicitly loaded into PC the next time around... resulting in a jump to some unknown place in the middle of the code, and a large headache to debug.