Table of Contents

last revision
12 November 2016, 9:46am
book quality
just begun

* Arduino and Avr programming using mainly Assembler

This booklet contains info about my experiments with the arduino boards, such as duemilenove, pro mini, eleven etc. There is also a booklet 'avr' which deals with assembly programming of the arduino boards.

This book is about programming the avr microcontroller probably with an arduino type board. Its purpose is to investigate the avr architecture from an assembly programming perpective.

Generally my interest is focussed on coding rather than hardware, or circuit design or robotics or what-not. So I am largely content to experiment over a serial connection.

Boards ‹↑›

pro mini - based on atmega328. connect with ftdi cable to usb 32K flash (program) - 2K bootload 2K sram (data). 1K eeprom 3.3v and 8 megaherz 5v and 16 megaherz External interrupts. pin 2,3

uno - atmega328, 5v 16megaherz?

Books ‹↑›

The mazidi book looks a good and simple resource for programming the arduino in assembler. But the examples must be modified for the atmega328

http://www.microdigitaled.com/AVR/Code/AVR_codes.htm all the mazidi books examples as plain text source code are available here which is an amazing resource. Since all the examples are very carefully and well written.

wget -c --user-agent=Mozilla --no-directories --accept='Example*.*' -r -l 1 http://www.microdigitaled.com/AVR/Code/AVR_codes.htm This wget got most of the chapters but not all. -c continue, -r -l1 recursive but only one level, --accept file name pattern.

The examples need to be adapted because they are for the m32def device eg txen -> txen0, ucsrb -> ucsr0b. Also, many "out" instructions need to be replaced by "sts" since we are dealing with extended i/o space. I think

The examples are all uppercase and use tabs between mnemonics and register names.

make names better

 rename 's/\.TXT$/.txt/' *.TXT

insert an example using vim

 :r mazidi/Example11_5.asm

Goals ‹↑›

To learn how to call code using indirect addressing (ie using the x, y, z registers) so we can do something like

 call bx  ; x86 architecture

To develop a simple repl loop, "read, evaluate, print, loop" So a simple read-only forth style interface over a serial connection- what used to be known as a Forth "Home Brew"

play chess on an atmega328 microcontroller over a serial connection.

Develop a byte code system. Ie, a simple interpreter of byte code for the atmega328

Tools ‹↑›

Setting Up Bluetooth From Linux ‹↑›

HC05 ....

The HC-05 is a very inexpensive and widely used bluetooth chip which is compatible and easy to use from an Arduino board The default baud rate is 38400 (not 9600), 8 data bits, 1 stop bit, no parity bit.

https://www.itead.cc/wiki/Serial_Port_Bluetooth_Module_%28Master/Slave%29_:_HC-05 Lots of good info about the HC=05

http://www.instructables.com/id/Modify-The-HC-05-Bluetooth-Module-Defaults-Using-A/ Modify hc05 with at commands

The hc-05 flashes once when it is connected, and twice when it is paired to another device.

Using the hc05 bluetooth board we can connect to an arduino microcontroller using a linux computer with no wires!!

http://pi19404.github.io/pyVision/2015/04/03/22/ this page was very usefull for getting it all working and giving command line incantations.

It is possible blueman does this for you, below

in /etc/bluetooth/rfcomm.conf put, changing the mac address

    rfcomm0 {
      bind no;
      device 98:D3:31:XX:XX:XX;  # change this to your HC05 mac address
      channel 1;
      comment "Serial Port";
    }

install cutecom a nice gui serial terminal

 sudo apt-get install cutecom

Now pair the device using 'blueman', your device should appear with an option to connect via serial

Start the cutecom serial terminal

 sudo cutecom

Note!! On my system cutecom has to be started as root or you wont be able to connect to open /dev/rfcomm0

and in the device box type /dev/rfcomm0 (etc). Set the baud rate to 57600, and click open device. Now type your commands and see the robot move (if its a robot you are controlling). Of course this assumes you have code on the arduino that receives serial data and executes commands.

Baud Rate: in my experiments the robot moved even when the baud rate was not correct (??).

or use picocom with hc-05 connected to usb port

 sudo picocom -b 38400 --echo /dev/ttyUSB0

Unusually the default baud rate for HC-05 bluetooth is 38400 when connected via usb, but 9600 when connected via bluetooth...

Type control-a control-x to exit picocom

minicom is another alternative

 sudo minicom -b 38400 -D /dev/ttyUSB0

Then turn off hardware flow control (control-a o) and turn echo on Minicom allows scripts which could be useful. Turning hardware flow control off only seems necessary when connected via usb

Gotchas ‹↑›

If you connect tx->tx and rx->rx Wrong! (hc05 to arduino) the hc-05 will pair and connect but no do anything.

Sometimes picocom will say

 FATAL: cannot open /dev/rfcomm5: Device or resource busy
but why??? maybe the device is being used by some other process (cutecom ...)

Make sure you are using

 sudo !!!
 sudo minicom or sudo picocom

Or makes sure that you are using the last /device Eg if you try /dev/rfcomm3 when blueman has assigned /dev/rfcomm4 then rfcomm3 will be "busy"

Stack ‹↑›

The avr chips usually have a hardware stack which is used for procedure calls (call, rcall) and can be used by the programmer explicitly with "push" and "pop". However the stack is 8 bit, obviously, so it is a bit clumsy to use it to pass pointers to code or data space.

The stack pointer registers should be initialized before using the stack (ie calling a procedure).

initialize the stack, must be done before any "call/rcall"

    .include "m328Pdef.inc"

    ldi r21, high(ramend)        
    out sph, r2             
    ldi r20, low(ramend)
    out spl, r21
    

Serial Terminals ‹↑›

