基于STM32的实验室巡检遥控小车:温湿度采集与避障设计
发布时间:2026/10/29 1:12:49 锦皓数字建站

实验室有个一直没解决的问题温湿度记录仪分布在好几个房间每次巡检都得拿着本子挨个房间跑记录完还要回工位手动录入电脑。数据录入繁琐不说经常一个房间漏记就得重新跑一趟。后来我干脆做了台遥控小车把温湿度采集、避障、无线遥控集中到一个嵌入式项目里用单片机作为主控在手机上就能遥控它去各个房间转一圈数据直接传回来。这篇就是我这台“基于单片机的实验室巡检遥控小车”的完整方案从需求拆解、硬件选型到代码实现和调试实录都写清楚适合正在做嵌入式项目、单片机毕设或者实验室里真有类似需求的朋友直接参考复用。1. 项目定位与需求拆解先搞清楚巡检车要做什么1.1 巡检场景的特殊性决定了方案走向实验室巡检和普通遥控玩具车最大的区别在于任务明确、环境固定、需要长期稳定运行。实验室里有很多精密仪器、线缆、试剂架小车不能在房间里乱冲乱撞所以避障功能不是加分项而是必要的安全底线。同时巡检的核心目的是采集环境数据温湿度为主所以传感器的选型和数据回传方式才是这个项目的“灵魂”小车能跑只是载体。我在需求阶段把功能拆成了四层底层是运动控制电机驱动和PWM调速保证小车灵活移动第二层是遥控通道手机或遥控手柄发出指令第三层是感知层温湿度、避障传感器实时采集环境信息和障碍物距离第四层是调度层把遥控指令、避障结果、传感器数据统一处理决定小车下一步动作。这样拆完以后每一层都能单独测试出问题也好定位总比把所有功能揉在一个主循环里强。1.2 主控选型为什么选STM32而不是51单片机很多朋友问做一个巡检小车51单片机不够吗答案是够但不够舒服。51单片机在纯粹的入门学习阶段确实经典Keil写代码、烧录、点个灯逻辑链路简单。可一旦要同时处理无线串口中断、PWM调速、避障传感器轮询、DHT11时序还要跑一个简单的状态机51的资源就很紧张了定时器少、PWM输出需要模拟、串口只有一个的话还要来回切换。我在项目里选用的是STM32F103C8T6这个料价格便宜、网上资料多最重要的是它有多个定时器、多个串口外设资源完全够这个项目用后面想扩展循迹、摄像头、OLED显示也留有余地。但反过来讲如果你目前只熟悉51也不是不能用我的建议是先把整体思路理解透把51当作学习载体把功能跑通一遍定时器模拟PWM、串口接收遥控指令、GPIO读传感器后续再换到STM32上做优化。思路是相通的差别只在寄存器配置和外设初始化方式。1.3 无线遥控方式蓝牙、2.4G还是红外遥控方案我对比过三种红外遥控、蓝牙串口模块、NRF24L01 2.4G模块。红外遥控最便宜但问题非常明显——必须对准才能收发小车在实验室里转个角度就失效了体验极差直接排除。NRF24L01的优点是延迟低、可以组一对多通信适合做个手柄遥控器缺点是调试稍微复杂一点SPI时序得仔细配而且部分实验室里其他设备也用2.4G频段存在干扰风险。蓝牙模块HC-05/HC-06是我最终采用的方案原因很直接手机自带蓝牙装个串口调试APP就能当遥控器用不用额外做手柄硬件数据透传模式拿来传温湿度数据也顺手协议定好帧格式就能用。成本就十几块钱实验室里演示给其他人看也很方便。注意在选无线方案之前最好先确认实验室环境下哪些频段已经被占用。我这次特意选了蓝牙因为我们这边2.4G测试设备太多蓝牙用自适应跳频反而更省心。2. 硬件平台搭建选型比想象中更重要2.1 车体结构和电机驱动模块的取舍车体我用的是常见的亚克力两驱底盘配TT减速电机没有上四驱。原因很简单两驱加差速转向在室内平地上完全够用重量轻、耗电少、驱动模块压力小而且转弯半径控制起来更线性。四驱看起来马力大但四个电机转速一致性很难保证反而容易出现跑偏的情况电池消耗也快。实验室巡检毕竟不是越野要的是稳和可控。驱动模块我对比过L298N和TB6612FNG这两个最常见的型号。L298N便宜、经典、资料多但压降比较大接近2V模块本身发热明显TB6612FNG体积小、压降低0.5V左右更适合电池供电的场景。我最终选了L298N理由很真实手头正好有一块而且L298N那个自带接线端子布局对新手很友好不容易插错线。如果你的电池电压本来就低比如单节锂电池供电那我建议优先用TB6612FNG否则启动瞬间电压跌落太厉害。两种模块的接线逻辑是相同的都是IN1/IN2控制方向、ENA/ENB控制PWM调速。2.2 传感器选型温湿度和避障用什么最合适温湿度这块我选了DHT11。实验室环境本身相对稳定温度波动不大DHT11的±2℃温度和±5%湿度精度已经完全够用没必要上DHT22或者SHT30那种高精度传感器成本和时序复杂度都会上一个台阶。DHT11用单总线协议一根数据线就能完成双向通信时序上的微秒级延时是唯一的坑点后面代码部分我会专门讲。避障传感器我建议用E18-D80NK光电开关而不是超声波模块。E18-D80NK本质是红外反射式传感器输出开关量TTL信号检测距离3到80厘米可调直接给单片机一个高/低电平就能判断有没有障碍物不需要计算回波时间。超声波HC-SR04精度更高但角度窄、对倾斜表面容易漏检而且需要自己写测距时序。在巡检车这种低速运动场景里开关量避障足够可靠代码也简单得多。我把检测距离调到30厘米左右——再远的话小车在实验室通道里反而会被误触发频繁停车。2.3 供电设计别让“掉压重启”毁了整台车供电是整个项目最容易翻车的地方。我给小车用的方案是两节18650锂电池串联电压约7.4V接到L298N的电机电源输入然后用一个LM2596S降压模块把7.4V降到5V给STM32和传感器供电。这里有个关键点电机和单片机一定要分开供电回路只在公共端共地。我最初犯过错误直接用L298N自带的5V输出给单片机供电结果电机一转单片机上电压瞬间掉到4.2V直接复位重启。原因是电机的启动电流普遍是额定电流的好几倍TT减速电机堵转电流能到1A以上这个电流冲击会把供电轨拉垮。正确的做法是电池经过降压模块之后输出端并联一个大容量电解电容我用的是1000uF/16V和一个104瓷片电容稳定瞬态响应。电机电源两端同样并联一个1000uF电容减轻启停时的尖峰。计算一下容量需求两节2000mAh的18650电池小车正常巡检跑10分钟读取传感器加运动控制撑死消耗不到600mAh所以续航完全够用。3. 软件核心实现协议、PWM与状态机3.1 自定义通信协议遥控指令和数据回传如何定帧手机蓝牙串口发出的原始数据如果直接拿过来控制电机会遇到一个典型问题蓝牙无线传输不稳定偶尔会丢字节或者错位。直接控制的话可能出现“按前进结果小车猛向左转”的异常行为。所以我自己定了一个简单的数据帧协议来解决这个问题。帧格式是帧头2字节0x55 0xAA 功能码1字节 数据长度1字节 数据N字节 校验字节所有字节累加取低8位。手机端每次发送的遥控指令是一条固定长度的帧功能码0x01代表运动控制数据字段第一位是方向指令值域0到5停止、前进、后退、左转、右转、急停第二位是速度档位0到100。接收端先找帧头帧头匹配后再做累加和校验校验通过才执行。如果数据错误直接丢弃不动作。这个习惯虽然只是几行代码但它把无线通信的可信度提高了一大截。回传数据也用同样的帧结构功能码0x02表示环境数据数据字段依次是温度整数部分、湿度整数部分、超声波避障状态、电量低压警示位。手机APP那边按帧解析显示在调试界面上。3.2 STM32 PWM初始化与电机控制函数STM32F103C8T6的定时器资源很丰富我选了TIM2输出四路PWM分别控制L298N的ENA/ENB和两个电机的方向逻辑。初始化代码大概是这个思路void Motor_TIM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // PA0~PA3 配置为复用推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); // 定时器预分频和周期 TIM_TimeBaseStruct.TIM_Prescaler 72 - 1; // 72MHz/72 1MHz TIM_TimeBaseStruct.TIM_Period 100 - 1; // 10kHz PWM周期0.1ms TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStruct); // PWM模式1输出比较 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse 0; TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStruct); TIM_OC2Init(TIM2, TIM_OCInitStruct); TIM_OC3Init(TIM2, TIM_OCInitStruct); TIM_OC4Init(TIM2, TIM_OCInitStruct); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); }这里把PWM频率选在10kHz高于人耳明显可闻的频段电机声音会更安静一些也避免频率过低导致电机转动不连续。占空比直接对应速度档位100%全速30%以下电机基本带不动车TT电机的启动占空比阈值大约在这个范围内。因此在遥控程序里做了个死区处理速度档位低于30就当0处理防止小车停在原地嗡嗡叫。电机控制函数封装好之后上层就不用管寄存器了void Motor_SetSpeed(int left_speed, int right_speed) { // left_speed/right_speed 为 -100 到 100正负表示方向 if (left_speed 0) { GPIO_SetBits(GPIOB, GPIO_Pin_0); // IN1 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // IN2 TIM_SetCompare1(TIM2, left_speed); } else { GPIO_ResetBits(GPIOB, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_1); TIM_SetCompare1(TIM2, -left_speed); } // 右电机同理用PB2/PB3控制IN3/IN4TIM2_CH3/PB模拟时用CH2对应PA1 // 实际接线时请按原理图对应修改 }3.3 遥杆指令解析与主循环状态机控制起来最稳小车的行为控制我用了状态机思路而不是在中断里直接怼电机指令。状态机的状态包括空闲、前进、后退、左转、右转、避障暂停。每个状态下系统只响应“当前状态下合法”的事件。比如前进状态下碰到障碍自动切换到避障暂停这个切换的优先级高于遥控指令——毕竟撞上实验设备的后果可比“停车不够快”严重多了。遥控指令在这个状态下会被缓存避障解除后再执行不会丢指令。主循环的骨架是这样while (1) { // 1. 串口接收并解析遥控帧 if (UART_RxFlag 1) { UART_RxFlag 0; Parse_RemoteFrame(); } // 2. 读避障传感器 obstacle_left GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_5); obstacle_front GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6); obstacle_right GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7); // 3. 状态机执行一次转移 StateMachine_Update(); // 4. 每2秒读一次DHT11并回传 sensor_tick; if (sensor_tick 2000) { sensor_tick 0; DHT11_Read(); Send_SensorFrame(temperature, humidity); } delay_ms(10); }DHT11读取要注意单总线协议主机先拉低总线至少18ms然后释放并延时20-40us接着读取从机应答和数据位。每一位的时间窗口是50us左右0和1的区别只在高电平持续时长不一样0大约是26-28us1大约是70us。我用一个微秒级延时函数配合IO读取来实现uint8_t DHT11_ReadByte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { while (DHT11_DQ_IN() 0); // 等待起始 delay_us(40); if (DHT11_DQ_IN() 1) { byte | (0x80 i); while (DHT11_DQ_IN() 1); // 等待结束 } } return byte; }需要强调的是DHT11的读取间隔必须大于1秒我实际用了2秒间隔否则会读到清零值这个问题网上很多人踩过。3.4 避障逻辑的精细调校别让小车变成“碰碰车”避障传感器我装了三个方向左前方、正前方、右前方都接在STM32的普通GPIO上用内部上拉读取。E18-D80NK的输出在没有障碍物时是高电平检测到障碍物时输出低电平逻辑直接在状态机里判断。我把三个方向的避障当成一组“优先级事件”处理只要正前方有障碍无条件进入避障暂停如果正前方没有但左前方有则进入右转右前方有障碍则进入左转左右都有则原地后退。这个逻辑一开始写得比较简单——只要有障碍就往反方向转结果在小车走廊里频繁左右横跳。后来加了个“延时确认”机制检测到障碍后先保持当前动作100ms再确认一次如果确认还是障碍才执行转向这样瞬间晃过的人影或者传感器抖动就不会导致误判。实际测试效果好了很多小车走起来不像碰碰车了更像一个谨慎的巡检员。4. 巡检逻辑与传感器联动设计4.1 房间巡检流程从手动遥控到半自动巡检小车做出来以后最初几个版本纯粹是手动遥控拿手机按方向键让小车开到每个房间的温湿度计附近等传感器读数稳定后传回数据。这个方案演示很直观但长时间用下来还是会心累毕竟每次得盯着屏幕控制方向。后来我在软件里加了简单的半自动模式小车启动后先原地转一圈用避障传感器加一个简易的“通道估算”逻辑判断当前区域是否有开阔空间如果是就按直线前进直到撞到障碍再停下如果不是则原地旋转90度继续探测。这样小车能在几个固定点位之间自动巡航手机端只需要在关键位置发一个“采集”指令。这个逻辑本身不复杂但它把“巡检工具”和“遥控玩具车”区分开了——前者讲究能自动完成任务后者只关心被操控的娱乐性。如果你的场地更固定还可以升级成循迹方案在地面贴反射胶带让小车沿固定路线跑可靠性更高。4.2 数据回传与上位机显示手机端怎么看到数据蓝牙模块配好后手机端我用现成的串口调试工具就能完成最基本的数据接收设置波特率9600HC-05默认配对成功后就能看到小车回传的温湿度帧。为了读出可读的数据我在小车端直接拼好了一行ASCII字符串发送比二进制帧直观得多格式是这样的T:25.3 C H:56.2%。调试阶段用这种方式看原始数据最方便事件排查的时候一眼就能看出数据是自己发出的还是外部串口工具发的。如果想把数据存成日志可以后期在电脑上接一个USB转TTL写一个Python脚本接收串口数据并写入CSV文件。我在调试时就是用的这个方法一边遥控小车跑路线一边在电脑上记录对应的温湿度数据用来验证每个房间的采集点是否稳定。这个脚本思路很简单pyserial读数据、按行写文件就行不需要额外的上位机框架。4.3 低压检测和应急保护实验室设备安全不能省实验室环境毕竟不像家里那么随意小车的安全性必须认真对待。我加了两层保护第一层是电池电压监测用STM32的ADC读取电池分压电压当电压低于6.8V时小车强制进入“慢速模式”并且语音提醒蜂鸣器每秒响一次低于6.3V直接停车防止锂电池过放损坏第二层是急停按钮硬件上串联在驱动模块使能引脚上紧急情况拍一下按钮直接切断电机电源这个开关我从最开始就焊上了至今没出过问题。注意急停按钮一定要接在电机驱动板的使能端而不是单片机的GPIO上。单片机哪怕断电了之后还会复位重启万一复位瞬间正好输出一个错误的PWM信号反而更危险。5. 调试实录常见问题与排查技巧5.1 电机不转、单侧不转、跑偏——机械和电气混在一起查这个问题是新手最容易碰到的。我总结了排查顺序先看驱动板电源指示灯是否正常测量电机电源电压再看单片机输出的PWM引脚有没有波形用万用表频率档或者示波器看占空比接着查L298N的IN引脚和单片机GPIO是不是刚好一一对应很多人为了省事把IN接反了最后检查共地问题——这个太关键了L298N的逻辑电源GND和单片机的GND之间如果不导通所有信号全是虚电压电机怎么都不会转。还有一种常见情况是电机跑偏左右两个TT减速电机转速天然存在差异比如左边实测转速比右边快10%。解决办法不是在代码里硬调占空比而是在PWM初始化里做一次校准测量两轮空载转速后给慢的那侧一个基础补偿值让两侧速度尽量对齐。这个方法对直行稳定性的改善非常明显。5.2 蓝牙指令乱跳变、偶尔“幽灵按键”——协议和干扰双管齐下蓝牙串口数据出现问题时第一个排查方向是波特率。HC-05模块出厂默认波特率通常是9600但有的时候批次不同默认值是38400所以我每次拿到新模块都先用AT命令读一下参数发送ATUART?确认波特率。第二个方向是电源干扰蓝牙模块如果和电机驱动共用一个电源电机启停瞬间的毛刺会被蓝牙天线接收并转成乱码表现为小车自己会动。我的处理方式是蓝牙模块VCC单独从5V降压输出取电并且在该模块电源引脚旁边加一个104电容和10uF电容。第三个方向是协议本身这也是我前面设计帧头校验的原因——校验失败直接丢帧不让垃圾数据生效。实测下来加了校验后“幽灵按键”的现象基本杜绝。5.3 STM32下载失败、无法连接——BOOT0和硬件复位的坑用STM32CubeProgrammer或者FlyMcu给STM32下载程序时经常遇到“连接失败”然后一脸懵。我整理一下最常见的几个场景如果按了“下载”之后提示无法连接先用串口助手发一下0x7F看是否有回显。没有回显说明硬件链路有问题检查CH340驱动是否安装成功、USB转TTL的TX/RX有没有交叉接一定要RX接TXTX接RX、芯片供电是否正常。如果有回显但是下载失败绝大多数情况是BOOT0引脚没有拉高。STM32上电后BOOT0为低电平时从Flash启动为高电平时进入系统存储器引导只有在这个模式下才能用串口下载程序。所以下载的时候把BOOT0跳到1按一下复位键下载完再把BOOT0跳回0再次复位。这个流程走不顺的话我推荐在原理图上直接把BOOT0引脚焊一个单刀双掷开关省得每次拿镊子拨跳线帽。5.4 调试技巧速查从“玄学”到“可复现”整套项目调试下来我给自己总结了几个习惯分享出来或许能帮你少走弯路第一每次改代码只改一个变量。比如调左右轮速补偿一次只调5实测跑直线看效果切忌同时改好几个参数出了问题根本不知道是哪个改动引入的。第二用串口打印关键状态量。我在状态机每个状态切换处都加了一行串口输出类似STATE: FORWARD - OBSTACLE_PAUSE配合手机串口工具实时查看就不用靠猜了。第三给所有接线加标签。小车线束一多找一根IN1线头能找半小时用标签机打几个小牌挂上去后面调试效率成倍提升。第四修完一个功能立刻全量回归测试。有一次我只改了个蜂鸣器结果发现遥控指令全失灵了排查半天发现是把串口中断的NVIC配置注释掉了——这种问题只有回归测试才能抓出来。结尾这个项目从画框图到整车跑通前后花了一周多时间踩得最深的坑其实是供电和机械层面的问题而不是代码。很多嵌入式项目看起来难在算法实际做起来会发现物理世界的电流、电压、摩擦力才最不讲道理。如果你也打算做类似的巡检小车我的建议是先买齐硬件搭一个最小系统让轮子转起来再做遥控再往上加传感器和巡检逻辑每层都验证通过再往上走。硬件焊接检查三遍再上电一个项目下来能帮你避掉大部分新手陷阱。最后分享一个我在实际使用中的小技巧给小车底部粘两条覆铜板胶带就是那种电磁炉线圈用的铜箔胶带一方面能降低电机电磁辐射对蓝牙模块的干扰另一方面底盘在轻微摩擦地面时不容易积静电。实验室环境铺了防静电地板冬天有些地方其实静电不小好几次小车莫名复位都是这个原因。这个细节可能不是标准的教科书方案但实测下来对整机稳定性提升非常明显。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。