驱动器设计的一个基本问题是决定如何映射到外设寄存器。多年来,嵌入式开发人员已经使用了许多不同的方法,例如建立结构来定义位图,或者简单地将所需的值写入寄存器;然而,我一直最喜欢的方法是创建一个映射到外设寄存器的指针数组。这种方法提供了一种将外设寄存器分组到逻辑通道的优雅方式,并提供了一种简单的方法来初始化外设以及访问其数据。指针数组方法很容易移植,可以用来创建标准的API和应用程序代码,它们可以在不同的硬件平台上工作,允许共享应用程序代码。如果编写得当,它还可以创建更容易阅读和理解的代码,从而使软件维护更容易。
指针数组的概念是一种相对直接的映射到外设的方法。其思想是创建一个数组,其中数组的每个索引都是指向特定类型的外围寄存器的指针。例如,对于具有多个GPIO端口的微控制器,将设置一个指针数组来访问每个可用端口的方向寄存器(清单1)。将设置另一个指针数组来访问输入和输出寄存器。每个寄存器类型都将与其自己的指针数组相关联。
清单1–GPIO的指针数组
注意指针数组的声明方式是很重要的。指针数组portsddr是一个指向可变uint16的常量指针。请注意,声明是从右向左定义的。指向寄存器的指针是一个常量指针,但将其声明为易失性uint16_t会通知编译器,所指向的值可能会自行改变,而无需软件的交互。
嵌入式开发人员使用这种方法进行内存映射有很多好处。首先,它允许将功能相同的寄存器逻辑地组合在一起。这使得软件工程师可以将每个外设视为MCU的一个独立通道。例如,定时器1和定时器2可以看作是两个不同的定时器通道。设置每个定时器的周期寄存器只需要简单地写入周期指针阵列的适当通道索引。指针数组的索引变成了通道访问索引。例如,指针数组索引0将与定时器1相关联;指针数组索引1将与定时器2相关联。
接下来,当外设开始看起来像通道时,创建一个抽象的方法就变得容易了,不仅可以初始化,还可以访问每个外设数据。这允许使用一个简单的循环来初始化每个外设(清单2)。只需使用正确的通道索引,就可以访问外设的数据。这使得驱动程序框架不仅易于理解和重用,而且是一个抽象设备寄存器的框架。
清单2–定时器初始化循环
最后,它允许开发人员为每个外设创建配置表。嵌入式开发人员可以创建一个可重用的驱动程序,将配置表作为参数,而不是总是编写定制的初始化代码。然后,初始化函数一次循环一个通道,并通过指针数组初始化外设寄存器。这使得驱动程序成为一个库模块,一次又一次地被测试,产生可以加速下一个项目的经过验证的代码。