minicom, picocom, microcom, cutecom (easiest, graphical)

If gibberish is returned from arduino, check that baud rates match.

At Commands ‹↑›

It appears that it is not possible to configure an HC-05 via bluetooth which makes sense because it would invalidate the security of the device.

Connect HC-05 to the usb to uart converter board as follows

 rx to tx, tx:rx, 3v3 to vcc, gnd to gnd, easy...

We can use the old AT modem commands to configure an HC-05 chip I use cutecom serial monitor with baud=38400, line-ending=cr,lf device=/dev/ttyUSB0 (when the HC-05 is plugged into a usb port).

The CR+LF line ending is important, it wont work otherwise

When connecting the HC-05, hold down the little button (which it hopefully has, otherwise proceedure is different). The led will blink very slowly

Type "AT" to receive "OK" for confirmation of communication

see if communication is happening with HC-05 bluetooth chip

 AT

display the module working state

 AT+STATE?
(eg: "INITIALIZED" "READY" "PAIRABLE" "PAIRED" "INQUIRING" "CONNECTING" "CONNECTED""DISCONNECTED" "NUKNOW" )

show the bluetooth password or pin code

 AT+PSWD   or AT+PSWD?

change the bluetooth connection pin code

 AT+PSWD=<password>

check the current baud rate, stop bit and parity

 AT+UART?

set the baud rate to 38400, no stop bit, no parity

 AT+UART=38400,0,0

set the baud rate to 115200, stop bit, with parity

 AT+UART=115200,1,2

get the hc-05 bluetooth address (not mac address)

 AT+ADDR?
eg: +ADDR:1234:56:abcdef (NAP: UAP : LAP)

These addresses are used when binding master (server) and slave (client)

set module name (default hc-05)

 AT+NAME=<Param>

get bluetooth device name (address eg: 00:02:72:OD:22:24)

 AT+RNAME? 0002,72,od2224\r\n

check connect mode (0=fixed address, 1=any address, 2=slave loop)

 AT+ CMODE?

set fixed address 98:D3:31:20:93:CB

 AT+BIND=98d3,31,2093cb

Master Slave ‹↑›

In the master slave mode, the HC-05 can automatically connect to another HC-05 bluetooth chip. This obviates the need to have to manually connect via bluetooth in order to control a robot, for example

When I typed at+init the 2 modules bound together. But... there is disagreement about cmode=0/1 which is fixed address mode???

I had to configure the master HC-05 (which is plugged into the usb port of my laptop) at 38400 baud even though the slave (on the arduino robot) is apparently running at 57600. This works! I get coherent responses from the robot serial connection. If I set the master at 57600 I get gibberish responses from the robot (which indicates a baud mismatch). It seems that when the HC-05 is plugged into the usb port via the usb-to-uart converter it always runs at 38400 no matter what its configured baud rate is!!!???

On my HC-05 at+cmode=0 is fixed address and must be used with at+bind On my HC-05 at+cmode=1 binds to any address.

The key to all this is the at+init command which actually binds the hc-05 to the other hc-05. When cmode=1 (connect to any device) after at+init the hc-05 blinks slowly while it search for another device in range and then binds to it. But when cmode=0 and bind=addr the hc-05 connects quickly after at+init (2 short flashes)

set an HC-05 to master mode, and bind it to the slave (on the robot) ------ AT+ORGL // factory reset AT+RMAAD // clear all paired devices AT+UART=38400,0,0 // baud rate, stop, parity AT+ROLE=1 // master mode AT+CMODE=0 // fixed address connection mode, yes (or AT+CMODE=1 connect to any device in transmission range) AT+BIND=98D3,31,3069B0 // address of slave AT+INIT ,,,

The slave usually doesnt have to be configured...

set an HC-05 to slave mode

 AT+ROLE=0

Windows ‹↑›

install driver cp2102 to use the usb to uart device. Then putty at baud rate 38400 maybe com70

Interpreter ‹↑›

The following is the bare bones code for running an interpreter over a serial connection. Here the mcu receives data via serial and executes the appropriate command. A more sofisticated system could read the serial stream for a whole line and modify commands with parameters etc, after that, if/loop/ etc. A tiny command interpreter is what is needed, maybe an adaption of python, forth, lua or tcl

This is 'arduino' c...

a simple interpreting loop getting commands from serial

  void setup() {
    Serial.begin(57600);
    // ...
  }

  void loop() {
    if (Serial.available() > 0) {
      data = Serial.read();
      Serial.print("rx:");
      Serial.print(data);
    }
    if (data == 'a') {
      // execute command for 'a'
    }
    else if (data == 'b') {
    }
    else {}
  }

SYNTAX FOR ARDUINO C

   * print as ascii
   >> Serial.write(a); 
 Many arduino boards use the atmega328 avr chip which
 has 32K of program memory

 * turn on the led on arduino pin 13
 ----

 ;hello.asm
 ;  turns on an LED which is connected to PB5 (digital out 13)

 .include "/usr/share/avra/m328Pdef.inc"
 ;or .include "/usr/share/avra/m328def.inc"

   ldi r16,0b00100000
   out DDRB,r16   ; DDRB == data direction register B
   out PortB,r16  ; send a 'high' to PB5 only
   Start:
     rjmp Start   ; hang in an infinite loop

upload this hex file to the arduino with

 avrdude -p m328p -c stk500v1 -b 57600 -P /dev/ttyUSB0 -U flash:w:hello.hex

This is not working. The problem is the 'stk500' which may be correct for the 'uno' arduino but not for the duemilenovo. see below for a working example. The problem seems to be the -c stk500v1 option

also try for arduino uno s etc

 -b 115200 and the port -P /dev/ttyACM0

Architecture ‹↑›

