Introductory Tutorial

Tutorial for Programming AVR Devices in Ada

Introduction

The tutorial should lower the entrance difficulties of programming Atmel's AVR microcontrollers using the Ada compiler AVR-GCC, aka AVR-GNAT. GNAT is the free GNU Ada compiler, which is part of the GNU Compiler Collection. We assume basic knowledge of programming microcontrollers in general and also some basic knowledge of Ada.

There are tutorials at avrfreaks.net about the AVR controllers and at http://en.wikibooks.org/wiki/Ada_Programming about Ada.

Although I originally come from a Unix background I do most of my work now in a MS-Windows environment. The tutorial aims to leave out the details of the hosting OS as much as possible.

Prerequisites

You need the following hard- and software to write and run your own Ada programs on AVR micros:

  • A PCB or breadboard carrying an AVR. Again see AVRFreaks for suggestions of prototyping and development kits. Personally I use the AVR Butterfly as it is cheap (< 25€) and has a lot of peripherals. A standard development board is the STK500 from Atmel. Recently the Arduinos became quite popular. You can also solder your own PCB or put the controller directly on a breadboard.

  • AVR-gcc compiler including avr-libc, AVR-Ada, and the avr-binutils. If you run MS-Windows I strongly recommend to get the latest WinAVR distribution. It has all the necessary tools nicely packaged. Some Linux distros also provide AVR development tools (Ubuntu, Suse). They probably do not contain the latest bug fixes and patches. I therefore recommend to build your own tool-chain using our build script "build-avr-ada.sh". It completely automates the download of all source packages and the latest patches and it builds and installs everything.

  • A programming device and corresponding software. If you use an AVR-Butterfly or the popular Arduino hardware you only need a serial or USB cable. For all other development kits you need something like PonyProg or AVRDUDE. Atmel's STK500 also works very well (you'll need their AVR-Studio on MS-Windows).

  • A development environment, i.e. a text editor for writing your programs. I am a hardcore Emacs user, don't expect any other recommendations from my part. WinAVR comes with PN (programmer's notepad), other people use Notepad++, Eclipse or AVR-Studio.

Generating Target Code

The compiler generates AVR object code based on your Ada code. The AVR-Ada tool-chain is based on the general GNAT tools from GCC and the Binutils. The tool-chain includes the compiler itself and some helper programs:

  • project manager (avr-gnatmake)
  • preprocessor (avr-gnatprep)
  • compiler (avr-gcc)
  • assembler (avr-as)
  • binder (avr-gnatbind)
  • link manager (avr-gnatlink)
  • linker (avr-ld)

All these tools either come from the GCC or Binutils packages. A typical user does not have to care about the separate tools. The only interface that one uses is the project manager and the corresponding project files. The project manager avr-gnatmake knows about the helper programs and invokes them appropriately.

Some other programs are outside the proper tool-chain. They are best managed by the Unix make program and the corresponding Makefile.

''There should be a page about the intrinsics of the build process.''

Simple Example

Let's start with a simple example. Consider an ATmega168 (or any other AVR part) that is on a bread board or in the STK500 development board. The example program should switch one port of the controller to output. One of the port pins is connected to a LED. The other pin of the LED is connected via a resistor to Vcc, i.e. 5V. To switch on the LED the corresponding pin must be set to low or ground level, i.e. 0V.

The source code of the program is in a file called led_on.adb

with AVR;      use AVR;   -- 1: make available general type definitions and names
with AVR.MCU;             -- 2: make use of the controller specific ports and pins
                          -- 3:
procedure LED_On is       -- 4:
   LED : Boolean renames MCU.PortB_Bits (3); -- 5: rename controller pin 3 of port B to a name of
                                             -- 6: the problem domain
begin                                        -- 7:
   MCU.DDRB_Bits := (others => DD_Output);   -- 8: set data direction of all pins of port B to output
   LED := Low;                               -- 9: draw output pin to low level, which turns on the LED
end LED_On;                                  -- 10:

We also need a GNAT project file (GPR) that contains all the Ada specific settings and a list of required source files. Create a file called build.gpr.

                                        -- 1: enable access to the standard AVR library by extending
project Build extends "avr_app.gpr" is  -- 2: the name of the project has to match the file name

   for Main use ("led_on");             -- 4: the name of the main program

   for Object_Dir use "obj";            -- 6: hide the generated files in a subdir
   for Exec_Dir use ".";                -- 7: keep the main files in the current directory

   for Source_Files use ("led_on.adb"); -- 9: the list of required source files

