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

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:
    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

/*** 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

We will start reading the code above starting with its payload.

  1. 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.)

  2. The _delay_ms (2718L); call results in an empty loop for the duration of about 2.718 s. That requires that the F_CPU C preprocessor macro matches the actual MCU frequency used in the circuit.

  3. Both of the actions above are enclosed in the main loop coded as while (1) { }. Given that the condition in while is always true, the loop will continue indefinitely until the MCU is reset or the power is cut down.

  4. 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.

  5. The int main () lines begin the definition of the “main” function, defined here as returning a value of the int type (a signed integer) and accepting an indefinite number of arguments. The return 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.
  6. 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

  1. Create the blink.c and Makefile files as shown above.

  2. 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 and F_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 
    
  3. 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

  1. Connect the device to the development system’s USB port. Check that the respective device file is created – either /dev/ttyUSB1 or similar.

  2. 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:
    1. type in the command above but do not start it with ⏎ Enter yet;
    2. press the reset button on the device;
    3. release the button and immediately press ⏎ Enter to start the command.
  3. 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.
  4. 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

  1. 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

  1. "5.1.2.2.1 Program Startup" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
  2. "5.1.2.2.3 Program termination" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
  3. "7.22.4.4 The exit function" (PDF). WG14 N1570 Committee Draft. 2011-04-12. Retrieved 2012-11-19.
  4. "Arduino Uno". Retrieved 2014-07-14.