The atmel atmega chips are 8 bit risc processors. They have a stack and a register file. Data memory and program memory are separate (program memory is stored in flash).

Avr Assembly ‹↑›

avr assembly is not case sensitive

a so so avr assembly site

 https://sites.google.com/site/avrasmintro/

Macros ‹↑›

Setting up the stack needs to be a macro and not a function because function calls with rcall etc, are not available until the stack pointer has been set!

macro example to set up the stack pointer

  .include "m328Pdef.inc"
    
  .macro initstack
    ldi r21,high(ramend)  
    out sph,r21        
    ldi r21,low(ramend)    
    out spl,r21        
  .endmacro
  
    initstack

Def ‹↑›

we can give registers more descriptive names with the .def assembler directive

eg --------- .def r0 = acc .def r18 = counter ,,,

Labels ‹↑›

labels require a colon, eg buffer: not buffer labels cant start with dot (ie local labels in nasm) cant have dots in them...

Constants ‹↑›

define constants

   .equ score = 100
   .set score = 100      ; the same 

Data Segment ‹↑›

In the atmel avr architecture, code and data space are separate.

specify the data segment

 .dseg

an example of creating a buffer in the data segment --------- .dseg counter .byte 2 .cseg

,,,,

Code Segment ‹↑›

With .db we can define string constants such as "test" but without any escaped characters (eg \n)

define some constant data in the code segment

  .include "m328Pdef.inc"
  .cseg
    greet: .db "hello", 0
    ;greet: .db "hello", '\n', 0
    message: .db 'h','e','l','l','o',0  ; a harder way to do it

Mnemonics ‹↑›

avr assembly, as with all microcontroller assemblies has a plethora of mnemonics or aliases for referring to ports and registers. Eg: tccr0b means "timer/counter 0 control register b". These are in addition to all the standard assembly mnemonics such as "ldi" etc.

Arithmetic Instructions ‹↑›

These instructions perform some simple arithmetic on an 8 bit register.

add, sub, inc, dec

Jumps ‹↑›

jumps go to a particular instruction if the given condition is met. There are conditions which are specific to signed and unsigned numbers among others.

brne - branch if not equal

Skips ‹↑›

skips are similar to jumps except that they only skip one (the next) instruction, if the condition is met.

Procedures ‹↑›

rcall -

Logical Instructions ‹↑›

Andi ‹↑›

andi - logical and with immediate (literal) value

Eor ‹↑›

exclusive or. This can be used for toggling bits, but the operand cant be immediate. eg "eor r20, 1" is an error and won't be compiled

Bits ‹↑›

toggle the least significant bit in r17

    eor r17, 1
    eor r17, 0x01    ; the same

toggle the most significant bit in r17

    eor r17, 1<<8
    eor r17, 0b10000000      ; this is the same, but more verbose

Moving Data ‹↑›

Table Read Instructions Lpm ‹↑›

Reading data from the code segment (flash memory) is often referred to as table reading. The code segment data is often assumed to be "constant" since it is not usually altered during the execution of a program (although it can be with the spm instructions)

There is usually much more code memory (eg 32K in atmega328) than sram data memory (2K in atmega328) so it is a good idea to store long strings here.

lpm Rn, Z - load program memory lpm Rn, Z+ - load program memory and increment pointer

read a string stored in the program memory ------ .include "m328Pdef.inc" .dseg .cseg

buffer: .db 5, "hello" ldi r16, 5 ; load string count into r16 ldi zh, high(buffer<<1) ldi zl, low(buffer<<1) inc zl nextchar: lpm r20, z ; get contents of byte at z into r16 inc zl dec r16 brne nextchar here: rjmp here ; loop for ever ,,,

In ‹↑›

in gets data from an i/o port

Ldi ‹↑›

ldi - load immediate value

an example using ldi

    ldi r16, 0b00000101   

Indirect Addressing With Ld And St ‹↑›

load contents of sram location 0x130 into r18 ------ .include "m328Pdef.inc" .eseg ; define eeprom data buffers here ; 1024 bytes of eeprom in atmega328 ebuffer: .db 5, "hello" .dseg ; define sram data buffers here. ; 2k bytes of sram in atmega328 dbuffer: .byte 30 .cseg ldi xl, 0x30 ldi xh, 0x01 ld r18, x

here: rjmp here ; loop for ever ,,,

store 0 in 16 addresses starting at 0x0060 ------ .include "m328Pdef.inc"

.dseg buffer: .byte 16

.cseg ldi r16, 16 ldi xl, 0x60 ldi xh, 0x00 ldi r20, 0x0 again: st x+, r20 dec r16 brne again

here: rjmp here ; loop for ever

,,,

Sts ‹↑›

store direct to data space

Arithmetic In Assembler ‹↑›

Division ‹↑›

There is no division instruction for the avr atmega mcs. This can be done with repeated subtraction or another technique.

Adding Two Byte Numbers ‹↑›

use addc.

Strings ‹↑›

It appears that avra doesnt support special chars such as \n

define some "constant" strings in the code segment

  .include "m328Pdef.inc"
  .cseg
    greet: .db "hello", 0
    message: .db 'h','e','l','l','o',0  ; a harder way to do it

create a 20 byte string buffer in the data segment (sram)

  .include "m328Pdef.inc"
  .dseg
    input: .byte 20 
  .cseg
    greet: .db "hello", 0

Program Data Space ‹↑›

Serial Interface ‹↑›

The avr atmega chips have a serial uart interface. This needs to be configured to a certain baud rate, parity and stop bit. The serial interface is very useful for communicating with the atmega from a computer, since the standard arduino usb connection can be treated as a serial connection. Also, in a forth-style system the serial connection is used to issue commands and "program" the chip.

