这篇文章是嵌入式软件系列:Ada用于嵌入式C开发人员
之前,我们已经看到我们可以使用表示条款为记录类型指定特定的布局。如前所述,这在与硬件、驱动程序或通信协议交互时非常有用。在本文中,我们将为两个特定的用例扩展这个概念:寄存器覆盖和数据流。在我们讨论这些用例之前,我们将首先解释Size方面和Size属性。
尺寸方面和属性
Size方面表示表示一个对象所需的最小位数。当应用于类型时,Size方面告诉编译器类型T的记录或数组组件不要小于X位。因此,这方面的一个常见用法就是确认预期:开发人员指定Size来告诉编译器T应该适合X位,编译器会告诉他们是否正确(或错误)。
当指定的size值大于必要值时,它会导致对象在内存中比其他情况下更大。例如,对于某些枚举类型,我们可以说对于Enum类型的size使用32;字面值的数量本来只需要一个字节。这对于未检查的转换很有用,因为这两种类型的大小需要相同。同样,它也适用于与C语言的接口,其中enum类型只是映射到int类型,因此比Ada需要的其他类型更大。我们将在本系列的后面讨论未检查的转换。
让我们看一个例子:
——my_device_types。ads包My_Device_Types is type UInt10 is mod 2 ** 10 with Size => 10;My_Device_Types结束;
在这里,我们说UInt10类型的对象必须至少有10位。在本例中,如果代码编译,则确认这些值在封装到一个封闭记录或数组类型时可以用10位表示。
如果指定的大小大于编译器默认使用的大小,那么它可能会影响对象的大小。例如,对于UInt10,任何达到或包括16在典型的机器上都没有区别。然而,任何超过16的对象都会促使编译器使用更大的对象表示。例如,这对于未检查的转换是很重要的。
Size属性指示表示类型或对象所需的位数。可以使用size属性来检索类型或对象的大小:
——show_device_types。亚行Ada.Text_IO;使用Ada.Text_IO;My_Device_Types;使用My_Device_Types;Show_Device_Types is UInt10_Obj:常量UInt10:= 0;开始Put_Line(“UInt10类型的大小:”&正面的'Image (UInt10'大小));Put_Line(“UInt10对象的大小”&正面的图像(UInt10_Obj的大小));Show_Device_Types结束;
在这里,我们检索的是UInt10类型和该类型对象的实际大小。请注意,大小不一定要匹配。例如,尽管UInt10类型的大小预计是10位,UInt10_Obj的大小可能是16位,这取决于平台。此外,复合类型(数组、记录)中的这种类型的组件也可能是16位的,除非它们被打包。
寄存器覆盖
寄存器覆盖使用表示子句创建便于从寄存器操作位的结构。让我们看一个简单的电源管理控制器示例,该控制器包含寄存器,例如系统时钟启用寄存器。注意这个例子是基于一个实际的架构:
——注册。adb type Bit is mod 2 ** 1 and Size => 1;UInt5是mod 2 ** 5, Size => 5;UInt10是mod 2 ** 10大小=> 10;subtype USB_Clock_Enable是Bit;——System Clock Enable Register type PMC_SCER_Register is record——保留位Reserved_0_4: UInt5:= 16#0#;——只写。USBCLK: USB_CLOCK_ENABLE:= 16#0#;——保留位Reserved_6_15: UInt10:= 16#0#;end record with Volatile, Size => 16, Bit_Order =>; for PMC_SCER_Register use record Reserved_0_4 at 0 range 0 .. 4; USBCLK at 0 range 5 .. 5; Reserved_6_15 at 0 range 6 .. 15; end record; -- Power Management Controller type PMC_Peripheral is record -- System Clock Enable Register PMC_SCER : aliased PMC_SCER_Register; -- System Clock Disable Register PMC_SCDR : aliased PMC_SCER_Register; end record with Volatile; for PMC_Peripheral use record -- 16-bit register at byte 0 PMC_SCER at 16#0# range 0 .. 15; -- 16-bit register at byte 2 PMC_SCDR at 16#2# range 0 .. 15; end record -- Power Management Controller PMC_Periph : aliased PMC_Periphal with Import, Address => System'To_Address (16#400E0600#);
首先,我们声明系统时钟启用寄存器——这是代码示例中的PMC_SCER_Register类型。该寄存器中的大部分位是保留的。但是,我们对第5位感兴趣,它用于激活或禁用系统时钟。为了实现这个位的正确表示,我们做以下操作:
- 我们使用USB_Clock_Enable类型声明该记录的USBCLK组件,该类型的大小为1位。
- 我们使用一个表示子句来表示USBCLK组件是在第0字节的第5位。
在声明系统时钟启用寄存器并指定其各个位作为记录类型的组件之后,我们在代码示例中声明电源管理控制器类型- pmc_peripheral记录类型。在这里,我们声明两个16位寄存器作为PMC_Peripheral的记录组件。这些寄存器用于启用或禁用系统时钟。我们在声明中使用的策略与我们在上面看到的类似:
- 我们将这些寄存器声明为PMC_Peripheral记录类型的组件。
- 我们使用一个表示子句来指定PMC_SCER寄存器位于第0字节,而PMC_SCDR寄存器位于第2字节。
因为这些寄存器有16位,所以我们使用从0到15位的范围。
通过声明PMC_Periph类型的PMC_Periph对象,可以访问实际的电源管理控制器。在这里,我们使用声明中的address方面指定内存映射寄存器的实际地址(十六进制的400E0600)。当我们在对象声明中使用Address方面时,我们是在指示该对象内存中的地址。
因为我们在PMC_Periph声明中指定了内存映射寄存器的地址,所以这个对象现在是这些寄存器的一个覆盖。这也意味着对该对象的任何操作都对应于对电源管理控制器寄存器的实际操作。我们将在有关的文章中讨论有关覆盖的更多细节将结构映射到位域(来)。
最后,在一个测试应用程序中,我们可以通过简单的记录组件选择来访问电源管理控制器的任何寄存器的任何位。例如,我们可以使用pmc_peripheral .PMC_SCER.USBCLK来设置PMC_SCER寄存器的USBCLK位:
——enable_usb_clock。亚行与寄存器;Enable_USB_Clock is beginUSBCLK: = 1;Enable_USB_Clock结束;
这个代码示例使用了Ada语言的许多方面和关键字。其中之一是Volatile方面,我们在本节中已经讨论过Volatile和原子对象.为PMC_SCER_Register类型使用Volatile方面可以确保该类型的对象不会存储在寄存器中。
在示例的PMC_SCER_Register记录类型的声明中,我们使用Bit_Order方面来指定记录类型的位顺序。在这里,我们可以选择以下选项之一:
- High_Order_First:记录的第一个位是最重要的位。
- Low_Order_First:记录的第一个位是最低有效位。
来自Registers包的声明还使用Import,这在创建覆盖时有时是必要的。当在对象声明上下文中使用时,它避免了默认初始化(对于具有默认初始化的数据类型)。方面导入也将在解释如何进行的下一篇文章中进行讨论将结构映射到位域.
在PMC_Peripheral记录类型的组件的声明中,我们使用别名关键字来指定这些记录组件可以通过组件名称之外的其他路径访问。因此,编译器不会将它们存储在寄存器中。这是有意义的,因为我们想要确保我们访问的是特定的内存映射寄存器,而不是编译器分配的寄存器。注意,出于同样的原因,我们也在PMC_Periph对象的声明中使用别名关键字。
手臂和svd2ada
正如我们在前一节中看到的与设备Ada提供了强大的特性来描述硬件架构的底层细节,同时又不放弃其强大的键入功能。然而,当您有一个复杂的体系结构时,为所有这些低级细节创建规范可能会很麻烦。
幸运的是,对于Arm Cortex-M设备,GNAT工具链提供了一个名为“svd2ada”的Ada绑定生成器,它接受这些设备的cmis - svd描述,并创建与体系结构匹配的Ada规范。CMSIS- svd描述文件基于Cortex微控制器软件接口标准(CMSIS),是Arm Cortex微控制器的硬件抽象层。
请参阅svd2ada项目页面该工具的详细信息。
更多信息请参阅嵌入式软件系列:Ada用于嵌入式C开发人员