中国电子技术网

设为首页 网站地图 加入收藏

 
 

利用与硬件无关的方法简化嵌入式系统设计:基本知识

关键词:ADI 硬件 嵌入式系统设计 传感器

时间:2025-02-20 15:00:15      来源:ADI

本文将演示一种加速嵌入式系统设计原型阶段的方法,说明如何将与硬件无关的驱动程序和传感器结合使用,简化整个嵌入式系统的器件选择。同时还将介绍嵌入式系统的器件、典型软件结构以及驱动程序的实现。后续文章“利用与硬件无关的方法简化嵌入式系统设计:驱动程序实现”将进一步探讨执行过程。

作者: Giacomo Paterniani,现场应用工程师

摘要

本文将演示一种加速嵌入式系统设计原型阶段的方法,说明如何将与硬件无关的驱动程序和传感器结合使用,简化整个嵌入式系统的器件选择。同时还将介绍嵌入式系统的器件、典型软件结构以及驱动程序的实现。后续文章“利用与硬件无关的方法简化嵌入式系统设计:驱动程序实现”将进一步探讨执行过程。

简介

通过使用与硬件无关的驱动程序,设计人员可以自由选择微控制器或处理器的类型来管理传感器,而不受硬件的限制。这种方法的优势在于,除了供应商提供的基本软件层外,还可以添加额外的软件层,同时简化传感器的集成。本文将以惯性测量单元(IMU)传感器为例,说明如何实现与硬件无关的驱动程序,不过,这种方法同样适用于其他类型的传感器和器件。驱动程序采用C语言编写,并在一款通用微控制器上进行了测试。

器件选择

IMU传感器主要用于运动检测,以及通过加速度和角速度来测量运动强度。本示例选择使用ADIS16500 IMU传感器(图1),因为与复杂且昂贵的分立设计方案相比,该传感器能够为精确的多轴惯性检测与工业系统的集成提供简单且经济高效的方法。

 

图1.ADIS16500评估板。

主要应用包括:

• 导航、稳定性和仪器仪表
• 无人机和自动驾驶车辆
• 智能农业和施工机械设备
• 工厂/工业自动化、机器人
• 虚拟/增强现实
• 运动物联网

 

图2.ADIS16500框图。

ADIS16500是一款精密微型机电系统(MEMS) IMU,内置一个三轴陀螺仪、一个三轴加速度计和一个温度传感器。参见图2。该IMU的灵敏度、偏置、对准、线性加速度(陀螺仪偏置)和坐标轴原点(加速度计位置)已在工厂校准。这意味着在各种条件下都能提供精确的传感器测量。

通过该接口,微控制器可以写入和读取用户控制寄存器,并读取输出数据寄存器,从而获得加速度计、陀螺仪或温度传感器数据。为此,管理该接口所需的全部软件和固件均已完成开发。图2所示为数据就绪(DR)引脚。该引脚是一个数字信号,指示何时可从传感器读取新数据。DR引脚可被视为通过通用输入/输出(GPIO)端口的输入,因此可通过微控制器轻松管理。

从硬件的角度来看,IMU传感器和微控制器将使用SPI接口连接,该接口是由nCS、SCLK、DIN和DOUT引脚组成的4线接口。DR引脚应连接到微控制器的其中一个GPIO。此外,IMU传感器需要3 V至3.6 V的电源电压,因此3.3 V就足够了。

了解嵌入式系统的典型软件结构

 

图3.嵌入式系统的软件/固件结构。

了解嵌入式系统的通用软件和固件结构对于与传感器驱动程序连接至关重要。这将帮助设计人员构建一个灵活且易于集成到任何项目的软件模块。此外,驱动程序必须以模块化的方式实现,以使设计人员能够依赖于现有函数添加更高级的函数。

嵌入式系统的软件结构如图3所示。在图3中,层次结构从应用层开始,应用代码就是在这一层编写的。应用层包括main文件、依赖于传感器的应用模块,以及依赖于管理处理器配置的外设驱动程序的模块。此外,在应用层中,还有与微控制器必须处理的任务相关的所有模块。例如,通过中断或轮询、状态机等管理任务的所有软件。应用层级别根据项目的类型而有所不同,因此不同项目中实现的代码也不同。在应用层,系统的所有传感器根据其数据手册进行初始化和配置。传感器驱动程序提供的所有公共函数均可调用。例如,读取负责输出数据的寄存器,或者写入一个寄存器以更改设置/校准的程序。