By combining these techniques with and hc05 bluetooth chip we have a simple way to wirelessly control and program an arduino.

formula for value of ubrr register

 #define ubrr (FOSC/(16*BAUD))-1

Transmitting ‹↑›

By removing umsel0 code something positive happened. the little yellow tx light on the arduino uno lit up. good. wow its finally working, printing out yes yes yes the whole time. nice

To test, open a serial terminal with device /dev/ttyACM0 or something similar and select baud 9600.

transmit using the usart serial interface

  .include "m328Pdef.inc"
    
    ldi r21,high(ramend)  
    out sph,r21        
    ldi r21,low(ramend)    
    out spl,r21        
  
    ldi r16,(1<<TXEN0)     ; enable transmit  
    sts ucsr0b, r16       
    ldi r16,(1<<UCSZ01)|(1<<UCSZ00)
    ;ldi r16,(1<<UCSZ01)|(1<<UCSZ00)|(1<<umsel0) ;no good
    sts ucsr0c, r16 ; usart control/status register
    ldi r16,0x66    ; the baud rate, 9600 on 16 megaherz chip
    sts ubrr0l,r16
    ldi r16,0x00    ; the baud rate, 9600
    sts UBRR0H,r16
  
  again:
    ldi r17,'y'    
    call trnsmt      
    ldi r17,'e'    
    call trnsmt      
    ldi r17,'s'    
    call trnsmt      
    ldi r17,' '    
    call trnsmt      
    rjmp again      
  
  trnsmt:
    ; sbis doesnt work because ucsr0a is not in standard
    ; i/o space so it cant be used with sbis - skip if bit in i/o is set. 
    ; sbis UCSR0A, UDRE0
    lds r16, UCSR0A       ; usart control, status register
    sbrs r16, UDRE0       ; is UDR empty?
    rjmp trnsmt           ; if not, then just wait 
    sts UDR0,r17          ; if empty tx next character
    ret
    

transmit a string through the usart serial

  .include "m328Pdef.inc"
    
  .cseg
  
   message: .db 5, "hello"      ; counted string message
    
    ldi r21,high(ramend)  
    out sph,r21        
    ldi r21,low(ramend)    
    out spl,r21        
  
    ldi r16,(1<<TXEN0)     ; enable transmit  
    sts UCSR0B, r16       
    ldi r16,(1<<UCSZ01)|(1<<UCSZ00)
    ;ldi r16,(1<<UCSZ01)|(1<<UCSZ00)|(1<<umsel0) ;no good
    sts UCSR0C, r16      
    ldi r16,0x66    ; the baud rate, 9600 on 16 megaherz chip
    sts UBRR0L,r16
    ldi r16,0x00    ; the baud rate, 9600
    sts UBRR0H,r16
  
    rcall transmitx

  here: rjmp here

  ; forth style header
  serial:
    .db 6, "serial"
  serialx:
    ret

  transmit: 
    .dw serial
    .db 8, "transmit"
  transmitx:
    mov xh, high(message)
    ldi r17,'y'    
    call tx
    ldi r17,'e'    
    call tx      
    ldi r17,'s'    
    call tx      
    ldi r17,' '    
    call tx      
    ret

  tx:
    lds r16, UCSR0A       ; usart control, status register
    sbrs r16, UDRE0       ; is usart data register empty?
    rjmp tx               ; if not, then just wait 
    sts UDR0,r17          ; if empty tx next character
    ret
    

transmit a string stored in program memory via usart serial ------ .include "m328Pdef.inc" .dseg .cseg

buffer: .db 8, "hello it"

; start the serial usart in transmit mode ldi r16,(1<<txen0) ; enable transmit sts ucsr0b, r16 ; put in usart control status reg ldi r16,(1<<ucsz01)|(1<<ucsz00) ; 8 bit, 1 stop, no parity sts ucsr0c, r16 ldi r16,0x66 ; the baud rate, 9600 on 16 megaherz chip sts ubrr0l,r16 ldi r16,0x00 ; the baud rate, 9600 sts ubrr0h,r16

ldi zh, high(buffer<<1) ; not sure why <<1 low byte of address ldi zl, low(buffer<<1) lpm r16, z+ ; 1st byte of string is the count nextchar: lpm r20, z+ ; get contents of byte at z into r16, increment

tx: lds r17, UCSR0A ; usart control, status register sbrs r17, UDRE0 ; is usart data register empty? rjmp tx ; if not, then just wait sts UDR0,r20 ; if empty tx next character

dec r16 ; decrement counter brne nextchar ; loop while counter > 0

here: rjmp here ; loop for ever ,,,

Receiving Rx ‹↑›

