[PASM] calling the right sub-routine depending on a value

I'd like to call one of perhaps 40 different sub-routines based on the value stored in a register...

For instance a "command_ID" value of 1 should cause the CMD_WATCHDOG sub-routine to be called (and in fact this part does work).
                        cmp     command_ID, #0 WZ    
              IF_E      MOVS    :Exec_MODIFIED_JUMP, #CMD_HARD_RESET
                        cmp     command_ID, #1 WZ    
              IF_E      MOVS    :Exec_MODIFIED_JUMP, #CMD_WATCHDOG          
                       'many more of these.....

                        NOP
:Exec_MODIFIED_JUMP     CALL    #PLACEHOLDER


                       '......

PLACEHOLDER             NOP
PLACEHOLDER_ret         ret


                       '......

'-----------------------------------------------------------------
' COMMAND SUB-ROUTINES - these are called by self-modifying
'   code at ":Exec_MODIFIED_JUMP" above, see comments!
'-----------------------------------------------------------------

                       '......

'-----------------------------------------------------------------
CMD_WATCHDOG
                        'debug
                        mov     sertx_byte_out, #"W"
                        call    #Ser_TX_Byte
                        mov     sertx_byte_out, #"D"
                        call    #Ser_TX_Byte
                        mov     sertx_byte_out, LINEFEED
                        call    #Ser_TX_Byte
          
CMD_WATCHDOG_ret        ret          
'-----------------------------------------------------------------
                       '......

The problem is that the code never returns to the point just after :Exec_MODIFIED_JUMP.

I think this is because it's the compiler that deals with the returning address for the CALL opcode, but I'm using MOVS to alter the jump address myself, so perhaps the compiler is actually putting the return address into PLACEHOLDER_ret always.

How can I rewrite this code so that my subroutines know where to return to?

Comments

  • To fix the returns, use your own return pointer.

    change this
    :Exec_MODIFIED_JUMP     CALL    #PLACEHOLDER
    
    to
    :Exec_MODIFIED_JUMP     JMPRET    return_ptr,#0
    
    (you can get rid of the PLACEHOLDER now. Also don't forget to allocate the return_ptr variable somewhere)
    Then, instead of RET, do a JMP return_ptr (WITHOUT THE #).

    Also, you might be able to reduce the memory usage by using a jump table, like this
                            MOVS :Exec_MODIFIED_JUMP,#JUMPTABLE
                            ADD   :Exec_MODIFIED_JUMP,command_ID
    
                            NOP
    :Exec_MODIFIED_JUMP     JMPRET    return_ptr,0
    
    JUMPTABLE LONG CMD_HARD_RESET, CMD_WATCHDOG ' more of those
    
  • Jump table is definitely the way to go when you have more than 3-4 commands.
  • Wuerfel_21Wuerfel_21 Posts: 564
    edited 2020-01-04 - 17:01:04
    Another alternative that can sometimes be advantageous is the DJNZ chain:
                            rdbyte command_ID,somewhere  wz
                      if_nz jmp #not_cmd0
                            ' Implement command 0 here
                            jmp #somewhere_else
    
    not_cmd0                djnz command_ID,#not_cmd1
                            ' Implement command 1 here
                            jmp #somewhere_else
                            
    not_cmd1                djnz command_ID,#not_cmd2
                            ' Implement command 2 here
                            jmp #somewhere_else
    
    not_cmd2                djnz command_ID,#not_cmd3
                            ' Implement command 3 here
                            jmp #somewhere_else
                            
                            ' You get the idea
    
  • Wuerfel_21 wrote: »
    ...Also, you might be able to reduce the memory usage by using a jump table, like this...

    Fantastic! It works well and is efficient in both time and memory. Thank you very much.

  • JonnyMacJonnyMac Posts: 6,423
    edited 2020-01-05 - 19:29:26
    I'm kind of an intermediate level PASM coder -- so my code is probably a bit more verbose than from the pros (who do really cool things in PASM). This is how I setup my jump tables to keep the code clean and easy to follow, and to handle illegal command values.
    getcmd                  rdlong  t1, par                 wz      ' check for command
            if_z            jmp     #getcmd
            
                            min     t1, #0                          ' limit range
                            max     t1, #4                          ' commands + 1
                            add     t1, #jmptable                   ' prep for jump
                            jmp     t1                              ' do it
                                                                     
    jmptable                jmp     #cmdexit                         
    do_cmd1                 jmp     #cmd1                            
    do_cmd2                 jmp     #cmd2                            
    do_cmd3                 jmp     #cmd3                            
                                                                     
    cmdexit                 mov     t1, #0                          ' clear old command
                            wrlong  t1, par
                            jmp     #getcmd 
    
                            
    ' handler from cmd == 1
    
    cmd1                    ' code here
                            ' -- write result to p1pntr
    
                            jmp     #cmdexit
    
  • Thanks JonnyMac. I've been in Atmel land for so long that I forgot a lot of this kind of thing. It's so nice to have so much RAM to play with for a change, I'm probably 30% of the way through my project and I've not yet used up 10% of available RAM! Such an awesome chip.
Sign In or Register to comment.