Forays into Assembler

I've been playing around with an arduino for the past few weeks now (no concrete project just messing around with the hardware and software) and in the last few days I've delved heavily into assembly language programming, so I wanted to share my experiences and programs, with two goals in mind. To educate others who want to start learning assembler, and to have others who are knowlegeable in low level programming to evaluate it.

The first Program I wrote was basically a copy of the helloworld (blink on/blink off) program listed elsewehere on these forums, I wont go indepth for the initial setup for compilers/editors. I am running gnu-linux (Gentoo) and I installed an avr assembler (avra) and avrdude to burn to the chip. I wrote a simple script to invoke both programs without having to constantly retype the command parameters. All my programing was done in a low-level text editor (nano) with no syntax highlighting or error checking.

Here is a dissection of my "hello world2" program.

The arduino is set up with two LED's on arduino pin 12 and pin 13, and a push switch (wired to +5v with a pull-down to ground) on pin PIN2. The basic idea is that the lights switch based on the status of the switch.

all my commentary/descriptions are done in code

;; Simple program to read a switch pin, and turn on an LED if the switch
;; is pressed, uses Arduino pin 13 (PORTB5) as the on LED
;; and pin 12 (PORTB4) as the off LED, pin 2 (PORTD2) as the switch pin


.nolist                         
.include "m168def2.inc"          ; Assembly include statement
.list

.equ ONPin = 5                  ;equivelant of a #define statement, makes it easier to remember the name
.equ OFFPin = 4

      rjmp      main              
main:
;; the next few lines of code set up the pins on the arduino
;;ldi, is the command LoaDImediate, i.e. save the value to the specified register.
;; NOTE: this command only works on the registers r16-r32(general use registers) registers r1-r15, are 
;; program registers (used as working space for calculations by the program) and above r32 are all the 
;; ports/special purpose registers and sram


