= Subroutines =

A subroutine is a commonly used section of code that is used many times in a program.

There is a list of common subroutines between the 8D hac and the 60 hac.

For example, if you are always doing a delay of a certain length, you would make a subroutine that delays that amount. Then you would use the JSR instruction to Jump to SubRoutine? with an address like this:

JSR LF000

LF000 is a label and it really should be called something like this:

JSR Ldelay

So somewhere else in the code there will be a label that defines the memory address:

LF000: RTS; Return after the delay

Now the amount of time it takes to jump to a subroutine and return will always be the same with a processor running at the same speed, so this little function will always delay the same time.

The RTS instruction means to return back to the code that originally called it.

How does it know how to do this? It uses the stack (think of a stack of plates). When the JSR is used, it saves the current program counter on the stack. When the RTS instruction is used, it takes the value from the top of the stack and goes back to running from that location.

== Branching rather than jumping ==

Sometimes you will see things like this:

BSR LFF3B? ; Go to the multiply subroutine

Now, why would the programmer do a branch rather than a jump? The programmer was being efficient, because doing a BSR uses a 1 byte RELATIVE address. A JSR takes a 2 byte EXTENDED address.

The problem with doing a BSR is that it can only go from -127 to +128 bytes from it's current location. A JSR can go anywhere in the PROM from 0 to $FFFF.

You can save some space in your code if you know that your subroutine is going to be somewhere in the range of -127 to +128 bytes away. As a programmer, you don't actually need to know the exact number because you are using a 68HC11Labels|label? for the subroutine. The assembler will warn you if the subroutine is too far away. A '''SMART''', optimizing assembler will replace any JSRs that you use with BSRs if the called subroutines are close enough. Who knows, maybe it could save you 20 bytes. That's a lot in the programming world.

== More advanced example ==

Now, smart programmers will always document their subroutines, because there are no specific rules with how you get data into subroutines.

In the GM code, it seems like they use the following guidelines:

  • To pass in 1 byte, put it in the A accumulator.
  • To pass in 2 bytes, put one in the A and the other in the B accumulator
  • To pass in a 16 bit (2 byte) value, put the MSB in B and LSB in A.
  • To return values, put them in the A, B or both registers.

If you are writing new subroutines, ALWAYS comment them with what you need to call it, what registers it uses/overwrites, and what it writes to output. Even better programmers will save the values of the registers they use on the stack and restore them when the subroutine returns.

************************************************* * Xmit to SPI Data * * DATA FROM A Reg LOAD SPI SR AND TRANSMITS * TO SELECED DEVICE VIA SPI * * CALL: * A = DATA TO BE XMITED VIA SPI * * RETURNING: * A = RX'ED SPI DATA (O IF SPI FAIL) * *************************************************

LF1F9? : PSHX ; Save X reg STAA L4000 ; To SPI S/Reg. LDX #$4001 ; BCLR 0,X,$80 ; Clr b7, TO START SPI XMIT ;

; WAIT FOR SPI TO FINISH, 256 CYC MIN ; LDAA #22 ; TIME DELAY VAL CLC ; CLR CY, SPI I/O ERR FLG

LF206: BRSET 0,X,$80,LF210 ; BR IF b7, SPI DONE ; ... else DECA ; DEC DELAY VALUE BNE LF206 ; BR IF DELAY NOT DONE ; ... else SEC ; BRA LF213 ;

; ; GET RETURNED DATA FROM SPI ;

LF210: LDAA L4000 ; Fm SPI S/Reg

LF213: PULX ;

RTS ;

If you read the comments, you should be able to use the subroutine without actually reading what it does, but that wouldn't be any fun! So here's the breakdown:

LF1F9? : PSHX ; Save X reg

This pushes the value in the X register onto the stack. Remember this for when the subroutine is done. The label LF1F9? should actually be called something like LxmitSPI? to give it a bit more memorable name.

STAA L4000 ; To SPI S/Reg.

This is where the subroutine takes the data that you passed in to it. It grabs whatever is in the A register and puts it at memory location $4000. The user needs to make sure they set up the A register beforehand. This is a special location in the 68HC11MemoryMap|memory map? because it is for the SPI register.

The rest of the code does some stuff. If anyone else wants to fill it in, go ahead. An important thing to notice is that the X register gets destroyed here:

LDX #$4001 ;

LF210: LDAA L4000 ; Fm SPI S/Reg

This is one of the important parts: Saving the data from the SPI bus, which will then be returned in the A register. Whoever called the subroutine will use this result somehow.

LF213: PULX ;

RTS ;

These last two lines are very important. Remember when we saved the X register because it got destroyed? The PULX command takes whatever is on the top of the stack (which was the contents of the X register) and puts it in the X register. (There are equivalent instructions for the A and B registers as well)

The RTS command returns us from the subroutine and back to whoever called the subroutine. It does some tricks behind your back... the address of the program counter is saved on the stack when the routine was called, and the RTS instruction restores it.

= Reentrant =

Programmers should always make subroutines 'reentrant'. This means that they can be called multiple times by a larger subroutines.

Because registers are very limited on the processor, the programmer must always be aware which registers they are affecting in their subroutine.

If you don't do this, you'll end up corrupting the values in your larger routines and it will cause mysterious bugs in '''other''' places in your program.

The way to save the registers is to push them on the stack in the beginning, then pop (PUL instructions) them off the stack in the reverse order.

Every time you change a subroutine you should doublecheck which registers are affected.

= Good Subroutines =

What makes a good subroutine? Here are a few guidelines, but remember than rules can be broken:

  • '''Reentrant''': You never know how people are going to use your code, so make sure that it is reentrant.
  • '''Stack''': Always make sure it cleans up the stack, otherwise after running for a while, your code will mysteriously crash.
  • '''Single point of entrance, single point of exit''': This is something that gets broken once in a while (in the $58 code) and it causes confusion for programmers. Ideally, you should always have 1 RTS instruction only. All of the other times you should BRA (branch always) to the RTS instruction at the very end. I know that it uses an extra byte and wastes 3 clock cycles, but it is so much more readable.
  • '''Documentation''': This is at a level above assembly program since it doesn't affect the code that is generated, but it makes life a hell of a lot easier for other people to use your code.

People should never need to read your subroutine to understand what is going to happen, it should be all described in the comments.

A good subroutine will have:

  • Descriptive Short Name: mul8x8 is good, func1 is bad!
  • Long Description: This should summarize what happens.
  • Parameters: A description of what needs to be in place before the programmer jumps to a subroutine.
  • Return parameters: When the subroutine returns, what happened? Is there a result stored somewhere?
  • Side effects: What else happens?
  • Clock cycles: How many clock cycles does this take? It may depend on the parameters that are used.
  • Subroutines Used: What subroutines does it use internally?

-- AlexHarford - 30 Jun 2006

Topic revision: r1 - 30 Jun 2006 - 22:53:19 - AlexHarford
 
This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback