PIC Microcontoller Memory Method

Big Table lookups

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'
        ....
        etc

As 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

        end

PS: 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

http://www.bubblesoftonline.com/demo/dtimg.html

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 OffsetH

but 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:

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.