PIC18 .asm Switch Construct

Thread Starter

joeyd999

Joined Jun 6, 2011
5,287
I know you all are using C to program PIC18s these days, but I am still crunching along writing awesome code in .asm on a daily basis -- and loving every minute of it.

I came up with a neat new trick today -- out of necessity -- and I wanted to share.

Suppose you had a number of subroutines, and you wanted to call one (and only one!) of them based upon an index value. The 'C' equivalent would be:

Code:
switch (index)
{
    case 0: routine0(); break;
    case 1: routine1(); break;
    ...
    case n: routinen(); break;
}
In .asm, you'd normally do something like this:

Code:
    movlw    high jtab    ;ensure jump in page
    movwf    pclath

    rlncf    index,w        ;double index
    addwf    pcl,f        ;and make computed jump

jtab    bra    vect0        ;vector to call for specified routine
    bra    vect1
    ...
    bra    vectn

vect0    call    routine0    ;and make the call
    bra    jdone        ; "break" equivalent
vect1    call    routine1
    bra    jdone
    ...
vectn    call    routinen
    bra    jdone

jdone                ;finished
There are a couple of things I don't like about this. First, the need to ensure pclath points to the proper page -- and the associated problem of ensuring that all destinations within the jump table are within that page -- gets annoying after a while. I previously solved this issue with another trick, a routine called CJUMP, detailed here. Using CJUMP, the code reduces to the following:

Code:
    movf    index,w        ;get index in wreg
    call    cjump        ;and jump to indexed vector

    bra    vect0        ;vector to call for specified routine
    bra    vect1
    ...
    bra    vectn

vect0    call    routine0    ;and make the call
    bra    jdone        ; "break" equivalent
vect1    call    routine1
    bra    jdone
    ...
vectn    call    routinen
    bra    jdone

jdone                ;finished
This is a bit better, as I no longer need to deal with PCLATH or worry about my jump table spanning page boundaries. But, I still have *two* tables. Can I reduce this to one simple table? Yes!

First, I want it to be possible that my subroutines may be anywhere in the address space, so I must use either CALLs or GOTOs, not RCALLs or BRAs. So I changed my CJUMP routine as follows:

Code:
;****************************************
;** CJUMP2 -- Preform a computed jump  **
;**           over 2 word instructions **
;****************************************
;**   w = index (0 to 63)              **
;****************************************

cjump2    rlncf    wreg,f

;********************************************
;** CJUMP -- Preform a computed jump       **
;**          over single word instructions **
;********************************************
;**   w = index (0 to 127)                 **
;********************************************

cjump    rlncf    wreg,f

    addwf    tosl,f
    skpnc
    incf    tosh,f

    return
I've now got two options: a computed jump into either a table of single word instructions or a table of double word instructions (CJUMP2). Using this, I can try something like:

Code:
    movf    index,w        ;get index in wreg
    call    cjump2        ;and jump to indexed vector

    call    routine0    ;and make the call
    call    routine1
    ...
    call    routinen

jdone                ;finished
But this is not correct. If the index is 0, routine0, routine1, and all the subsequent routines will be executed. I only want routine0 called, followed by a branch to JDONE. What to do?

Using the PIC18 PUSH instruction, I can preset the return stack to point to anywhere I like. If I set it to JDONE prior to indexing in the table, and use GOTOs instead of CALLs in the table, then I get what I desire:

Code:
    push            ;create a new stack entry
    movlw    high jdone    ;copy return address to stack
    movwf    tosh
    movlw    low jdone
    movwf    tosl

    movf    index,w        ;get index in wreg
    call    cjump2        ;and jump to indexed vector

    goto    routine0    ;and make the call
    goto    routine1
    ...
    goto    routinen

jdone                ;upon RETURN, each routine ends up here
Further, I can simplify with a macro:

Code:
switch    macro    idxreg,retaddr

    push            ;create a new stack entry
    movlw    high retaddr    ;copy return address to stack
    movwf    tosh
    movlw    low retaddr
    movwf    tosl

    movf    idxreg,w    ;get index in wreg
    call    cjump2        ;and jump to indexed vector

    endm
For final code that looks like this:

Code:
    switch    index,jdone    ;index to routine, exit at jdone

    goto    routine0    ;and make the call
    goto    routine1
    ...
    goto    routinen

jdone                ;upon RETURN, each routine ends up here
Short, sweet, easy to read. I thought this was pretty neat. Comments welcome.

PS -> Why does this new editor turn my tabs into spaces?!!!!!
 
Last edited:

NorthGuy

Joined Jun 28, 2014
611
Or you can read the table with TBLRD. You fit 16-bit jump addresses into consecutive instructions. You'll need one instruction per address as opposed to 2 instructions for GOTOs. Assuming the table is at address JUMPADDR in program memory, and ignoring "U" bytes (as you did):

Code:
movwf TBLPTRL
rlncf TBLPTRL
clrf TBLPTRH
movlw JUMPADDR_H
addwf TBLPTRH, f
movlw JUMPADDR_L
addwfc TBLPTRL, f
 
push
tblrd*+
movwf TOSL
tblrd*+
movwf TOSH
return
Will run a little bit faster and will use only half of the memory compared to GOTOs. Easily expandable to higher indices too.
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
5,287
Or you can read the table with TBLRD...
Um, yea. That's an alternative to the computed goto -- of which there are many alternatives. But that wasn't the point of my post.

I have proposed what I think is an elegant solution to a C style switch statement (with successive index and breaks at the end of each case) that is concise and easy to read and maintain in .asm.
 
Top