杭州住房和城市建设局网站,网站建设维护需要懂哪些知识,个人网页设计创意图片,做什么类型网站可以吸引用户衡山派TSensor驱动设计说明#xff1a;基于RT-Thread Sensor框架的温度传感器驱动与HAL层实现 最近在衡山派开发板上做项目#xff0c;需要用到板载的温度传感器#xff08;TSensor#xff09;来监控芯片温度。一开始我以为直接读个寄存器就行#xff0c;结果发现RT-Threa…衡山派TSensor驱动设计说明基于RT-Thread Sensor框架的温度传感器驱动与HAL层实现最近在衡山派开发板上做项目需要用到板载的温度传感器TSensor来监控芯片温度。一开始我以为直接读个寄存器就行结果发现RT-Thread下有一套标准的Sensor设备驱动框架用起来虽然规范但对新手来说理解起来有点门槛。折腾了两天总算把驱动流程搞明白了今天就把TSensor驱动的完整设计从框架接入到HAL层实现给大家掰开揉碎了讲清楚。这篇教程适合正在使用衡山派硬件平台并且想在RT-Thread操作系统下开发传感器驱动的朋友。咱们会深入Driver层和HAL层的分工看看中断是怎么处理的关键的数据结构长什么样最后再给一个能直接跑起来的Demo。目标是让你看完后不仅能用好这个TSensor驱动更能理解RT-Thread Sensor框架的设计思路以后自己写其他传感器驱动也能举一反三。1. 源码与模块架构驱动代码在哪怎么组织的拿到一个新的驱动第一件事就是找到源码在哪理清它的组织架构。衡山派TSensor驱动的源代码都放在bsp/artinchip/这个目录下结构很清晰bsp/artinchip/drv/tsen/drv_tsen.c这是Driver层的实现文件。它主要负责对接RT-Thread的Sensor设备驱动框架把我们的硬件操作“包装”成框架能识别的标准接口。简单说它就是框架和硬件之间的“翻译官”。bsp/artinchip/hal/tsen/hal_tsen.c这是HAL层硬件抽象层的实现文件。它包含了直接操作TSensor控制器的底层函数比如初始化硬件、读写寄存器、处理中断等。这部分代码是和硬件强相关的。bsp/artinchip/include/hal/hal_tsen.h这是HAL层的接口头文件里面声明了所有HAL层的函数以及关键的数据结构定义。Driver层就是通过包含这个头文件来调用HAL层功能的。这样的分层设计好处很明显Driver层关心的是“怎么用RT-Thread的规矩办事”保证了驱动能无缝集成到RT-Thread的设备模型中应用层可以用rt_device_find、rt_device_read这些标准API来操作。HAL层关心的是“怎么让衡山派的TSensor硬件动起来”。它把硬件操作的细节封装起来这样即使硬件有变动也只需要修改HAL层上面的Driver层和应用代码基本不用动。原文还特别提到如果你不用RT-Thread而是裸机Baremetal开发你可以直接使用HAL层的接口非常灵活。2. 关键流程设计驱动是怎么启动和工作的理解了代码结构咱们来看看驱动运行的两个核心流程初始化和中断处理。2.1 初始化流程驱动是怎么挂载到系统里的TSensor驱动的初始化入口是drv_tsen_init函数它通过INIT_DEVICE_EXPORT这个宏声明这样RT-Thread系统在启动时就会自动调用它。这个初始化函数主要干了四件事就像给一个设备上电并登记入册初始化模块时钟clk任何外设要工作首先得有时钟信号。这一步就是打开TSensor控制器的时钟源。注册中断告诉系统当TSensor产生中断时该调用哪个函数来处理。这是实现异步数据读取的关键。初始化默认参数给TSensor控制器设置一些默认的工作参数比如采样模式、报警阈值等具体参数在后面的数据结构里讲。向设备框架注册Sensor设备这是最重要的一步。调用rt_hw_sensor_register()这个函数把我们实现好的驱动“设备”和“操作函数”告诉RT-Thread的Sensor子系统。注册成功后在应用层就能通过设备名找到这个温度传感器了。2.2 中断处理流程数据是怎么“主动”送上门的TSensor支持中断方式读取数据这比让应用程序不停地去问轮询要高效得多CPU不用忙等待。这里有两种工作模式非周期模式当应用程序调用驱动接口比如ops-fetch_data()主动要数据时硬件才启动一次温度转换。转换完成后硬件产生一个中断。周期模式TSensor控制器会按照你预先设置好的时间周期自动进行温度转换并产生中断像闹钟一样准时。无论哪种模式中断来了之后处理流程是这样的进入中断处理函数hal_tsen_irq_handle。硬件会通过中断标志位INT Flag告诉我们具体是哪个测量通道的数据准备好了。驱动会逐个检查这些标志位。对于每一个有数据准备好的通道驱动从硬件寄存器里把原始的采样值读出来。这个原始值会被缓存到一个全局变量或结构体成员中比如后面会提到的aic_tsen_ch结构体里的latest_data字段。应用程序随后再读取数据时驱动实际上是从这个缓存里把数据取出来经过换算后返回给应用。这样就完成了一次中断驱动的数据采集。3. 数据结构设计驱动内部怎么管理信息驱动里用两个核心的结构体来管理一切理解它们就理解了驱动内部的数据流转。3.1 struct aic_tsen_dev (Driver层)这个结构体是Driver层内部使用的用来管理整个TSensor控制器设备。你可以把它想象成这个温度传感器设备的“总经理办公室”里面存放着设备的核心档案和资源链接。struct aic_tsen_dev { struct rt_sensor_device dev; // 最重要的成员RT-Thread传感器设备对象 u32 pclk_rate; // 外设时钟频率 struct aic_tsen_ch *ch; // 指向具体通道配置信息的指针 };dev这是RT-Thread框架定义的标准传感器设备结构。驱动通过填充这个结构体向系统表明“我是一个标准的传感器设备”。它里面包含了设备名、类型、厂商信息、以及最重要的——操作函数集 (ops)。pclk_rate记录TSensor模块的时钟频率一些时间相关的计算比如周期采样会用到它。ch一个指针指向具体的通道配置信息。这引出了下一个更详细的结构体。3.2 struct aic_tsen_ch (HAL层)这个结构体属于HAL层它管理着一个TSensor通道的所有配置和状态。如果芯片有多个温度测量点比如CPU核、GPU、外部传感器每个点都会对应一个这样的结构体实例。它就像是每个测量点的“详细工位说明书”。struct aic_tsen_ch { int id; // 通道ID bool available; // 该通道是否可用 char name[16]; // 通道名称 enum aic_tsen_mode mode; // 工作模式周期/非周期 enum aic_tsen_soft_mode soft_mode; // 软件模式 bool diff_mode; // 是否为差分模式 bool inverted; // 数据是否取反 u16 latest_data; // 最新采集的原始数据注意实际温度值 * 10 u16 smp_period; // 周期采样模式下的采样周期单位秒 // 高温报警相关配置 bool hta_enable; u16 hta_thd; // 高温报警阈值实际温度值 * 10 u16 hta_rm_thd; // 高温报警恢复阈值实际温度值 * 10 // 低温报警相关配置 bool lta_enable; u16 lta_thd; // 低温报警阈值 u16 lta_rm_thd; // 低温报警恢复阈值 // 过温保护配置 bool otp_enable; u16 otp_thd; // 过温保护阈值 // 温度校准参数 int slope; // 斜率实际斜率 * 10000 int offset; // 偏移量实际偏移量 * 10000 aicos_sem_t complete; // 用于同步的信号量在等待数据完成时使用 };这里有几个关键点需要注意也是我当初容易搞混的地方latest_data、hta_thd等温度相关值它们存储的是“实际温度值乘以10”后的整数。比如latest_data为 285表示温度是 28.5 度。这样做是为了用整数运算来避免浮点数带来的效率和精度问题。slope和offset这是用来对原始采样值进行线性校准的参数公式一般是实际温度 原始值 * slope offset。它们也被放大了10000倍同样是出于定点数运算的考虑。complete信号量这是一个同步机制。在非周期模式下应用程序调用fetch_data后驱动可能需要在中断处理函数里等待数据转换完成。这个信号量就是用来让驱动线程“睡眠等待”直到中断发生、数据准备好后再“唤醒”它去读取数据。4. Driver层接口设计框架要求我们提供什么Driver层需要实现一个标准的操作函数集struct rt_sensor_ops并赋值给rt_sensor_device结构体。这样Sensor框架就知道怎么调用我们的驱动了。struct rt_sensor_ops { rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len); rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg); };4.1 aic_tsen_fetch - 读取温度数据这个函数对应fetch_data操作。项目说明函数原型rt_size_t aic_tsen_fetch(struct rt_sensor_device* sensor, void* buf, rt_size_t len)功能读取TSensor的温度数据。参数sensor: 传感器设备指针。buf: 用来存放读取结果的缓冲区类型是struct rt_sensor_data。len: 缓冲区的长度以rt_sensor_data为单位。返回值成功读取的数据个数成功时为1失败时返回错误码小于0。注意事项根据原文当前实现仅支持读取CPU位置的TSensor数据。它的内部工作流程通常是检查参数 - 调用HAL层的hal_tsen_get_temp获取温度值 - 将获取到的温度值填充到buf指向的rt_sensor_data结构体中 - 返回1。4.2 aic_tsen_control - 控制接口这个函数对应control操作用来实现一些IO控制命令比如设置采样率、报警阈值等。项目说明函数原型rt_err_t aic_tsen_control(struct rt_sensor_device* sensor, int cmd, void* args)功能TSensor驱动的ioctl接口用于控制设备。参数sensor: 传感器设备指针。cmd: 控制命令码。args: 命令对应的参数。返回值成功返回RT_EOK失败返回错误码。注意事项原文明确指出这个接口目前暂未实现具体的控制命令所以统一返回-1失败。但这不影响基本的温度数据读取功能。5. HAL层接口设计直接操作硬件的“武器库”HAL层的函数声明都在hal_tsen.h里Driver层通过调用它们来实际操作硬件。咱们挑几个核心的看看// 开关TSensor控制器 void hal_tsen_enable(int enable); // 开关某个具体的测量通道 void hal_tsen_ch_enable(u32 ch, int enable); // 初始化一个TSensor通道 int hal_tsen_ch_init(struct aic_tsen_ch *chan, u32 pclk); // 获取温度会进行原始数据到实际温度的转换 int hal_tsen_get_temp(struct aic_tsen_ch *chan, s32 *val); // 将原始数据转换为温度值 s32 hal_tsen_data2temp(struct aic_tsen_ch *chan); // 将温度值转换为原始数据用于设置阈值 u16 hal_tsen_temp2data(struct aic_tsen_ch *chan, s32 temp); // 显示通道状态调试用 void hal_tsen_status_show(struct aic_tsen_ch *chan); // TSensor中断处理函数在初始化时注册给系统 irqreturn_t hal_tsen_irq_handle(int irq, void *arg);这些函数名都很直观hal_tsen_get_temp是Driver层fetch_data最终会调用的核心函数。hal_tsen_data2temp和hal_tsen_temp2data则利用了前面提到的slope和offset参数进行校准计算。6. 实战Demo在应用层怎么使用这个驱动理论说了这么多最后来看看怎么在应用程序里使用这个TSensor驱动。RT-Thread的Sensor组件提供了一个非常方便的测试命令sensor_polling它的部分代码就是最好的Demo。static void sensor_polling(int argc, char **argv) { rt_uint16_t num 10; // 默认读取10次 rt_device_t dev RT_NULL; rt_sensor_t sensor; struct rt_sensor_data data; // 存放传感器数据的结构 rt_size_t res, i; rt_int32_t delay; // 1. 查找设备。argv[1]是设备名比如 temp_cpu dev rt_device_find(argv[1]); if (dev RT_NULL) { LOG_E(Cant find device:%s, argv[1]); return; } // 可以指定读取次数 if (argc 2) num atoi(argv[2]); sensor (rt_sensor_t)dev; // 2. 计算读取延迟使用设备支持的最小周期但不小于100ms delay sensor-info.period_min 100 ? sensor-info.period_min : 100; // 3. 打开设备 rt_device_open(dev, RT_DEVICE_FLAG_RDONLY); // 4. 循环读取数据 for (i 0; i num; i) { // 调用标准设备读取接口内部会调用我们实现的 aic_tsen_fetch res rt_device_read(dev, 0, data, 1); if (res ! 1) { LOG_E(read data failed!size is %d, res); } else { // 显示数据这里会解析data结构体打印出温度值 sensor_show_data(i, sensor, data); } rt_thread_mdelay(delay); // 延迟一段时间再读下一次 } // 5. 关闭设备 rt_device_close(dev); }使用起来非常简单查找设备通过rt_device_find和你在驱动里注册的设备名来找到这个传感器。打开设备以只读方式打开。循环读取在一个循环里调用rt_device_read。驱动框架会自动调用到我们写的aic_tsen_fetch函数把温度数据填充到data里。处理数据data结构体里包含了时间戳、传感器类型和具体的温度数值。关闭设备用完记得关闭。你可以在RT-Thread的MSH命令行里输入类似sensor_polling temp_cpu 5的命令来读取5次CPU温度数据非常方便测试和调试。整个驱动的脉络就是这样应用层调用标准API - RT-Thread Sensor框架 - 衡山派TSensor Driver层 - 衡山派TSensor HAL层 - 硬件寄存器。理解每一层的职责和它们之间的接口是掌握这个驱动乃至任何RT-Thread设备驱动的关键。希望这篇详细的解析能帮你少走弯路。