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 main: 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 or here: rjmp there // same as rjmp 0! there:
. . . 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.
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. stop: rjmp stop ;don't run on past end of program
http://en.wikipedia.org/wiki/Intel_HEX#Record_types 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.
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.
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.
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 http://avra.sourceforge.net/README.html 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.
:020000020000FC :0A0000000FEF04B900E005B9FFCFCF :00000001FF
[:][Byte Count][Address][Record Type][Data][Checksum]
So, the following line of the hex file can be split like this:
[:][0A]   [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'.
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.
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.
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.
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 RESET: ldi R16,0x20 out DDRB,R16 ldi R18,0x00 ldi R17,0x00 ldi R20,0x20 Loop: ldi R19,0xE8 aloop: 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
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 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 00000000 00000000 0000005a 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 0000005a 2**0 ALLOC
Disassembly of section .text:
0: 00 c0 rjmp .+0 ; 0x2 <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
c: 38 ee ldi r19, 0xE8 ; 232
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>
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
http://www.ladyada.net/learn/avr/avrdude.html 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:-
https://sites.google.com/site/boisemicromouse/resources/using-avrdude-from-the-cmd-line http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__demo__project_1demo_ihex.html http://www.avr-asm-tutorial.net/avr_en/AVR_TUT.html http://www.avr-asm-tutorial.net/avr_en/beginner/REGISTER.html http://www.avr-asm-download.de/beginner_en.pdf
assembly code not working check port addresses/addressing this c code works http://balau82.wordpress.com/2011/03/29/programming-arduino-uno-in-pure-c/
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 init: rjmp main .org 0x020 .global main 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 loop: 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.