Table of Contents
Programming AVR with ADA programming language
The purpose of this page is to provide information on building and running embedded software on Atmel AVR family devices using Ada programming language. Some information can be obtained in sourceforge project AVR-Ada. Unfortunately AVR-Ada project is no longer being developed. AdaCore, the company maintaining GNAT compiler recently released cross compiler version of GNAT for AVR. All tools are cross-platform, if binaries are not available they can be compiled to run on any platform as sources are attainable to download.
January 2012
Software
To start writing embedded code in Ada, several software packages are required:
- AdaCore GPS (part of GNAT AVR package)
- WinAVR GNU gcc tools and libraries for AVR
- Atmel AVR Studio 5 (Only for device XML description file)
CRT - C Run-Time Library
Usually programs written in Ada comes with extensive run-time library. In embedded environment especially such low-end as in AVR8 platform, memory cost of such RTL is very high. Additionally there's no OS and HAL. In embedded world initialization code is device-specific and CRT1) must consist:
- Program entry point
- Vector table
- Stack initialization
- Global variables initialization
AdaCore provides sample initialization code written in assembler for ATmega2560. Instead of writing code for other chips it is easier (and safer) to use CRT code from WinAvr. Compiled CRT files can be found in \avr\lib\
subdirectory of WinAvr distribution. For example: AtMega8 uses following file: \avr\lib\avr4\crtm8.o
. It is advised to compile WinAvr rather than using pre-compiled files.
Device specyfication package
Each AVR device has a number of internal peripherals and registers associated with them. Registers are located at various memory locations specific to the type of the device. AdaCore provides tool (avr_gen) for generating Ada package based on Atmel XML device specification files, but only in redundant AVRStudio 4 format. I rewritten similar tool which works with current device specification format (AVRStudio 5). Please note that tool is designed to work with AVR8 device description files (using it with AVR32 and Xmega families will require modifications).
To build this tool following software is required:
- GNAT x86 (for your OS)
- XMLAda (Both software packages are available on http://libre.adacore.com)
To build device specification package:
- Build or unzip tool
- Copy device specification file from
<Program Files>\Atmel\AVR Studio 5.0\devices\
into tool directory - Run
main device.xml >avr-device.ads
wheredevice
is changed into appropriate name. Ex:main atmega8.xml >avr-atmega8.ads
Makefile
When all required files and tools are installed, machine code can be compiled using avr-gnatmake
tool. It's handy to keep compilation and programming scripts in makefile and invoke them using make
.
Sample makefile for AtMega8 is:
all: main.hex main.lss sizedummy program: avrdude -pm8 <PROGRAMMER SPECIFIC> -Uflash:w:"main.hex" main.elf: force avr-gnatmake main -o $@ -Os -mmcu=avr4 --RTS=zfp -largs crtm8._o -nostdlib -lgcc -mavr4 -Tdata=0x00800200 main.lss: main.elf -avr-objdump -h -S main.elf >"main.lss" main.hex: main.elf avr-objcopy -O ihex $< $@ sizedummy: main.elf -avr-size --format=avr --mcu=atmega8 main.elf clean: $(RM) *.o leds *.ihex *.ali *.elf *.hex *.lss *.map
The most important line is:
avr-gnatmake main -o $@ -Os -mmcu=avr4 --RTS=zfp -largs crtm8._o -nostdlib -lgcc -mavr4 -Tdata=0x00800200
-Os
turns on size optimization–RTS=zfp
sets Ada RTL to 'Zero FootPrint'-mmcu=avr4
and-mavr4
sets device architecture to avr4 (AtMega8 specific, change for other devices)-largs
allows passing arguments to linkercrtm8._o
forces linker to link Ada code with CRT. Please note that the file extension was changed from.o
to._o
, if the extension remained unchanged file would be removed when usingmake clean
-nostdlib
forces linker not to attach any libraries by itself-lgcc
required when using-nostdlib
, allows resolving reference to proceduremain
-Tdata=0x00800200
sets data code section to appropriate address
Setting up GPS
GPS (GNAT Programming Studio) is an editor maintained by AdaCore and it's default editor for software written in Ada. If makefile is present it is easy to use it to develop embedded software in Ada.
- Select
Build→Settings→Targets
- Select
Run→Run Main
and changeTarget model
tomake
- Select
Clean→Clean All
and changeTarget model
tomake
- Assure that command in text box is
make clean
- Select
Project→Build *
and changeTarget model
tomake
- Assure that command in text box is
make all
- Now code can be generated and run just like in any other Ada project.
Useful code samples
Following code samples are written and tested for ATmega8 device.
Lighting LED
Assuming LED anode is connected to AtMega8 PORTB pin 0 and cathode is connected to the power rail trough appropriate resistor.
with Interfaces; use Interfaces; with AVR.AtMega8; use AVR.AtMega8; procedure Main is -- LED LedPort : Unsigned_8; for LedPort'Address use AVR.atmega8.PORTB'Address; LedPin : constant := 2#00000001#; begin -- Initialize DDRB := 1; -- only pin 0 is output -- turn on LedPort := LedPort or LedPin; -- turn off LedPort := LedPort and not LedPin; -- toogle LedPort := LedPort xor LedPin; -- infinite loop: end of program loop end loop; end Main;
Using interrupts
avr-interrupts.adb
-- AVR ATMEGA8 - Interrupts -- Maciej Kucia Krakow 2012 -- MIT/X11 license with System.Machine_Code; package body AVR.Interrupts is procedure Enable is begin System.Machine_Code.Asm ("sei", Volatile => True); end Enable; procedure Disable is begin System.Machine_Code.Asm ("cli", Volatile => True); end Disable; end AVR.Interrupts;
avr-interrupts.ads
-- AVR ATMEGA8 - Interrupts -- Maciej Kucia Krakow 2012 -- MIT/X11 license package AVR.Interrupts is -- Enables interrupts procedure Enable; -- Disables interrupts procedure Disable; private pragma Inline(Disable); pragma Inline(Enable); end AVR.Interrupt;
Buffered interrupt driven UART
avr-uart.ads
with Interfaces; use Interfaces; with AVR.strings; use AVR.strings; with System; package AVR.UART is Flaga_A : boolean := False; -- Initialize timer and port B. procedure Init; -- wypisuje znak procedure WriteChar (char : Character); procedure Write(d : Unsigned_8); -- zwraca ilosc znakow w function BufferCnt return Integer; -- wczytuje znakow w buforze wejsciowym function Read return Unsigned_8; function ReadChar return Character; -- wypisuje lancuch znakow procedure WriteString( str: AVR_String); procedure WriteStringP( str: AVR_String); -- wypisuje liczbe procedure WriteInteger (i : Integer; b: Integer); -- Wysyla znaki z bufora procedure ProcessWrite; private pragma Volatile(Flaga_A); pragma Inline_Always( BufferCnt); pragma Inline_Always(processWrite); procedure Last_Chance_Handler (Source_Location : System.Address; Line : Integer); pragma Export (C, Last_Chance_Handler, "__gnat_last_chance_handler"); end AVR.UART;
avr-uart.adb
with System.Machine_Code; use System.Machine_Code; with AVR.atmega8; use AVR.atmega8; with AVR.strings; use AVR.strings; with AVR.Interrupt; with Environment; package body AVR.UARTis type Unsigned_3 is mod 2 ** 3; for Unsigned_3'Size use 8; type Unsigned_4 is mod 2 ** 4; for Unsigned_4'Size use 8; -- Bufory cykliczne type TablicaUART8 is array (0..7) of Unsigned_8; type TablicaUART16 is array (0..15) of Unsigned_8; BuffWe : TablicaUART8; pragma Volatile(BuffWe); BuffWeP : Unsigned_3 :=0 ; -- poczatek pragma Volatile(BuffWeP); BuffWeK : Unsigned_3 :=0 ; -- koniec pragma Volatile(BuffWeK); BuffWeC : Integer range 0..8 :=0 ; -- ile pragma Volatile(BuffWeC); BuffWy : TablicaUART16; pragma Volatile(BuffWy); BuffWyP : Unsigned_4 :=0 ; -- poczatek pragma Volatile(BuffWyP); BuffWyK : Unsigned_4 :=0 ; -- koniec pragma Volatile(BuffWyK); BuffWyC : Integer range 0..16 :=0 ; -- ile pragma Volatile(BuffWyC); -- Przerwania procedure USART_RxComplete; pragma Machine_Attribute (USART_RxComplete, "signal"); pragma Export (C, USART_RxComplete, AVR.Interrupt.vec_uart_rx); procedure USART_RxComplete is rec : Unsigned_8; begin while (UCSRA and UCSRA_RXC) = 0 loop null; end loop; rec := UDR; --UDR := rec; --echo if BuffWeC <= 8 then BuffWe(Integer(BuffWeK)) := rec; BuffWeK:=BuffWeK + 1; BuffWeC:=BuffWeC+1; end if; Flaga_A := True; end USART_RxComplete; procedure USART_TxComplete; pragma Machine_Attribute (USART_TxComplete, "signal"); pragma Export (C, USART_TxComplete, AVR.Interrupt.vec_uart_dree); procedure USART_TxComplete is begin if BuffWyC > 0 then while (UCSRA and UCSRA_UDRE) = 0 loop null; end loop; UDR := BuffWy(Integer(BuffWyK)); BuffWyK:=BuffWyK + 1; -- ada dba o zaokraglanie BuffWyC:=BuffWyC-1; else UCSRB := UCSRB and not (UCSRB_UDRIE); end if; end USART_TxComplete; function BufferCnt return Integer is begin return Integer(BuffWeC); end; -- Zamiany typow function To_Char is new Ada.Unchecked_Conversion (Target => Character, Source => Unsigned_8); function To_Uint8 is new Ada.Unchecked_Conversion (Target => Unsigned_8, Source => Character); procedure Write (d : Unsigned_8) is begin if BuffWyC < 16 then BuffWy(Integer(BuffWyP)) := d; BuffWyP := BuffWyP + 1; BuffWyC := BuffWyC + 1; end if; end Write; procedure WriteChar (char : Character) is begin Write(To_Uint8(char)); end WriteChar; characters : constant array (0 .. 15) of Character := ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'); procedure WriteInteger (i : Integer; b: Integer) is help : Integer range 0..9 :=0; tmp : Integer := i; arr : array (0..9) of Character; begin if tmp < 0 then WriteChar('-'); tmp := -tmp; end if; if tmp = 0 then WriteChar('0'); else while tmp /= 0 loop arr(help) := characters( tmp mod b ); tmp := tmp / b; help := help+1; end loop; while help /=0 loop WriteChar(arr(help-1)); help := help - 1; end loop; end if; end WriteInteger; procedure ProcessWrite is begin -- Aktywacja przerwania if ( BuffWyC > 0) then UCSRB := UCSRB or UCSRB_UDRIE; end if; end ProcessWrite; -- wypisuje lancuch znakow na uart procedure WriteString( str: AVR_String) is begin for I in str'Range loop WriteChar (str(I)); end loop; end WriteString; -- odczyt z pamieci function Get_PGM_Byte (Addr : System.Address) return Unsigned_8 is Result : Unsigned_8; begin Asm ("lpm %0, Z", Outputs => Unsigned_8'Asm_Output ("=r", Result), Inputs => System.Address'Asm_Input ("z", Addr)); return Result; end Get_PGM_Byte; function Get_PGM_Char (Addr : System.Address) return Character is U : Unsigned_8; begin U := Get_PGM_Byte (Addr); return Character'Val (U); end Get_PGM_Char; -- wypisuje lancuch znakow z pamieci programu procedure WriteStringP( str: AVR_String ) is C : Character; begin for U in str'Range loop C := Get_PGM_Char (str (U)'Address); WriteChar (C); end loop; end WriteStringP; function Read return Unsigned_8 is ret : Unsigned_8; begin if BuffWeC > 0 then ret := BuffWe(Integer(BuffWeP)); BuffWeP:= BuffWeP+1; BuffWeC:= BuffWeC-1; return ret; else return 0; end if; end Read; -- Odczyt znaku z bufora function ReadChar return Character is begin return To_Char(Read); end ReadChar; -- Initializacja UART procedure Init is begin -- Uruchamiam moduly odbiorczy i nadawczy USART UCSRB := UCSRB or UCSRB_RXEN or UCSRB_TXEN or UCSRB_RXCIE or UCSRB_UDRIE; -- 8 bit 1 bit stopu UCSRC := UCSRC or UCSRC_URSEL or UCSRC_UCSZ0 or UCSRC_UCSZ1; -- Wczytuje poprzednio obliczone wartosci baud --UBRRH := 0; UBRRL := 51; !!!!!!!!!!!!!!!!1 BuffWe(0):=0; BuffWy(0):=0; end; procedure Last_Chance_Handler (Source_Location : System.Address; Line : Integer) is begin null; -- error handling here end Last_Chance_Handler; end AVR.UART;
Example:
with AVR.UART; use AVR.UART;
Storing strings in program memory
-- AVR ATMEGA8 - Strings -- Maciej Kucia Krakow 2012 -- with Interfaces; use Interfaces; package AVR.strings is type AVR_String is array (Unsigned_8 range <>) of Character; type Progmem_String is new AVR_String; subtype PStr is Progmem_String; -- Strings stored in flash memory: Flash_String_HELLOADA : AVR_String := "Hello Ada!" & ASCII.LF; pragma Linker_Section (Flash_String_HELLOADA, ".progmem"); Flash_String1 : AVR_String := "This is string nr.1 " & ASCII.LF; pragma Linker_Section (Flash_String1, ".progmem"); Flash_String2 : AVR_String := "There is no new line after this"; pragma Linker_Section (Flash_String2, ".progmem"); end AVR.strings;
Timer0 PWM
avr-timer0pwm.ads
with Interfaces; use Interfaces; package AVR.TIMER0PWM is -- inicjalizuje timer0 w trybie PWM procedure InitPWM; -- ustawia wypelnienie PWM procedure SetPWM(val: Unsigned_8); private pragma Inline(SetPWM); pragma Inline(InitPWM); end AVR.TIMER0PWM;
ada-timer0pwm.adb
with AVR.AtMega8; use AVR.AtMega8; package body AVR.TIMER0PWM is procedure InitPWM is begin -- Tumer setup OCR1AH :=0; OCR1AL :=0; OCR1BH :=0; OCR1BL :=0; TCCR1A := TCCR1A_WGM10 or TCCR1A_COM1A1; TCCR1B := TCCR1B_WGM12 or TCCR1B_CS10; end InitPWM; procedure SetPWM(val: Unsigned_8) is begin OCR1AH := val; OCR1AL := val; end SetPWM; end AVR.TIMER0PWM;
TWI interface
avr-twi.ads
package AVR.TWI is TW_READ : constant := 1; TW_WRITE : constant := 0; TWI_FREQ : constant := 100_000; procedure Init; function Start(address : Unsigned_8) return Boolean; procedure Stop; function Write( data : Unsigned_8 ) return Boolean; function ReadAck return Unsigned_8; function ReadNak return Unsigned_8; private pragma Inline_Always(Init); end AVR.TWI;
avr-twi.adb
with AVR.AtMega8; use AVR.AtMega8; with Environment; package body AVR.TWI is -- Start TW_START : constant := 16#08#; TW_REP_START : constant := 16#10#; TW_STATUS_MASK : constant := 248; -- trans TW_MT_SLA_ACK : constant := 16#18#; -- SLA+W transmitted, ACK got TW_MT_SLA_NACK : constant := 16#20#; -- SLA+W transmitted, NACK got TW_MT_DATA_ACK : constant := 16#28#; -- data transmitted, ACK got TW_MT_DATA_NACK : constant := 16#30#; -- data transmitted, NACK got -- rec TW_MR_SLA_ACK : constant := 16#40#; -- SLA+R transmitted, ACK got TW_MR_SLA_NACK : constant := 16#48#; -- SLA+R transmitted, NACK got TW_MR_DATA_ACK : constant := 16#50#; -- data transmitted, ACK got TW_MR_DATA_NACK : constant := 16#58#; -- data transmitted, NACK got function To_Char is new Ada.Unchecked_Conversion (Target => Character, Source => Unsigned_8); procedure Init is begin -- Init twi prescaler and bitrate TWSR := 0; TWBR := Interfaces.Unsigned_8 ( ((Environment.F_CPU / TWI_FREQ) - 16 ) /2 ); end Init; -- zwraca false gdy failed function Start(address : Unsigned_8) return Boolean is twst : Unsigned_8; begin --Send start TWCR := TWCR_TWINT or TWCR_TWSTA or TWCR_TWEN; -- czekaj while ( (TWCR and TWCR_TWINT) = 0) loop null; end loop; twst := TWSR and TW_STATUS_MASK and 16#F8#; -- sprawdzam odpowiedz if ( twst /= TW_START) and ( twst /= TW_REP_START ) then return false; end if; -- Wysylam adress TWDR := address; TWCR := TWCR_TWINT or TWCR_TWEN; -- czekam while ( (TWCR and TWCR_TWINT) =0 ) loop null; end loop; -- sprawdzam twst := TWSR and TW_STATUS_MASK and 16#F8#; if ( (twst /= TW_MT_SLA_ACK) and (twst /= TW_MR_SLA_ACK) ) then return false; end if; return true; end Start; procedure Stop is begin TWCR := TWCR_TWINT or TWCR_TWEN or TWCR_TWSTO; while (( TWCR and TWCR_TWSTO ) /= 0) loop null; end loop; end Stop; -- true jezeli sie powiodlo function Write( data : Unsigned_8 ) return Boolean is twst : Unsigned_8; begin TWDR := data; TWCR := TWCR_TWINT or TWCR_TWEN; while ( (TWCR and TWCR_TWINT) = 0 ) loop null; end loop; twst := TWSR and TW_STATUS_MASK and 16#F8#; if (twst /= TW_MT_DATA_ACK) then return false; end if; return true; end Write; function ReadAck return Unsigned_8 is begin TWCR := TWCR_TWINT or TWCR_TWEN or TWCR_TWEA; while ( (TWCR and TWCR_TWINT) = 0 ) loop null; end loop; return TWDR; end ReadAck; function ReadNak return Unsigned_8 is begin TWCR := TWCR_TWINT or TWCR_TWEN; while ( (TWCR and TWCR_TWINT) = 0 ) loop null; end loop; return TWDR; end ReadNak; end AVR.TWI;
Watchdog
avr-watchdog.ads
package AVR.Watchdog is type Watchdog_Timeout is ( WT16k, WT32k, WT64k, WT128k, WT256k, WT512k, WT1024k, WT2048k ); for Watchdog_Timeout'Size use 8; -- init watchdog reset/interrupt procedure Enable(tout:Watchdog_Timeout); -- reset watchdog timer procedure Wdr; procedure Reset renames Wdr; -- disable watchdog reset/interrupt procedure Disable; private pragma Inline_Always (Wdr); pragma Inline_Always (Reset); pragma Inline_Always (Enable); pragma Inline_Always (Disable); end AVR.Watchdog;
avr-watchdog.adb
with System.Machine_Code; use System.Machine_Code; with AVR.AtMega8; use AVR.AtMega8; package body AVR.Watchdog is function WT2Uint8 is new Ada.Unchecked_Conversion (Watchdog_Timeout, Unsigned_8); procedure Enable(tout : Watchdog_Timeout) is begin WDTCR := WDTCR or WDTCR_WDCE or WDTCR_WDE or WT2Uint8(tout); WDTCR := WDTCR or WDTCR_WDCE or WDTCR_WDE or WT2Uint8(tout); end Enable; procedure Disable is begin WDTCR := WDTCR or WDTCR_WDCE or WDTCR_WDE; WDTCR := 0; end Disable; procedure Wdr is begin Asm ("wdr", Volatile => True); end Wdr; end AVR.Watchdog;