User Tools

Site Tools


I wanted a simple assembler example to use with the Uno LED. Here it is, a few lines of code to turn on the LED. No huge makefile, no obscure include files, no magic incantations, just a few lines of code. In fact there are more comments than actual code.

0x16 tells the code it is hex (the 0x part). 0x00 is a byte (8 bits) as in byte flag1 = 0x01;

0x0000 is a int or word (16 bits), as in int analogValue = analogRead (A0);

0x01234567 is a long (32 bits), as in unsigned long currentTime = millis();

B11110000, B indicates binary.

'Nothing' preceding a number indicates it is decimal.

The destination address of rjmp is not stored as an absolute value, but as the difference between the jump destination and the storage location following the jump. This is best explained with a few examples. In the code snippet accompanying the explanation of jmp (see above), rjmp is used to construct an endless loop: . . .

 .org 0x900
 rjmp main // same as rjmp -1

The instruction following the rjmp would be at address 0x901. The jump destination is 0x900, so the relative jump length is 0x900 −0x901 = −1. Again: repeating the same instruction over and over is stored as a jump backwards. Consequently, a relative jump of zero words as in

rjmp 0
here: rjmp there // same as rjmp 0!

. . . is similar to a nop, but needs two CPU cycles for execution. This is one cycle less that jmp needs. Also, the jump length for rjmp is limited to ±2K words, which means that it can be stored in just one word of program memory.

Using with avra

AVRA is an assembler for Atmel AVR microcontrollers, and it is almost compatible with Atmel’s own assembler AVRASM32. The programming principles and conceptions are based on the ANSI programming language “C”.

;;;;   assemble with:-    avra ledon.S
;;;;   get disassembly with:- avr-objdump -s -m avr5 ledon.S.hex (binutils-avr needed) 
;;;;   the output file produced is ledon.S.hex
;;;;   upload this file to the arduino using avrdude:-
;;;;   avrdude -p m328p -c Arduino -P /dev/ttyACM0 -b 115200  -Uflash:w:ledon.S.hex:i -v -v
; Load the binary pattern 11111111 into Register 16	              
LDI    R16,  0b11111111 
; send all the 1s to the Data direction register at address 0x04
; to configures all lines on Portb  as an Outputs      
OUT    0x04, R16              
; not really needed, already loaded wits 1s.
;but later if we put all zeros here the LED would go off 
LDI    R16,  0b11111111
; Send all the 1s to all lines on port B at address 0x05. 
;A 1 takes the line up to 5 volts and lights the LED
;A 0 takes the line down to 0 volts and the LED goes off.     
OUT    0x05, R16             
; If we had an LED connected to each line of port b, they would all light up.
rjmp stop                    ;don't run on past end of program 

Using avr-gcc

Intel hex file ledon.hex produced by typing:- avr-objcopy -O ihex ledon.o ledon.hex

ledon.hex is NOT what is sent to the arduino. Avrdude reads this file and extracts the hex bytes (opcodes/data)to be sent to the arduino.

The colon signals the start of a record. The first 2 digits indicate the number of data bytes The next four digits is the address (0000). The next 2 digits is the record type this means it is a data record. The next 24 digits (12 bytes)is the actual data sent to the arduino. The last two digits is a checksum.

Other Types

The data type 00 indicates that the line contains data bytes. The other type that must occur in every Intel Hex file is 01 as this is the end of file marker. Every file must end with an 01 type, which looks like this – :00000001FF.

The other types that you may see are 02 and 04 that show that the line is an extended address line.

Extended Addresses

You may have worked out that with a possible address range of 0x0000 to 0xFFFF, this Intel Hex file format can only store 64KB (0xFFFF) of data. Newer microcontrollers and certainly memory chips can hold much more data than this so how do we include data above 64KB?

The answer is to use a type that means that the line is an extended address rather than a line containing data and both 02 and 04 types do this, in a slightly different way. As the standard addressing is 0 to 0xFFFF, we need a marker that increases the address of the next block by 0x10000, the following block by 0x20000, then 0x30000 and so on.