receive 40 chars from usart into a buffer

  .include "m328Pdef.inc"
    
  .dseg
    buffer: .byte 60
  .cseg
  
    ; set up stack
    ldi r21,high(ramend)  
    out sph,r21        
    ldi r21,low(ramend)    
    out spl,r21        

    rcall serialx

    ldi r17,'o'    
    call trnsmt      
    ldi r17,'k'    
    call trnsmt      
    ldi r17,' '    
    call trnsmt      
 
  here:
    rcall acceptx
    rcall printx
    rjmp here 

  serial:
    .dw 0
    .db "serial", 0
  serialx:
    ldi r16,(1<<txen0)|(1<<rxen0)     ; enable transmit receive 
    sts ucsr0b, r16       
    ldi r16,(1<<ucsz01)|(1<<ucsz00)
    sts ucsr0c, r16      
    ldi r16,0x66    ; the baud rate, 9600 on 16 megaherz chip
    sts ubrr0l,r16
    ldi r16,0x00    ; the baud rate, 9600
    sts ubrr0h,r16
    ret

  
  print:
    .dw serial
    .db 5, "print"
  printx:
    ldi xl, low(buffer)       ; make x a pointer to buffer in sram
    ldi xh, high(buffer)      ; 
    ldi r20, 5                ; use r20 as a counter
  nextchar:
    ld r17, x+
  pause:
    lds r16, ucsr0a       ; usart control, status register, cant use sbis
    sbrs r16, udre0       ; is usart data register udr empty?
    rjmp pause            ; if not, then just wait 
    sts udr0,r17          ; if empty tx next character
    dec r20
    brne nextchar
    ret

  ; first byte is count.
  accept:
    .dw print          ; link to previous or null
    .db 6, "accept"
  acceptx:             ; exec token of function
    ldi xl, low(buffer)       ; make x a pointer to buffer in sram
    ldi xh, high(buffer)      ; 
    ldi r20, 10               ; use r20 as a counter
    mov yl, xl                ; save pointer in y to write count
    mov yh, xh 
  nextch:
  wait:
    lds r16, UCSR0A    ; get usart status info
    sbrs r16, RXC0     ; has a byte been received?
    rjmp wait          ; if not just wait for one.

    lds r17, udr0      ; get received byte
    st x+, r17         ; store in the buffer
    ; sts UDR0, r17      ; just send it back
    dec r20
    brne nextch        ; loop while r20 > 0
    ret

  trnsmt:
    lds r16, UCSR0A       ; usart control, status register, cant use sbis
    sbrs r16, UDRE0       ; is usart data register UDR empty?
    rjmp trnsmt           ; if not, then just wait 
    sts UDR0,r17          ; if empty tx next character
    ret
    

receiving using the usart serial interface

  .include "m328Pdef.inc"
    
  .dseg
  .cseg
  
  message: .db "hello",0

    ldi r21,high(ramend)  
    out sph,r21        
    ldi r21,low(ramend)    
    out spl,r21        
  
    ldi r16,(1<<txen0)|(1<<rxen0)     ; enable transmit receive 
    sts  UCSR0B, r16       
    ldi r16,(1<<UCSZ01)|(1<<UCSZ00)
    sts UCSR0C, r16      
    ldi r16,0x66    ; the baud rate, 9600 on 16 megaherz chip
    sts UBRR0L,r16
    ldi r16,0x00    ; the baud rate, 9600
    sts UBRR0H,r16
  
    ldi r17,'y'    
    call trnsmt      
    ldi r17,'e'    
    call trnsmt      
    ldi r17,'s'    
    call trnsmt      
    ldi r17,' '    
    call trnsmt      
  
  again:
    lds r16, UCSR0A    ; get usart status info
    sbrs r16, RXC0     ; has a byte been received?
    rjmp again
    lds r17, UDR0      ; get received byte
    inc r17            ; add one to the character
    sts UDR0, r17      ; just send it back
    rjmp again

  trnsmt:
    lds r16, UCSR0A       ; usart control, status register, cant use sbis
    sbrs r16, UDRE0       ; is usart data register UDR empty?
    rjmp trnsmt           ; if not, then just wait 
    sts UDR0,r17          ; if empty tx next character
    ret
    

Interrupts ‹↑›

Interrupts are a large and important topic in microcontrollers Assembly programming allows us to see clear what is going on when an interrupt is triggered.

jargon: isr - interrupt service routine

Priority ‹↑›

The priority of an interrupt is reflected by its position in the interrupt vector table. The lower the vector the higher the priority.

Context Saving ‹↑›

Interrupt may be the source of subtle errors since they are essentially a type of multitasking. Its probably a good idea to save the sreg (flag register).

save sreg in an isr

    .include "m328Pdef.inc"
    sampleIsr:
      ; push other registers to save here
      in r20, sreg
      push r20
      ; ...
      pop r20
      out sreg, r20
      ; pop other saved registers here
      reti

Vectors ‹↑›

Each type of interrupt has a fixed vector. So the jump to the isr must be placed at that vector

Pin Change Interrupts ‹↑›

Triggered on rising and falling edges. These can be configured for a number of pins.

External Or Hardware Interrupts ‹↑›

The external interrupt int0 appears mapped to pin 2 (portD,2) on arduino uno/nano/duemilenove, and int1 to pin 3 (portD,3)

The code below works in conjunction with a switch connected from portd,2 to ground, and an led on portc.3. The led should toggle once when the button is pressed

working, tested on arduino "uno"

trigger an interrupt on falling edge of pin (eg button push)

  .include "m328Pdef.inc"
  .org 0        
    jmp  main           ; on reset vector
  .org 0x02      
    jmp  external0isr   ; external interrupt 0 vector 
  main:  
    ldi r20, high(ramend)
    out sph, r20
    ldi r20, low(ramend)
    out spl, r20        ; init stack 
    ldi r20, 0x2        ; int0 falling edge triggered
    sts eicra, r20      ; external interrupt control register

    ;sbi ddrc, 3        ; portc.3 = output 
    sbi ddrb, 5         ; portb.5 = output (led on pin 13?)
    sbi portb, 5        ; turn on led
    sbi portd, 2        ; activate pull-up (PD3 is int1)
    ldi r20, 1<<int0    ; enable external interrupt 0
    out eimsk, r20      ; external interrupt mask register
    sei                 ; globally enable interrupts
  here:  jmp  here    
  external0isr:
    in  r21, portb
    ldi  r22, 1<<5      ; portb5, toggle    
    eor  r21, r22
    out  portb, r21    
    reti

Adc ‹↑›

http://maxembedded.com/2011/06/the-adc-of-the-avr/ A reasonable explanation of the functioning of the adc for avr chips.

