这次我们来看一个功能相当全面的STM32智能小车项目。它集成了超声波避障、循迹清扫、舵机控制、清扫模块、车速控制、电压检测、WIFI控制和OLED显示,几乎囊括了嵌入式课程设计或电子竞赛中智能小车的主流功能。对于正在学习STM32、想从零搭建一个综合性实践平台的同学来说,这个项目提供了一个清晰的模块化实现思路。
项目的核心在于如何将多个传感器和执行器有机地整合在一块STM32微控制器上,并通过软件逻辑实现协同工作。它不是一个简单的遥控车,而是一个具备一定自主决策能力的“清扫机器人”原型。本文将带你拆解这个项目的各个模块,从硬件选型、电路连接到软件逻辑,并重点分析多任务调度、中断处理以及WIFI通信等关键技术的实现。无论你是想复现整个项目,还是借鉴其中某个模块(比如PWM控制舵机、超声波测距算法或ESP8266的AT指令控制),都能从中找到可参考的代码和设计思路。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 主控芯片 | STM32系列微控制器(常见如STM32F103C8T6) |
| 核心功能 | 自动避障、自动循迹、远程WIFI控制、实时状态显示 |
| 感知模块 | 超声波模块(HC-SR04)、红外循迹传感器(TCRT5000) |
| 执行模块 | 直流电机(带驱动模块)、舵机、清扫毛刷电机 |
| 人机交互 | OLED显示屏(I2C/SPI)、WIFI模块(ESP8266) |
| 电源管理 | 电压检测电路(ADC采样)、电机驱动独立供电 |
| 开发环境 | Keil MDK / STM32CubeIDE, 标准库或HAL库 |
| 适合场景 | STM32综合学习、嵌入式课程设计、电子竞赛、机器人原型开发 |
2. 适用场景与使用边界
这个项目非常适合以下几类开发者:
- STM32中级学习者:已经掌握GPIO、定时器、中断、ADC、PWM、串口等基础外设,希望通过一个综合项目提升系统设计能力。
- 嵌入式课程设计/毕业设计学生:项目功能明确、模块清晰、演示效果好,具备较高的完成度和创新性。
- 电子竞赛参与者:可作为智能搬运、循迹避障等赛题的基础平台,进行功能扩展。
- 机器人爱好者:希望了解如何将传感器数据融合,实现简单的自主行为。
使用边界与注意事项:
- 非商用产品级:这是一个教学演示级项目,在稳定性、鲁棒性、安全性方面未经过严格测试,不能直接用于商业或家庭服务机器人。
- 环境适应性有限:红外循迹对光线敏感,超声波在复杂表面可能测距不准,实际应用需根据环境优化算法或更换传感器(如激光雷达)。
- WIFI控制距离与延迟:依赖于本地路由器或手机热点,控制距离和实时性受网络环境影响。
- 电气安全:电机属于感性负载,启停时会产生反电动势,务必做好电源隔离和滤波,避免干扰单片机导致复位。
3. 环境准备与前置条件
在开始代码编写和硬件连接前,需要准备好以下软硬件环境。
硬件清单:
- 主控板:STM32最小系统板(如STM32F103C8T6核心板),需引出足够多的IO口。
- 电机驱动:L298N或TB6612FNG电机驱动模块,用于驱动两个直流减速电机(实现小车移动)。
- 感知传感器:
- 超声波模块:HC-SR04,用于前方障碍物检测。
- 循迹传感器:建议五路TCRT5000红外模块,检测地面黑线。
- 执行器:
- 舵机:SG90或MG90S,用于控制超声波模块的旋转扫描或清扫头角度。
- 清扫模块:一个小型直流电机带动毛刷。
- 通信与显示:
- WIFI模块:ESP8266(如ESP-01S),通过串口与STM32通信。
- OLED显示屏:0.96寸或1.3寸 I2C/SPI接口,用于显示速度、电压、模式等。
- 电源:
- 7.4V或12V锂电池组,为电机驱动供电。
- 5V/3.3V降压模块,为单片机、传感器、WIFI模块供电。
- 电压检测电路:简单的电阻分压电路连接到STM32的ADC引脚。
- 车体:四轮或两轮差速小车底盘。
软件环境:
- 集成开发环境(IDE):Keil uVision5(MDK-ARM)或 STM32CubeIDE。
- 单片机固件库:STM32标准外设库(StdPeriph Lib)或HAL库。推荐使用HAL库,其可移植性更好,STM32CubeMX配置方便。
- 串口调试助手:如XCOM、SSCOM,用于调试串口通信(与ESP8266、PC)。
- 网络调试助手:用于测试TCP/UDP通信,模拟手机APP与ESP8266的数据交互。
- STM32CubeMX(强烈推荐):用于图形化配置引脚、时钟、外设参数,并生成初始化代码框架。
4. 硬件连接与电路设计
硬件连接是项目的基础,错误的连接可能导致芯片损坏或功能异常。以下是关键模块的连接示意(以STM32F103C8T6常见引脚为例):
电机驱动(L298N)连接:
IN1->PA0(GPIO)IN2->PA1(GPIO)IN3->PA2(GPIO)IN4->PA3(GPIO)ENA->PA6(TIM3_CH1, PWM输出)ENB->PA7(TIM3_CH2, PWM输出)- 注意:电机驱动模块的电源
VCC接电池正极(如12V),GND接电池负极。STM32的GND必须与驱动模块的GND相连。驱动模块的输出OUT1/2、OUT3/4分别接左右电机。
超声波模块(HC-SR04)连接:
Trig->PB8(GPIO输出)Echo->PB9(GPIO输入,可配置为外部中断或输入捕获)
五路循迹传感器连接:
- 通常输出数字信号(检测到黑线输出低电平)。
- 传感器1
OUT->PB12(GPIO输入) - 传感器2
OUT->PB13(GPIO输入) - 传感器3
OUT->PB14(GPIO输入) - 传感器4
OUT->PB15(GPIO输入) - 传感器5
OUT->PA8(GPIO输入)
舵机(SG90)连接:
- 信号线
PWM->PA9(TIM1_CH2, PWM输出) - 注意:舵机供电需要稳定的5V,电流足够(可单独供电)。
WIFI模块(ESP8266-01S)连接:
TX->PA3(USART2_RX)RX->PA2(USART2_TX)VCC-> 3.3V (切勿接5V!)GND-> GNDCH_PD-> 3.3V
OLED(I2C)连接:
SCL->PB6(I2C1_SCL)SDA->PB7(I2C1_SDA)VCC-> 3.3VGND-> GND
电压检测电路:
- 使用两个电阻(如100k和10k)对电池电压进行分压,分压点连接至
PA0(ADC1_IN0)。计算关系:Vbat = ADC_Value * (3.3V/4096) * ((R1+R2)/R2)。
强烈建议:使用STM32CubeMX进行引脚配置,可以直观地避免引脚功能冲突,并自动生成初始化代码。
5. 软件架构与关键代码实现
项目的软件逻辑可以采用“前后台”或简单的实时操作系统(如FreeRTOS)思想。这里以前后台系统为例,在主循环中轮询执行不同任务。
5.1 主程序逻辑框架
int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); // 舵机PWM MX_TIM3_Init(); // 电机PWM MX_USART2_UART_Init(); // WIFI串口 MX_ADC1_Init(); // 电压检测 MX_I2C1_Init(); // OLED // 初始化外设驱动 OLED_Init(); Motor_Init(); Servo_Init(); Ultrasonic_Init(); Trace_Init(); ESP8266_Init(); // 设置WIFI模式、连接路由器 // 变量定义 uint32_t sys_tick = 0; uint8_t work_mode = MODE_AUTO; // 默认自动模式 float distance_cm = 0; uint16_t trace_value = 0; while (1) { // 任务1:系统心跳与状态更新(每100ms) if(HAL_GetTick() - sys_tick > 100) { sys_tick = HAL_GetTick(); Update_Voltage(); // 读取ADC更新电池电压 Update_OLED_Display(work_mode, distance_cm, Get_Voltage(), Get_Speed()); // 刷新OLED } // 任务2:读取传感器数据(每50ms) static uint32_t sensor_tick = 0; if(HAL_GetTick() - sensor_tick > 50) { sensor_tick = HAL_GetTick(); distance_cm = Get_Ultrasonic_Distance(); trace_value = Get_Trace_Value(); // 读取5路传感器状态,合并为一个值 } // 任务3:处理WIFI指令(非阻塞式) ESP8266_Receive_Process(); work_mode = Get_Current_Mode(); // 从WIFI解析或本地按键获取当前模式 // 任务4:核心决策与控制 switch(work_mode) { case MODE_AUTO: Auto_Mode_Process(distance_cm, trace_value); break; case MODE_WIFI: WIFI_Mode_Process(); // 执行从WIFI接收到的速度、方向指令 break; case MODE_CLEAN: Start_Clean_Motor(); // 同时可以结合自动避障或循迹 break; default: break; } // 其他任务... } }5.2 超声波测距驱动
超声波测距的关键在于精确测量高电平持续时间。推荐使用输入捕获功能。
// 使用TIM2的输入捕获功能测量Echo高电平时间 float Get_Ultrasonic_Distance(void) { static float distance = 0; HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); HAL_Delay(10); // 触发信号至少10us高电平,这里用延时简单实现 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待Echo上升沿,在输入捕获中断中记录时间t1 // 等待Echo下降沿,在输入捕获中断中记录时间t2 // 计算高电平时间: time_us = (t2 - t1) * TIM时钟周期 // 距离: distance = (time_us * 0.034) / 2; (单位:cm) // 简化版:使用普通GPIO和HAL_Delay(不精确,仅示意) uint32_t timeout = 100000; // 超时计数 while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_RESET) { if(--timeout == 0) return distance; // 超时返回上次值 } uint32_t start = HAL_GetTick(); while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_SET) { if(HAL_GetTick() - start > 50) break; // 超时50ms } uint32_t end = HAL_GetTick(); uint32_t pulseWidth = end - start; // 单位ms,实际应用需用us级定时器 distance = (pulseWidth * 0.034 * 1000) / 2; // 粗略计算 return distance; }5.3 电机PWM速度控制
通过调节PWM占空比控制电机速度。
// 设置左轮电机速度和方向 void Set_Motor_Left(int16_t speed) { // speed范围: -1000 ~ 1000 if(speed > 0) { HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed); // 设置PWM占空比 } else if(speed < 0) { HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -speed); } else { // 刹车或滑行 HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0); } }5.4 WIFI通信协议与解析
STM32通过串口以AT指令集控制ESP8266。通常让ESP8266工作在Station模式,连接手机热点或路由器,并创建TCP Server。
// ESP8266初始化序列 void ESP8266_Init(void) { UART_SendString("AT\r\n"); // 测试通讯 HAL_Delay(1000); UART_SendString("AT+CWMODE=1\r\n"); // 设置为Station模式 HAL_Delay(1000); UART_SendString("AT+CWJAP=\"Your_SSID\",\"Your_PASSWORD\"\r\n"); // 连接WiFi HAL_Delay(5000); UART_SendString("AT+CIPMUX=1\r\n"); // 开启多连接 HAL_Delay(1000); UART_SendString("AT+CIPSERVER=1,8080\r\n"); // 创建TCP服务器,端口8080 HAL_Delay(1000); } // 在串口中断回调函数中接收并解析数据 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { // WIFI串口 // 将接收到的字节存入缓冲区 wifi_rx_buffer[wifi_rx_index++] = wifi_rx_byte; // 判断是否收到一条完整指令(例如以'\n'结尾) if(wifi_rx_byte == '\n') { wifi_rx_buffer[wifi_rx_index] = '\0'; // 字符串结束符 Parse_WIFI_Command(wifi_rx_buffer); // 解析指令 wifi_rx_index = 0; // 重置缓冲区索引 } // 重新启动接收中断 HAL_UART_Receive_IT(&huart2, &wifi_rx_byte, 1); } } // 简单的指令解析示例 void Parse_WIFI_Command(char* cmd) { // 示例指令格式: “MODE:AUTO” 或 “SPD:500,500” 或 “DIR:F” if(strstr(cmd, "+IPD")) { // ESP8266收到数据的前缀 // 提取实际数据部分,例如":MODE:AUTO" char* data = strchr(cmd, ':'); if(data != NULL) { data++; // 跳过冒号 if(strncmp(data, "MODE:AUTO", 9) == 0) { current_mode = MODE_AUTO; } else if(strncmp(data, "SPD:", 4) == 0) { int left_spd, right_spd; sscanf(data, "SPD:%d,%d", &left_spd, &right_spd); Set_Motor_Left(left_spd); Set_Motor_Right(right_spd); } // ... 解析其他指令 } } }6. 功能测试与效果验证
完成硬件连接和软件编写后,需要分模块进行测试,最后进行系统联调。
6.1 电机与舵机基础测试
目的:验证PWM输出和电机驱动逻辑是否正确。步骤:
- 编写一个测试函数,让左右电机以不同速度正转、反转、停止。
- 观察小车是否按预期移动,有无异常震动或单边不动。
- 测试舵机,让其旋转到0度、90度、180度,观察实际角度是否准确。预期结果:小车能平稳前进、后退、左转、右转。舵机能精确转动到指定角度。失败排查:检查PWM定时器配置(频率、占空比)、电机驱动模块接线(输入逻辑、使能)、电源电压是否足够。
6.2 超声波避障测试
目的:验证测距准确性及避障决策逻辑。步骤:
- 在OLED上实时显示超声波测得的距离。
- 用手或书本在车前不同距离移动,观察显示值是否变化且大致准确。
- 编写简单避障逻辑:当距离
< 20cm时停车,然后后退并转弯。 - 实际运行小车,测试其面对障碍物的反应。预期结果:距离显示稳定,小车能在设定距离前做出避障动作。失败排查:检查Trig和Echo引脚是否接反、供电是否稳定、代码中测量时间的逻辑是否正确(特别是单位换算)。
6.3 红外循迹测试
目的:验证传感器能正确识别黑白线,并实现巡线行走。步骤:
- 在OLED上以二进制或柱状图形式显示5路传感器的状态(0/1)。
- 制作一条黑色电工胶带轨迹。
- 将小车放在轨迹上,观察传感器状态变化。
- 实现PD(比例-微分)循迹算法:根据传感器偏离中心的位置计算左右轮速度差。
// 简单的PD循迹算法示例 int Calculate_PD(int sensor_value) { // sensor_value: 将5路传感器状态编码为一个值,例如 0b00100 表示中间传感器检测到黑线 int error = 0; // 根据编码计算误差,例如中间为0,左偏为负,右偏为正 // ... int output = Kp * error + Kd * (error - last_error); last_error = error; return output; }- 将输出叠加到电机基础速度上,测试小车循迹能力。预期结果:小车能沿着黑线平稳前进,在弯道处能平滑转弯。失败排查:调整传感器离地高度、优化阈值、调节PD参数(Kp, Kd)。
6.4 WIFI控制测试
目的:验证手机或电脑能通过网络控制小车。步骤:
- 确保ESP8266正确连接到手机热点。
- 使用网络调试助手,输入ESP8266的IP和端口(如
192.168.1.100:8080),建立TCP连接。 - 发送指令字符串,如
“SPD:600,600”。 - 观察小车是否按指令运动,OLED上模式是否切换。预期结果:能建立稳定连接,指令响应及时(网络延迟内)。失败排查:检查ESP8266的TX/RX是否与STM32交叉连接、供电是否为3.3V、AT指令序列是否全部成功、手机和模块是否在同一局域网。
6.5 电压检测与清扫模块测试
目的:实现电量监控和清扫功能。步骤:
- 用万用表测量电池实际电压,与OLED显示电压对比,校准ADC计算公式中的分压电阻比值。
- 编写低电压报警逻辑,当电压低于阈值时,让小车停止并闪烁LED或OLED提示。
- 测试清扫电机控制,确保能正常启停。预期结果:OLED显示电压值与万用表测量值误差在0.1V以内。清扫电机能正常工作。
7. 系统整合与多任务调度
当所有模块独立测试通过后,需要整合到一个协调的系统里。关键在于处理好不同任务的时间尺度:
- 高速任务:电机PWM控制、舵机控制(需要高频率的定时器中断或DMA)。
- 中速任务:超声波测距(约20Hz)、循迹采样(约50-100Hz)、WIFI数据解析。
- 低速任务:OLED刷新(10Hz)、电压检测(1Hz)、系统状态机更新。
整合建议:
- 使用定时器中断:为超声波、循迹、OLED刷新分配不同的定时器,在中断服务程序(ISR)中设置标志位,在主循环中查询并执行对应任务。避免在ISR中执行耗时操作。
- 状态机设计:将小车的自动模式(如“前进-检测-避障-循迹”)设计成状态机,使逻辑清晰,易于调试。
- 资源共享与互斥:如果使用RTOS(如FreeRTOS),可以为不同任务创建不同的线程和队列、信号量。例如,一个线程专用于传感器数据采集,一个线程用于决策,一个线程用于电机控制。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 上电后单片机无反应 | 电源接反、电压不对、复位电路问题、晶振不起振 | 检查电源电压(3.3V)、测量晶振引脚波形、按复位键 | 确保电源极性正确,更换晶振或负载电容 |
| 电机不转或单边转 | 电机驱动模块使能(ENA/ENB)未开启、PWM无输出、逻辑输入错误、电源功率不足 | 用万用表测驱动模块输入输出逻辑、用示波器或LED测PWM引脚、单独测试电机 | 检查代码中PWM和GPIO初始化、确保电池有电、电机驱动模块与单片机共地 |
| 超声波一直返回0或超大值 | Trig/Echo接线错误、Echo上拉电阻未接、测量超时 | 检查接线、给Echo脚加10k上拉电阻、调整测量超时时间 | 确保Trig发出10us以上脉冲,Echo脚配置为上拉输入 |
| 循迹传感器始终触发或不触发 | 传感器离地太高/太低、环境光干扰、阈值设置不当 | 调节传感器高度、用手遮挡测试、测量传感器输出端电压 | 优化安装位置、调整比较器电位器或代码中的数字阈值 |
| WIFI模块无法连接网络 | AT指令错误、SSID/密码错误、供电不足(3.3V需足额电流) | 用USB-TTL模块直接连接ESP8266,用串口助手手动发AT指令测试 | 确保发送的AT指令格式正确(\r\n结尾),使用电流足够的3.3V LDO供电 |
| OLED白屏或不显示 | I2C地址错误、接线错误、初始化序列不对 | 用逻辑分析仪抓取I2C波形、检查上拉电阻、尝试另一个I2C地址 | 确认OLED型号对应的I2C地址(通常0x78或0x7A),检查SCL/SDA是否接反 |
| 程序运行一段时间后死机 | 堆栈溢出、中断冲突、电源波动、看门狗未喂 | 检查中断优先级、是否有数组越界、用万用表监测电源电压 | 优化代码结构,启用独立看门狗(IWDG),在电源输入端加大的滤波电容 |
9. 优化与扩展方向
完成基础功能后,可以考虑以下优化,让项目更完善:
算法优化:
- 避障算法:引入“势场法”或“动态窗口法”,使避障路径更平滑。
- 循迹算法:使用更先进的PID或模糊控制,提高过急弯的稳定性。
- 数据滤波:对超声波和电压ADC采样值进行滑动平均或卡尔曼滤波,减少噪声。
功能扩展:
- 手机APP:开发一个简单的Android APP,通过Socket发送控制指令,并接收小车传回的视频流(需增加摄像头模块)。
- 路径规划:结合WIFI定位或视觉里程计,实现简单的定点导航。
- 语音控制:增加语音识别模块(如LD3320),实现语音指令控制。
- 环境监测:增加温湿度、空气质量传感器,让小车成为移动监测平台。
工程化改进:
- 模块化编程:将每个硬件驱动(Motor.c, Ultrasonic.c)和业务逻辑(Auto.c, WIFI_Ctrl.c)彻底分离,方便复用和调试。
- 参数可配置:将PID参数、避障距离、速度等宏定义改为可通过WIFI或串口在线调整的变量。
- 增加日志系统:通过串口打印详细的运行状态和错误信息,方便远程调试。
这个STM32智能小车项目就像一块功能丰富的“试验田”,几乎涵盖了嵌入式开发的所有基础知识点。从最开始的点灯、按键,到后来的定时器、PWM、ADC、中断,再到综合性的多传感器数据融合和通信,每一步都对应着实际能力的提升。建议在开发时,务必遵循“分模块调试、逐步集成”的原则,先让每个部分单独跑起来,再思考它们如何协同工作。当小车最终能按照你的指令,或自主地在地上跑起来时,那种成就感是看再多教程都无法比拟的。