Introduction:
This guide presents a brief introduction to HC11 assembly programming. Assembly is a very different language compared to C. It is a low-level language, the lowest level besides machine code itself. In fact, assembly is nothing more than mnemonics assigned to machine code.
The first step in understanding assembly is how it is assembled. You write assembly instructions, and when your program is completed, you run it through an assembler. The AS11 program takes your assembly instructions, and generates a S19 file. This S19 file is the “executable” file that is sent to your board for it to run on. You do not have a compiler in assembly, since it is nothing more than a mnemonic for machine code.
The second step in understanding assembly is syntax. Since this guide is written for the Motorola HC11 assembly programming, the format used will be according to this CPU.
There are five different kinds of statements that can appear in your code. These are as follows:
1. Labels
Labels are very important in assembly programming. They specify locations in memory where code is executed, where data is stored, where loops go, etc. If you want to call a subroutine, you have to jump to its “label.” If you ever used GOTOs in C, labels are used just the same way. In fact, labels are your only real way to easily branch (jump) around in your code. Labels are ALWAYS on the left hand side of the screen. No spaces before them.
ldaa #100
loop_label
deca
bne loop_label
2. Instructions
display_string
ldx #sample_string * IX => address of string
jsr OUTSTRG * display the string
Instructions are mnemonics for machine code. What does this mean? It means that they specify an action for the CPU to carry out. These instructions are incredibly simple. Examples include loading a register with a value, adding a number to a register, clearing the value at a memory location, etc. None of the instructions are anywhere close to your C statements in terms of complexity. This is a double-edged sword. While the instructions are incredibly easy to understand...individually, it nonetheless takes a fair number of instructions to do a simple task like a “for” loop in C. Instructions ALWAYS have at least a space between them and the left side of the screen. Use a tab or hit the space key a couple of times. This is how the assembler differentiates between labels and instructions.
Instructions usually have two parts to them. There is an opcode (like ldaa, staa, inc, etc.) and an operand (like $100, #sample_string, etc.) The opcode specifies the kind of instruction, and the operand specifies the data for that instruction to operate with. Note: there are some instructions that have no operand (like INX or SWI).
3. Comments
* This is an example of a comment
ldaa #$10 * Here is a comment
Comments are explanations for programmers. They help someone looking at the code to understand it. They do not affect the final object file or S19 file. The assembler ignores comments.
4. Variables
count fcb 0
tenbytes rmb 10
test_str fcc ‘Hello world!’
fcb $04
Variables represent values stored in memory. They are called variables because the value that they hold can change during the course of your program. Variables come in many sizes but the regular smallest size used for the HC11 is the byte. This is eight bits that have been reserved in memory to hold a value that you assign to it. All variables have a label assigned to them, that represents their memory location. You could avoid using a label to refer to a variable, but it is not recommended. Labels make it easy to reference variables.
There are many different kinds of variables that you can declare in assembly. The first kind shown above is the “fcb”. This kind of declaration allows you to reserve a byte in memory for your variable, and initialize at the same time. In the case of the “count” example, a single byte in memory was grabbed and initialized to 0. Note that this initialization only happens once, when you load your program onto the board. Subsequent running of your code without reloading will not reset the fcbs to their original values. If you want to re-initialize, you can do it in your code or reload your program.
“rmb” is short for reserve memory byte. This means that you obtain a location in memory but you don’t bother initializing it when you declare it. Not only that, but the number after the rmb refers to the number of bytes you grab. The “tenbytes” declaration grabs 10 bytes in memory without initializing them.
Another kind of variable declaration is for a string. This is useful when you want to display a bunch of characters on the screen, like a message. The “fcc” followed by the string inside apostrophes declares the string and its value. You refer to the string by its label of course.
Note that you initialize memory locations without labels. The “$04” inserted on the line after the string initializes the next memory location past the “!” of Hello World to $04. This is useful because when you call BUFFALO routines such as OUTSTRG, it will know when it has reached the end of the string. Otherwise, BUFFALO would have no clue when it should finish displaying characters.
5. Equates
PORTA EQU $1000
...
ldaa PORTA
anda #000001
bne INPUT_HIGH
Equates are very important, although sometimes neglected. They represent word substitutions for values. For example, PORT A on the HC11, a port that has several input and output pins for external connection to the outside world, is mapped to memory address $1000. That means that any reading from memory address $1000 or writing to memory address $1000 refers to PORTA. You could write $1000 throughout your code, but it would be easier if you just called it PORTA and referred to that. The assembler will replace every instance of the equate with the value associated with it. So “ldaa PORTA” is the same as “ldaa $1000.”
All things being equal, the more equates you use, the easier your programs will be to debug and modify.
Addressing Modes:
Now that you have a feel for the syntax involved in assembly, it’s important to know how memory is treated with instructions. There are several kinds of “addressing modes” involved with instructions. The best way to explain them is with examples.
1. Immediate
ldaa #$100
Immediate addressing is where the operand contains the actual value to load. In other words, the above instruction will load accumulator A with hexadecimal value $100. It does not refer to any memory location. The actual value to load is contained on the line of code that is written.
2. Direct/Extended
ldaa $100
Direct or extended addressing (there is a difference between the two, but we’ll ignore it for this brief guide) refers to an address in memory. The operand contains the address in memory where the value is at. In a sense it’s like a pointer. In the above instruction, accumulator A will be loaded with the value stored at memory address $100. That value could be anything from $00 to $FF.
3. Index
ldaa #$FF
ldx #$3000
staa 0,x
staa 1,x
Index addressing usually involves the IX or IY registers. These are special 16 bit registers in the HC11 that are capable of pointing to any memory location. The way index addressing works is that you can load IX or IY with a memory address, and then add an offset to it. The above examples show register A being loaded with $FF and then storing that value to memory address $3000 (the staa 0,x instr.) The next instruction shows $FF being stored to memory address $3001. That first value specifies the number of bytes to go past IX in memory to store the value at. Had that value been a 3 or a 4, then register A would have been storing its value at $3004 or $3005. This is useful for dealing with tables of data stored in memory.
Writing Code:
This is perhaps the most important part of the guide, but it will be the thinnest part as well. The way to learn assembly programming is to write it. You have to get your feet wet in order to learn how to swim. However, some tips are presented below to help in some of your tasks.
1. Loops
Loops are code sequences that repeat themselves a certain number of times. The “For” loop is C is an example of a specified number of iterations. The code inside the loop runs a specified number of times.
One way to create a FOR loop in assembly is to use a register or a variable as a counter, and decrement that counter until it is zero. Every time a register or variable is incremented or decremented, a flag indicating whether or not the result is zero is updated. If the result equals zero, the flag is set, if not, the flag is cleared. Here is some example code:
ldaa #100 * A => number of iterations
loop
deca * Decrement the counter
bne loop * Keep branching until zero
This code shows a very simple “for” loop. It runs one hundred times. Any code that you wanted repeated one hundred times would be placed just after the loop label. Note, that you must not accidentally modify your counter register in your loop. If you are using register A as the counter, you must not use A for any other purpose. If you find yourself running out of registers to use, you can “push” the counter onto the stack or use a global variable.
ldaa #10 * A => number of iterations
loop
psha * Save the counter
ldaa #’A’ * Get ready to output an ‘A’
jsr OUTA * Output that character to screen
pula
deca
bne loop * Loop a total of 10 times
Here, OUTA uses the A register as an input for what character to echo to the terminal screen. By using the stack, we can preserve the counter and still use the A register.
2. Comparisons and Branching
There are several ways of doing comparisons in assembly. The most common way of performing a comparison between two values is with the “CMP” instruction. This instruction performs a subtraction operation. If the two values are equal to each other, the zero flag is set. If the two values are not equal, then flags such as the negative flag or the carry flag may be set. The important feature though is that you can then perform a conditional branch based on the results of the comparison.
ldaa value * get value for comparison
cmpa #$FF * Does value = $FF
beq set_high * If so, branch to set_high
bra set_low * Else branch to set low
set_high
... * code goes here
bra skip_low * Remember to skip the other code
set_low
... * code goes here
skip_low
Another way to do a comparison is with the boolean AND function. This function is useful for masking and comparing selective bits in a byte. For example, suppose we wish to see PORTA PA0 (an input pin) is high at 5V (a logic 1). We can use the AND function to test this:
ldaa PORTA * read PORTA
anda #000001 * check bit0 (PA0)
beq A_low * branch if low
... * <= code for handling PA0 high
bra skip_low
A_low
... * code goes here
skip_low
Note the slight difference in style between this comparison and the previous one. This is to show you that there are multiple ways of handling a comparison, each with their own advantages and disadvantages. The advantage of the former was that it clearly labels each part (set_high and set_low), however it requires some extra lines of code. The advantage of the latter style is that it is more compact, but it is not as easy to follow.
No comments:
Post a Comment