Adc is yet another acronym meaning analogue to digital conversion and is included in almost all microcontrollers. Most sensors work by varying resistance or voltage depending on some environmental factor such as light, sound, temperature, pressure etc. This varying resistance gets translated into a varying voltage input into one of the avr chips input pins. The varying voltage is refered to as an 'analogue signal' or analogue input.

The ADC module of the microcontroller converts this into a digital number within a certain range. So that the MC can then analyse the value of the input.

Pwm ‹↑›

This stands for pulse width modulation. There is a duty cycle. This is a kind of approximated analogue output using digital highs and lows.

pwm code for an atmega8 (not sure if applicable to m328)


#define fillrate OCR2A 

  // main()

  PORTB=0x00;
  DDRB=0x08;  //We use PORTB.3 as output, for OC2A, see the atmega8 reference manual

  // Mode: Phase correct PWM top=0xFF
  // OC2A output: Non-Inverted PWM
  TCCR2A=0x81;
  // Set the speed here, it will depend on your clock rate.
  TCCR2B=0x02;
  // for example, this will alternate between 75% and 42% PWM
  while(1)
  {
      fillrate = 191; // ca. 75% PWM
          delay_ms(2000);

              fillrate = 107; // ca. 42% PWM
                  delay_ms(2000);
   }

Timers ‹↑›

page 346 mazidi, use timer1 to toggle led once per second

http://electronics.stackexchange.com/questions/2057/polyphonic-sounds-from-a-microcontroller some timer info

timer (or counter) in normal mode, counts 0 to 255 then sets overflow. Prescaler, or divider, divides the system clock by value

set the prescaler

    ldi r16, 0b00000011    ; set timer prescaler = 64
    out TCCR0B, r16        ; timer control register B

Prescaler values are limited to the following. The higher the prescaler value, then the slower the timer with count up to 256

1, TCCR0B = 0b0000001 8, TCCR0B = 0b0000010 64, TCCR0B = 0b0000011 256, TCCR0B = 0b0000100 1024, TCCR0B = 0b0000101

To calculate delay for timer/counter user clock/prescaler * 256 eg 16 Mhz /1024 * 256 = ??

blinking a led using the timer (counter) with prescaler

  ; blinks an LED which is connected to PB5 (digital out 13)

   .include "m328Pdef.inc"

  rjmp start

  sleep:
    in r16, TIFR0          ; get timer flags register 0
    andi r16, 0b00000010   ; tov0 ~ timer overflow flag (bit 2) 
    breq sleep             ; if overflow not set, then loop
    ldi r16, 0b00000010    ; reset timer overflow flag by setting it
    out TIFR0, r16         ; to 1
    ret
  
  start:

    ldi r19,0b00100000  ; or try etc "sbi ddrb, 0"
    out DDRB,r19        ; ddrb ~ data direction register B
    ldi r16, 0b00000101    ; set timer prescaler = 1024
    out TCCR0B, r16        ; timer control register B
    
  again:
    ldi r19,0b10101010  ;  or try sbi instruction for toggling
    out PortB,r19       ; 
    rcall sleep
    ;ldi r19,0b00000000  ; turn off led
    com r19             ; complement all bits
    out PortB,r19       ; send to pin on port b
    rcall sleep
    rjmp again

If we preload the counter with eg 128 then the timer overflows twice as quickly. TCNT0 allows different delays to be generated not just fosc/n.

preload the timer counter

    ldi r16, 128        ; preload timer with 128
    out TCNT0, r16      ; timer count register := 128

blinking a led using the timer with prescaler and preloaded counter

  ; LED connected to PB5 (digital out 13)

   .include "m328Pdef.inc"

  rjmp start

  sleep:
    in r16, TIFR0          ; get timer flags register 0
    andi r16, 0b00000010   ; tov0 ~ timer overflow flag (bit 2) 
    breq sleep             ; if overflow not set, then loop
    ldi r16, 0b00000010    ; reset timer overflow flag by setting it
    out TIFR0, r16         ; to 1
    ret
  
  start:

    ldi r19,0b00100000  ; or try etc "sbi ddrb, 0"
    out DDRB,r19        ; ddrb ~ data direction register B
    ldi r16, 0b00000101    ; set timer prescaler = 1024
    out TCCR0B, r16        ; timer control register B
    
  again: 

    ldi r16, 128        ; preload timer with 128
    out TCNT0, r16
    ldi r19,0b00100000  ;  or "sbi" set bit in i/o port
    out PortB,r19       ; 
    rcall sleep
    ldi r19,0b00000000  ; turn off led, or "cbi" clear bit in i/o port
    out PortB,r19       ; send to pin on port b
    rcall sleep
    rjmp again

By using interrupts the avr is free to do other things The code below demonstrates the important technique of implementing an interrupt handler.

The code below is incorrect. The vector address is wrong

use the timer using interrupts ---------

.include "m328Pdef.inc" .def a = r16 ;general purpose accumulator .org 0000 rjmp onreset ; reset vector .org 0003 rjmp tim0_ovf ; timer0 overflow handler

onreset: sbi ddrb, 0 ; set portb0 for output ldi a, 0b00000101 ; set prescaler to /1024 out tccr0b, a ; timer/counter control register "b" ldi a, 0b00000010 ; enable timer-overflow interupt ;ldi a, 1<<TOIE0 ; another way to write the same thing ; toie0 timer 0 overflow interrupt enable

; No, timsk0 is not accessible with out ; out TIMSK0, a ; timer interrupt mask register

; sts = store direct to data space sts timsk0, a ; timer interrupt mask register sei ; enable interupts globally

; timer overflow interrupt handler tim0_ovf: ldi r20, 1 eor pinb, r20 ; toggle the 0 bit reti

; main loop start: nop ;do nothing rjmp start

,,,

TIMER0

