Assembly Language Guidelines

So, why would you need guidelines for your assembly language? Well, for a start, assembly is not a terribly structured language, so there aren't really any rules governing it. In a language like English this can be nice because it allows for creative expression and subtle nuances. However, these afforementioned itemes are not such a good idea when it comes to assembly. The intent behind a set of instructions is not necessarily particularly obvious. So without some explanation, a person reading the code can quickly loose the big picture of what is happening. Additionally, assembly programs, unlike a written language, use a large number of jumps that can leave the reader hopelessly confused as they try to figure out what

goto	KewllabLe1

or

decf reg1,f

or, (even worse)

goto sub1

might possibly mean and what its purpose is. Plus, a poorly written and structured program is very hard to debug and almost impossible to come back and modify. So, while you can write your programs any way you want, following some guidelines will probably almost certainly make your life easier at some point. That said, there is nothing to say that my guidelines are "right." They are just a suggestion, open to revision, to get you thinking about the subject.

  1. Comment your code

    Yes, that's right. Comment your code. I know, real men don't comment code. But real men also never have to edit their code. They either write it correctly the first time (which I seriously doubt ever happens) or they make other people edit it. And those other people certainly wish the code were commented.

  2. Comment your code!

    Yes, it really is that important. And don't write stupid comments like

    decf	temp1,f	;decrement temp1

    Anyone can see that! Write comments that explain what you are doing, why you are doing it, and how it fits into the overall flow of the program. Trust me, you will appreciate them when your code hits 1500 lines!

  3. Use a naming scheme

    My suggestion would be to have Microchip defined constants in all capitals, labels and user defined registers in lower case with word separated by underscores, and user defined constants having the beginning letters of each word capitalized and no spacing between word letters. Please use correct spelling, name functions, registers, and constants descriptively, and don't use many abbreviations. An abbreviation my seem obvious now but later you won't remember what it stood for. Examples:

     
    PORTB				;Microchip defined constant
    seven_segment_lookup_table	;User function
    bits_remaining			;User defined register
    TriggerInput			;User defined constant
    

    Using a coherant naming scheme can save you a lot of trouble when you are editing your code because you won't be guessing about what a particular name stand for.

  4. Use functions to modularize code

    Using functions (sections of code that you call) can make your code very easy to read, if you use descriptive names for your functions. Program flow quickly becomes apparent, and you will probably save code space because you will be more likely to re-use a function. And please do use the call instruction not a series of goto's. Using a call instruction tells the reader that after the function has completed its task (which should be very evident from its name) it is returning to the main program. The moment you use a goto, the reader has to actually find that label and see where the program is going from there. Consider the following code and how easy it is to follow:

    ;--------------------------------------------------------------------------------------------
    ;
    ;Function send_data
    ;
    ;Sends data to a MilesTag compatable sensor.
    ;Assumes that the high byte and low byte are in registers
    ;byte_1 and byte_2 and that the parity is in parity,0.
    ;Also assumes that the PWM has been appropriately set up send
    ;a 40khz carrier.
    ;
    ;The function requires a register bits_remaining
    ;
    send_data   movlw       d'16'                   ;We are sending 16 bits of data
                movwf       bits_remaining          ;(plus a parity bit which is handled at the end}
    send_header call        pwm_on                  ;Send a header pulse of 2400 instructions
                call        wait_2400_instructions
                call        pwm_off                 ;Send required off time
    send_data   call        wait_600_instructions   ;(dictated by data format)
                call        pwm_on
                btfsc       byte_1,7                ;If we are sending a 1
                call        wait_600_instructions   ;wait 1200 instructions
                call        wait_600_instructions   ;otherwise wait 600
                call        pwm_off
                rlf         byte_2,f                ;Rotate the next bit to send
                rlf         byte_1,f                ;into place
                decfsz      bits_left,f             ;Have we sent 16 data bits yet?
                goto        send_data               ;No, then continue sending
    send_parity call        wait_600_instructions   ;Yes, then we wait the required off time
                call        pwm_on                  ;And send the parity bit
                btfsc       parity,0                ;As before, if we are sending a 1
                call        wait_600_instructions   ;wait 1200 instructions
                call        wait_600_instructions   ;otherwise wait 600
                call        pwm_off                 ;End the transmission
                return                              ;and return to the calling program
    
    ;--------------------------------------------------------------------------------------------
    
    

    Admittedly, if you haven't read the MilesTag data protocol it may seem a bit mysterious, but if you have, it shouldn't be all that hard to see how I have implimented it. All "low level" code is being kept in the functions that I am calling so the program flow is not obstructed by a large number of changes to cryptically named registers. Additionally, writing code in functions forces you to break the task down into managable pieces, which ultimately makes it easier to code. Finally, if you write individual functions, it is very easy to write one and then thoroughly debug it. Once you have it debugged, you can move on to the next function. Any errors that occurr will be resulting from this next function not your first.

  5. Give your functions header data

    As demonstrated above, give some information about a function in the comments above it. You don't have to do this if it is really obvious, but it is nice to list any registers that the function requires and what conditions are required for it to function properly. Also, the line of "-"'s are a nice addition that lets you see exactly where a function begins and ends. Which brings us to the next point.

  6. Keep your function length manageable

    Basically, any function you write should fit on one screen of your computer. That means that it shouldn't be much longer than thirty lines. If your function gets considerably longer than that you probably need to rethink your program structure. There are some exceptions to this, though. They generally occurr when you are having to deal with the limited stack depth (eight levels) of the PIC. These instances should be the exception, though, not the rule.

  7. Define names for pins

    This one is somewhat optional but can greatly increase readability. It is a lot easier to guess the meaning of TriggerInput than it is PORTA,0, though the latter can be used if appropriately commented.

  8. Use constants

    If there is a literal value that you are using more than once in your program, define it as a named constant and then use the name in the program instead of the value. This makes the program much more readable, since we now know why you are using that literal, and also a lot easier to change in the case that you decide to change the constant's value

  9. Use relocatable assembly

    I don't recommend starting out with relocatable assembly, but start using it as quickly as possible. This allows you to put functions in separate files and keep registers and their values private to the function. (That basically means that only those functions within the text file have access to the register.) This allows you to re-use common register names, such as temp, without worrying about accidentally using one when you aren't supposed to. Additionally, this allows you to split large projects into separate files, grouping similar functions into the same file. This promotes code-reuse because you know that everything required for the function is kept within one file. So, you can include that one file in another project and know that it will work appropriately. Try to get ahold of a copy of The Quintesential PIC Microcontroller for more information on relocatable assembly. (I hope to post a tutorial in the near future, but who knows when I'll get around to it!)