;; the command out, is used to write the contents of the specified register to the portregister, 
;;NOTE this command only works on the first 32 ports registers, the rest are memory mapped (linked to 
;;sram registers, and you have to use commands like lds and sts to read and store their values (the 
;;proccesor treats those ports as though they are part of the sram), check the .inc file (m168def.inc) or 
;; or the atmega 168 datasheet to see which ports are memory mapped (all the serial intergace stuff is
;; memory mapped)



      ldi      r16,255                     
      out      DDRB,r16      ;B ports (pins 8-13) are out
      com      r16
      out      DDRD,r16      ;D ports pins (0-7) are in

;; the following loop polls the status of the "D" port pin number 2 with the command
;;"sbic     port,bit#" if the bit is clear i.e. 0, it skips over the next command (which in this case is a jump
;; to the label "on"
loop:
      sbic      PIND,2            ;skip next command if switch is off
      rjmp       on                    ;jump to on
      rjmp      off

on:      

      sbi      PORTB,ONPin      ;set ONPIN status to HIGH
      cbi      PORTB,OFFPin      ;set OFFPIN status to LOW
      rjmp       loop                     ;back to the beginning

off:

      sbi      PORTB,OFFPin      
      cbi      PORTB,ONPin      
      rjmp      loop

The equivelant in arduino pseudo c code would be

int ONLed = 13;
int OFFLed = 12;
int SwitchPin = 2;
int switchVal=0;

void setup(){
      PinMode(ONLed,OUT);
      PinMode(OFFLed,OUT);
      PinMode(SwitchPin,IN);
}


void loop(){
       val = digitalRead(SwitchPin)
       if(val==HIGH){
              DigitalWrite(ONLed,HIGH);
              DigitalWrite(OFFLed,LOW);
       }
       else{
              DigitalWrite(OFFLed,HIGH);
              DigtalWrite(ONLed,LOW);
       }
}

The two invaluable resources that I had where the atmega168 datasheet (invaluable in the example I will post next), and this Website, which is a bit hard to navigate and understand at times, but is enormous help. Just do not take copy his examples word for word, some of the ports (the serial ports and ADC ports) are not memory mapped on the chip he uses, so the commands he uses are incompatible with the atmega 168

This next example (not all the code could fit in the first page), shows how to implement a simple serial communication on the arduino, it demonstrates how subroutines work, how to set up the stack (it is used when calling subroutines).

The most difficult part of writing this program were the innacuracies in the examples that I read. The main problem being that the examples in the atmega 168 datasheet used that the serial communications registers as regular ports and not as memory mapped ports. Long story short: it took me several hours to figure out why the code wasnt assembling.

For newbies the biggest confusion is that some of the commands opperate only on a narrow range of registers. If you want to write to the output of pin D7 (pin 13) you would be tempted to use

ldi         PIND,0b10000000

which would not work as the command ldi only works with the general registers r16-r32. to write an outut to a port you would have to use the command out

ldi          r16,0b100000000
out         PIND,r16

The confusing part is that only 32 of the ports (memory adress 33-64) are usable with out and in. Because of the .def file you don't have to worry about knowing the exact address of each port and therefore it can become confussing when you use out on a port outside of its range and the assembler starts throwing wierd errors. These firsdt 32 ports include all the basic IO ports and pins, just like in arduino code you have to set the status of each pin (OUT or IN) and then set it to HIGH or LOW so for working with the pins there are 3 registers that interest you (they are briefly shown in the previous example)

DDRn (replace n with the letter name of the pin bank) sets the IN or OUT status the pins. write all 1's to it and all the pins will be set to output, if you only want to set one pin to output you can use something like

sbi       DDRB,7

which sets bit 7 of port DDRB to 1 (use cbi to clear it to 0) which coresponds to pin 13 on the arduino if you want to do a similiar opperation with a general register you must use the command sbr and cbr

;equivelant operation
clr       r16        
sbr      r16,7         ;set bit 7 to 1
out      DDRB,r16

The registers PORTn control the HIGH LOW status of the pins, the commands used are similiar to those described above.

the registers PINn is where the microcontrol stores the status of the pins if they are set as INPUT.

Anyway, on to serial communication There are several ports related so serial communication UDRn -The data port, this is where you write the data you want to send and read the data that was reveived (in/out is the same register)

UCSRnA - Serial Status and control register A, The mos interesting bit in this register is bit 5 (UDREn) which indicates that the bus is ready to send more data (Data register is empty)

UCSRnB - Serial Status and Control Register B, more settings for the serial bus the most important bits in this register are RXENn and TXENn which turn on the transmit and receive functions of the serial communications

UCSRnC - Serial Status and Control Register C, These settings control more options Settings for the number of data and stop bits are set in this register

UBRRnL and UBRRnH - two registers (to hold a 16bit number) that set the baud rate for the serial communications

In order to use the serial communications, all of the settings must be properly set afterwards sending a byte over the serial communication is as easy as checking that the i/o register is free and then writing your data to the register.

Since all of these registers are memory mapped (treated as part of sram by the proccesor) you must use sram commands to write/read them (ldi and out/in will not work).

NOTE: atmega 16 has only one serial communications bus so you must replace all the instances of n with 0 (i.e. UDRn becomes UDR0)

There is also a special Status REGister (SREG) which holds a bunch of usefull information about the result of the previous calculation. In fact there are special branching commands which execute a command only if a certain bit is set in SREG

ldi      r16,0
target:
com   r16
BRNE    target
....more code

brne only performs the jump to target if the previous calculation had a result of 0, in this case the complement of 0 is 255 so it will jump back to target on the first run, the complement of 255 is 0 so the special 0result bit in sreg is set, brne see's this and will continue with the program.

code like :

          ldi      r16,10
loop:
...execute stuff
          dec    r16
          brne   loop
...more code

is an important idom to remember as it is how you make itterative loops in assembler

the code to the serial program to follow in next post

;;program to demonstrate serial interface
;;communication
;;sources: atmega168 datasheet and http: avr-asm-tutorial.net 
      



.nolist
.include "m168def2.inc"
.list

.equ      fq=16000000      ;clock frequency = 16mhz
.equ      baud=9600      ;baudrate
.equ      bdrate=(fq/(16*baud))-1      ;baud divider

.def      mpr=r16            ;Universal register
.def      genreg=r17
.def      nc=r18            ;counter
.def      c=r19            ;character


      rjmp      main
main:
      ;init stack
      ldi      r17,high(ramend)
      out      sph,r17              ;registers sph and spl are special
      ldi      r17,low(ramend)      ;registers to hold the stack pointer
      out      spl,r17         ; after setting the initial stack pointer
      ;; (to the end of the sram) everything is done automatically
      
      rcall      serialstart     ;call subroutine serial start

;;tloop sends the letters A through Z over the serial connection
      ldi      c,'A'            ;start with letter a
      ldi      nc,90-65+1      ;calculation for 26 characters
tloop:
      ;;next few commands check to see if anything was transmitted, and wait 
      ;;until it is free, other continue with the transmission
      lds      r17,UCSR0A
      sbrs      r17,UDRE0      ;jump if the buffer is empty (otherwise loop)
      rjmp      tloop
      sts      UDR0,c            ;transmit letter
      inc      c            ;increment letter
      dec      nc            ;decrement counter
      brne      tloop            ;send the next letter
rloop:      

      ;;this next routine, waits for a byte across the serial connection
      ;;and then echo's it back
      lds       r17,UCSR0A
      sbrs      r17,RXC0      ;test if a character is received
      rjmp      rloop            ;wait if no
      lds      c,UDR0            ;read the character
rwait:
      lds      r17,UCSR0A
      sbrs      r17,UDRE0      ;wait until tx ready
      rjmp      rwait
      sts      UDR0,c            ;send the character
      cpi      c,0x0D            ;character sent is return char?
      brne      rloop            ;no, then loop
      ldi      c,0x0a            ;yes then load linefeed
      rjmp      rwait            ;and send

;;Start the serial connection

serialstart:
      
      ;store and set baudrate
      ldi       r17,high(bdrate)
      ldi      r16,low(bdrate)

      ;sts command= "sts  address,register"
        ;read as store the contents of register at sram address "address"

        ;set baud divider
        sts     UBRR0H,r17
        sts     UBRR0L,r16

        ;store bit code to enable transmission and receiving
        ldi     r17, (1<<RXEN0)|(1<<TXEN0)

        ;set trans and receiving
        sts     UCSR0B,r17

        ;store/set bit code for 8 bit data 2 bit stop
        ldi     r17, (1<<USBS0)|(3<<UCSZ00)
      sts     UCSR0C,r17

      clr      r17
      clr      r16      
      ret