Example Program #1: LED

Before diving into all the details of assembly, it might be helpful to look at a simple program to give you some context. For this program to work, an LED should be connected in series to PINB0 of your microcontroller.

Below is a simple program which will set the output of PINB0 high on an ATmega328P microcontroller.

	.include "m328pdef.inc"

	.cseg
	.org 	0x00
	ldi	r16,(1<<PINB0)		; load 00000001 into register 16
        out     DDRB,r16		; write register 16 to DDRB
        out     PORTB,r16		; write register 16 to PORTB

loop:	rjmp    loop			; stay in infinite loop

Code Breakdown

Just like when programming in C, assemblers have a directive to include the contents of another file in your code, written as .include. In this case the file we include is "m328pdef.inc" which contains definitions specific to the microcontroller that we are using (of course if you are using a different microcontroller you will need a different include file). Note that in assembly, directives are preceded with a period.

	.include "m328pdef.inc"

The next few lines introduce the directives .cseg and .org.

	.cseg
	.org 	0x00

The .cseg directive indicates that what follows is part of the code segment and should be placed in flash memory (as opposed to data memory or EEPROM).

Next we use the .org directive to specify the origin of our code segment. In this case we specify the address 0x00 to put our code at the beginning of flash memory. This ensures it is executed immediately when the microcontroller starts.

Note: By default, .cseg will start at the beginning of flash so the line .org 0x00 is not strictly necessary. However, it is good practice to always specify the origin to make it obvious where the code is being placed.


Now we get to our first instruction.

	ldi	r16,(1<<PINB0)		; load 00000001 into register 16

The instruction ldi - load immediate, lets us load an 8-bit constant value into one of the general purpose working registers between 16 and 31. The general purpose working registers are special memory locations directly connected to the microcontroller's arithmetic logic unit (ALU). We will have much more to cover later, but for now know that any time you need the CPU to operate on a value it must be placed in one of these registers. Here we use the ldi instruction to place the 8-bit value 00000001 into register 16.

Next we have the instruction out.

	out	DDRB,r16		; write register 16 to DDRB
	out	PORTB,r16		; write register 16 to PORTB

The instruction out lets us take a value loaded into one of the general purpose working registers and write it to one of the registers mapped to I/O memory. Here we write the contents of r16 (which is loaded with 00000001) to DDRB which will set PINB0 to output. We then write the same value to PORTB which will set the output of PINB0 high.

It is important to note that we cannot directly write a constant to an I/O register with the instruction out. For example, the following will not work

	out	DDRB,0b00000001		; incorrect

Only certain instructions work with constants which will usually be indicated by the word immediate in the name. out can only take a general purpose register as an operand which is why we had to first load the value we wanted to write with out into r16.

Remember: Only certain instructions can use a constant as an operand. This will usually be indicated by the word immediate in the name.


Now we get to the final line of our program:

loop:	rjmp	loop			; stay in infinite loop

Here we define a label loop. This label will mark the address in code where it appears. Labels are always followed by a colon when they are defined.

The loop label is followed by the instruction rjmp - relative jump. rjmp controls program flow by causing the microcontroller to jump to the address that's given as an operand.

Here we set the label loop as the jump target. By doing this we have setup an infinite loop. In each cycle the microcontroller will read the rjmp instruction, jump to the loop label, read the rjmp instruction again, jump the the loop label again - and so on ad infinitum.

Conclusion

And there you have it! A simple assembly language program. We have seen how to use assembler directives like .include, .cseg and .org. We also saw how to load constants to general purpose working registers using the instruction ldi and write values to I/O registers using the instruction out. Finally, we saw how to define labels and control program flow with the instruction rjmp.

Now that we've seen an example program, let's take a closer look at the structure of an assembly language program.

rjhcoding.com 2018