Simple Arduino Code

Read time: 56 minutes (14058 words)

While we are working on the design of our simple simulator, let’s take a short break and build our first real AVR code. This will not be much, but it is something we can run on the actual Arduino board, and it will be something we can get our simulator to do as well.

Blinking an LED!

You saw this code already, in the first lab project involving the AVR chip. In that project you used simple C code to send out a Morse Code, using the on-board LED on the Arduino UNO board.

This time, we will just blink that LED on and off using assembly language.

AVR I/O

The AVR chip, like many simple machines designed to control hardware, has some special registers (memory locations) where the bits are connected to pins on the chip. When you write a zero on those pins, no current will flow. If we hook up an LED to that pin, the LED will stay off. However, if we write a “1” into that one bit, the attached LED will light up.

Pretty simple.

The challenge is to get the board set up to work that way, and then cause the one bit to change at the right rate, so we can see the LED light up.

The memory locations associated with output pins on the chip are called “Ports”. One 8-bit port can control eight separate pins to which we can attach all kinds of things.

Those pins can be set up to be either output pins (for an LED, for example) or an input pin, which could be connected to some kind of sensor we want to check. We will play with those later.

We control which way we want signals to go on the pins by writing data into a second “register”, this one designed to switch each pin between input and output.

Note

You are free to choose which way each pin goes. THis is pretty handy when building systems.

Since our attiny85 chip only has six pins available, not all of the possible pins are used. For this chip, we have these pins available:

Pin Name Arduino Pin
1 PB5 D13 (on board LED)
2 PB3 D11
3 PB4 D12
5 PB0 D8
6 PB1 D9
7 PB2 D10

Note

The Arduino pin number can be used to wire things up for testing your code on the Arduino board.

We will set up our code to use PB5, which is connected to the on-board LED on the Arduino.

Just for reference, here is a diagram showing all the pin definitions on the Arduino board:

../_images/UNOV3PDF.png

In the documentation, the register where these I/O bits are located goes by the name PORTB. The I/O direction register is named DDRB (data direction register for PORTB). If we write a “1” into one bit in DDRB, then the corresponding pin in PORTB will be configured for output. Writing a “0” in that pin will make the corresponding pin in PORTB an input.

Writing the Code

Before we show “real” AVR assembly code, we have one major issue to deal with:

Funky AVR Registers

Reference:SFR_Registers

As we have learned, the chip designers can set up special internal registers that are used internally to do the work of the chip. Sometimes, but not always, those registers are made available to the programmer. In the case of the AVR family, there is a set of registers associated with I/O. As we have seen that means we will be able to use those registers to control signals arriving or leaving the chip, something we are sure to want to do.

All of these special I/O registers in the AVR chips are actually in a separate memory area, not normally available to the programmer. But the designers made them available by “memory mapping” them into the normal data memory area. What that means is that when you think you are accessing certain addresses in the data memory, you are actually accessing different locations internally.

Unfortunately, in designing these chips they were unable to do this cleanly. So the addresses you need to use to access them are different from what the manufacture tells you to use. The avr-gcc tools have a funny way of fixing this problem. You will see a lot of code with their fix, but I think their fix is way too ugly to use.

So I fixed their fix!

I am going to use the C preprocessor to clean up your code.

When you access one of these funny registers in the chip, I am going to ask you to wrap up that register name like so:

_(PORTB)

What the avr-gcc standard way of doing this looks like is this:

_SFR_IO_ADDR(PORTB)

Sorry, but I do not like that. It is way too “wordy, and it makes the code look far less like what the manufacturer uses in their code examples! YMMV!

