
1. 从“车外发生了什么”说起传感器到底在嵌入式系统里扮演什么角色很多人第一次接触 STM32 和传感器脑子里冒出来的画面是一块开发板插上几根杜邦线然后串口助手里跳出一串数字。这个画面没错但它只讲了一半。真正的问题是——STM32 本身没有眼睛、没有耳朵、没有皮肤它不知道车外有没有障碍物、光线是亮是暗、温度是高是低、前方是不是有一堵墙。传感器就是给 STM32 补上这些感官的“外设器官”。我做了几年嵌入式项目从最基础的 DHT11 温湿度到工业级的光电对射传感器、超声波模块、颜色传感器都折腾过。踩过的坑比写过的驱动还多。这篇文章不打算给你背一遍传感器分类手册而是想从“让 STM32 知道车外发生了什么”这个具体场景出发把传感器选型、接口设计、数据采集、常见故障排查这一整条链路讲透。不管你是刚学完 GPIO 和 ADC 的新手还是已经在做物联网网关、循迹小车、相扑机器人的老手都能从里面找到能直接抄作业的东西。先说清楚这个场景的核心需求。假设你手上有一台小车或者一个移动平台主控是 STM32你需要它感知外部环境。这个“感知”至少包括几类信息距离信息前方有没有障碍物、离多远、光线信息环境亮度、颜色识别、位置信息有没有压到黑线、有没有碰到边界、环境信息温度、湿度、烟雾浓度。每一类信息背后对应的传感器原理、输出信号类型、STM32 的接口方式都不一样。选错了传感器后面写再多代码都是白费。所以这篇文章的结构是这样先讲传感器和 STM32 之间的信号链路怎么设计再拆解几种典型传感器在“车外感知”场景下的具体用法然后给出一套完整的实操流程和参数计算最后把我这些年遇到的典型问题和排查方法整理成速查表。你不需要按顺序读但如果你正准备做一个带环境感知的 STM32 项目建议从头看一遍。2. 传感器与 STM32 的信号链路设计为什么选型比写代码更重要2.1 传感器输出的三种信号类型决定了你用什么外设去接传感器和 STM32 之间的通信本质上就是信号类型的匹配问题。你把传感器的输出信号类型搞清楚了STM32 这边用什么外设、怎么配置基本就定了。第一种是模拟信号输出。典型代表是 MQ 系列气体传感器、光敏电阻模块、部分红外测距传感器。这类传感器输出一个连续变化的电压STM32 需要用 ADC模数转换器去采集。这里有个关键点STM32 的 ADC 是 12 位的参考电压通常是 3.3V也就是说它能把 0 到 3.3V 的电压分成 4096 个等级。但很多传感器的输出范围并不是 0 到 3.3V比如 MQ3 酒精传感器在标准条件下输出可能只有 0.5V 到 2V 左右。如果你直接把它的输出接到 ADC 引脚分辨率就被浪费了。我一般的做法是先用万用表实测传感器在目标环境下的输出范围然后决定要不要加一个运放做信号调理。第二种是数字信号输出。包括 UART 串口、I2C、SPI、单总线这些。比如超声波模块 HC-SR04 用的是 Trig 和 Echo 两个 GPIO 做脉冲测量颜色传感器 GY-33 用的是 I2C一些激光测距模块用的是 UART。数字信号的好处是抗干扰能力强传输距离远而且数据本身就是经过传感器内部处理过的STM32 拿到就能用。但代价是你要写对应的通信协议驱动I2C 的时序、UART 的波特率、SPI 的时钟极性任何一个配错都读不到数据。第三种是开关量输出。最典型的就是五路循迹传感器、对射式光电传感器、碰撞开关。输出就是高电平或者低电平STM32 用 GPIO 直接读就行。看起来最简单但坑也不少。比如光电传感器的输出是开漏还是推挽需不需要上拉电阻阈值能不能调这些细节直接决定你的小车在黑色胶带和白色地板之间能不能稳定识别。提示选传感器之前先问自己三个问题——输出什么信号、供电多少伏、响应速度够不够。这三个问题答不上来后面一定出问题。2.2 供电与电平匹配3.3V 和 5V 混用是新手最大的坑STM32 的 GPIO 和 ADC 引脚绝大多数都是 3.3V 电平但市面上大量传感器模块是 5V 供电的。比如经典的 HC-SR04 超声波模块VCC 接 5VEcho 引脚输出也是 5V 电平。如果你直接把 Echo 接到 STM32 的 GPIO 上短时间可能能用但长期运行会损伤引脚甚至烧掉芯片。我自己的做法是分两种情况处理。如果传感器模块本身支持 3.3V 供电优先用 3.3V这样电平天然匹配。如果必须用 5V 供电那就在信号线上加一个电平转换电路。最简单的方案是用两个电阻做分压把 5V 降到 3.3V 左右。比如 Echo 输出 5V你用 1k 和 2k 电阻分压得到 3.33V刚好在安全范围内。但分压电阻会影响信号上升沿如果信号频率比较高比如超过 100kHz分压方案就不太靠谱了得用专用的电平转换芯片比如 TXS0108E 或者 74LVC245。还有一个容易被忽略的点传感器的供电电流。有些传感器模块标称 5V 供电但实际工作电流可能超过 100mA比如某些加热型气体传感器。如果你直接从 STM32 开发板上的 5V 引脚取电而开发板的 5V 又是从 USB 口过来的USB 口限流一般在 500mA 左右加上 STM32 本身和其他外设的消耗很容易导致传感器工作不稳定读数跳动。我的经验是大功率传感器单独供电共地就行。2.3 接口资源分配别等到引脚不够用了才后悔STM32 的引脚数量是有限的尤其是你同时接多个传感器的时候。我见过不少新手项目一开始随便分配引脚后来发现 I2C 和 SPI 的引脚冲突了或者定时器通道不够用了只能飞线或者换芯片。我的建议是在项目规划阶段就画一张引脚分配表。把每个传感器需要的接口类型、引脚编号、复用功能都列清楚。比如传感器接口类型所需引脚STM32 外设备注HC-SR04GPIOTrig, EchoTIM 输入捕获Echo 用定时器捕获测脉宽GY-33 颜色传感器I2CSCL, SDAI2C1需要上拉电阻五路循迹GPIO5 个输入普通 GPIO建议用中断MQ3 酒精ADC1 个模拟输入ADC1 通道需要预热串口屏UARTTX, RXUSART2注意波特率这张表看起来简单但能帮你避免 80% 的引脚冲突问题。另外STM32 的很多引脚有复用功能比如 PA9 和 PA10 既是 USART1 的 TX 和 RX也可以做普通 GPIO。在 CubeMX 里配置的时候它会自动帮你检查冲突但前提是你得先把需求理清楚。3. 典型传感器在“车外感知”场景下的实战拆解3.1 超声波测距HC-SR04 和 STM32 定时器捕获的配合超声波测距是“车外感知”里最常用的方案之一。HC-SR04 模块便宜、好用原理也简单Trig 引脚给一个 10 微秒的高电平脉冲模块发射超声波Echo 引脚会输出一个高电平高电平的持续时间就是超声波从发射到遇到障碍物反射回来的时间。STM32 这边要做的就是测量 Echo 高电平的持续时间。最常用的方法是定时器输入捕获。具体来说把 Echo 接到一个定时器的通道上配置成上升沿和下降沿都触发捕获。上升沿捕获的时候记录计数器的值下降沿捕获的时候再记录一次两次的差值乘以计数周期就是高电平时间。这里有个计算过程要搞清楚。假设你的定时器时钟是 72MHz预分频器设为 71那么计数频率就是 72MHz / (711) 1MHz也就是每 1 微秒计数一次。如果捕获到的差值是 580那高电平时间就是 580 微秒。超声波在空气中的速度大约是 340 米/秒也就是 34000 厘米/秒换算成每微秒 0.034 厘米。声音走的是来回两趟所以实际距离 580 * 0.034 / 2 9.86 厘米。代码层面用 HAL 库的话大致流程是这样的// 启动输入捕获 HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); // 在捕获回调函数里处理 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { if (is_rising_edge) { rising_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); is_rising_edge 0; // 切换为下降沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); } else { falling_value HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); pulse_width falling_value - rising_value; is_rising_edge 1; __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); } } }实测下来HC-SR04 在 2 厘米到 400 厘米范围内精度还不错但有几个坑要注意。第一测量周期不能太短两次测量之间至少间隔 60 毫秒否则前一次的回波还没消失后一次就发射了会读到错误数据。第二超声波对软质材料比如海绵、布料反射很弱容易测不到。第三多个超声波模块同时工作会互相干扰如果小车前后都装了超声波最好分时复用。3.2 颜色传感器 GY-33I2C 通信和颜色识别逻辑GY-33 是一款集成式颜色传感器能输出 RGB 值和色温通过 I2C 接口和 STM32 通信。它的好处是不需要你自己做白平衡和颜色空间转换模块内部已经处理好了。但 I2C 通信本身有不少细节要注意。首先是 I2C 地址。GY-33 的默认地址是 0x5A但有些模块可以通过焊接背面的电阻来改变地址。如果你总线上挂了多个 I2C 设备地址冲突了就很麻烦。我一般会在初始化的时候先扫描一遍总线确认设备地址。// I2C 地址扫描 for (uint8_t addr 1; addr 128; addr) { if (HAL_I2C_IsDeviceReady(hi2c1, addr 1, 1, 10) HAL_OK) { printf(Found device at 0x%02X\n, addr); } }GY-33 的读取流程一般是先写寄存器地址再读数据。它的 RGB 数据通常是 16 位的分两个字节传输。读回来之后要拼接成完整数值。这里有个字节序的问题有些模块是高字节在前有些是低字节在前数据手册上一般会写清楚。如果读出来的颜色明显不对比如红色物体读出来是蓝色那大概率是字节序搞反了。颜色识别的逻辑其实不复杂。你先把传感器对准已知颜色的物体记录下 RGB 值然后在代码里设定阈值范围。比如红色物体的 R 值明显高于 G 和 B蓝色物体的 B 值明显高于 R 和 G。但实际环境光会影响读数所以最好在固定的光照条件下做校准。我做过一个分拣小车的项目用的是 GY-33 识别红绿蓝三种颜色的积木在室内日光灯下识别率能到 95% 以上但拿到窗户边有阳光直射的地方就掉到 70% 左右。后来加了一个遮光罩问题就解决了。3.3 五路循迹传感器开关量采集和中断处理五路循迹传感器是循迹小车的标配。它由五个红外对管组成每个对管输出一个开关量信号。白色地面反射红外光接收管导通输出低电平黑色胶带吸收红外光接收管截止输出高电平。STM32 只需要读五个 GPIO 的状态就能判断小车相对于黑线的位置。看起来很简单但实际调试的时候问题不少。第一个问题是阈值调节。每个红外对管的灵敏度不一样有的模块上有电位器可以调有的没有。如果没有电位器你就得在代码里做软件滤波。我的做法是连续读多次取多数结果避免单次抖动导致误判。第二个问题是响应速度。如果你用轮询的方式读 GPIO在主循环里其他任务比较重的时候可能会错过黑线。所以建议用外部中断。把五个传感器的输出接到五个外部中断引脚上配置成双边沿触发。这样只要有状态变化STM32 立刻就能响应。// 外部中断回调 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { switch (GPIO_Pin) { case GPIO_PIN_0: // 最左侧传感器 track_status[0] HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); break; case GPIO_PIN_1: track_status[1] HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1); break; // ... 其他通道 } }第三个问题是环境光干扰。红外传感器对阳光中的红外成分很敏感室外使用时误判率会明显上升。解决办法是调制发射也就是让红外发射管以一定频率闪烁接收端只检测这个频率的信号。但大多数便宜的循迹模块没有这个功能所以室外循迹基本只能靠遮光或者换用其他方案。3.4 环境类传感器MQ3 酒精传感器和 ADC 采集的注意事项MQ3 是一款常用的酒精传感器输出模拟电压浓度越高电压越高。STM32 用 ADC 采集它的输出电压然后通过公式换算成浓度值。但这里有几个非常关键的细节新手很容易忽略。第一MQ3 需要预热。它内部有一个加热丝刚上电的时候读数是不准的通常需要预热 24 小时以上才能达到稳定状态。如果你只是做实验至少也要预热几分钟。我见过有人刚上电就读数然后纳闷为什么数值一直在跳。第二MQ3 的输出电压和浓度不是线性关系。它的 datasheet 上会给出一个对数坐标的曲线图你需要根据曲线拟合出一个公式。常见的做法是取两个标定点然后用对数公式计算。比如在洁净空气中输出电压是 V0在已知浓度 C1 下输出电压是 V1那么浓度 C 和电压 V 的关系大致是C C1 * (V / V1)^(log(C1/C0) / log(V1/V0))这个公式看起来复杂但实际用的时候你只需要把参数算出来然后在代码里做浮点运算就行。如果 STM32 没有浮点单元可以用查表法代替。第三ADC 的参考电压要稳定。STM32 的 ADC 默认用 VDDA 作为参考如果 VDDA 有波动采集结果就会跟着变。建议在 VDDA 引脚旁边加一个 100nF 和 10uF 的电容做滤波。另外ADC 采集的时候最好用 DMA 传输避免 CPU 频繁中断影响其他任务。4. 从零搭建一套车外感知系统的完整实操流程4.1 硬件选型和连接一张清单搞定假设你要做一个小车项目需要感知前方障碍物、地面循迹线、环境光线和颜色。我建议的硬件清单如下功能传感器型号接口数量备注前方测距HC-SR04GPIO 定时器1测距范围 2-400cm循迹五路红外GPIO 中断1检测黑线颜色识别GY-33I2C1识别红绿蓝环境光光敏电阻模块ADC1模拟输出主控STM32F103C8T6-1最小系统板连接的时候注意几点HC-SR04 的 VCC 接 5VEcho 经过分压后接 STM32 的 PA0定时器 2 通道 1。五路循迹的五个输出接 PB0 到 PB4配置成外部中断。GY-33 的 SCL 接 PB6SDA 接 PB7这是 I2C1 的默认引脚。光敏电阻模块的输出接 PA1这是 ADC1 的通道 1。供电方面STM32 最小系统板通过 USB 供电HC-SR04 和五路循迹直接从板子的 5V 引脚取电。GY-33 用 3.3V 供电。所有模块共地。4.2 STM32CubeMX 配置要点用 CubeMX 配置的时候有几个地方容易出错。第一定时器的输入捕获模式要选对。HC-SR04 的 Echo 接在 PA0 上对应 TIM2 的通道 1。在 CubeMX 里把 TIM2 的 Channel1 设为 Input Capture direct mode预分频器设为 71计数器周期设为 65535。这样计数频率是 1MHz最大能测 65.5 毫秒的脉冲对应距离大约 11 米足够用了。第二I2C 的时钟频率。GY-33 支持标准模式 100kHz 和快速模式 400kHz。我一般用 100kHz稳定性更好。CubeMX 里把 I2C1 的 Speed Mode 设为 Standard Mode 就行。第三ADC 的配置。光敏电阻的输出接 PA1对应 ADC1 的通道 1。在 CubeMX 里启用 ADC1选择通道 1采样时间设为 71.5 个周期。如果要用 DMA在 DMA Settings 里添加 ADC1 的 DMA 请求模式选 Circular这样 ADC 会连续采集不需要 CPU 干预。第四中断优先级。五路循迹用了外部中断HC-SR04 用了定时器捕获中断I2C 读取也可以做成中断或者 DMA。这些中断的优先级要合理分配。我的习惯是把定时器捕获中断设为最高优先级因为超声波测距对时间精度要求高外部中断设为中等I2C 和 ADC 设为最低。4.3 代码框架和关键函数实现整个项目的代码可以分成几个模块传感器驱动层、数据处理层、应用逻辑层。传感器驱动层负责和硬件打交道数据处理层负责滤波和换算应用逻辑层根据传感器数据做决策。先看超声波测距的完整实现。前面已经给了捕获回调的代码这里补充一下触发和距离计算的函数float get_distance(void) { // 发送 10us 触发脉冲 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待捕获完成超时返回 -1 uint32_t timeout 0; while (!capture_done) { if (timeout 100000) return -1.0f; } capture_done 0; // 计算距离单位厘米 float distance pulse_width * 0.034f / 2.0f; return distance; }颜色传感器的读取稍微复杂一点因为 I2C 通信需要按照 GY-33 的寄存器手册来操作。一般来说先写一个字节的寄存器地址然后读两个字节的数据。读回来的 16 位数据要拼接uint16_t read_color_channel(uint8_t reg) { uint8_t buf[2]; HAL_I2C_Mem_Read(hi2c1, GY33_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, buf, 2, 100); return (buf[0] 8) | buf[1]; }循迹传感器的读取最简单直接读 GPIO 就行。但为了稳定我一般会做一个简单的滤波uint8_t read_track_sensor(uint8_t index) { uint8_t count 0; for (int i 0; i 5; i) { if (HAL_GPIO_ReadPin(track_ports[index], track_pins[index])) { count; } delay_us(100); } return (count 3) ? 1 : 0; }4.4 数据融合和决策逻辑单个传感器的数据往往不够可靠所以需要做数据融合。比如小车前方测距如果超声波读到 15 厘米但红外循迹传感器没有检测到黑线那可能是前方有一个障碍物而不是到了赛道边缘。这时候决策逻辑应该是减速或者转向而不是继续直行。我的做法是给每个传感器数据加一个置信度。超声波连续三次读数差异小于 2 厘米置信度设为高如果差异大置信度设为低。循迹传感器如果五个通道中有三个以上同时触发说明可能到了十字路口或者终点置信度设为高。颜色传感器如果 RGB 值都在合理范围内置信度设为高。然后根据置信度和传感器数据做加权决策。比如避障逻辑if (distance_confidence HIGH distance 20.0f) { // 前方有障碍物减速并准备转向 motor_speed 30; if (left_distance right_distance) { turn_right(); } else { turn_left(); } }这套逻辑看起来简单但实际跑起来效果还不错。关键是要根据你的小车尺寸和速度调整阈值。比如小车宽度 15 厘米速度 0.5 米/秒那 20 厘米的刹车距离是合理的。如果速度更快阈值就要加大。5. 常见问题与排查技巧实录5.1 传感器读数跳动、不稳定的排查思路传感器读数跳动是最常见的问题原因可能有很多。我一般按照以下顺序排查现象可能原因排查方法解决方案读数小幅跳动电源纹波用示波器看 VCC 波形加滤波电容读数大幅跳动地线干扰检查共地是否良好单点接地偶尔读到极值通信错误检查 I2C/UART 波形加校验重试数值缓慢漂移温漂对比不同温度下读数软件补偿完全无数据接线错误检查引脚定义重新接线电源问题是最容易被忽略的。我遇到过一个小车项目超声波读数一直在 10 厘米到 30 厘米之间跳换了三个模块都没用。后来用示波器看 5V 电源发现纹波有 200mV原因是电机启动的时候电流突变把电源拉低了。加了一个 470uF 的电解电容在电源输入端问题就解决了。5.2 I2C 通信失败的几种典型情况和修复方法I2C 通信失败也是高频问题。GY-33 读不到数据串口打印出来全是 0 或者 255。我总结了几种典型情况第一种是上拉电阻缺失。I2C 总线需要上拉电阻通常 4.7k 到 10k。有些模块自带上拉有些没有。如果总线上所有设备都没有上拉通信肯定失败。用万用表测 SCL 和 SDA 对 VCC 的电阻如果是无穷大那就需要外接上拉。第二种是地址错误。GY-33 的地址是 0x5A但 HAL 库的地址参数需要左移一位变成 0xB4。如果你直接传 0x5AHAL 库会把它当成 7 位地址实际发送的是 0x5A 左移一位再加读写位结果就不对了。这个坑我踩过好几次后来养成了习惯所有 I2C 地址都写成(addr 1)的形式。第三种是时钟频率太快。有些便宜的 I2C 设备不支持 400kHz只能跑 100kHz。如果你在 CubeMX 里设了 400kHz通信就会时好时坏。解决办法是把速度降到 100kHz 试试。第四种是总线电容过大。I2C 总线的电容不能超过 400pF如果走线太长或者挂的设备太多电容超标会导致波形上升沿变缓通信失败。这时候需要降低上拉电阻阻值或者用 I2C 缓冲器。5.3 中断冲突和优先级配置的坑STM32 的中断优先级配置不对会导致程序跑飞或者响应延迟。我遇到过一个案例超声波捕获中断和串口接收中断优先级设成一样结果串口数据量大的时候超声波捕获会丢中断测距数据偶尔变成 0。STM32 的中断优先级分为抢占优先级和子优先级。抢占优先级高的可以打断抢占优先级低的中断。我的配置原则是时间敏感的中断如定时器捕获设最高抢占优先级通信类中断如 UART、I2C设中等低速中断如按键设最低具体数值上STM32F103 的 NVIC 优先级分组一般设为 Group 2也就是 2 位抢占优先级和 2 位子优先级。抢占优先级可以设 0 到 30 最高。我一般把定时器捕获设为 0UART 设为 1外部中断设为 2其他设为 3。还有一个坑是中断服务函数里做太多事情。比如在捕获回调里做浮点运算、调用 printf这些都会延长中断处理时间导致其他中断被延迟。正确的做法是在中断里只做标记和数据保存实际处理放到主循环里。5.4 传感器校准和长期稳定性的经验传感器用久了会漂移这是物理特性决定的。光电传感器的红外发射管会老化气体传感器的敏感材料会消耗超声波换能器会受温度影响。所以定期校准是必须的。我的做法是在系统里加一个校准模式。比如循迹传感器在开机的时候让小车在白色地面上转一圈记录五个通道的最大值然后以此作为阈值基准。颜色传感器也是类似先对着白色纸板校准白平衡再开始识别颜色。对于气体传感器校准周期更长一般几个月一次。如果你做的是长期运行的物联网网关建议在代码里加一个漂移检测逻辑如果连续一段时间读数都偏高或者偏低就自动调整基准值。提示校准的时候一定要在目标工作环境下进行。在实验室校准好的颜色传感器拿到室外阳光下可能完全不准。6. 从单传感器到多传感器融合进阶思路6.1 为什么需要多传感器融合单个传感器总有局限性。超声波测距在 2 厘米以内有盲区红外测距受颜色影响大激光测距贵而且对人眼有风险。如果你只靠一个传感器做避障在某些场景下一定会出问题。多传感器融合的思路就是用不同原理的传感器互相补充提高系统的鲁棒性。比如前方避障可以用超声波加红外组合。超声波负责中远距离20 厘米到 400 厘米红外负责近距离0 到 30 厘米。当超声波读数小于 20 厘米时切换到红外传感器判断具体距离。这样既避免了超声波盲区又降低了成本。6.2 简单的加权融合算法实现多传感器融合不一定非要用卡尔曼滤波简单的加权平均在很多场景下就够用了。比如你有两个测距传感器一个超声波一个红外你可以根据它们的置信度分配权重float fused_distance(float us_dist, float ir_dist, float us_conf, float ir_conf) { if (us_conf ir_conf 0) return -1.0f; return (us_dist * us_conf ir_dist * ir_conf) / (us_conf ir_conf); }置信度可以根据传感器的历史表现动态调整。比如超声波连续多次读数稳定置信度就高如果读数跳动大置信度就降低。这样融合出来的距离值比单个传感器更可靠。6.3 在 FreeRTOS 下管理多个传感器任务如果你的 STM32 项目用了 FreeRTOS传感器采集可以做成独立的任务。每个任务负责一个传感器通过队列把数据发给决策任务。这样做的好处是传感器之间互不干扰某个传感器出问题不会影响其他传感器。// 超声波任务 void vUltrasonicTask(void *pvParameters) { float distance; while (1) { distance get_distance(); xQueueSend(ultrasonic_queue, distance, portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(60)); } } // 决策任务 void vDecisionTask(void *pvParameters) { float us_dist, ir_dist; while (1) { xQueueReceive(ultrasonic_queue, us_dist, portMAX_DELAY); xQueueReceive(infrared_queue, ir_dist, portMAX_DELAY); // 融合决策 make_decision(us_dist, ir_dist); } }任务优先级要根据实时性要求来定。超声波和循迹的实时性要求高优先级设高一些颜色识别和环境光采集可以慢一点优先级设低一些。栈大小也要注意浮点运算和 printf 比较耗栈建议每个任务至少 256 字。7. 一些零散但重要的实操心得7.1 杜邦线的可靠性问题杜邦线便宜方便但可靠性真的差。我遇到过好几次小车跑着跑着传感器突然没数据了查了半天发现是杜邦线松了。尤其是小车震动比较大的时候杜邦线接触不良是家常便饭。后来我改用硅胶线加焊接或者用带锁扣的端子问题就少多了。如果你非要用杜邦线至少用热熔胶固定一下接口。7.2 传感器安装位置的影响传感器的安装位置对测量结果影响很大。超声波传感器不能装得太低否则地面反射会干扰也不能装得太高否则近处的小障碍物测不到。我的经验是装在离地 15 到 20 厘米的高度稍微向下倾斜 5 度左右。颜色传感器要尽量靠近被测物体但也不能太近否则会挡住环境光。一般 2 到 5 厘米比较合适。7.3 调试工具的选择调试传感器的时候串口打印是最常用的手段。但 printf 本身很耗时间如果你在中断里调用 printf很容易导致其他中断丢失。我的做法是用一个环形缓冲区中断里只往缓冲区写数据主循环里再从缓冲区读出来打印。另外逻辑分析仪和示波器是排查通信问题的利器。I2C 波形、UART 波特率、PWM 占空比用示波器一看就清楚了。如果预算有限一个几十块钱的 USB 逻辑分析仪也能解决大部分问题。7.4 代码版本管理和文档记录嵌入式项目的代码和硬件配置是强相关的。今天调好的参数过两个月可能就忘了为什么这么设。所以我养成了一个习惯每次调试完在代码注释里写清楚传感器的型号、接线方式、关键参数和调试日期。比如// HC-SR04 超声波模块 // Trig: PA0, Echo: PA1 (经过 1k/2k 分压) // 定时器: TIM2, 预分频 71, 计数频率 1MHz // 测距公式: distance pulse_width * 0.034 / 2 // 调试日期: 2024-03-15, 室温 25 度, 实测误差 1cm这样下次再碰这个项目或者别人接手你的代码能省很多时间。传感器这个东西说复杂也复杂说简单也简单。核心就是搞清楚它的输出信号类型、供电要求、通信协议然后匹配 STM32 的外设资源。剩下的就是调试和积累了。我刚开始做嵌入式的时候一个超声波模块调了三天才读出正确数据现在回头看其实就是定时器捕获的极性配置搞反了。所以遇到问题不要慌按照信号链路一步一步查总能找到原因。