FreeRTOS传感器采集任务 ——SensorTask 传感器采集任务整体实现

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏》《Ai智能体专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

[一、SensorTask 任务整体概述](#一、SensorTask 任务整体概述)

二、电池电压采集实现

三、编码器转速采集

[3.1 工作原理](#3.1 工作原理)

3.2、无硬件编码器的数据模拟方案

3.3、与仿真电机模型联动

[四、IMU 数据读取](#四、IMU 数据读取)

[4.1、I²C 总线基础](#4.1、I²C 总线基础)

[4.2、MS6DSV 传感器工作原理](#4.2、MS6DSV 传感器工作原理)

[1. 加速度计原理](#1. 加速度计原理)

2、陀螺仪原理

五、全局快照

[5.1、SensorSnap_Set 写入接口](#5.1、SensorSnap_Set 写入接口)

[5.2、SensorSnap_Get 读取接口](#5.2、SensorSnap_Get 读取接口)

总结


前言

在前面系列文章中,我们先后完成了通信任务 CommTask控制任务 ControlTask的设计与实现。依托 FreeRTOS 任务通知、双模式状态机、运动学逆解与 PID 闭环,已经搭建起机器人底盘运动控制的核心调度与驱动体系,能够正常解析上位机速度指令,并完成电机 PWM 闭环输出。

但运动控制只是机器人运行的一部分,整机稳定运行还依赖实时状态监测,例如电池电压、车轮转速、车体姿态等关键数据。为此系统独立拆分出SensorTask 传感器采集任务,作为整机状态感知核心,统一负责各类外设数据的周期性采集、处理与汇总,为控制逻辑、上位机上报、设备安全防护提供可靠数据源,实现控制 + 感知双任务协同架构。


一、SensorTask 任务整体概述

SensorTask 属于系统底层常驻任务,采用周期性轮询的运行方式,独立于控制任务,避免传感器采集占用运动控制时序。核心职责如下:

初始化各类传感器(电池 ADC、编码器、IMU)。

周期性采集原始传感器数据。

做必要的单位换算与整形。

统一封装所有传感器数据,生成系统状态快照( g_status_snap**),快照的写入采用临界区保护,保证一次性完整拷贝,通过** SensorSnap_Get函数进行读取(也是临界区保护),供其他任务共享调用( 读快照**);**

提供线程安全的快照读写接口,供 UplinkTask (上报任务)使用。

二、电池电压采集实现

电池电压是机器人安全运行的重要指标,电压过低会导致动力不足、芯片工作异常,因此需要实时监测。

因为电池电压本质上还是属于模拟信号,mcu只能处理数字信号,因此这里我们采用ADC模数采集方案,利用 STM32 片内 ADC 外设,将电池模拟电压信号转化为数字量。通过电阻分压电路,将高压电池电压降压至单片机允许采样范围,再通过 ADC 采样换算出真实电压值。

本项目使用开发板上的光敏电阻电路来模拟电池电压变化,在不接入真实电池的情况下即可完成整套采样链路验证。

原理:光越强 -> 光敏二极管导通越强 -> 阻抗越低 -> 分压越少 -> 采样电压越低。

为保证数据平稳,本模块采用TIM8 触发 ADC3 + DMA 循环采样的方式,连续多次采样后取平均值,消除瞬时干扰带来的数值波动。

具体:TIM8 以固定频率触发 ADC3 进行一次采样,采样结果通过 DMA 自动搬运到 adc_buf 缓冲区中,并以循环模式不断刷新。这里我们设置缓冲长度为 32,也就是说系统会连续保存最近 32 次采样值。随后,软件对 adc_buf 中的数据进行累加求和并取平均,得到更稳定的原始 ADC 数值 raw

STM32 的 ADC 通常为 12 位(0..4095),参考电压 VREF=3300mV。电压计算公式为:电压(mV) = raw * 3300 / 4095。本代码即用宏 VREF_MV=3300ADC_MAX=4095 完成此转换。

三、编码器转速采集

编码器是底盘速度闭环的核心反馈来源,单纯PID开环控制无法抵抗摩擦、电压波动等干扰,必须依靠编码器反馈实际轮速。

3.1 工作原理

编码器的本质,就是把 "轮子转动" 这个机械动作,转换成 MCU 可识别的电信号 ------ 也就是脉冲信号

轮子转得越快,编码器输出的脉冲就越密集;转速越慢,脉冲的间隔就越稀疏。对单片机而言,它不需要 "亲眼看见" 轮子如何转动,只需要统计单位时间内脉冲数量,结合编码器线数、车轮周长,即可换算出车轮实际行进速度。

举例:

假设一个增量式编码器参数是 500 PPR(每圈 500 个脉冲),意味着编码器每转一圈输出 500个脉冲。

如果 MCU 在运行过程中统计到总共收到了 1000 个脉冲,就可以推断轮子转了 1000 / 500 = 2 圈。

轮子转过的圈数 = 收到的总脉冲数 ÷ PPR

测速的逻辑也很简单:速度 = 单位时间内转过的距离。可以换成频率来算。

假设在 1 秒内收到 1000 个脉冲(频率就是1000HZ),轮子转一圈是 500 脉冲,所以 1000 脉冲 / 秒就等于 1000 / 500 = 2 圈 / 秒,如果轮子周长是 628mm,那么速度就是 2 × 628 = 1256mm/s

3.2、无硬件编码器的数据模拟方案

在调试阶段,若没有真实的电机和编码器硬件,这时候就可以用一种巧妙的方法:用 MCU 自己 "生成" 一串脉冲信号,再把这串脉冲通过跳线送回 MCU 的输入捕获引脚,从而在没有硬件编码器的情况下,也能完整跑通轮速采集链路。

具体实现步骤:

生成模拟脉冲:让TIM10/TIM14定时器输出 PWM 方波,把这个方波当作 "模拟编码器脉冲";

跳线回环:用杜邦线把 PWM 输出的 IO 口连接到TIM2的输入捕获 IO 口;

捕获与计算:TIM2捕获到规律的脉冲波形,像读取真实编码器一样测量脉冲间隔,计算出转速。

总体流程框图如下:

3.3、与仿真电机模型联动

本项目中,我们可以通过仿真电机模型得到左右轮的轮速结果vL_meas/vR_meas,再反向推导出 "对应轮速的编码器脉冲频率"。将这个频率设置到定时器 PWM 输出上,让 TIM10 输出左轮对应频率的方波,让 TIM14 输出右轮对应频率的方波 ------ 这一步就相当于 "生成编码器脉冲"。随后 TIM2 的输入捕获模块会去采集这串方波,通过测量相邻脉冲之间的时间间隔 dt 得到频率 hz = 1/dt,再根据轮子周长和编码器脉冲数(PPR)换算出最终的速度(mm/s)。这样就完整复现了真实系统的闭环链路。

四、IMU 数据读取

IMU(惯性测量单元)是机器人姿态感知的核心传感器,本项目使用的 MS6DSV六轴传感器,正是通过标准 I²C 总线 与 MCU 进行通信。

4.1、I²C 总线基础

I²C是嵌入式开发中非常常见的板级短距离串行通信总线,用来让 MCU 和各种外设芯片交换数据。它的定位有点像 "主控带着一串从设备一起聊天",优点是省引脚、连线简单、生态成熟。

I²C 最经典的特点是只需要两根线就能完成通信:

SCL(时钟线):由主设备提供时钟,控制通信时序;

SDA(数据线):数据按位在这条线上传输。

I²C 是典型的主从模型。

主设备(MCU)发起通信、决定时序;

从设备有自己的地址(常见 7 位,也支持 10 位),主设备通过地址 "点名" 某个从设备,然后进行读或写;

时序流程图如下:

具体可以参考以下博客:
STM32 I2C 总线通信实战|从原理到 OLED 屏数据收发(超详细)---STM32 HAL库专栏_单片机接oled显示屏原理图-CSDN博客

I2C 写寄存器时序(流程图)如下:

I2C 读寄存器时序(流程图)如下:

4.2、MS6DSV 传感器工作原理

MS6DSV 是一款高性能 3 轴加速度计 + 3 轴陀螺仪 六轴 MEMS 惯性传感器,是机器人获取姿态、运动、振动信息的核心部件。

MS6DSV 使用 GPIO 模拟 I2C 与 MCU 通信,遵循标准 I2C 主从机制。

该模块采用的是ST公司的 LSM6DSV16X六轴传感器芯片为核心。,实物图如下:

1. 加速度计原理

加速度计内部采用 MEMS 微机械结构,内置可移动质量块。

当设备发生运动或受到重力作用时,质量块产生位移,引起电容变化,传感器将其转换为电信号,最终输出 三轴线性加速度。

静止时:可测量重力加速度,用于计算倾斜角度。

运动时:可测量运动加速度,用于判断运动状态。

单位:mg(1g = 9.8m/s²)

2、陀螺仪原理

陀螺仪基于 科里奥利力(Coriolis Effect) 工作。内部驱动结构持续振动,当设备发生旋转时,振动块受到垂直方向的力,产生可检测的电容变化,从而解算出 三轴角速度

用于检测旋转快慢与方向。

单位:mdps(毫度 / 秒)。

加速度计 + 陀螺仪共同构成 六轴姿态感知系统

在 SensorTask 主循环中

五、全局快照

SensorTask 每隔一段时间会采集一次数据:电池电压、轮速、IMU(加速度 / 角速度 / 温度)。这些数据属于系统状态,不仅要给 UplinkTask 发送给上位机,可能还要给 LCD 显示、日志记录、监控任务使用。

如果每个任务都自己去读 ADC / 编码器 / IIC,那么耦合度会非常高:每个任务都要懂硬件细节;同时时序会混乱,不同任务读到的数据可能不是同一时刻的。

为了解决这个问题,本项目引入了一个共享的状态缓存 ------g_status_snap。它是一个在协议层定义的结构体(protocol.h),包含了机器人所有关键状态信息。

cpp 复制代码
typedef struct
{
    uint16_t battery_mv;   /* 电池电压, mV */
    int16_t  temp;         /* 温度, ℃ * 100 */
    int16_t  vx;           /* x 方向速度, mm/s */
    int16_t  vy;           /* y 方向速度, mm/s (差速底盘固定为 0) */
    int16_t  vz;           /* 角速度, mrad/s */
    int16_t  gx;           /* 角速度 X, 0.01 deg/s */
    int16_t  gy;           /* 角速度 Y, 0.01 deg/s */
    int16_t  gz;           /* 角速度 Z, 0.01 deg/s */
    int16_t  ax;           /* 加速度 X, mg */
    int16_t  ay;           /* 加速度 Y, mg */
    int16_t  az;           /* 加速度 Z, mg */
} proto_status_t;

为了保证多任务并发访问的线程安全,项目为这个全局快照提供了两个线程安全的访问函数:SensorSnap_SetSensorSnap_Get,它们是 g_status_snap 的唯一写入入口和唯一读入口。

5.1、SensorSnap_Set 写入接口

该函数由 SensorTask 调用,用于更新全局状态快照。

cpp 复制代码
void SensorSnap_Set(const proto_status_t *s)
{
    if (!s) return;           // 入参判空,防止空指针
    taskENTER_CRITICAL();     // 进入临界区,禁止任务切换
    g_status_snap = *s;       // 结构体整体拷贝,原子更新
    taskEXIT_CRITICAL();      // 退出临界区
}

关键点:使用 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 保护整个拷贝过程,确保数据是一次性完整写入的,避免其他任务读到 "半新半旧" 的撕裂数据。

5.2、SensorSnap_Get 读取接口

该函数由其他任务(如 UplinkTask、日志任务)调用,用于安全地获取当前系统状态。

cpp 复制代码
void SensorSnap_Get(proto_status_t *out)
{
    if (!out) return;         // 入参判空,防止空指针
    taskENTER_CRITICAL();     // 进入临界区,禁止任务切换
    *out = g_status_snap;     // 结构体整体拷贝,原子读取
    taskEXIT_CRITICAL();      // 退出临界区
}

关键点:读取操作同样被临界区保护,确保读取过程不会被写入操作打断,保证拿到的是一份完整、一致的状态数据副本。


总结

整个机器人下位机三大核心任务分工明确、层层协作:

CommTask 通信任务:负责上位机指令接收、解析与分发,作为控制指令输入源头;

ControlTask 控制任务:接收运动指令,完成运动学逆解、PID 运算、PWM 电机输出;可通过SensorSnap_Get读取真实轮速快照,实现全闭环调速;

SensorTask 传感器任务:统一采集所有硬件状态,生成系统快照;为控制闭环、状态上报、设备监控提供稳定数据源。

三者依托 FreeRTOS 实时调度,基于状态快照实现数据解耦,无强耦合依赖,系统稳定性与可维护性大幅提升。

相关推荐
木子单片机1 小时前
基于51单片机出租车计费设计
stm32·单片机·嵌入式硬件·51单片机·keil
济6172 小时前
FreeRTOS 上报任务设计---UplinkTask 上行数据上报任务详解
stm32·嵌入式·freertos
踏着七彩祥云的小丑15 小时前
嵌入式测试学习第1天:电路基础核心概念
单片机·嵌入式硬件
Deitymoon18 小时前
STM32F103——超声波模块
stm32·单片机·嵌入式硬件
你怎么知道我是队长21 小时前
计算机系统基础22---计算机的基本组成---IO控制方式
单片机·嵌入式硬件
風清掦21 小时前
【STM32学习笔记-12】Unix 时间戳、BKP 备份寄存器与 RTC 实时时钟
笔记·stm32·单片机·嵌入式硬件·学习·实时音视频·unix
hoiii18721 小时前
基于STM32的扫地机器人源码工程
stm32·单片机·机器人
济6171 天前
ARM Linux 驱动开发篇---Linux字符设备驱动代码阅读指南---附设备树LED驱动实战案例
linux·嵌入式·嵌入式linux驱动开发