应用层下面是传感器的驱动层,这一层有两种类型的接口。可从应用层调用的所有函数都在这一层实现。此外,函数的原型插入到驱动程序标头文件(.h)中。因此,通过查看传感器驱动程序的标头文件,您可以了解驱动程序的接口以及可从较高层级调用的函数。较低级别的层将与特定外设驱动程序连接,这些外设驱动程序依赖于管理传感器的微控制器。外设驱动程序包括管理微控制器外设的所有模块,例如SPI、I2C、UART、USB、CAN、SPORT等,或管理处理器内部模块的模块,例如定时器、内存、ADC等。由于它们与硬件紧密相关,因此称为低级函数。例如,由于微控制器不同,因此每个SPI驱动程序都是不同的。我们以ADIS16500为例。接口是SPI,因此其驱动程序将与微控制器的SPI驱动程序封装在一起。对于不同的传感器和不同的接口也是如此。例如,如果另一个传感器具有I2C接口,那么同样地,将在传感器的初始化过程中与微控制器的I2C驱动程序封装一起。

传感器驱动程序的下层是外设驱动程序,各类微控制器的外设驱动程序各不相同。如图3所示,外设驱动程序和低级驱动程序是分开的。本质上,外设驱动程序通过可用的通信协议提供读写函数。由于低级驱动程序将管理信号的物理层,因此它非常依赖于设计人员所使用的硬件。外设和低级驱动层往往通过可视化工具从微控制器的集成开发环境(IDE)生成,具体取决于安装微控制器的评估板。

驱动程序实现

与硬件无关的方法支持在不同应用、不同微控制器或不同处理器中使用相同的驱动程序。这种方法取决于驱动程序的实现方式。要了解驱动程序的实现方式,首先要看接口,或图4中的传感器标头文件(adis16500.h)。

标头文件包含有用的公共宏。其中包括寄存器的地址、SPI最大速度、默认输出数据速率(ODR)、位掩码,以及加速度计、陀螺仪和温度传感器的输出灵敏度,这些宏与用于表示数据的位数(16或32)有关。图4显示了这些宏,其中仅显示了几个寄存器的地址作为示例。本文引用的代码可参见附录。

 

图4.ADIS16500标头文件(adis16500.h)中显示的宏。

附录中的图3显示了包括adis16500.h在内的每个模块均可使用的所有公共变量和公共类型声明,其中定义了新的类型,以便更有效地管理数据。例如,ADIS16500_XL_OUT类型被定义为包含三个浮点的结构,每个轴(x、y和z)一个浮点。此外,还通过枚举来支持不同的传感器配置,使设计人员能够灵活地选择符合自身需求的配置。最值得关注的是使驱动程序与硬件无关的部分。在公共变量部分的开头(附录中的图3),有三个关键的类型定义:指向三个基本函数的指针,或者SPI发送和接收函数,以及为生成正确的停转时间,两次SPI访问之间所需的延迟函数。这些代码还显示了可指向的函数的原型。SPI发送函数将指向待发送值的指针作为输入,然后返回可供检查的内容,以确定发送是否成功。SPI接收函数也是如此,该函数将指向变量的指针作为输入,这个指针将存储接收时读取的值。延迟函数以浮点数作为输入,表示设计人员想要等待的微秒数,不返回任何内容(void)。通过这种方式,设计人员可以在应用层(例如在main文件中)利用这些特定的原型来声明这三个函数。声明后,他们可以将这三个函数赋值给ADIS16500_INIT私有结构的字段。附录中的图2列举了一个示例,以帮助更好地理解最后一步。

SPI发送器、接收器函数和延迟函数在main文件中声明为静态函数,因此属于应用层。这些函数依赖于外设驱动程序函数,因此传感器驱动程序本身与硬件无关。这三个函数被分配给一个变量的字段,而这些字段是指向函数的指针。这样一来,设计人员可以封装传感器和微控制器,而无需修改传感器驱动程序代码。如果设计人员更换微控制器,他们只需将三个静态函数内的低级函数替换为新微控制器的相应函数,从而调整main文件。通过这种方法,驱动程序变得与硬件无关,因为设计人员不需要更改传感器的驱动程序代码。微控制器的IDE中通常包含spiSelect、spiReceive、spiUnselect、chThdSleepMicroseconds等低级函数。在本例中,所用的微控制器评估板是SDP-K1,它嵌入了STM32F469NIH6 Cortex®-M4微控制器。该IDE是ChibiOS,这是一个免费的Arm®开发环境。