end Build;

At the end we also need a Makefile that glues all the commands together. Copy the Makefile from the exampels of your AVR-Ada installation. There are some parameters that have to be set in the Makefile.

  • MCU, the name of the processor, that is atmega8 here
  • ADA_TARGETS, the name of the main program, led_on
  • GPR, the name of the GNAT project file, here: build.gpr

you can leave the rest as it is.

Now in a command window (shell, DOS-Box, xterm, etc.) you type

make

and you will see a bunch of commands and their respective output coming by. The last command will display the use of the different memory sections and the percentage of free space.

Your program is now stored in a file named "led_on.elf". This file has to be transfered to your micro-controler. The exact handling depends on your hardware equipment. We assume an ISP-programmer like the mkII or the STK-500 driven by avrdude. Review the settings in your Makefile: you have to specify the port that you use, e.g. com4, and the type of the programmer, e.g. stk500v2. Read the help output of avrdude to see what other options are available.

Accessing Registers

Most of the processor's registers are 8 bits wide, some have 16 bits. All registers can be read and written like a normal variable of the same width.

   A : Unsigned_8;
   B : Unsigned_8;
begin
   ...
   A := MCU.PinA;
   MCU.PortB := B;

All registers are declared in the Ada specification AVR.MCU as of type Interfaces.Unsigned_8 or Interfaces.Unsigned_16 respectively. The name of a register in the Ada specification matchs the name in the corresponding data-sheet.

Additionally all 8-bit registers are also declared as arrays of 8 booleans. The name of the register as a boolean array is the standard name with the suffix _Bits.

--  Bit_Number indexes the single bits in a byte
type Bit_Number   is new Nat8 range 0 .. 7;
--  Give access to every single bit in a byte as a boolean.
type Bits_In_Byte is array (Bit_Number) of Boolean;

That permits to set or to clear single bits within a register.

MCU.DDRB_Bits (3) := DD_Output;
MCU.DDRB_Bits (2) := DD_Input;
MCU.PortB_Bits (3) := True;
Key_Is_Open := MCU.PinB (2);

The preferred coding style is a Boolean renaming of the selected pin.

declare
   Key : Boolean renames MCU.PinB (2)
begin
   if Key = Low then
      ...

IO Ports

All the AVR micro-processors have one or several general purpose input / output (GPIO) ports. The ports are typically named by single letters, eg. port B. Each port consists of three registers,

  • a data direction register
  • an input register
  • an output register

If a port pin is configured as input, an internal pull-up resistor can be activated by writing to the output register. See the Atmel data sheets about the architecture of the microprocessor that you use.

MCU.DDRB       -- the data direction register of port B
MCU.PortB      -- the output register of port B (or pull-up configuration for inputs) 
MCU.PinB       -- the input register of port B

See also the page about [SimpleIO].

Specify AVR Device

Atmel has released more than 50 different AVR micrcontrollers in different memory sizes, different internal capabilities, different instrunctions, etc. The user has to provide this information to the compiler to refer to the correct ports and to make use of the correct instruction set. The standard way of providing this information is by setting the option -mmcu=.

avr-gcc -c -mmcu=atmega328p ...

If you want to refer to the symbolic names of the different ports and registers in your Ada code, you have to "with" the corresponding package specification. You also have to "use" it depending on your programming style.

with AVR.ATMEGA328p; use AVR.ATMEGA328p;

When you write potentially reusable packages, you want to stay independant of a specific MCU. Otherwise the users have to change the with and use clauses in all files that they use. The preferred programming style in this case is to refer to AVR.MCU in your application code.

with AVR.MCU; use AVR.MCU;

A corresponding file was automatically created in the AVR library directory of your device at the installation. It contains a renaming of the part specific definitions of the used MCU.

with AVR.ATMEGA328P;
package AVR.MCU renames AVR.ATMEGA328P;

If you use the Makefile and gpr file provided by AVR-Ada you don't have to set that file manually. Simply set the device name in the top of the Makefile. You can refer to AVR.MCU everywhere in your code. When you change the microcontroller in the project it is sufficient to change only the corresponding definition at the top of your makefile.


Related

Wiki: SimpleIO