Description
This week-end I was writing a driver for what we call a component in the
Ada_Drivers_Library. It's an external chip that communicates with the CPU
though communication buses (I2C, SPI, UART).
Most of the time, writing a driver for these components is very similar to
writing a driver for the internal devices of the MCU. There's a bunch of
registers of which you have to change some of the fields to a given value.
The big difference is that those registers are not mapped in the CPU address
space, so it means we cannot directly use the representation clauses like we
do for the internal devices.
What we do now is:
- Read the raw value of the register
- Use bit shifts and masks like we would do in C...
- Write back the register
So I was writing another driver and I was thinking that it is actually possible
to use representation clauses and do unchecked conversion to and from the raw
representation. It's just very tedious to write it manually.
The idea is to use SVD2Ada to produce a slightly different output that can be
used to write better component drivers.
SVD2Ada would generate:
- Representation of the registers (that's already implemented)
- Sub programs to read and write those registers using Unchecked_Conversion
I attach and a prototype of what it could look like. There are 4 files:
-
sgtl5000_registers.ads: generated by SVD2Ada. It contains definition of the
registers and the sub-programs to read and write them. It's a generic package
so the developer of the driver can define the sub-programs to read and write
raw register data. -
sgtl5000_registers.adb: generated by SVD2Ada. It contains the implementation
of the sub-programs to read and write registers. To do that it uses
Ada.Unchecked_Conversion and the sub-programs provided by the user -
sgtl5000.ads: written by the developer. The interface of the driver.
-
sgtl5000.adb: written by the developer. It uses SGTL5000_Register to
implement the driver.
with HAL; use HAL;
with System; use System;
generic
type Driver (<>) is limited private;
with function Read_Register (This : in out Driver; Addr : UInt8) return UInt8;
with procedure Write_Register (This : in out Driver; Addr : UInt8; Data : UInt8);
package SGTL5000_Registers is
SR_Register_Address : constant := 16#0F#;
type SR_Register is record
AWD : Boolean := False;
EOC : Boolean := False;
JEOC : Boolean := False;
JSTRT : Boolean := False;
STRT : Boolean := False;
OVR : Boolean := False;
Reserved_6_7 : HAL.UInt2 := 16#0#;
end record
with Volatile_Full_Access, Size => 8,
Bit_Order => System.Low_Order_First;
for SR_Register use record
AWD at 0 range 0 .. 0;
EOC at 0 range 1 .. 1;
JEOC at 0 range 2 .. 2;
JSTRT at 0 range 3 .. 3;
STRT at 0 range 4 .. 4;
OVR at 0 range 5 .. 5;
Reserved_6_7 at 0 range 6 .. 7;
end record;
function Read (This : in out Driver) return SR_Register;
procedure Write (This : in out Driver; Reg : SR_Register);
end SGTL5000_Registers;
with Ada.Unchecked_Conversion;
package body SGTL5000_Registers is
function To_UInt8 is new Ada.Unchecked_Conversion (SR_Register, UInt8);
function To_Register is new Ada.Unchecked_Conversion (UInt8, SR_Register);
----------
-- Read --
----------
function Read (This : in out Driver) return SR_Register is
begin
return To_Register (Read_Register (This, SR_Register_Address));
end Read;
-----------
-- Write --
-----------
procedure Write (This : in out Driver; Reg : SR_Register) is
begin
Write_Register (This, SR_Register_Address, To_UInt8 (Reg));
end Write;
end SGTL5000_Registers;
with HAL; use HAL;
with HAL.I2C; use HAL.I2C;
package SGTL5000 is
type SGTL5000_DAC (Port : not null Any_I2C_Port) is
tagged limited null record;
procedure Setup (This : in out SGTL5000_DAC);
private
SGTL5000_I2C_Addr : constant := 16#0C#;
function Read_Register (This : in out SGTL5000_DAC;
Addr : UInt8)
return UInt8;
procedure Write_Register (This : in out SGTL5000_DAC;
Addr : UInt8;
Data : UInt8);
end SGTL5000;
with SGTL5000_Registers;
package body SGTL5000 is
package Registers is new SGTL5000_Registers (SGTL5000_DAC,
Read_Register,
Write_Register);
use Registers;
-----------
-- Setup --
-----------
procedure Setup (This : in out SGTL5000_DAC) is
SR : SR_Register;
begin
SR := Read (This);
-- Here we use represenation clauses instead of bit masks and shifts
SR.AWD := False;
SR.STRT := True;
Write (This, SR);
end Setup;
-------------------
-- Read_Register --
-------------------
function Read_Register
(This : in out SGTL5000_DAC;
Addr : UInt8)
return UInt8
is
Status : I2C_Status;
Data : I2C_Data (1 .. 1);
begin
This.Port.Mem_Read
(Addr => SGTL5000_I2C_Addr,
Mem_Addr => UInt16 (Addr),
Mem_Addr_Size => Memory_Size_8b,
Data => Data,
Status => Status);
return Data (Data'First);
end Read_Register;
--------------------
-- Write_Register --
--------------------
procedure Write_Register
(This : in out SGTL5000_DAC;
Addr : UInt8;
Data : UInt8)
is
Status : I2C_Status with Unreferenced;
begin
This.Port.Mem_Write
(Addr => SGTL5000_I2C_Addr,
Mem_Addr => UInt16 (Addr),
Mem_Addr_Size => Memory_Size_8b,
Data => (1 => Data),
Status => Status);
end Write_Register;
end SGTL5000;