The first block has addresses in the normal 0..0xFFFF range. Then we have an extended address marker that means add 0x10000 to all subsequent addresses giving possible addresses of 0x10000 to 1FFFF. Then we might have further extended addresses that add 0x20000 to each following address, 0x30000, 0x40000 etc.

Both 02 and 04 types do this.

02 Linear Address Type

This has the following form for each 64KB block:

1st Block – add 0x00000: :020000020000FE 2nd Block – add 0x10000: :020000021000FD 3rd Block – add 0x20000: :020000022000FC 4th Block – add 0x30000: :020000023000FB

The format is shift the value after the 02 marker 4 places left, then 0x1000 becomes 0x10000, 0x2000 becomes 0x20000 and so on. Most files with extended addressing include the first :020000020000FE zero marker but this does nothing – 0x0000 shift left 4 is still zero.

04 Extended Address Type

This does the same thing but has a slightly different format

:020000040000FA :020000040001F9 :020000040002F8 :020000040003F7 :020000040004F6

This time the value after the 04 marker is shifted left 16. Therefore 0x0001 also becomes 0x10000 etc.

Code does not assemble

It important to note that many code listing for the AVR you are likely to find on the web are often unsuitable for use with avr-gcc and the avr assembler avr-as. Though the avr instruction set is uniform throughout the AVR family, the specific assembly directives used (for instance to include header files and defined constants) in the source available on the web may vary from those used by the binutils AVR assembler, avr-as. To use such source code with avr-as, you'll need to make a number of changes such as:

Adding an architecture definition, such as “.arch atmega8” Changing “.def a=b” and “.equ a=b” lines to “#define a b” Replacing any .include directives by equivalent #includes And likely more changes we are unaware of…

Syntax varies between assemblers, and some 'include' files provided by one environment may not be available when using a different environment. All these differences means that there are a lot of assembler examples that will not assemble.

The first important thing to note is that the syntax and the assembler need to match. In this case, the first comment in the file points out that the assembler in use is avra avra assembler

The next point to notice about the example, is that there are no 'include' (.h) files included. The assembly will fail if the assembler cannot find any .h files referenced in the code, so none are used.

The code above is so simple, that you could even get by without the assembler with a bit of patience. Below is what is sent to the programmer, avrdude. This is NOT what is sent to the arduino. This is intel hex format, and the programmer, avrdude, uses the information in this file to determine what to send to the arduino. The 10 bytes between the square brackets on the middle line is what is sent to the arduino. The square brackets are not in the actual file, I have inserted them to highlight the 10 bytes.


[:][Byte Count][Address][Record Type][Data][Checksum]

So, the following line of the hex file can be split like this:

[:][0A] [0000] [00] [0FEF04B90FEF05B9FFCF] [CF]

Instructions are 16-bits, or 4 hex digits. The RJMP opcode is described in the ATMEGA datasheet as : 1100 kkkk kkkk kkkk

0b'1100 = 0xC

So, an instruction that has 0xC as the first digit, implies that it is an RJMP instruction:

According to the addresses specified in the .hex file, the actual program is only 0x00F8 bytes long.

$ avr-gcc -c ledon.S

You can make sure it's correct with objdump:

$ avr-objdump -d ledon.o 
ledon.o:     file format elf32-avr
Disassembly of section .text:
00000000 <stop-0x8>:
   0:	0f ef       	ldi	r16, 0xFF	; 255
   2:	04 b9       	out	0x04, r16	; 4
   4:	00 e0       	ldi	r16, 0x00	; 0
   6:	05 b9       	out	0x05, r16	; 5
00000008 <stop>:
   8:	00 c0       	rjmp	.+0      	; 0xa <stop+0x2>

code extracted from intel hex file, ledon.S.hex :- 0FEF04B900E005B9FFCF