附录中的图4显示了应用级别的可调用函数原型。这些原型以及附录中图2和图3讨论的所有其他软件和固件都可在传感器驱动程序的标头文件(adis16500.h)中找到。首先,初始化函数(adis16500_init)将指向ADIS16500_INIT结构的指针作为输入,并返回状态代码,以指示初始化是否成功。初始化函数的实现在传感器驱动程序的源文件(adis16500.c)中完成。附录中的图5所示为adis16500_init函数的代码。首先,定义名为ADIS16500_PRIV的类型,其中至少包含ADIS16500_INIT结构的所有字段,然后声明一个属于该类型的私有变量_adis16500_priv。在初始化函数中,应用层传递的ADIS16500_INIT结构的所有字段将赋值给私有变量_adis16500_priv的字段。这意味着,对传感器驱动程序的任何后续调用都将使用由应用层传入的SPI读写函数和处理器延迟函数。这一点很关键,正因如此,传感器驱动程序才能与硬件无关。如果设计人员想要更改微控制器,只需更改传递给adis16500_init函数的函数即可,不需要修改传感器驱动程序代码本身。在初始化函数开头,_adis16500_priv变量的已初始化字段设置为false,因为初始化过程尚未完成。在该函数结束时,该字段将设置为true,然后返回。设计人员每次调用另一个公共函数(附录中的图4)时,都会执行以下检查:如果_adis16500_priv.initialized为true,可以继续;如果为false,将立即返回ADIS16500_RET_VALERROR错误。这是为了防止用户在没有先初始化传感器驱动程序的情况下调用函数。继续讨论初始化函数,执行以下步骤:

1. 通过读取ADIS16500_REG_ PROD_ID寄存器,检查预先已知的产品ID。
2. 将应用层(main.c)传递的值写入ADIS16500_REG_MSC_CTRL寄存器的相应位字段,设置数据就绪(DR)引脚极性。
3. 将应用层(main.c)传递的值写入ADIS16500_REG_MSC_CTRL寄存器的相应位字段,设置同步模式。
4. 将应用层(main.c)传递的值写入ADIS16500_REG_DEC_RATE寄存器,设置抽取率。

初始化函数取决于读写寄存器函数(附录中的图6)。因此,为_adis16500_priv变量赋值之后,需要完成上述四个例程。否则,在调用读取或写入寄存器函数时,它们不知道该使用哪个SPI发送器、接收器和处理器延迟函数。

参考附录中的图4,在初始化函数之后,还可以调用其他公共函数。下面是已实现例程的功能描述,所示为低级别例程。本文的第二部分将详细介绍驱动程序的其他已实现函数。以下所有函数只能在初始化函数之后调用。为此,将在每个函数开头仔细检查,以确定传感器是否已初始化。如果未初始化,程序会立即返回错误。

u adis16500_rd_reg_16

该函数用于读取16位寄存器。该函数实现可参见附录中的图6。输入包括ad,这是一个uint8_t变量,表示要读取的寄存器的地址,以及*p_reg_val,这是指向uint16_t类型变量的指针,表示读取值将赋值的目标。要通过SPI协议读取寄存器,需要访问两次SPI;第一次访问是为了发送地址,第二次是为了读回被寻址寄存器的值。两次访问之间需要有停转时间,因此需要延迟函数。在第一次访问过程中,我们发送读/写位,在本例中为1(R = 1,W = 0),寄存器地址移位8位,再补充8位0,因此序列如下:

R/W | AD6 | AD5 | AD4 | AD3 | AD2 | AD1 | AD0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

其中AD代表地址,R/W代表读/写位。

经过延迟后,函数通过SPI读取值,并将该值传递给输入指针。ADIS16500的寄存器具有一个包含高位值(8个最高有效位)的高地址和一个包含低位值(8个低有效位)的低地址。为了获得16位的完整值(低位和高位),使用低地址作为ad已经足够,因为低地址和高地址是连续的。

u adis16500_wr_reg_16

