Generic GPIO abstraction layer for bare-metal Ada applications.
gpio_generic provides a hardware-independent GPIO interface for embedded systems. It separates configuration and data operations through distinct generic packages, enabling flexible instantiation patterns and compile-time optimization.
- Hardware-agnostic GPIO abstraction
- Type-safe pin configuration with discriminated records
- Separate control and data operation packages
- Combined interface package for convenience
- Support for all standard GPIO modes (Input, Output, Alternate, Analog)
- Configurable pull resistors, drive types, and speed levels
- Zero runtime overhead through generic instantiation
The package is organized into three layers:
Gpio_Types- Common types and configuration recordsGpio_Control- Pin configuration operationsGpio_Data- Pin data operations (set, clear, toggle, read)Gpio_Interface- Combined control and data interface (instantiates Control and Data internally)
package Gpio_Types is
GPIO_Error : exception;
GPIO_Unsupported : exception;
type Pin_Mode is (Input, Output, Alternate, Analog);
type Pull_Resistor is (None, Pull_Up, Pull_Down);
type Drive_Type is (Push_Pull, Open_Drain);
type Speed_Level is (Low_Speed, Medium_Speed, High_Speed, Very_High_Speed);
type Logic_Level is (Low, High);
subtype Alternate_Function is Natural range 0 .. 15;
type Gpio_Config (Mode : Pin_Mode := Input) is record
Pull : Pull_Resistor := None;
case Mode is
when Output | Alternate =>
Drive : Drive_Type := Push_Pull;
Speed : Speed_Level := Medium_Speed;
Init_State : Logic_Level := Low;
case Mode is
when Alternate =>
AF : Alternate_Function := 0;
when others => null;
end case;
when others => null;
end case;
end record;
end Gpio_Types;generic
type Pin_T is private;
with procedure Driver_Configure (P : Pin_T; Cfg : Gpio_Types.Gpio_Config);
with procedure Driver_Set (P : Pin_T);
with procedure Driver_Clr (P : Pin_T);
with procedure Driver_Toggle (P : Pin_T);
with function Driver_Read (P : Pin_T) return MT.Bit;
package Gpio_Interface is
subtype Pin is Pin_T;
procedure Configure (P : Pin; Cfg : Gpio_Types.Gpio_Config);
procedure Set (P : Pin);
procedure Clr (P : Pin);
procedure Toggle (P : Pin);
function Read (P : Pin) return MT.Bit;
end Gpio_Interface;Create a hardware-specific package with pin type and driver procedures:
-- stm32g431_gpio.ads
package STM32G431_GPIO is
type Pin is private;
type Port_Letter is (A, B, C, D, E, F, G);
subtype Pin_Number is Natural range 0 .. 15;
function Make_Pin (Port : Port_Letter; Nbr : Pin_Number) return Pin;
procedure Driver_Configure (Dev : Pin; Cfg : Gpio_Types.Gpio_Config);
procedure Driver_Set (Dev : Pin);
procedure Driver_Clr (Dev : Pin);
procedure Driver_Toggle (Dev : Pin);
function Driver_Read (Dev : Pin) return MT.Bit;
private
-- Hardware-specific implementation
end STM32G431_GPIO;Create a vendor-neutral GPIO package at the MCU level:
-- gpio.ads (in stm32g431 crate)
with Gpio_Interface;
with STM32G431_GPIO;
package Gpio is new Gpio_Interface
(Pin_T => STM32G431_GPIO.Pin,
Driver_Configure => STM32G431_GPIO.Driver_Configure,
Driver_Set => STM32G431_GPIO.Driver_Set,
Driver_Clr => STM32G431_GPIO.Driver_Clr,
Driver_Toggle => STM32G431_GPIO.Driver_Toggle,
Driver_Read => STM32G431_GPIO.Driver_Read);In your board package, create pin objects using the hardware driver's Make_Pin function:
-- board.ads
with Gpio;
with STM32G431_GPIO;
package Board is
Led : Gpio.Pin := STM32G431_GPIO.Make_Pin (STM32G431_GPIO.B, 8);
Spi_1_Cs : Gpio.Pin := STM32G431_GPIO.Make_Pin (STM32G431_GPIO.A, 4);
procedure Initialize;
end Board;-- board.adb
with Gpio_Types;
package body Board is
procedure Initialize is
Led_Cfg : constant Gpio_Types.Gpio_Config :=
(Mode => Gpio_Types.Output,
Pull => Gpio_Types.None,
Drive => Gpio_Types.Push_Pull,
Speed => Gpio_Types.Low_Speed,
Init_State => Gpio_Types.Low);
begin
Gpio.Configure (Led, Led_Cfg);
end Initialize;
end Board;-- Application code
with Board;
with Gpio;
procedure Main is
begin
Board.Initialize;
loop
Gpio.Set (Board.Led);
delay 0.5;
Gpio.Clr (Board.Led);
delay 0.5;
end loop;
end Main;Output pin with push-pull drive:
Cfg : constant Gpio_Types.Gpio_Config :=
(Mode => Gpio_Types.Output,
Pull => Gpio_Types.None,
Drive => Gpio_Types.Push_Pull,
Speed => Gpio_Types.High_Speed,
Init_State => Gpio_Types.Low);Input pin with pull-up resistor:
Cfg : constant Gpio_Types.Gpio_Config :=
(Mode => Gpio_Types.Input,
Pull => Gpio_Types.Pull_Up);Alternate function (e.g., UART TX on AF7):
Cfg : constant Gpio_Types.Gpio_Config :=
(Mode => Gpio_Types.Alternate,
Pull => Gpio_Types.Pull_Up,
Drive => Gpio_Types.Push_Pull,
Speed => Gpio_Types.Very_High_Speed,
Init_State => Gpio_Types.High,
AF => 7);Analog input:
Cfg : constant Gpio_Types.Gpio_Config :=
(Mode => Gpio_Types.Analog,
Pull => Gpio_Types.None);The Gpio_Config type uses Ada's discriminated records to ensure type safety. Only valid fields are accessible for each mode, with compile-time verification of configuration correctness.
Hardware abstraction through generics provides zero runtime overhead with no virtual dispatch, compile-time binding to hardware drivers, and type safety with private pin types.
The separation between generic interface (gpio_generic), MCU driver (stm32g431_gpio), MCU-level facade (gpio), and board-level pin objects (Board.Led) enables portability and clear separation of concerns.
Add to your alire.toml:
[[depends-on]]
gpio_generic = "^0.1.0"machine_types- ProvidesMT.Bittype for GPIO read operations
MIT OR Apache-2.0 WITH LLVM-exception