generate a very short delay using timer0 and toggle led on pb5

  .include "m328Pdef.inc"
  
  .macro  initstack
    ldi  r20,high(ramend)
    out  sph,r20
    ldi  r20,low(ramend)
    out  spl,r20
  .endmacro
    
    initstack
    ldi  r16,1<<5      ; toggle bit for portb,5
    sbi  ddrb,5        ; portb,5 is output
    ldi  r17,0       
    out  portb,r17     ; turn off all of portb 
  begin:
    rcall  delay  
    eor  r17,r16       ; xor toggle
    out  portb,r17
    rjmp begin
    
  delay:
    ldi r20, 0xf2  
    out tcnt0, r20   ; preload timer0 with 0xf2
    ldi r20, 0x01    ; wave form mode ???
    out tccr0a, r20  ; 
    ldi r20, 0x01    ; timer/counter control register b
    out tccr0b, r20  ; timer0 normal mode, internal clock, no prescale
  again:  
    in r20, tifr0     ; read timer0 interrupt flag register
    sbrs r20, tov0    ; just wait until overflow
    rjmp again
  
    ldi r20, 0x0
    out tccr0b, r20    ; stop timer 0
  
    ldi r20, (1<<tov0)   ; clear timer overflow flag
    out tifr0, r20       ; set to 1 to clear (!)
    ret

Compare Match Mode ‹↑›

In this mode we can trigger an event when the counter reaches a certain value

The following needs to be adapted to the atmega328

using compare match mode

  .include "m328Pdef.inc"  
  
  .macro initstack
    ldi  r16,high(ramend)
    out  sph,r16
    ldi  r16,low(ramend)
    out  spl,r16
  .endmacro
    
    initstack
    ldi  r16,0x08
    sbi  ddrb,3      
    ldi  r17,0    
  begin:  
    out  portb,r17    
    rcall  delay
    eor  r17,r16      
    rjmp   begin
  
  delay:
    ldi r20,0
    out tcnt0,r20     ; load counter with zero
    ldi r20,9         ; the value to compare to
    out ocr0a,r20     ; timer output compare register
  
    ldi r20,0x09
    out tccr0a,r20     
  again:  
    in r20, tifr    
    sbrs r20, ocf0  
    rjmp again
    ldi r20, 0x0
    out tccr0, r20    ; stop timer
    ldi r20,1<<ocf0   ;  
    out tifr,r20      ; 
    ret
  ,,,,

TIMER1 

  The atmega328 appears to have 3 timer/counters. Timer1 is
  the second and is a 16 bit counter. This is useful for 
  producing "visible" delays, that is, delays that can be 
  seen when toggling an led, for example. The 8 bit timer delays
  tend to be in the milliseconds even when using the maximum
  prescaler.
  

  * one millisecond delay with timer1
  --------
  .include "m32def.inc"
      
    ldi  r16,high(ramend)  
    out  sph,r16
    ldi  r16,low(ramend)
    out  spl,r16
  
    sbi  ddrb,5    
  begin:
    sbi  portb,5    
    rcall delay_1ms  
    cbi  portb,5    
    rcall delay_1ms  
    rjmp begin
  
  delay_1ms:
    ldi  r20,high(65536-8000)
    out  tcnt1h,r20    
    ldi  r20,low(65536-8000)
    out  tcnt1l,r20    
  
    ldi  r20,0x0
    out  tccr1a,r20    
    ldi  r20,0x1
    out  tccr1b,r20    
  again:  
    in  r20,tifr    
    sbrs  r20,tov1  
    rjmp  again
    ldi  r20,1<<tov1  
    out  tifr,r20    
    ldi  r19,0
    out  tccr1b,r19    
    out  tccr1a,r19    
    ret  

TIMER1 INTERRUPTS ....

compare/match a, compare/match b, overflow, capture event.

Delays ‹↑›

simple blinking code using about a 1 second delay

  ;  turns on an LED which is connected to PB5 (digital out 13)

  .include "m328Pdef.inc"

  ldi r19,0b00100000  ; or try etc "sbi ddrb, 0"
  out DDRB,r19        ; ddrb ~ data direction register B

 again:
  ldi r19,0b00100000  ;  
  out PortB,r19       ; 
  clr r16             ; set reg16 := 0
  clr r17
  ldi r18, 40 
  here:              ; a delay loop 40*256^2 instructions
    dec r16          ; 
    brne here 
    dec r17
    brne here 
    dec r18
    brne here 
  ldi r19,0b00000000
  out PortB,r19
  ldi r18, 40
  far:
    dec r16 
    brne far 
    dec r17
    brne far 
    dec r18
    brne far 

  rjmp again

Arduino ‹↑›

install the arduino java software

 sudo apt-get install arduino

Then select the serial device and board (eg duemilenove) Then select file/preferences and check box for verbose info during upload

run arduino software with privelidges

 sudo arduino

If you dont have the right priviledges the serial port will be greyed out.

Compiling With Avra ‹↑›

obtain a file called "m328Pdef.inc". This contains all the aliases or mnemonics for the atmega328. If you are using a different avr chip obtain the appropriate file.

remove the semi colon from the line ;.device ATmega328P

This allows the compiler to check things like memory ranges and allows use of high(ramend) etc, eg for initializing the stack.

Command Line Upload ‹↑›

The trick is to select verbose output in the arduino ide to see the appropriate switches for avrdude for a given arduino board etc.

The following details how to upload code, eg a hex file to an arduino board from the unix command line.

It doesnt seem to matter what usb socket the cable is plugged into at all. (at least not on my linux mint machine)

upload to duemilenove arduino

 sudo avrdude -v -v -v -v -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i

Notice here the device and baud rate are different.

upload to "uno" compatible freetronics arduino board

 sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:hello.hex:i