该函数用于写入16位寄存器。该函数实现可参见附录中的图6。输入包括ad,这是一个uint8_t类型变量,表示要写入的寄存器的地址,以及reg_val,这是uint16_t类型变量,表示要写入寄存器的值。对于读取函数,需要考虑低地址和高地址以及低位值和高位值。因此,根据数据手册,要想写入ADIS16500的寄存器,需要在发送时访问两次SPI。第一次访问将发送等于0的R/W位,接着是低寄存器地址,然后是低位值,因此序列如下:

R/W | AD6 | AD5 | AD4 | AD3 | AD2 | AD1 | AD0 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |,其中D代表数据。

第二次SPI发送器访问将发送等于0的R/W位,接着是高寄存器地址,然后是高位值,因此序列如下:

R/W | AD14 | AD13 | AD12 | AD11 | AD10 | AD9 | AD8 | D15 | D14 | D13 | D12 | D11 | D10 | D9 | D8 |。

写入和读取寄存器函数实际上也可以定义为私有,因此从驱动程序软件模块外部不可见,也不可调用。将它们定义为公共是为了能够调试。这样一来,设计人员能够快速访问传感器中的任何寄存器以进行读取或写入,从而帮助解决问题。

u adis16500_rd_acc

该函数从输出数据寄存器读取x、y、z加速度数据,并返回它们的值,单位为[m/sec2]。该函数实现可参见附录中的图7。输入是指向ADIS16500_XL_OUT结构的指针,它只嵌入三个字段:以浮点类型表示的x、y、z加速度。在这三个轴上,读取加速度的方式是相同的,唯一的区别在于要读取的寄存器。每个轴有其各自要读取的寄存器:x轴必须在x加速度输出数据寄存器上读取,y和z轴也在相应寄存器上读取。加速度值将用32位值来表示,因此要读取的寄存器有两个。一个读取高16位,一个读取低16位。因此,通过查看代码可知,将进行两次寄存器读取访问,再加上适当的移位和OR位运算,得到整个二进制值并存储在名为_temp的私有int32_t变量中。然后,数据将经过二进制转二进制补码的转换。转换后,用二进制补码值除以灵敏度(单位为[LSB/(m/sec2)]),这样最终将获得以[m/sec2]为单位的加速度值。此值将记录到指针的x、y或z字段,该指针指向已作为输入传递的结构。

u adis16500_rd_gyro

陀螺仪读取函数与加速度读取函数的实现方法完全相同。毫无疑问,该函数将读取x、y、z陀螺仪数据,单位为[°/sec]。其实现方法可参见附录中的图8。与加速度函数类似,函数的输入是指向ADIS16500_GYRO_OUT结构的指针,该结构嵌入以浮点类型表示的x、y和z陀螺仪数据。读取的寄存器是陀螺仪输出数据寄存器。二进制值将用32位表示,要获得二进制补码值,需要完成与加速度函数相同的步骤。完成二进制到二进制补码转换后,用得到的值除以灵敏度(单位为[LSB/(°/sec)]),最终得到以[°/sec]为单位的值,然后该值将记录到指针的x、y或z字段,该指针指向已作为输入传递的结构。

结论

本文阐述了嵌入式系统的典型软件/固件堆栈,介绍了IMU传感器的驱动程序实现。与硬件无关的方法为各种传感器或器件提供了可重复使用的方法,即使接口(SPI、I2C、UART等)不同也没关系。后续文章“利用与硬件无关的方法简化嵌入式系统设计:驱动程序实现”进一步详细解释了传感器驱动程序的实现方法。

作者简介

Giacomo Paterniani拥有博洛尼亚大学生物医学工程学位,并在摩德纳-雷焦·艾米里亚大学获得电子工程硕士学位。毕业后,他在摩德纳-雷焦·艾米里亚大学担任了一年研究员。2022年4月,他作为研究生现场应用工程师加入ADI公司的研究生项目。2023年4月,他成为一名现场应用工程师。

  • 分享到:

 

猜你喜欢

  • 主 题:英飞凌汽车方案引领智能座舱新纪元
  • 时 间:2025.03.12
  • 公 司:英飞凌&品佳集团

  • 主 题:便携式电子产品与 AI 时代下的 WiFi 与 BLE 技术革新
  • 时 间:2025.03.25
  • 公 司:Arrow&村田