Tutorial: Hello World in Assembler (using avra on debian wheezy)

After programming the arduino in Pure C as described by Balau (Programming Arduino Uno in pure C | Freedom Embedded). I attempted to complete this tutorial http://forum.arduino.cc/index.php/topic,37154.0.html by Acedio.

This is just the same tutorial updated for debian wheezy. Not much was changed.

0. Install software

sudo apt-get install avra avrdude
  • avra is an avr assembler
  • avrdude is used to upload the code to the arduino uno

1. Create a project folder.

All commands in this tutorial assume you are working in ~/Arduino/ASM.

2. Write some assembly code ( or use Acedio 's example )

Here is led.s as written by Acedio.

.nolist                  ; We don't want to actually include defs in our listing file.
.include "m168def.inc"      ; m168def.inc defines all the pins on the Mega168 so we can
                  ; use them by their names rather than addresses (not fun).
.list                  ; We DO want to include the following code in our listing ;D

      rjmp      main       ; You usually place these two lines after all your
main:                  ; directives. They make sure that resets work correctly.

      ldi      r16,0xFF ; LoaD Immediate. This sets r16 = 0xFF (255)
      out      DDRB,r16 ; Out writes to SRAM, which is one way of accessing
                   ; pins. DDRB controls PORTB's in/out state.

      ldi      r16,0x00 ; r16 is where we'll store current LED state
                   ; 0x00 means all off. This is preserved over loops.

loop:                  ; This is a label (like main above) where we can come back to

      com      r16       ; Flip all the bits in r16 (one's complement)
                  ; So, originally, we go from 0x00 (off) to 0xFF (on)

      out      PORTB,r16 ; set all B pins to current state. PORTB is where our favorite flashing pin is (pin 13)!


; Waiting for a specified time:
; Ok, so we want to wait one second between each LED flip.
; Our Arduino should be clocked at 16Mhz, so that means we have to wait
; 16 million cycles between flips. Different instructions take different 
; amounts of clock cycles to complete, so we have to count the cycles 
; to get an accurate wait time. We're going to use a combination of a
; word and a byte to get the number of cycles right, since just a single
; byte or word can't hold near enough to wait for our needed time.

; Counting cycles:
; Below you'll see a couple instructions with numbers in their comments.
; The numbers represent how many clock cycles the instruction takes.
; You can find all of these cycle amounts in the atmega168 datasheet.
; 1/2 means it can take either one or two cycles depending on the flag
; state. For these branch instructions (BRNE), they take one cycle if
; the condition is false, and two cycles if it's true. We'll only care
; about the two cycle case, since that will be happening 99% of the time.
; Also, just for extra laziness we wont care about the outside loop's cycles.
; The inner is the really time critical one anyway ;D

; The math:
; The Arduino is clocked at 16Mhz, or 16000000 cycles per second.
; Our main loop takes 4 cycles to complete one loop, since SBIW
; takes 2 cycles and BRNE will (usually) take 2 cycles. This means
; that we need to find an X < 256 and Y < 65536 so that X*Y*4 = 16000000.
; X=100 and Y=40000 fit that bill quite nicely, so we'll go with those.
; The outer loop runs 100 times, and the inner runs 40000 times per outer
; loop. This gives us about 1 second per flip!

      ldi      r17,100            ; r17 is our outer loop counter
outer:
      ; ZH and ZL are a word pseudo-register. It's actually a combination
      ; of r31 and r30, giving us 16 bits to use for SBIW. Handy!
      ; The HIGH and LOW macros just give us the high and low bits of a
      ; number.

      ldi      ZH,HIGH(40000)       ; 1
      ldi      ZL,LOW(40000)       ; 1

inner:
      ; These next two instructions SuBtract Immediate from Word and
      ; BRanch if Not Equal. Basically, we subtract one from the Z psuedo
      ; register (which begins with ZL, the low bits), and then, if we haven't
      ; reached 0, go back to the inner label. Otherwise we keep on going with
      ; the check for the outer loop!

      sbiw      ZL,1             ;2   \_ Main Loop (4 cycles)
      brne      inner             ;1/2 /

      ; The following instructions DECrement and BRanch if Not Equal. BRNE
      ; works exactly the same as above. DEC is shorthand for subtract one
      ; from this register. Only takes one clock cycle, too!

      dec      r17             ;1
      brne      outer             ;1/2

      ; Finally we've reached the end of the loop. Relative JuMP just brings
      ; us back to the label listed. Back to loop we go!

      rjmp      loop

3. Use avra to create a .hex file for avrdude

All we need to do is run:

avra led.s

Unfortunately this won't work because we need to specify the m168def.inc file. This file can be found in /usr/share/avra/m168def.inc . But this file contains some preprocessor directives like #ifndef, #define, #pragma and #endif which aren't supported by avra. We work around this problem by copying a modified m168def.inc file to our project folder:

sed '/#ifndef\|#define\|#pragma\|#endif/d' /usr/share/avra/m168def.inc > ~/Arduino/ASM/m168def.inc

This sed command will simply remove all lines containing these unknown preprocessor directives and write the output to a new file in our project folder.

Now run:

avra led.s

4. Upload the hex file

avrdude -F -V -c arduino -p ATMEGA328P -P /dev/ttyACM0 -b 115200 -U flash:w:led.s.hex

Note: You might have to modify your serial port: /dev/ttyACM0

Nice. Well, the code reminds me of a younger me (half my current age) that used to do some 386 ASM (interrupts, task table, protection, virtual x86 mode, rep movsd etc?).

Now all that experience was for understanding computer architecture instead of actually writing code. Writing in assembly is too difficult and it is hard to reuse code. You can do shortcuts and speed up one place or two. You can probably blink an LED with 64 bytes of assembly code but have to spend 1KB with Arduino. That is getting less and less important now.

What about doing assembly on the Debian? Just kidding.