基于STM32与ESP8266的低成本实验室巡检小车设计与实现
1. 项目缘起与整体设计思路
1.1 为什么选择"巡检小车"这个方向
实验室巡检这件事,做过的人都知道有多枯燥。每天晚上十点,值班的人要挨个检查温湿度、看看气瓶阀门关没关、确认设备电源状态,一圈走下来二十分钟起步,遇到刮风下雨还得硬着头皮去。我在实验室待了几年,见过太多因为忘记关设备导致第二天跳闸的情况,也见过培养箱温度异常一整晚没人发现,第二天整批样品报废。这些事逼着我去想:能不能用一套低成本的嵌入式方案,让小车替人跑这一圈?
这个项目的核心目标很明确——做一台能自主或半自主移动、能采集环境数据、能把数据回传到上位机的小车。它不需要多高级的SLAM建图,也不需要激光雷达,就是一台"能走、能看、能报"的巡检工具。适合谁参考?电子类专业做课程设计的学生、刚入行想练手的嵌入式工程师、以及实验室里想搞点自动化改造的技术员。整套方案基于STM32F103C8T6主控加ESP8266无线模块,成本控制在两百元以内,全部代码和原理图都可以复现。
1.2 方案选型的几个关键取舍
主控选STM32F103C8T6,不是因为它最强,而是因为它最"稳"。这颗Cortex-M3内核的芯片主频72MHz,64KB Flash、20KB SRAM,对于小车这种需要同时跑电机控制、传感器采集、串口通信的场景绰绰有余。更关键的是它的生态——标准库、HAL库资料铺天盖地,最小系统板十几块钱就能买到,烧录器用ST-Link或者串口ISP都行。我试过用51单片机做类似项目,定时器资源不够用,PWM精度也差,最后还是换回了STM32。
无线通信这块,ESP8266是绕不开的选择。它支持802.11 b/g/n,可以工作在Station模式连路由器,也可以开AP模式让手机直连。价格便宜到离谱,十几块钱一个模块,AT指令集成熟,NonOS SDK开发环境也不算复杂。有人会问为什么不直接用蓝牙?蓝牙传输距离太短,实验室这种几十米跨度的场景,WiFi才是正解。至于电机驱动,我用的是L298N双H桥模块,虽然效率一般、发热明显,但胜在皮实、逻辑简单、支持PWM调速,对于巡检小车这种低速场景完全够用。
传感器方面,DHT11负责温湿度采集,虽然精度一般(温度±2℃,湿度±5%RH),但胜在便宜、单总线协议简单、驱动代码网上一抓一大把。如果要更高精度可以换SHT30,但成本会翻几倍。显示用LCD1602,主要是为了调试方便,能实时看到传感器数据和WiFi连接状态,正式部署时其实可以省掉。
1.3 系统整体架构拆解
整个系统分成三层:感知层、控制层、通信层。感知层包括DHT11温湿度传感器和红外避障模块,负责采集环境和障碍物信息;控制层是STM32F103C8T6,负责电机PWM调速、传感器数据读取、状态机调度;通信层是ESP8266,通过UART与STM32通信,把数据打包成TCP或MQTT协议发到上位机。
供电部分需要特别注意。STM32和ESP8266都要3.3V,而电机和L298N需要7-12V。我用的是两节18650锂电池串联(7.4V),一路直接给L298N供电,另一路经过AMS1117-3.3降压给主控和WiFi模块。这里有个坑:ESP8266在发射瞬间电流能冲到300mA以上,如果降压芯片余量不够,会导致STM32复位。我一开始用LM1117,带不动,后来换成AMS1117加470μF电容才稳住。
提示:电机电源和主控电源一定要分开走线,共地但不共电源,否则电机启动瞬间的电压跌落会把单片机拉复位。
2. 核心硬件细节与实操要点
2.1 STM32F103C8T6最小系统板的关键引脚分配
拿到最小系统板第一件事是理清引脚。STM32F103C8T6有48个引脚,其中可用GPIO有37个。我的分配方案是这样的:PA0-PA3接四个红外避障模块输入;PA8、PA9、PA10、PA11分别接L298N的IN1-IN4控制电机正反转;PB6、PB7接DHT11的数据线(其实只用一根,另一根备用);PA2、PA3复用为USART2的TX/RX接ESP8266;PB0-PB5接LCD1602的数据口。
这里要重点说VBAT引脚。很多新手不知道,STM32F103C8T6的VBAT引脚(引脚1)如果不接,内部RTC和备份寄存器会工作异常。虽然这个项目没用到RTC,但建议还是把VBAT接到3.3V,避免莫名其妙的复位问题。另外BOOT0和BOOT1的跳线帽要确认好,正常运行时BOOT0接地,用串口下载时BOOT0接高电平。
PWM输出我用的是TIM3的通道1和通道2(PA6、PA7),配置成72MHz主频下72分频,这样计数频率是1MHz,自动重装载值设为1000,PWM频率就是1kHz。为什么选1kHz?因为L298N的开关频率上限大概在40kHz,但低频下电机噪音大,高频下开关损耗高,1kHz是个折中点,实测电机运行平稳且发热可控。
2.2 ESP8266的AT指令配置与避坑
ESP8266出厂固件默认是AT固件,波特率通常是115200。接线很简单:VCC接3.3V,GND共地,TX接STM32的RX,RX接STM32的TX,CH_PD接3.3V使能。但这里有个经典问题——ESP8266的TX是3.3V电平,STM32的RX也是3.3V,可以直接连;但如果用5V单片机,必须加电平转换。
配置流程我整理成了一张表,方便对照操作:
| 步骤 | AT指令 | 预期返回 | 说明 |
|---|---|---|---|
| 1 | AT | OK | 测试模块是否响应 |
| 2 | AT+CWMODE=1 | OK | 设置为Station模式 |
| 3 | AT+CWJAP="SSID","密码" | WIFI CONNECTED / OK | 连接路由器 |
| 4 | AT+CIPMUX=0 | OK | 单连接模式 |
| 5 | AT+CIPSTART="TCP","IP",端口 | CONNECT / OK | 建立TCP连接 |
| 6 | AT+CIPSEND=长度 | > | 进入发送模式 |
| 7 | 数据内容 | SEND OK | 发送数据 |
踩过的坑:AT+CIPSTART之后如果返回"ERROR"或者"ALREADY CONNECTED",说明之前连接没断开,要先发AT+CIPCLOSE。还有AT+RESTORE恢复出厂设置时,模块会重启,这时候串口会输出一堆乱码,然后才返回"ready",如果程序里死等"OK"就会卡死。我的做法是恢复出厂后延时3秒再发AT测试,连续三次失败才报错。
注意:ESP8266的供电一定要足,建议单独用一个3.3V稳压芯片给它,不要和STM32共用一路LDO,否则WiFi发射时的电流冲击会让单片机工作不稳定。
2.3 DHT11与LCD1602的驱动细节
DHT11是单总线协议,时序要求比较严格。起始信号是主机拉低至少18ms,然后拉高20-40μs,接着释放总线。DHT11响应时会拉低80μs,再拉高80μs,然后开始传输40位数据。每一位数据以50μs低电平开始,高电平持续26-28μs表示"0",持续70μs表示"1"。
我用的是TIM4做微秒级延时,因为HAL_Delay只能到毫秒级,满足不了DHT11的时序。具体做法是配置TIM4预分频为71,计数频率1MHz,这样每个计数就是1μs。读取时先关中断,避免时序被打断,读完再开中断。实测下来,如果中断没关,WiFi模块的串口中断会插进来,导致数据校验失败。
LCD1602用4位数据模式,只占用4个IO口(DB4-DB7),加上RS、RW、E三个控制脚,一共7根线。初始化顺序不能乱:先延时15ms,写0x33,延时5ms,写0x32,然后写0x28(4位模式、2行显示、5x7点阵),写0x0C(开显示、关光标),写0x06(光标右移),最后写0x01清屏。这个顺序错一步屏幕就不亮或者显示乱码。
3. 软件架构与完整实现流程
3.1 基于标准库的工程模板搭建
在Keil4下建工程,我习惯用标准库而不是HAL库,原因是标准库代码量小、执行效率高、寄存器操作直观。新建工程的步骤:先建一个文件夹结构,里面分Start、Library、User、Hardware四个子目录。Start放启动文件startup_stm32f10x_md.s和系统文件;Library放STM32F10x_StdPeriph_Driver;User放main.c和stm32f10x_it.c;Hardware放自己写的电机、DHT11、LCD、ESP8266驱动。
关键配置项:在Options for Target里,C/C++选项卡的Define要加"USE_STDPERIPH_DRIVER,STM32F10X_MD",Include Paths要把四个目录都加进去。Debug选项卡选ST-Link Debugger,Settings里Flash Download要勾选"Reset and Run"。如果下载失败,检查BOOT0是不是接地了,还有ST-Link的SWDIO和SWCLK有没有接反。
主程序的状态机设计是这样的:系统上电后先初始化时钟、GPIO、定时器、串口、LCD、DHT11,然后进入主循环。主循环里每500ms读一次DHT11,每100ms检查一次避障传感器,每2秒通过ESP8266发一次数据。电机控制采用非阻塞方式,用定时器中断产生PWM,主循环只负责更新占空比目标值,避免用delay卡死整个系统。
3.2 电机PWM调速与避障逻辑实现
电机调速的核心是TIM3的PWM输出。配置步骤:使能TIM3和GPIOA时钟,配置PA6、PA7为复用推挽输出,TIM3预分频72-1,自动重装载1000-1,通道1和通道2设为PWM模式1,使能输出。占空比通过TIM_SetCompare1和TIM_SetCompare2设置,范围0-1000对应0-100%速度。
避障逻辑我用的是最简单的"触边转向"策略:四个红外模块分别检测左前、右前、左后、右后。如果左前和右前同时检测到障碍,说明正前方有墙,后退200ms然后右转300ms;如果只有左前检测到,说明左边有障碍,右转150ms;如果只有右前检测到,左转150ms。这个策略虽然笨,但在实验室走廊这种结构化环境里够用了。
实操心得:红外避障模块的检测距离受环境光影响很大,黑色地面检测距离会缩短到2-3cm,白色地面能到15cm。调试时要用实际地面校准电位器,别在桌面上调好了直接拿到地上跑。
3.3 ESP8266数据回传与上位机对接
STM32通过USART2和ESP8266通信,波特率115200。发送数据的格式我定义成JSON字符串:{"temp":25,"humi":60,"status":"normal"}。为什么用JSON?因为上位机解析方便,Python的json库一行代码就能转成字典,而且可读性好,调试时直接看串口输出就知道数据对不对。
发送流程:先发AT+CIPSEND=长度,等模块返回">",然后把JSON字符串发出去,等" SEND OK"。如果10秒内没收到"SEND OK",就重发一次,连续三次失败就重启ESP8266(发AT+RST)。这里要注意,AT+CIPSEND的长度是字节数,不是字符数,中文字符要算3个字节,不过我们的JSON里全是英文和数字,一个字符一个字节,不用纠结。
上位机我用Python写了一个简单的TCP服务器,监听8080端口,收到数据后解析JSON并打印到控制台,同时存到CSV文件里。如果要做得更完整,可以加个Flask网页实时显示曲线,或者接入MQTT服务器实现远程监控。ESP8266接OneNet这类物联网平台也是类似思路,把TCP换成MQTT协议,用AT+MQTT指令集就行。
4. 调试过程中的典型问题与排查实录
4.1 单片机下载失败与复位异常
下载失败是新手遇到最多的问题。现象是Keil里点Download,进度条走到一半报"Flash Download failed"。排查顺序:第一,检查ST-Link驱动装没装,设备管理器里有没有识别到;第二,检查SWDIO和SWCLK接线,这两根线不能太长,超过15cm就容易通信失败;第三,检查BOOT0跳线,下载时必须接地;第四,如果之前程序里把SWD引脚复用成了普通GPIO,需要按住复位键再点下载,松开复位后程序才能进下载模式。
复位异常更隐蔽。现象是电机一启动,LCD就花屏或者单片机重启。原因基本是电源问题。我的解决方案是:电机电源和主控电源完全分开,用两路独立的稳压;在STM32的VDD和VSS之间并一个100nF陶瓷电容加一个10μF钽电容;ESP8266的VCC和GND之间并一个470μF电解电容。改完之后连续跑了两小时没再复位。
4.2 ESP8266连接不稳定的排查思路
WiFi连接不稳定表现为:有时候能连上路由器,有时候连不上;连上之后隔几分钟就掉线。排查步骤:先发AT+CWJAP?查询当前连接状态,如果返回"No AP"说明没连上,检查SSID和密码有没有特殊字符;如果返回IP地址但很快掉线,检查路由器是不是开了AP隔离或者MAC过滤;如果模块发热严重,检查供电电压是不是低于3.0V。
我遇到过一次特别诡异的情况:模块能连上路由器,但TCP连接总是失败。后来发现是路由器分配的IP和模块之前静态配置的IP冲突了。解决办法是发AT+CIPSTA="0.0.0.0"清除静态IP,改用DHCP自动获取。还有一次是AT+CIPSTART返回"DNS Fail",原因是用了域名而不是IP,而模块的DNS服务器没配置,发AT+CIPDNS_CUR="114.114.114.114"手动指定DNS就好了。
4.3 传感器数据跳变与滤波处理
DHT11的数据偶尔会跳变,比如温度突然从25℃跳到80℃。这是单总线协议在干扰环境下误码导致的。硬件上可以在数据线加一个4.7kΩ上拉电阻,线长控制在20cm以内。软件上加滤波:连续读三次,取中间值;如果三次差值超过5℃,直接丢弃本次数据,沿用上一次的有效值。
LCD1602显示乱码通常是初始化时序问题。如果屏幕亮但全是方块,检查初始化顺序有没有写错;如果屏幕亮但只显示一行,检查0x28指令有没有发成功;如果屏幕完全不亮,调对比度电位器,或者检查背光引脚有没有接限流电阻。我习惯在初始化后加一个1秒延时再显示内容,给LCD内部电路稳定时间。
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 下载失败 | BOOT0未接地 / SWD线太长 | 接地BOOT0,缩短SWD线 |
| 电机启动复位 | 电源共地不共电 / 滤波不足 | 分开供电,加滤波电容 |
| WiFi频繁掉线 | 供电不足 / IP冲突 | 独立3.3V供电,改用DHCP |
| DHT11数据跳变 | 干扰 / 上拉不足 | 加4.7k上拉,软件滤波 |
| LCD显示乱码 | 初始化时序错误 | 严格按顺序写指令 |
5. 项目扩展方向与个人实操体会
5.1 从巡检小车到环境监控节点的升级路径
这台小车目前只能跑固定路线,如果要升级成真正的自主巡检系统,有几个方向可以走。第一是加超声波模块做测距,配合舵机扫描,实现简单的避障建图;第二是加MPU6050陀螺仪做航向角积分,让小车能走直线而不是跑偏;第三是把数据上传到云端,用MQTT协议接入物联网平台,实现手机远程查看和历史数据回溯。
如果实验室面积大,还可以考虑多台小车组网。每台小车负责一个区域,通过WiFi把数据汇总到一台树莓派或者旧笔记本上做集中处理。这时候STM32的负担就轻了,只负责底层运动控制和传感器采集,复杂逻辑交给上位机。这种架构在工业巡检机器人里很常见,叫"边缘采集+集中决策"。
5.2 我在这个项目里踩过的几个坑
第一个坑是贪便宜买了劣质18650电池,标称3000mAh实际只有800mAh,小车跑十分钟就没电。后来换了正规品牌的动力电池,续航直接翻倍。第二个坑是L298N的散热片没装,连续跑二十分钟后芯片烫到手不能碰,PWM输出开始不稳定。加了个小散热片和风扇之后问题解决。第三个坑是ESP8266的固件版本太老,不支持AT+CIPDNS_CUR指令,升级到最新AT固件后才正常。
还有一个经验:调试阶段一定要把关键日志打到LCD上,不要只依赖串口。因为小车跑起来之后串口线拖着很不方便,LCD能实时显示当前状态、WiFi连接情况、传感器数值,一眼就能看出问题在哪。等调试稳定了再把LCD去掉,能省点电。
5.3 给后来者的几点实用建议
如果你准备动手做这个项目,我的建议是分阶段验证。第一阶段只调通STM32最小系统,点个灯、串口打印"Hello";第二阶段加上电机驱动,让轮子转起来;第三阶段加传感器,把数据读到LCD上;第四阶段加WiFi,把数据发到电脑;最后才把所有功能整合到一起。不要一上来就全部焊好写代码,出了问题根本不知道是哪部分的锅。
元器件采购方面,STM32最小系统板建议买带ST-Link接口的版本,省得自己接SWD线;ESP8266买ESP-01S,体积小、引脚少、够用;L298N买带光耦隔离的版本,能减少电机对主控的干扰。焊接的时候,电机线用粗一点的,信号线用细的,电源线走短线,减少压降。最后,代码一定要用版本管理,我用的Git,每次改完能回退,比复制粘贴文件夹靠谱多了。
这个项目从画原理图到跑通全部功能,我大概花了三个周末。第一个周末搭硬件,第二个周末写驱动,第三个周末调通信和整合。如果你有51单片机基础,上手会更快;如果只学过Arduino,可能需要补一下寄存器操作和中断的概念。但总体来说,这是一个非常适合练手的嵌入式项目,涉及的知识点覆盖了GPIO、定时器、PWM、UART、单总线协议、状态机设计,做完之后对单片机的理解会上一个台阶。