My fix is simple. In the config.inc file (shown below, I add this line:

#define _(s)    _SFR_IO_ADDR(s)

Then I use that “macro” to wrap up the registers with this issue so the compiler will not complain about them.

Note

The set of registers we need to worry about includes these, which cover most projects you will work on for this class.

  • PORTx
  • DDRx
  • TIFRx
  • TCCROx
  • TCNTx
  • OCRxA
  • OCRxB
  • TIFRx
  • SPL
  • SPH
  • SREG

(Note: the “x” above can be any letter of digit)

For any others where there is a problem, the compiler will tell you there is a problem by displaying an error message that looks like this:
setup.S:6: Error: number must be positive and less than 32

When you see that, wrap up the offending register name like this:`` _(name)`` and compile again.

Device Configuration

I like to set up my projects using a simple scheme.

The first file I write is a configuration file that uses the C Preprocessor define directive to set up the important project settings. Here is the file for this project:

config.inc
1
2
3
4
5
6
7
8
9
#include <avr/io.h>

#define LED_PIN     5
#define LED_PORT    _(PORTB)
#define LED_DIR     _(DDRB)

// include this line to avoid SFR_REG issues
#define _(s)    _SFR_IO_ADDR(s)

Note

The include line at the top of this file is important. In any file where you will be using any predefined AVR register names, you need to include this line. I just place that line in the config.inc file, then include this file in every project file.

Main Loop

Next, I create the basic structure of the code. Since we are not going to be using the IDE for this example, this will be a “normal” C program that looks like this:

main.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
; blink - blink an LED on PB5 on and off at 1/2 second rate

#include    "config.inc"

    .global     main
    .extern     setup
    .extern     blink

    .section    .text
main:
        rcall   setup
1:      rcall   blink
        rjmp    1b


Note

All assembly language files written for the AVR must end in .S so the compiler knows how to process the file.

There sure is not much here. But it is enough! This file is set up the same way as the code you write in the Arduino IDE, only that system does not ask you to create a main function, the IDE creates it for you.

There are a few things to note here.

If a label in this file is to be referenced in another file, we need to mark that label as .global. Similarly, if we refer to a label defined in another file, we need to mark that label as .extern. These tags are used by the linker to hook all the application components together,

Look closely at that loop. The label at the top of the loop is just a number followed by a colon. The branch later, refers to that number, but with one letter added:

  • f - look for the closest label with this number in the forward direction.
  • b - look for the closest label with this number in the backward direction.

This is quite handy. You can “reuse” these simple numbers as you wish to create markers for branching. No more coming up with strange names for labels.

This project is pretty simple, We are going to make the LED blink, and all I need to do for that is set up the chip, which is done in the setup function, then set up a loop that calls a function named blink, which will “toggle” the LED. That means it will switch the LED off if it is on, and on if it is off, when called. How we do all of that is our next files.

Chip Initialization

The call to setup prepares the chip for this project. It will configure the chip properly, using definitions from the included config.inc file.

setup.S
1
2
3
4
5
6
7
#include    "config.inc"
    .global      setup
    .section    .text
setup:
    sbi     LED_DIR, LED_PIN
    cbi     LED_PORT, LED_PIN
    ret

Here, all we need to do is configure the I/O port we will be using. That involves setting one pin on the output port for output. I also like to set that pin to off, so the LED is initially off. That really does not matter in this project.

Blinking Logic

I put all the logic involved in making the blink happen in a separate file. (If this was a C++ project, that would be a class file). This file simply defines the blink function.

blink.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include "config.inc"

    .global     blink
    .extern     delay
    .section    .text
blink:
    // toggle LED
    in      r24, LED_PORT
    ldi     r25, (1 << LED_PIN)
    eor     r24, r25
    out     LED_PORT, r24
    rcall   delay
    ret

This bit of code is actually pretty simple, but getting at the I/O port is a bit involved. Even though it is in the data memory (so it seems) we cannot access it using normal memory reference instructions. Instead, we use common I/O instructions called in and out.

What we are doing her is loading the current contents of the output port we have where the lED is connected (PORTB). We than create a “mask” which has a “1” in the right bit position for the pin attached to the LED (Pin 5) LED. The C “shift” operator builds that mask. The eor instruction applies the Exclusive-OR logical operator to “flip” the bit. That is a cool trick you can use, regardless of the current setting on that pin. Once we have flipped the bit, we put everything back, and the LED toggles.

Notice that we actually read and wrote all the bits in that I/O port register, but we modified only one bit in there!

Delay Logic

We have one more task to deal with: delaying! In your Arduino lab, you called a library routine that caused your program to “delay” for some amount of time. I bet you used that call without even wondering what was happening inside. The answer is a bit complicated, so we will cheat and do things in a very simple way. We will write our own delay routine.

Wasting Time

So, how do we get a program to waste time (and why would you want to do that when blowing up space aliens needs to happen FAST!

What we need to do is absolutely nothing a bunch of times!

From your study up to this point, you know that it takes some amount of time to do each instruction. How much time the processor takes to do that one instruction depends on how fast the clock is in that system.

Let’s see, the AVR chip (at least on the Arduino) is running at 16 MHz. Furthermore, most instructions in that chip complete in one clock tick. If we want to delay our program for even a half a second, we may need to do nothing 8 million times! Yikes!

If we set up a loop that does nothing, we might be able to burn a few clock ticks. Let’s do some research into that idea.

In a previous lecture, we examined the basic pattern needed to set up a while loop. We do not want to loop forever, so we need a way to stop the loop. This sounds line a job for a counter variable.

But this is only an 8-bit machine, and the biggest number we can get to is 255. Even doing nothing in a loop 255 times is not going to slow the code down much. Bit it is a start.

A simple loop just needs to test a variable using a compare instruction, then is does a conditional branch around the loop body. The loop body is where we do work. At the end of the loop body, we branch back up to out conditional test and see if we keep going.

Here is the logic we need for this simple loop:

delay.S
1
2
3
4
5
6
7
8
9
#include "config.inc"
    .global      delay
    .section    .text
delay:
        ldi      r26, 255
1:      dec      r26
        brne     1b
        ret

Well, that is certainly a loop, and it will delay a bit. But by how much. Let’s do some calculations and figure that out!

Freq = 16000000 MHz

Seconds per tick = 1/16000000 = 62.5ns

ldi time = 1 tick
dec time = 1 tick
brne time = 1 tick of no branch, otherwise 2 ticks

SO, the time for this loop is this:

1 + 255 * (1 + 2) + 1 = 767 ticks

767 * 62.5 = 47.9375us

t i a bit short of 1/2 second. So, how do we slow it down more?

Why don’t we wrap this loop up in another loop and see what happens:

delay.S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "config.inc"

    .section    .text
delay:
        ldi     r25, 255
2:      ldi     r26, 255
1:      dec     r26
        brne    1b
        dec     r25
        brne    2b
        ret

1 + 255 * ( 1 + 255 * 3 + 1) + 1 = 12.224ms

Boy, we still are not there, but you get the idea! We are wasting a huge bunch of cycles by just spinning the machine through a loop. This is not quite what the library routine does, but it does something similar.

We now have enough code to try this out (after doing one tweak on the delay routine which I am not going to show (guess why).

Building the Code

We are at a point where we need to compile (actually assemble) this code and see it run. But how do we do that? I might think about using the Arduino IDE, but that system will not process this project. Instead, we need to fall back to our old friend make and see if we can get that to help out.

Note

I am going to show how to process this program in your Linux VM. However, I set this one up differently, using the Vagrant tool on my MacBook laptop. The notes on that setup are in the Appendix. In this lecture I will show how to manually configure your Linux VM to build this code.

Step 1: Install the AVR Tools

The avr-gcc compiler is available for any platform. In fact that is the tool set included in the Arduino IDE. Here is how you install those tools on your Linux VM system:

$ sudo apt-get install binutils-avr gcc-avr avr-libc avrdude

Warning

This assumes your VM is already set up for program development, meaning make is available. If not, add make to that list.

Once this completes, you are ready to code.

Step 2: Add a Makefile

As usual, we need a Makefile to help build the project code.

Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# source files
TARGET	:= $(shell basename $(PWD))
CSRCS	:= $(wildcard *.c)
COBJS	:= $(CSRCS:.c=.o)
SSRCS	:= $(wildcard *.S)
SOBJS	:= $(SSRCS:.S=.o)
OBJS	:= $(COBJS) $(SOBJS)

LST	:= $(TARGET).lst

# define the processor here
MCU		:= atmega328p
FREQ	:= 16000000L

# define the USB port on your system (this works on Linux)
PORT	:= /dev/ttyACM0
PGMR	:= arduino

# tools
GCC		:= avr-gcc
OBJDUMP	:= avr-objdump
OBJCOPY	:= avr-objcopy
DUDE	:= avrdude

UFLAGS	:=  -v -D -p$(MCU) -c$(PGMR)
UFLAGS		+= -P$(PORT)
UFLAGS		+= -b115200

CFLAGS	:=  -c -Os -mmcu=$(MCU)
CFLAGS		+= -DF_CPU=$(FREQ)

LFLAGS	:= -mmcu=$(MCU)
LFLAGS	+= -nostartfiles

.PHONY all:
all:	$(TARGET).hex $(LST)

# implicit build rules
%.hex:	%.elf
	$(OBJCOPY) -O ihex -R .eeprom $< $@

%.elf:	$(OBJS)
	$(GCC) $(LFLAGS) -o $@ $^ 

%.o:	%.c
	$(GCC) -c $(CFLAGS) -o $@ $^

%.o:	%.S
	$(GCC) -c $(CFLAGS) -o $@ $<

%.lst:	%.elf
	$(OBJDUMP) -C -d $< > $@

# utility targets
.PHONY:	load
load:
	$(DUDE) $(DUDECONF) $(UFLAGS) -Uflash:w:$(TARGET).hex:i

.PHONY:	clean
clean:
	$(RM) *.hex *.lst *.o *.elf

Note

There is one important line in this file that you may need to change for your project and your processor:

This MCU definition is set for the attiny85 here. To compile this code for the Arduino UNO board we use in this class, change that line to this:

MCU := atmega328p

What happens when make runs here is very different from the normal C++ process we have been using. The build process for the AVR involves different files in different formats. It looks something like this:

file(s).S -> file(s).o -> app.elf -> app.hex -> load to chip

Most of the work is being done in those “implicit” make target rules that process files with certain name extensions.

Note

An elf file is just an object file in a different format, one that is common in the Linux world. We will look at the .hex file later.

This Makefile will also produce a listing file that can be useful when debugging your code.

Step 3: Compile your project

Now, we can process our code files and build the file we need to load on the chip!

$ make
avr-gcc -c -mmcu=attiny85 -o blink.o blink.S
avr-gcc -c -mmcu=attiny85 -o delay.o delay.S
avr-gcc -c -mmcu=attiny85 -o main.o main.S
avr-gcc -c -mmcu=attiny85 -o setup.o setup.S
avr-gcc -w -Os -mmcu=attiny85 -o blink.elf blink.o delay.o main.o setup.o
avr-objcopy -O ihex -R .eeprom blink.elf blink.hex
avr-objdump -C -d blink.elf > blink.lst
rm blink.elf

Note

That last line is a bit annoying. I might like to keep the .elf file around until I explicitly “clean: it away, but `make has a bunch of predefined internal rules we have not looked into, that it uses on standard projects. Apparently, there was a rule that decided that we did not need that .elf file.

The AVR “Executable” File

The final product of all this work is a single text file that is the equivalent of an executable on other systems. Here is that file:

blink.hx
1
2
3
:1000000085B190E2892785B901D00895AFEFAA950F
:10001000F1F7089502D0F4DFFECF259A2D980895C8
:00000001FF

This file format was invented way back in the 1970s. I tells a loader program what binary data to load into memory using a string of hex characters. The actual format is well defined, but not important here.

Examining the Listing File

The listing file we produced is a detailed “dump” of the final code sent to the chip. It is actually produced by “disassembling” the .elf file we produced.

Here is the listing file produced from this project code:

blink.lst
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

blink.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__ctors_end>:
   0:	85 b1       	in	r24, 0x05	; 5
   2:	90 e2       	ldi	r25, 0x20	; 32
   4:	89 27       	eor	r24, r25
   6:	85 b9       	out	0x05, r24	; 5
   8:	01 d0       	rcall	.+2      	; 0xc <delay>
   a:	08 95       	ret

0000000c <delay>:
   c:	af ef       	ldi	r26, 0xFF	; 255
   e:	aa 95       	dec	r26
  10:	f1 f7       	brne	.-4      	; 0xe <delay+0x2>
  12:	08 95       	ret

00000014 <main>:
  14:	02 d0       	rcall	.+4      	; 0x1a <setup>
  16:	f4 df       	rcall	.-24     	; 0x0 <__ctors_end>
  18:	fe cf       	rjmp	.-4      	; 0x16 <main+0x2>

0000001a <setup>:
  1a:	25 9a       	sbi	0x04, 5	; 4
  1c:	2d 98       	cbi	0x05, 5	; 5
  1e:	08 95       	ret

Yes, that is long and hard to read, but it shows exactly what ends up on the chip. Since is was produced from the actual binary code to be loaded on the chip, we lose all the register and user names from the original files, and see the raw numbers (in hex) instead.

Step 4: Loading the Code on the AVR

The tool used to load the “hex” file on the hip is called avrdude (who comes up with these names?)

Here is the command we will use:

$ make load

Getting this running when doing your work on the class VM requires some setup so the VM can access the USB connection to the processor board. That is not covered here. Check in the Appendix for that setup.