在Ada中维护记录成员的固定内存地址

Maintaining fixed memory addresses for record members in Ada

我3天前安装了GNAT-GPS和AVR-ELF来玩。我眨了眨眼,想我可能会再玩一些。我没有非 VHDL Ada 经验。

这是我在 C 中工作的场景:

我已将其设置为使用 GPIO typedef,我可以参考设置 GPIO 引脚所需的所有信息(即引脚号、引脚注册地址、dd 注册地址和端口注册地址)。然后我对 LED0 做同样的事情,这样逻辑上我可以将 LED0 连接到 GPIO15,它本身连接到 AVR 微控制器的 PB1。

我尝试在 Ada 中做同样的事情。我觉得我可能正在用 Ada 编写 C;如果在 Ada 中有更好的方法,请随时告诉我。

我为特定引脚设置了 AVR 寄存器以连接到其短名称参考:

       -- 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;

然后我设置它的短名称引用以连接到它的封装引脚号:

   -- 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;

接下来我定义一个记录来保存引脚的所有参数:

   type gpio_t is record
      pin   : Unsigned_8;
      pin_reg   : Unsigned_8;
      dd_reg    : Unsigned_8;
      port_reg  : Unsigned_8;
   end record;

这是为了允许我编写以下函数:

 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;

将来,我希望它成为:

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;

此 gpio_map 函数用于将封装引脚 gpio_t 连接到封装引脚编号:

gpio_map(gpio15, Pin15_pin, Pin15_pin_reg, Pin15_dd_reg, Pin15_port_reg);

我发现如果我使用这个函数,LED 会被正确初始化:

core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(Pin15_pin))); -- works

但如果我这样做,则没有正确初始化:

core_reg_write(gpio15.dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- does not work

然而,这有效:

core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- works

我很清楚我有

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

还有那个

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

如何为记录成员维护固定的内存地址,即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

如果我也能得到就更好了

gpio15.pin = 1 @ address (same as Pin15_pin address)

抱歉这个问题太长了;希望它有助于澄清。

你不能通过两种类型的赋值真正得到你想要的。所做的只是复制当前值,而不是寄存器地址。这是一个选项:

创建一个与您的 gpio_t 类型相似的类型,但要使其与您的微控制器的寄存器映射完全匹配。这意味着您不会在其中存储密码,您需要包括所有周围的寄存器。这是我从另一个文件中为不同的 micro 找到的示例,但希望能作为示例

type Register_Layout is limited record
      DIR      : Unsigned_32;
      DIRCLR   : Unsigned_32;
      DIRSET   : Unsigned_32;
      DIRTGL   : Unsigned_32;
      OUTVAL   : Unsigned_32;
      OUTCLR   : Unsigned_32;
      OUTSET   : Unsigned_32;
      OUTTGL   : Unsigned_32;
      INPUT    : Unsigned_32;
      CTRL     : Unsigned_32;
      WRCONFIG : Unsigned_32;
      EVCTRL   : Unsigned_32;
   end record
      with
         Pack,
         Volatile,
         Size => 12*32;

应限制记录类型,以确保它是通过引用而不是通过复制传递的。

注意:您也可以使用表示子句来提供结构的字节和位布局。这将取决于您使用的编译器。

一旦你的微控制器布局与数据表相匹配,你就可以创建一个变量并将其映射到你想要的地址,就像你对单个变量所做的那样

Register_B : Register_Layout with
      Address => System'To_Address(Some_Address),
      Volatile => True,
      Import => True;

这会将整个记录变量映射到该地址。

之后,您需要修改函数调用以将整个记录作为参数,而不仅仅是寄存器。例如:

Core_Reg_Write_DIR(Register_B, Shift_Left(1,Integer(PB1_Pin)));

如果你想让事情变得更花哨,并通过引脚选择正确的寄存器和掩码值,那么你要么需要使用

  1. CASE 语句
  2. 访问数组types/addresses(以引脚类型为索引)。
  3. 一种从引脚计算寄存器地址和掩码并将其用于使用引脚作为参数的函数调用内局部声明变量的地址属性的方法。

您真的不能对单独的记录组件进行不同的寻址(这在 C 和 C++ 中也是如此)。

经过一番思考,我决定继续我在 C 中所做的工作。在那里,我定义了以下 typedef

typedef struct {
    IO_REG_TypeDef_t portr;
    IO_REG_TypeDef_t ddr;
    IO_REG_TypeDef_t pinr;
    volatile uint8_t pin;
} GPIO_TypeDef_t;

而IO_REG_t本身定义为

typedef struct {
    volatile uint8_t* io_reg;
} IO_REG_TypeDef_t;

很明显,gpio 的关键参数都在 typedef 中。我想在 Ada 中做同样的事情。再次,如果我在 Ada 中说 C,请原谅我;欢迎提出更多符合 Ada 标准的方法。

我定义了gpio引脚组件:

   -- GPIO15 is PB1 on ATmega328p 28 DIP
   gpio15_pin_reg : Unsigned_8;
   for gpio15_pin_reg'Address use Atmega328p.PINB'Address;
   gpio15_dd_reg : Unsigned_8;
   for gpio15_dd_reg'Address use Atmega328p.DDRB'Address;
   gpio15_port_reg : Unsigned_8;
   for gpio15_port_reg'Address use Atmega328p.PORTB'Address;
   gpio15_pin : constant Unsigned_8 := 1;

寄存器读写函数定义:

   procedure core_reg_write (reg: in out Unsigned_8; value: in Unsigned_8) is
   begin
      reg := value;
   end core_reg_write;

   function core_reg_read (reg: in Unsigned_8) return Unsigned_8 is
      value : Unsigned_8;
   begin
      value := reg;
      return value;
   end core_reg_read;

然后定义一条记录,这一次,它围绕着 pin 变量,而不是 pin、dd 和端口寄存器的变量,而是它们的地址:

   type gpio_t is record
      pin       : Unsigned_8;
      pin_reg_addr  : System.Address;
      dd_reg_addr   : System.Address;
      port_reg_addr     : System.Address;
   end record;

给定 gpio 引脚的记录已组装:

   gpio15 : gpio_t := (gpio15_pin, gpio15_pin_reg'Address, gpio15_dd_reg'Address, gpio15_port_reg'Address);

获取此记录并设置引脚参数的程序定义:

   procedure gpio_output (gpio : in gpio_t) is
      dd_reg : Unsigned_8;
      for dd_reg'Address use gpio.dd_reg_addr;
   begin
      core_reg_write(dd_reg, core_reg_read(dd_reg) or shift_left(1,integer(gpio.pin)));
   end gpio_output;

   procedure gpio_hi (gpio : in gpio_t) is
      port_reg : Unsigned_8;
      for port_reg'Address use gpio.port_reg_addr;
   begin
      core_reg_write(port_reg, core_reg_read(port_reg) or shift_left(1,integer(gpio.pin)));
   end gpio_hi;

   procedure gpio_lo (gpio : in gpio_t) is
      port_reg : Unsigned_8;
      for port_reg'Address use gpio.port_reg_addr;
   begin
      core_reg_write(port_reg, core_reg_read(port_reg) and not shift_left(1,integer(gpio.pin)));
   end gpio_lo;

在每个过程中,由于缺乏更好的描述,所需的寄存器被手动取消引用。

以下序列位于 begin 关键字之后:

 -- Initialize
   gpio_output(gpio15); 

 For_loop_0:
   loop

    -- turn on
        gpio_hi(gpio15);

        -- loop
      Lazy_delay_1:
    for I in Unsigned_32 range 0 .. 100_000 loop
        null;
    end loop Lazy_delay_1;

    -- turn off
        gpio_lo(gpio15);

        -- loop
      Lazy_delay_2:
    for I in Unsigned_32 range 0 .. 100_000 loop
        null;
    end loop Lazy_delay_2;

   end loop For_loop_0;

并且 LED 灯闪烁。

这实现了我想要的,但我对采用复合 gpio_t-like 类型并且不需要手动取消引用 address/pointer.

的其他方法持开放态度

好的,在看了你的例子之后,我在 Ada 中想出了一个类似的解决方案。也就是说,我真的不在乎这里公开的访问类型如何。我将保留我之前的答案,因为我觉得直接使用记录总体上是一种更好的方法,但为了具体回答你的问题,这是我在 GNAT GPL 2017 中使用手工制作的运行时测试的一个例子(对于另一个芯片,但这已经足够了验证编译)。尝试在 GNAT 的非嵌入式版本中编译它遇到了编译器崩溃(我假设是因为地址不适合 windows)。希望这给出了一个更符合您个人要求的示例

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;

玩了一会儿之后,在这个在线编译器 (https://www.tutorialspoint.com/compile_ada_online.php) 中我开始工作了:


with Ada.Text_IO; use Ada.Text_IO;
with Interfaces; use Interfaces;
with System; use System;

procedure Hello is

    -- pseudo hardware registers, unknown addresses, known contents
    temp0 : interfaces.unsigned_8 := 2#00000101#; -- pinr
    temp1 : interfaces.unsigned_8 := 2#10000000#; -- ddr
    temp2 : interfaces.unsigned_8 := 2#10000000#; -- portr

    -- core
    type io_reg_t is limited record
        io_reg  :   interfaces.unsigned_8;
    end record;
    pragma volatile(io_reg_t); --  Verify relevance.

    -- processor
    gpio15_pinr : aliased io_reg_t;
    for gpio15_pinr'address use temp0'address;
    gpio15_ddr : aliased io_reg_t;
    for gpio15_ddr'address use temp1'address;    
    gpio15_portr : aliased io_reg_t;
    for gpio15_portr'address use temp2'address;
    gpio15_pin : constant interfaces.unsigned_8 := 1;

    procedure core_reg_write_old (reg: in out unsigned_8; value: in unsigned_8) is
    begin
      reg := value;
    end core_reg_write_old;

    procedure core_reg_write (reg: access io_reg_t; value: in unsigned_8) is
    begin
      reg.io_reg := value;
    end core_reg_write;

    function core_reg_read (reg: access io_reg_t) return Unsigned_8 is
    begin
      return reg.io_reg;
    end core_reg_read;    

    -- gpio
    type gpio_t is record
        pinr    :   access io_reg_t;
        ddr     :   access io_reg_t;
        portr   :   access io_reg_t;
        pin     :   interfaces.unsigned_8;
    end record;
    pragma volatile(gpio_t); -- Verify relevance.

    procedure gpio_output (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
    end gpio_output;

    procedure gpio_hi (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
    end gpio_hi;

    procedure gpio_lo (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
    end gpio_lo; 

    gpio15 : gpio_t := (
        pinr    => gpio15_pinr'access,
        ddr     => gpio15_ddr'access,
        portr   => gpio15_portr'access,
        pin     => gpio15_pin
    );

    -- led
    type led_t is record
        gpio    :   gpio_t;
    end record;

    led0 : led_t := (gpio => gpio15);

    procedure led_init (led : in led_t) is
    begin
        gpio_output(led.gpio);
    end led_init;

    procedure led_on (led : in led_t) is
    begin
        gpio_hi(led.gpio);
    end led_on;

    procedure led_off (led : in led_t) is
    begin
        gpio_lo(led.gpio);
    end led_off;

begin
  put_line("Hello, world!");
  -- Does it match the original value of 5?
  put_line(gpio15.pinr.io_reg'Image);

  -- Does modification via variable alter the value returned?
  temp0 := 203;
  put_line(gpio15.pinr.io_reg'Image);

  -- Does modification via record alter the value returned?
  gpio15.pinr.io_reg := 89;
  put_line(gpio15.pinr.io_reg'Image);

  -- Writes value in temp2 (128) to temp0.
  core_reg_write_old(temp0,temp2);

  put_line(gpio15.pinr.io_reg'Image);
  put_line(gpio15.ddr.io_reg'Image);
  put_line(gpio15.portr.io_reg'Image);
  put_line(gpio15.pin'Image);

  -- Writes value of pin (1) to pinr via record.  
  --core_reg_write(gpio15.ddr,gpio15.pin);

  -- Writes 1 shifted value of pin times and or's that with ddr reg
  --gpio_output(gpio15);
  led_init(led0);

  put_line(gpio15.pinr.io_reg'Image);
  put_line(gpio15.ddr.io_reg'Image);
  put_line(gpio15.portr.io_reg'Image);
  put_line(gpio15.pin'Image);

  --gpio_hi(led0.gpio);  
  led_on(led0);

  put_line(gpio15.pinr.io_reg'Image);
  put_line(gpio15.ddr.io_reg'Image);
  put_line(gpio15.portr.io_reg'Image);
  put_line(gpio15.pin'Image);

  --gpio_lo(led0.gpio);   
  led_off(led0);

  put_line(gpio15.pinr.io_reg'Image);
  put_line(gpio15.ddr.io_reg'Image);
  put_line(gpio15.portr.io_reg'Image);
  put_line(gpio15.pin'Image);

end Hello;

我为我的嵌入式环境修改了这个但是编译失败,投诉:

undefined reference to `__gnat_last_chance_handler’

对于行“reg.io_reg := value”和“return reg.io_reg”。

我发现如果我的访问类型明确声明为“not null”,我实际上并不需要 last_chance_handler。

所以更新后的程序变成了:


with Interfaces; use Interfaces;
with System;
with Atmega328p;

procedure Main is

   -- core
    type io_reg_t is limited record
        io_reg  :   interfaces.unsigned_8;
    end record;
    pragma volatile(io_reg_t); -- Verify relevance.

    type dd_io_reg_t is new io_reg_t;

    -- Location?
    gpio15_pinr : aliased io_reg_t;
    for gpio15_pinr'address use Atmega328p.PINB'Address;
    gpio15_ddr : aliased io_reg_t;
    for gpio15_ddr'address use Atmega328p.DDRB'Address;    
    gpio15_portr : aliased io_reg_t;
    for gpio15_portr'address use Atmega328p.PORTB'Address;
    gpio15_pin : constant interfaces.unsigned_8 := 1; 

    procedure core_reg_write (reg: not null access io_reg_t; value: in interfaces.unsigned_8) is
    begin
        reg.io_reg := value;
    end core_reg_write;

    function core_reg_read (reg: not null access io_reg_t) return interfaces.unsigned_8 is
    begin
        return reg.io_reg;
    end core_reg_read;    

    -- gpio
    type gpio_t is record
        pinr    :   not null access io_reg_t;
        ddr     :   not null access io_reg_t;
        portr   :   not null access io_reg_t;
        pin     :   interfaces.unsigned_8;
    end record;
    pragma volatile(gpio_t); --  Verify relevance.

    -- gpio_output
    procedure gpio_output (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
    end gpio_output;

    procedure gpio_hi (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
    end gpio_hi;

    procedure gpio_lo (gpio : in gpio_t) is
    begin
        core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
    end gpio_lo; 

    gpio15 : gpio_t := (
        pinr    => gpio15_pinr'access,
        ddr     => gpio15_ddr'access,
        portr   => gpio15_portr'access,
        pin     => gpio15_pin
    );

    -- led
    type led_t is record
        gpio    :   gpio_t;
    end record;

    led0 : led_t := (gpio => gpio15);

    procedure led_init (led : in led_t) is
    begin
        gpio_output(led.gpio);
    end led_init;

    procedure led_on (led : in led_t) is
    begin
        gpio_hi(led.gpio);
    end led_on;

    procedure led_off (led : in led_t) is
    begin
        gpio_lo(led.gpio);
    end led_off;

begin

    -- Initialize
    -- Writes value of pin (1) to pinr via record.  
    --core_reg_write(gpio15.ddr,gpio15.pin);

    -- Writes 1 shifted value of pin times and or's that with ddr reg
    --gpio_output(gpio15);
    led_init(led0);

 For_loop_0:
   loop

        -- turn on
        --gpio_hi(led0.gpio);  
    led_on(led0);

        -- loop
      Lazy_delay_1:
    for i in interfaces.unsigned_32 range 0 .. 100_000 loop
        null;
    end loop Lazy_delay_1;

        -- turn off
        --gpio_lo(led0.gpio);   
    led_off(led0);

        -- loop
      Lazy_delay_2:
    for i in interfaces.unsigned_32 range 0 .. 100_000 loop
        null;
    end loop Lazy_delay_2;

   end loop For_loop_0;

end Main;

修改后我编译烧进了单片机

并且 LED 闪烁。

以后我会用这个。