If we use the wrong baud rate, avrdude gives messages,

 avrdude: stk500_recv(): programmer is not responding
 avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00

the same without verbose output

 sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:hello.hex:i

avrdude upload command for "uno" board connected via usb

 usr/share/arduino/hardware/tools/avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:/tmp/build4770305616360723053.tmp/Blink.cpp.hex:i

below is a typical avrdude command from the ide

 /usr/share/arduino/hardware/tools/avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:/tmp/build5695632546898703228.tmp/sketch_mar13a.cpp.hex:i

tools
avra - an assembler to use with arduino
avrdude - a tool to program the arduino board

Nop ‹↑›

code to make an avr microcontroller do nothing

   here: rjmp here

Sound ‹↑›

http://avrprog.pbworks.com/w/page/9345379/AvrSound a good simple program to play midi file squeakily on a piezo buzzer

Piezo Buzzers ‹↑›

This is a small device for playing sound. Apparently it can also operate as a sensor

The piezo buzzer can make tunes by simply toggling the output on a pin at a certain frequency. But this job is perhaps better done with pulse width modulation using a %50 duty-cycle square wave, at different frequencies.

All this can be done much better with timers and interrupts. we can also simplify by using the pinb trick to toggle and output pin

circuit: pin13->100R(??)-->piezo-->Ground

buzz a piezo connected to arduino pin 13 then to ground

.include "/usr/share/avra/m328Pdef.inc"

  ldi r19,0b00100000
  out DDRB,r19
 again:
  ldi r19,0b00100000
  out PortB,r19
  clr r16
  clr r17
  ldi r18, 4 
  here:
    dec r16 
    brne here 
    dec r17
    brne here 
    dec r18
    brne here 
  ldi r19,0b00000000
  out PortB,r19
  ldi r18, 4
  far:
    dec r16 
    brne far 
    dec r17
    brne far 
    dec r18
    brne far 

  rjmp again

Power ‹↑›

Batteries are rated by their mAh which means "milliamp hours" so an AA battery rated at 1000mAh could in theory produce 1 milli-amp for 1000 hours. If a project is powered by battery or solar panel, then its really a good idea to reduce power consumption.

Arduino Uno draws 42mA (per hour)

https://www.openhomeautomation.net/arduino-battery/ an excelent tutorial for powering an atmega328 on a breadboard with only 2 AA batteries and no voltage regulator. The atmega is powered at 3V with an external crystal. Also discusses ways to make the atmega328 use less power

http://arduino.cc/en/main/standalone How to put the arduino on a bread-board powered with a ~9volt battery and using a 7805 voltage regulator to reduce voltage to 5V. Apparently this type of regulator wastes energy

http://arduino.cc/en/Tutorial/ArduinoToBreadboard similar to above but without incircuit programming of the atmega chip.

https://alanbmitchell.wordpress.com/2011/10/02/operate-arduino-for-year-from-batteries/ another low power article

Low Power ‹↑›

The atmega328 has 5 modes to reduce power consumption which can be activated through code.

Ideas to reduce power consumption, use the on-chip oscillator and clock at 8Megahz. Use a 'switching' voltage regulator. run chip at 3V or 3.3V turn off unused stuff, like ADC etc

Clock ‹↑›

you can throttle the internal clock of the ATmega328 on the fly. see CLKPR. You can educe the internal 8MHz clock to 31.250kHz with two lines of code.

Sleep Modes ‹↑›

http://www.engblaze.com/hush-little-microprocessor-avr-and-arduino-sleep-mode-basics/ a tutorial about avr sleep modes.

Battery Power ‹↑›

CR2032 20mm coin cell battery runs at 3V and doesnt require a voltage regulator. but only has a rating of 200mAh so low power consumption is necessary.

LR123 type lithium cell. high amp hours rating

Solar Power ‹↑›

Powering an arduino or atmega with solar seems feasible put panels in series to increase voltage, and in parallel to increase amperage.

http://www.instructables.com/id/Solar-powered-arduino-on-the-back-of-a-playing-car/ This is a very interesting blog on how to create a playing card sized solar panel to power an arduino

Avra ‹↑›

avra is an assembler for use with avr chips. This is good if you dont like c or java.

to use register names you need an 'inc' file for the chip

 eg: /usr/share/avra/tn13def.inc

avra doesnt seem to come with an inc file for the atmega328 but one should be findable

compile a file to a hex file

 avra hello.asm

Vim And Avr Programming ‹↑›

Vim can be used to compile, and upload to the arduino board code in a text file, or even code contained within another type of text document.

map the key sequence ';av' to compile the whole file with avra

 map ;as :!avra %<cr>
or >> map ;as :!avra % -o %:r.hex<cr>

The second example is not necessary since by default avra creates a file called name.hex where the source file is 'name.asm'

In the examples below, the complete assembly program is supposed to be within 2 'markers' within a document. The markers are '---' on a line by itself and ',,,' on a line by itself. These 2 markers mark the beginning and end of the assembly program within the document. Also the cursor needs to be between these 2 markers.

compile an avr assembly program within a document to 'test.hex'

 map ,a :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm;

compile source and upload to the arduino 'duemilanove' board

 map ,u :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/ttyUSB0 -b57600 -D -Uflash:w:test.hex:i

compile source and upload to the arduino 'uno' board

 map ,v :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; sudo avrdude -p atmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:test.hex:i

open a serial terminal to communicate with an arduino

 map ,s :! sudo picocom -b 9600 --echo /dev/ttyACM0

Type control-a control-x to exit picocom

The following is useful for determining how much space is left within a boot file (which is limited to 512 bytes)

see how big a compiled file is without running it

 map ;ab :?^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; avra test.asm; ls -la

DOCUMENT-NOTES:

avrdude options
-c the programmer
-P the serial port