AVR programming introduction/Simplistic program
In this task we will consider the two basic stages in the AVR software development: building the code and uploading of the resulting binary to the MCU.
Before you begin
[edit | edit source]In this task, we will need the following hardware and software facilities:
- either a simplistic device based on ATmega8 (or compatible) or one of the Arduino boards (Uno or Nano);
- a development environment, including:
- GCC and GNU Binutils built for the AVR target;
- the GNU Make build automation tool;
- AVR Libc;
- the Avrdude image loading tool;
- the usual system facilities, such as GNU Bash and a Text editor (GNU nano, Vim, GNU Emacs, or some other);
- note that on Debian (and, presumably, its derivatives) it’s possible to install the AVR-specific tools listed above with a command like:
# apt-get install -- avr-libc binutils-avr gcc-avr avrdude
Blinking a LED
[edit | edit source]
/*** blink.c – Blink a LED at PB5, repeatedly -*- C -*- */
#include <avr/io.h> /* for DDRB, PORTB, etc. */
#include <util/delay.h> /* for _delay_ms () */
int
main ()
{
/* Arduino boards have a LED at PB5 */
DDRB |= ((1 << DDB5));
while (1) {
PORTB ^= ((1 << PB5));
_delay_ms (2718L); /* waste cycles for 2.71 s */
}
/* not reached */
/* . */
return 0;
}
/*** blink.c ends here */
### Makefile -*- Makefile -*-
MCU = atmega8
F_CPU = 7372800
CC = avr-gcc
CFLAGS = -O2 -Wall -std=gnu11 -mmcu=$(MCU)
CPPFLAGS = -DF_CPU=$(F_CPU)L
OBJCOPY = avr-objcopy
.PHONY: default
default: blink.hex blink
blink: blink.c
%.hex: %
$(OBJCOPY) -O ihex $< $@
### Makefile ends here
Reading the code
[edit | edit source]We will start reading the code above starting with its payload.
The
PORTB ^= ((1 << PB5));
line toggles (changes to high if it was low and vice versa) the PB5 MCU output (D 13 on Arduino Uno.)The
_delay_ms (2718L);
call results in an empty loop for the duration of about 2.718 s. That requires that theF_CPU
C preprocessor macro matches the actual MCU frequency used in the circuit.Both of the actions above are enclosed in the main loop coded as
while (1) { }
. Given that the condition inwhile
is always true, the loop will continue indefinitely until the MCU is reset or the power is cut down.Now, the
DDRB |= ((1 << DDB5));
line above configures the PB5 MCU pin we use as an output.Otherwise, this pin will be left configured as input and the PORTB change above will result in the internal pull-up resistor being connected to and disconnected from the pin. The current through such a resistor, however, is likely to be insufficient to drive the LED connected to the pin.
The
int main ()
lines begin the definition of the “main” function, defined here as returning a value of theint
type (a signed integer) and accepting an indefinite number of arguments. Thereturn 0;
line, which appears after the “endless” main loop, satisfies the formal requirement for a non-void
function to return a value.- It’s the
main
function specifically that the run-time C environment passes control to after completing initializations. - Such a declaration of this function is one of the allowed by the standard.[1]
- The return value of 0 is defined by the standard for
main
as the code for successful completion.[2][3] - The run-time environment actually implemented by GCC and AVR Libc for the AVR target does not pass any meaningful arguments to
main
and does not interpret the value returned in any way.
- It’s the
- Finally, the
#include
preprocessor directives at the top of the code request the following headers:avr/io.h
- includes the definitions for the
DDRB
,DDB5
,PORTB
,PB5
macros, as used in the code; util/delay.h
- includes both the declaration and definition of the (inline)
_delay_ms ()
function also used in this example.
Building
[edit | edit source]Create the
blink.c
andMakefile
files as shown above.Build the example with
make
:$ make avr-gcc -O2 -Wall -std=gnu11 -mmcu=atmega8 -DF_CPU=7372800 ../blink.c -o blink avr-objcopy -O ihex blink blink.hex $
- NB
When using an MCU other than ATmega8, or the clock frequency other than 7.3728 MHz, the
MCU
andF_CPU
build parameters are to be set accordingly.In particular, for the Arduino Uno board (as of revision 3), which uses an ATmega328P MCU and a 16 MHz crystal,[4] the Make invocation could be like:
$ make MCU=atmega328p F_CPU=16000000
Check for no errors in the output, and that the
blink.hex
file is created, containing the image to upload in the Intel hex format.
Uploading and observing operation
[edit | edit source]Connect the device to the development system’s USB port. Check that the respective device file is created – either
/dev/ttyUSB1
or similar.Check whether the Optiboot bootloader is reachable by downloading the current MCU’s flash memory image into an Intel hex format file, like:
$ avrdude -P /dev/ttyUSB1 -c arduino -b 115200 -p atmega8 \ -U flash:r:"$(mktemp --suffix=.hex -- ./XXXX)":i avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e9307 avrdude: reading flash memory: Reading | ################################################## | 100% 1.02s avrdude: writing output file "96TF.hex" avrdude: safemode: Fuses OK avrdude done. Thank you. $
- NB
- In order to start Optiboot, Avrdude tries to reset the MCU by toggling the RTS and DTR lines of the interface. If these lines are not connected to the Reset MCU pin (on some USB-to-serial adapters these lines aren’t wired at all), the MCU has to be reset manually as follows:
- type in the command above but do not start it with ⏎ Enter yet;
- press the reset button on the device;
- release the button and immediately press ⏎ Enter to start the command.
Upload the image we got to the MCU flash memory, like:
$ avrdude -P /dev/ttyUSB1 -c arduino -b 115200 \ -p atmega8 -U flash:w:blink.hex:i avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e9307 avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file "blink.hex" avrdude: writing flash (90 bytes): Writing | ################################################## | 100% 0.02s avrdude: 90 bytes of flash written avrdude: verifying flash memory against blink.hex: avrdude: load data flash data from input file blink.hex: avrdude: input file blink.hex contains 90 bytes avrdude: reading on-chip flash data: Reading | ################################################## | 100% 0.01s avrdude: verifying ... avrdude: 90 bytes of flash verified avrdude: safemode: Fuses OK avrdude done. Thank you. $
- NB
- The note on starting the bootloader above remains valid in this case just as well.
Check that the operation of the system is as expected by estimating the blinking period and comparing it to the one given in the code.
Research
[edit | edit source]Change the argument to
_delay_ms ()
in the code and observe how the behavior of the system changes. Also, could you name where the orignial number possibly comes from? Note that you will need to repeat the building and uploading stages after each change.
References
[edit | edit source]- ↑ "5.1.2.2.1 Program Startup" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
- ↑ "5.1.2.2.3 Program termination" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
- ↑ "7.22.4.4 The exit function" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
- ↑ "Arduino Uno". Retrieved 2014-07-14.