The point to take away from this example is that makefiles, header files and magic incantations are all designed to make life easier, but if you want to, you can do without them once you understand what is going on.

Ultimately, how you work probably depend on why you are using assembly in the first place. A lot of comments on forums indicate that assembly language programming is pointless, but I believe assembler is a very useful method for understanding the Uno hardware, and what makes the it 'tick'.;id=76;url=http%3A%2F%2Felectrons%2Epsychogenic%2Ecom%2Fmodules%2Farms%2Fart%2F3%2FAVRGCCProgrammingGuide%2Ephp


A micro-controller accepts instructions in Binary (base 2) format, zeros or ones is all it will accept. The instructions are presented to the micro-controller as low or high voltages. With low voltage representing zero and high voltage representing one.Binary is ideal as a machine language, but it is very error prone when we try to work with it. Long strings of ones and zeros are hard to comprehend, so hexadecimal (base 16) was introduced.

Instruction set

When a micro-controller is designed, it is given an instruction set, these are the only instructions it will accept. These instructions are just numbers, and they must be provided to the micro-controller in binary form as previously stated.The micro-controller will work through each instruction, carrying out one instruction at a time. If the micro-controller is turned off or reset, it will always restart at the 'beginning'. The location of the 'beginning' is defined by the manufacturer. The manufacturer of a micro-controller will provide a user manual which explains how to use the chosen device. The manual will contain the instruction set.

Example Instructions in binary and hex

Here is a small sample of instructions in binary and hex which apply to the Atmel atmega328 (Arduino uno) micro-controller.

send CPU to sleep: Binary=1001.0101.1000.1000 hex=9588

Add register R1 to register R0: Binary=0000.1100.0000.0001 hex=0C01

Subtract register R1 from register R0: Binary=0001.1000.0000.0001 hex=1801

As can be seen from the above, hex is easier to comprehend than binary, but even hex is a bit obscure. If we wanted to give instructions to the micro-controller, we would need to look up the hex code for each instruction. It is possible to do this, but to make things a bit easier to remember, mnemonics are used.

Example Instruction mnemonic The add instruction from earlier, hex=0C01 becomes

ADD Rt,Rs much easier to understand.

The assembler

Finally, the assembler. Remember that the instructions must be given to the micro-controller in binary. Now we are writing mnemonics, we need something to turn the mnemonics back into binary, and this is one of the things the assembler does for us.

See this forum post where a new user is struggling with assembler blink code, and is told to insert a magic incantation with no explanation of what it does or why it works.

In this same thread, one responder suggests that the gnu compiler collection GCC probably isn't the best choice for avr assembly.

“I suggest the asm that goes with GCC (avr-gcc). Then you can link C and asm files, in time. You can also do asm files only and link them together like you can if working with C-files only and the toolchain will link the asm files together and you don't have to bang your head keeping track of address labels using .org statements. Using AvrASM, you can only write 1 big asm file.”

“This (the belief that gnu-as is designed for machine code generation) has been mentioned, several times, quite recently.” Or only use it if you are a machine!

Using Linux and the avr assembler (avr-as) the code is assembled by typing the following command

avr-as blinky.S compiles and uploads, but doesn't work as expected

blinky.S being the name of the source file being assembled. I have not specified an output file name, so the default output file is called: a.out (the a.out file format is elf32-avr)

Assembly source code - blinky.S

click on the blinky.S tab to download the code to your computer, ready to compile and upload.

;;;;blinky.S,   assemble with    avr-as blinky.S to get a.out
.set DDRB,  0x04
.set PORTB,  0x05
.org 0x0000
    rjmp RESET
    ldi R16,0x20
    out DDRB,R16
    ldi R18,0x00
    ldi R17,0x00
    ldi R20,0x20
    ldi R19,0xE8
    inc R17
    cpi R17,0x00
    brne aloop
    inc R18
    cpi R18,0x00
    brne aloop
    inc R19
    cpi R19,0x00
    brne aloop
    eor R16,R20
    out PORTB, R16
    rjmp Loop

