Tutorial: Hello World in Assembler (Linux)

I've been searching for the past few days for a nice, easy introduction to getting some assembly code running on my Arduino without using AVR Studio. I run Linux so I just wanted to see if it was possible to not use Wine in order to get it working. I found a few sources, but it was all really spread out and there really weren't any general "Hello World" type introductions other than lwhi's great post ( http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1201426232/3#3 ). So I decided to write a bit of an introduction to AVR assembly on the Arduino in Linux! This should (theoretically) work on AVR Studio, though, so you Windows type folks can head down to the code if you wish! It's my first tutorial so go easy ;D

ASSEMBLERS
There are several assemblers for AVR on Linux, all of which have their pros and cons. The popular ones I stumbled upon were avr-as (part of the avr-gcc package), avra, and AVR Studio (with Wine). I decided to go with avra since it's (mostly) AVRASM compatible. Running it is as simple as:

avra <YOUR FILE>.s

avra will then pop out a .s.hex which you can use with avrdude to upload to your Arduino like so (after pressing the reset button):

avrdude -p m168 -P /dev/ttyUSB0 -c stk500v1 -b 19200 -F -u -U flash:w:<YOUR FILE>.s.hex

Make sure that /dev/ttyUSB0 is replaced with the location of your ttyUSB port. /dev/ttyUSB0 is what it is for my Ubuntu Hardy install.
Now that you know how to upload the code to your Arduino, lets open up a text editor and get some code going!

.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

I had a heck of a time finding the m168def.inc file. It is really nowhere to be found on Atmels website. Does anyone know where I can get it without downloading AVR Studio? Either way, I need to pop in a second post to continue.

I ended up finding the m168def.inc file elsewhere online. You can get it inside of the zip file here: http://home.wanadoo.nl/electro1/avr/definitions.htm. For use with avra, you'll need to comment out (; ) all the preprocessor directives starting with a #, since avra is AVRASM v1 compatible and # directives were added in v2 I think. It doesn't affect the code as far as I can tell, so no worries! Anyway, I hope you're able to get it working! I know a lot of this is probably not the correct/best way of doing things, but it's working for me so far. If there are any corrections I should make, though, please share!

LINKS:
http://www.avr-asm-tutorial.net/avr_en/index.html - A good tutorial for AVR ASM.
www.atmel.com/dyn/resources/prod_documents/doc2545.pdf - Handy-dandy datasheet for the Atmega168.
http://www.linusakesson.net/scene/craft/ - If you need inspiration for learning ASM, watch this! This was done on an Atmega88, with half the memory of the atmega168! Yay ASM ;D

that linusakesson.net link is awesome :slight_smile:

That was all done on an Atmega88? And I thought people struggled to get it displaying pong... Mindblowing! I might have to brush up my assembler skills.

Andrew

PS That demo takes me right back to the eighties.

Hi Acedio-

Thanks for the assembler tutorial. I'd tried a similar program but didn't get it to work. I didn't have the initial "rjmp main". Perhaps that was the problem. In any case your code works like a charm.

Now I don't have to go out a buy a PDP-8 or PDP-11!

Cheers-
-Michael