I installed the GNAT-GPS and the AVR-ELF 3 days ago to play with. I got a blinky going and thought I might play around some more. I have no non-VHDL Ada experience.
Here's the scenario I have working in C:
I have it set up so that using a GPIO typedef, I can refer to all the information necessary to set up an GPIO pin (i.e. pin number, pin reg address, dd reg address and port reg address). Then I do the same for, say LED0, so that logically I can connect LED0 to GPIO15, which is itself connected to PB1 of the AVR microcontroller.
I try to do the same in Ada. I feel like I might be writing C in Ada; feel free to let me know afterwards if there's a better way to do this in Ada.
I set up the AVR registers for a particular pin to connect to its short name reference:
-- PB1
PB1_Port_reg : Unsigned_8;
PB1_Dd_reg : Unsigned_8;
PB1_Pin_reg : Unsigned_8;
for PB1_Port_reg'Address use AVR.Atmega328p.PORTB'Address;
for PB1_Dd_reg'Address use AVR.Atmega328p.DDRB'Address;
for PB1_Pin_reg'Address use AVR.Atmega328p.PINB'Address;
PB1_Pin : constant := 1;
Then I setup its short name reference to connect to its package pin number:
-- ATmega328p DIP28 Pin15 is PB1
Pin15_Port_reg : Unsigned_8;
Pin15_Dd_reg : Unsigned_8;
Pin15_Pin_reg : Unsigned_8;
for Pin15_Port_reg'Address use PB1_Port_reg'Address;
for Pin15_Dd_reg'Address use PB1_Dd_reg'Address;
for Pin15_Pin_reg'Address use PB1_Pin_reg'Address;
Pin15_Pin : constant := PB1_Pin;
Next I define a record to hold all the parameters for the pin together:
type gpio_t is record
pin : Unsigned_8;
pin_reg : Unsigned_8;
dd_reg : Unsigned_8;
port_reg : Unsigned_8;
end record;
This is to allow me to write the following function:
procedure gpio_map (gpio_t_dest : in out gpio_t; gpio_t_pin, gpio_t_pin_reg, gpio_t_dd_reg, gpio_t_port_reg : in Unsigned_8) is
begin
gpio_t_dest.pin := gpio_t_pin;
gpio_t_dest.pin_reg := gpio_t_pin_reg;
gpio_t_dest.dd_reg := gpio_t_dd_reg;
gpio_t_dest.port_reg := gpio_t_port_reg;
end gpio_map;
In the future, I'll be looking to have it as:
procedure gpio_map_future (gpio_t_dest : in out gpio_t; gpio_t_src : in gpio_t) is
begin
gpio_t_dest.pin := gpio_t_src.pin;
gpio_t_dest.pin_reg := gpio_t_src.pin_reg;
gpio_t_dest.dd_reg := gpio_t_src.dd_reg;
gpio_t_dest.port_reg := gpio_t_src.port_reg;
end gpio_map;
This gpio_map function is used to connect a package pin gpio_t to a package pin number:
gpio_map(gpio15, Pin15_pin, Pin15_pin_reg, Pin15_dd_reg, Pin15_port_reg);
I find that the LED is correctly initialized if I use this function:
core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(Pin15_pin))); -- works
But is not correctly initialized if I do:
core_reg_write(gpio15.dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- does not work
This, however, works:
core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- works
It is clear to me that I have
Pin15_pin = 1 @ address (don't care - a variable)
Pin15_pin_reg = (don't care) @ address 0x23
Pin15_dd_reg = (0b00000000) @ address 0x24
Pin15_port_reg = (don't care) @ address 0x25
And that
gpio15.pin = 1 @ address (don't care, but not same as Pin15_pin address)
gpio15.pin_reg = (don't care) @ address IS NOT 0x23
gpio15.dd_reg = (don't care) @ address IS NOT 0x24
gpio15.port_reg = (don't care) @ address IS NOT 0x25
How do I maintain fixed memory addresses for record members, i.e., get
gpio15.pin_reg = (don't care) @ address 0x23
gpio15.dd_reg = (don't care) @ address 0x24
gpio15.port_reg = (don't care) @ address 0x25
And even better if I can also get
gpio15.pin = 1 @ address (same as Pin15_pin address)
Sorry for the long question; hoping it helped make it clear.
Ok, after looking at your example, I came up with a similar solution in Ada. That said, I don't really care for how exposed access types are here. I'll leave my previous answer since I feel using records directly is a better method overall, but to specifically answer your question, here is an example I tested out in GNAT GPL 2017 using a handmade runtime (for another chip, but it was enough to verify compilation). Trying to compile it in a non embedded version of GNAT met with compiler crashes (I am assuming because the addresses were bad for windows). Hopefully this gives an example that better fits your personal requirements
registers.ads
with Interfaces;
-- Basic Register type and functionality
package Registers with Pure is
type Register is limited private;
type Register_Access is access all Register with Storage_Size => 0;
procedure Core_Reg_Write
(Target : not null Register_Access;
Value : Interfaces.Unsigned_8)
with Inline;
function Core_Reg_Read
(Source : not null Register_Access)
return Interfaces.Unsigned_8
with Inline;
private
type Register is limited record
Value : Interfaces.Unsigned_8;
end record
with Volatile, Size => 8;
end Registers;
registers.adb
package body Registers is
procedure Core_Reg_Write
(Target : not null Register_Access;
Value : Interfaces.Unsigned_8)
is begin
Target.Value := Value;
end Core_Reg_Write;
function Core_Reg_Read
(Source : not null Register_Access)
return Interfaces.Unsigned_8
is begin
return Source.Value;
end Core_Reg_Read;
end Registers;
io_registers.ads
with Registers;
-- Specific Register types and functionality
package IO_Registers with Pure is
-- Use different ones for each register to avoid accidental copy/paste
-- errors.
type Port_Register is new Registers.Register_Access;
type DD_Register is new Registers.Register_Access;
type Pin_Register is new Registers.Register_Access;
type Pin_Number is new Positive range 1 .. 8;
type GPIO_Register is record
Port_Reg : Port_Register;
DD_Reg : DD_Register;
Pin_Reg : Pin_Register;
Pin : Pin_Number;
end record;
end IO_Registers;
predefined_registers.ads
with Registers;
with System;
package Predefined_Registers is
-- Fake addresses here, since I don't have your atmega package
GPIO_15_Pin_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000400#),
Volatile,
Convention => C,
Import;
GPIO_15_DD_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000401#),
Volatile,
Convention => C,
Import;
GPIO_15_Port_Reg : aliased Registers.Register
with
Address => System'To_Address(16#80000402#),
Volatile,
Convention => C,
Import;
GPIO_15_Pin : constant := 1;
end Predefined_Registers;
program.adb
with IO_Registers;
with Predefined_Registers;
procedure Program is
GPIO_15 : IO_Registers.GPIO_Register :=
(Port_Reg => Predefined_Registers.GPIO_15_Port_Reg'Access,
Pin_Reg => Predefined_Registers.GPIO_15_Pin_Reg'Access,
DD_Reg => Predefined_Registers.GPIO_15_DD_Reg'Access,
Pin => Predefined_Registers.GPIO_15_Pin);
begin
-- Notice the use of IO_Registers for this call. The new types were
-- created there, so the corresponding ops were too
IO_Registers.Core_Reg_Write(GPIO_15.Port_Reg,16#01#);
end Program;