Code Disassembly

use: avr-objdump -h -S a.out > blinky.dis

to get the disassembly into a file called blinky.dis. You can open blinky.dis. in a text editor and see the machine code the assembler has produced. this is what it looks like:-

a.out: file format elf32-avr

Sections: Idx Name Size VMA LMA File off Algn

0 .text         00000026  00000000  00000000  00000034  2**0
1 .data         00000000  00000000  00000000  0000005a  2**0
                CONTENTS, ALLOC, LOAD, DATA
2 .bss          00000000  00000000  00000000  0000005a  2**0

Disassembly of section .text:

00000000 <RESET-0x2>:

 0:    00 c0           rjmp    .+0          ; 0x2 <RESET>

00000002 <RESET>:

 2:    00 e2           ldi    r16, 0x20    ; 32
 4:    04 b9           out    0x04, r16    ; 4
 6:    20 e0           ldi    r18, 0x00    ; 0
 8:    10 e0           ldi    r17, 0x00    ; 0
 a:    40 e2           ldi    r20, 0x20    ; 32

0000000c <Loop>:

 c:    38 ee           ldi    r19, 0xE8    ; 232

0000000e <aloop>:

 e:    13 95           inc    r17
10:    10 30           cpi    r17, 0x00    ; 0
12:    01 f4           brne    .+0          ; 0x14 <aloop+0x6>
14:    23 95           inc    r18
16:    20 30           cpi    r18, 0x00    ; 0
18:    01 f4           brne    .+0          ; 0x1a <aloop+0xc>
1a:    33 95           inc    r19
1c:    30 30           cpi    r19, 0x00    ; 0
1e:    01 f4           brne    .+0          ; 0x20 <aloop+0x12>
20:    04 27           eor    r16, r20
22:    05 b9           out    0x05, r16    ; 5
24:    00 c0           rjmp    .+0          ; 0x26 <aloop+0x18>
Produce Intel hex file

use: avr-objcopy -O ihex a.out aout.hex to produce the intel hex file (aout.hex) that the avrdude programmer will send to the arduino using:-

avrdude -p m328p -c avrisp -P /dev/ttyACM0 -b 115200 -D -Uflash:w:aout.hex -v -v

Check that your arduino com port is correct, ie. /dev/ttyACM0 (Linux)

on windows, it will look more like this, but check that the arduino is attached to com5 if not insert the correct com port:

avrdude -p m328p -c avrisp -P com5 -b 115200 -D -Uflash:w:aout.hex AVRDUDE options

Although 'Yikes invalid device signature' is displayed, the upload works and the LED starts blinking.

more about avr-objcopy

Excellent assembler guides found here:-

assembly code not working check port addresses/addressing this c code works

Compiling assembler files with avr-gcc without C runtime 2014-01-27

Written down so that I’ll remember this the next time I’m trying to do this: If you want to compile assembler files with avr-gcc to be used with your C files, just compile them like your .c files.

avr-gcc -c -mmcu=atmega328p -o foo.o foo.S

If you want to run your assembler code standalone, without the C runtime support (crt*.o), you’ll need to tell the linker that the entry point is inside your program instead of the C initialization routines. The command-line option -e for avr-ld is what you’re looking for. Consider the following assembler program:

    #include <avr/io.h>
    .section text
    .org 0
    .global init
    rjmp main
    .org 0x020
    .global main
    ; on Arduino, this will light up the on-board LED
    ldi 16, 0xFF
    out _SFR_IO_ADDR(DDRB), 16
    out _SFR_IO_ADDR(PORTB), 16
    rjmp loop

To start the execution from init, compile and link it like this:

avr-gcc -c -mmcu=atmega328p -o foo.o foo.S avr-ld -e init -o foo.elf foo.o avr-objcopy -O ihex foo.hex foo.elf

Also, check out avr-libc’s manual on assembler programs.

arduino_command_line_assembler.txt · Last modified: 2021/10/26 19:21 (external edit)