Next Previous Contents

7. Hardware Drivers

Oftentimes, it can be advantageous to create small application helpers in assembly language to decrease codespace and increase execution speed of the overall program. An example of this would be the transfer of characters to a FIFO (First-In, First-Out) storage buffer for transmission over a serial port. This simple action could be performed by an assembly language driver which would execute much quicker than coding it in C. The following discussion outlines a method of interfacing a C program with an assembly language subroutine.

The first step in creating the assembly language code for the driver is to determine how to pass the C arguments to the assembly language routine. The cc65 toolset allows the user to specify whether the data is passed to a subroutine via the stack or by the processor registers by using the __fastcall__ function declaration (note that there are two underscore characters in front of and two behind the fastcall declaration). When __fastcall__ is specified, the rightmost argument in the function call is passed to the subroutine using the 6502 registers instead of the stack. Note that if there is only one argument in the function call, the execution overhead required by the stack interface routines is completely avoided.

Without __fastcall__, the argument is loaded in the A and X registers and then pushed onto the stack via a call to pushax. The first thing the subroutine does is retrieve the argument from the stack via a call to ldax0sp, which copies the values into the A and X. When the subroutine is finished, the values on the stack must be popped off and discarded via a jump to incsp2, which includes the RTS subroutine return command. This is shown in the following code sample.

Calling sequence:

        lda     #<(L0001)  ;  Load A with the high order byte
        ldx     #>(L0001)  ;  Load X with the low order byte
        jsr     pushax     ;  Push A and X onto the stack
        jsr     _foo       ;  Call foo, i.e., foo (arg)

Subroutine code:

_foo:   jsr     ldax0sp    ;  Retrieve A and X from the stack
        sta     ptr        ;  Store A in ptr
        stx     ptr+1      ;  Store X in ptr+1
        ...                ;  (more subroutine code goes here)
        jmp     incsp2     ;  Pop A and X from the stack (includes return)

If __fastcall__ is specified, the argument is loaded into the A and X registers as before, but the subroutine is then called immediately. The subroutine does not need to retrieve the argument since the value is already available in the A and X registers. Furthermore, the subroutine can be terminated with an RTS statement since there is no stack cleanup which needs to be performed. This is shown in the following code sample.

Calling sequence:

        lda     #<(L0001)  ;  Load A with the high order byte
        ldx     #>(L0001)  ;  Load X with the low order byte
        jsr     _foo       ;  Call foo, i.e., foo (arg)

Subroutine code:

_foo:   sta     ptr        ;  Store A in ptr
        stx     ptr+1      ;  Store X in ptr+1
        ...                ;  (more subroutine code goes here)
        rts                ;  Return from subroutine

The hardware driver in this example writes a string of character data to a hardware FIFO located at memory location $1000. Each character is read and is compared to the C string termination value ($00), which will terminate the loop. All other character data is written to the FIFO. For convenience, a carriage return/line feed sequence is automatically appended to the serial stream. The driver defines a local pointer variable which is stored in the zero page memory space in order to allow for retrieval of each character in the string via the indirect indexed addressing mode.

The assembly language routine is stored in a file names "rs232_tx.s" and is shown below:


; ---------------------------------------------------------------------------
; rs232_tx.s
; ---------------------------------------------------------------------------
;
; Write a string to the transmit UART FIFO

.export         _rs232_tx
.exportzp       _rs232_data: near

.define         TX_FIFO $1000    ;  Transmit FIFO memory location

.zeropage

_rs232_data:    .res 2, $00      ;  Reserve a local zero page pointer

.segment  "CODE"

.proc _rs232_tx: near

; ---------------------------------------------------------------------------
; Store pointer to zero page memory and load first character

        sta     _rs232_data      ;  Set zero page pointer to string address
        stx     _rs232_data+1    ;    (pointer passed in via the A/X registers)
        ldy     #00              ;  Initialize Y to 0
        lda     (_rs232_data)    ;  Load first character

; ---------------------------------------------------------------------------
; Main loop:  read data and store to FIFO until \0 is encountered

loop:   sta     TX_FIFO          ;  Loop:  Store character in FIFO
        iny                      ;         Increment Y index
        lda     (_rs232_data),y  ;         Get next character
        bne     loop             ;         If character == 0, exit loop

; ---------------------------------------------------------------------------
; Append CR/LF to output stream and return

        lda     #$0D             ;  Store CR
        sta     TX_FIFO
        lda     #$0A             ;  Store LF
        sta     TX_FIFO
        rts                      ;  Return

.endproc


Next Previous Contents