1. 项目概述:STM32F103智能小车的核心设计
去年在指导大学生电子设计竞赛时,我带着学生用STM32F103C8T6开发了一款智能循迹避障小车。这个看似简单的项目实际上包含了嵌入式开发的多个关键技术点:GPIO控制、传感器数据采集、实时决策逻辑等。下面我就把这个项目的完整实现过程拆解开来,分享给想要入门STM32开发的同行们。
这款智能小车的核心功能分为两大部分:
- 循迹功能:通过三个红外传感器组成的阵列检测地面黑色轨迹线,实现自动巡线行驶
- 避障功能:利用超声波模块实时检测前方障碍物距离,当距离小于20cm时自动执行避障动作
选择STM32F103C8T6作为主控是因为它性价比极高(市场价约10元),具有丰富的外设资源,完全能满足这个项目的需求。整个系统采用双电源设计:12V锂电池直接给电机驱动供电,通过LM1117稳压到5V给控制器和传感器供电。这种设计既保证了电机动力,又确保了控制系统的稳定性。
提示:对于初次接触STM32的开发者,建议先单独测试每个功能模块(如单独测试电机转动、单独测试红外传感器等),确认无误后再进行系统集成,这样可以大幅降低调试难度。
2. 硬件设计与关键器件选型
2.1 核心器件清单与选型考量
在多次项目迭代后,我总结出以下最优硬件配置方案:
| 器件名称 | 型号/参数 | 数量 | 选型理由 |
|---|---|---|---|
| STM32最小系统板 | F103C8T6 | 1 | 性价比高,48引脚完全够用,内置硬件PWM可用于后续电机调速 |
| 电机驱动模块 | L298N | 1 | 双H桥设计,最大驱动电流2A,支持PWM调速,带散热片 |
| 直流减速电机 | TT马达 | 4 | 6V/200RPM,自带减速箱,扭矩足够推动小车 |
| 红外循迹模块 | TCRT5000 | 3 | 数字输出(高低电平),检测距离可调,响应速度快 |
| 超声波模块 | HC-SR04 | 1 | 2cm-400cm检测范围,精度±3mm,性价比高 |
| 锂电池 | 18650两节串联 | 1组 | 提供12V电压,容量2000mAh以上,带充放电保护板 |
| 稳压模块 | LM1117-5.0 | 1 | 将12V降压到5V,最大输出电流800mA,需加散热片 |
在实际采购时要注意几个关键点:
- L298N模块要选带光耦隔离的版本,避免电机干扰MCU
- 红外传感器最好选择带电位器调节灵敏度的型号
- 18650电池一定要选带保护板的,防止过放损坏
2.2 电路设计与接线规范
电源系统设计
电源部分是项目中最容易出问题的地方。我们的设计方案是:
code复制12V锂电池 → L298N(电机驱动)
↓
LM1117 → 5V → STM32
→ 传感器
这种设计有三个优点:
- 电机大电流不会影响控制电路稳定性
- 5V稳压模块带短路保护,避免传感器短路导致系统崩溃
- 方便单独测量各模块电流
关键接口定义
STM32的引脚分配需要特别注意外设冲突问题。经过多次优化,最终确定的引脚分配如下:
| STM32引脚 | 功能 | 备注 |
|---|---|---|
| PA0-PA2 | 红外传感器输入 | 配置为上拉输入 |
| PA3-PA4 | 超声波Trig/Echo | PA3推挽输出,PA4浮空输入 |
| PB0-PB3 | L298N控制信号 | 推挽输出,后续可改为PWM输出 |
| PB6-PB7 | 预留I2C接口 | 用于扩展OLED显示 |
| PA9-PA10 | 预留USART | 用于蓝牙模块通信 |
注意:PA0-PA2必须启用内部上拉电阻,因为红外传感器输出是开漏结构。我曾遇到过因未启用上拉导致传感器误检测的问题。
3. 软件开发环境搭建与工程架构
3.1 Keil MDK开发环境配置
对于STM32F103开发,我推荐使用Keil MDK-ARM V5.38版本,这个版本对Cortex-M3内核的支持最稳定。安装时需要注意:
- 安装完成后务必注册(社区版有32KB代码限制)
- 通过Pack Installer安装STM32F1xx_DFP最新器件支持包
- 在Options for Target中勾选"Use MicroLIB",这个精简版C库可以显著减少代码体积
一个常见的坑是忘记安装ST-Link驱动。建议使用官方的ST-Link Utility工具自动安装驱动,否则Keil可能无法识别下载器。
3.2 工程目录结构设计
良好的工程结构能大幅提高开发效率。我们的项目采用模块化设计:
code复制STM32_Car_Project/
├── Core/
│ ├── Inc/ // 头文件目录
│ │ ├── main.h // 全局定义
│ │ ├── motor.h // 电机驱动模块
│ │ ├── track.h // 循迹模块
│ │ └── ultrasonic.h // 超声波模块
│ └── Src/
│ ├── main.c // 主程序
│ ├── motor.c // 电机驱动实现
│ ├── track.c // 循迹算法
│ └── ultrasonic.c // 测距实现
├── Drivers/
│ └── STM32F1xx_HAL_Driver // HAL库文件
└── MDK-ARM/
└── STM32_Car_Project.uvprojx // Keil工程文件
这种结构的好处是:
- 功能模块相互独立,便于单独测试
- 头文件和源文件分离,提高可读性
- 第三方驱动库与业务代码隔离,方便升级维护
4. 核心代码实现与关键技术解析
4.1 电机驱动模块实现
电机控制是项目的基础,我们采用L298N的GPIO控制模式(非PWM)。关键点在于电机转向的真值表:
| IN1 | IN2 | 电机A状态 |
|---|---|---|
| 0 | 0 | 停止 |
| 1 | 0 | 正转 |
| 0 | 1 | 反转 |
| 1 | 1 | 刹车 |
对应的驱动代码实现:
c复制void Motor_Control(Motor_State state)
{
switch(state) {
case MOTOR_FORWARD: // 前进
HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
break;
case MOTOR_LEFT: // 左转
HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, GPIO_PIN_RESET);
break;
// 其他状态类似...
}
}
经验:在实际测试中发现,电机从正转切换到反转时应该加入10ms的延时,否则容易导致L298N桥臂短路。这是很多初学者容易忽略的问题。
4.2 红外循迹算法优化
基础的红外循迹逻辑很简单:
- 中间传感器检测到黑线:直行
- 左边传感器检测到:左转
- 右边传感器检测到:右转
但实际测试发现这种简单逻辑会导致小车"蛇形走位"。我们改进为状态机算法:
c复制Track_State last_state = TRACK_NONE;
void Track_Adjust(void)
{
Track_State current = Track_Get_State();
if(current == TRACK_MID) {
Motor_Control(MOTOR_FORWARD);
}
else if(current == TRACK_LEFT && last_state != TRACK_LEFT) {
Motor_Control(MOTOR_LEFT);
HAL_Delay(150); // 转向时间根据车速调整
last_state = TRACK_LEFT;
}
// 其他状态处理...
// 无黑线时保持上次动作
if(current == TRACK_NONE) {
return;
}
}
这种算法通过记录上一次状态,避免了频繁转向,使行驶轨迹更加平滑。
4.3 超声波测距的精准实现
HC-SR04模块的测距原理是:
- 给Trig引脚10us高电平触发测距
- 模块自动发送8个40kHz超声波脉冲
- 检测Echo引脚高电平持续时间t
- 距离 = (t * 声速)/2
关键实现代码如下:
c复制float Ultrasonic_Get_Distance(void)
{
// 发送10us触发信号
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);
Delay_us(10);
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);
// 等待回响信号
while(!HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN));
// 计算高电平持续时间
uint32_t start = HAL_GetTick();
while(HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN)) {
if(HAL_GetTick() - start > 30) // 超时保护
return 999.0f;
}
uint32_t duration = HAL_GetTick() - start;
return (duration * 0.017f); // 0.017=340/2/10000
}
避坑指南:超声波模块对电源噪声敏感,建议在VCC和GND之间加100uF电容。实测发现这样可以将测距波动从±5cm降低到±1cm。
5. 系统调试与性能优化
5.1 硬件组装注意事项
组装时需要特别注意以下几点:
- 红外传感器安装高度距离地面1.5cm最佳,太高会降低灵敏度,太低容易刮擦地面
- 超声波模块应朝前上方倾斜约15度,避免地面反射干扰
- 电机与轮子连接要用螺丝固定,热熔胶在长时间运行后会松动
- 所有线缆要用扎带固定,避免缠绕到车轮
5.2 常见问题排查手册
根据多次调试经验,我总结了以下常见问题及解决方法:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 电机单边转动 | L298N某一路桥臂损坏 | 更换L298N模块 |
| 循迹时频繁抖动 | 传感器响应延迟过大 | 降低车速或增加转向延时 |
| 超声波一直返回0 | Echo引脚未接 | 检查接线 |
| 系统随机复位 | 电机电流过大导致电压跌落 | 在12V电源端并联大电容(1000uF以上) |
| 红外传感器始终触发 | 环境光干扰 | 调整电位器增大阈值或增加遮光罩 |
5.3 性能优化技巧
-
电源优化:
- 在LM1117输入输出端各并联100uF和0.1uF电容
- 电机电源线要足够粗(建议18AWG以上)
-
软件优化:
c复制// 在主循环中加入看门狗喂狗 while(1) { HAL_IWDG_Refresh(&hiwdg); // ...其他代码 } -
机械优化:
- 在车轮内侧加装轴承减少摩擦
- 使用硅胶轮胎增加抓地力
6. 功能扩展与进阶改进
基础功能实现后,可以考虑以下扩展方向:
6.1 PWM电机调速
将PB0-PB3改为TIM3的PWM输出通道:
c复制// 在motor.c中增加
void Motor_PWM_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 1MHz计数频率
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 99; // 10kHz PWM
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 50; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
// 其他通道类似...
}
6.2 蓝牙遥控功能
添加HC-05蓝牙模块:
- 连接USART1(PA9/PA10)
- 使用串口中断接收手机APP指令
- 定义控制协议如:
- 'F':前进
- 'B':后退
- 'L':左转
- 'R':右转
6.3 多传感器融合算法
进阶方案可以结合:
- MPU6050获取姿态数据
- 编码器测量实际车速
- 红外+超声波多传感器数据融合
实现更复杂的运动控制算法。
在项目开发过程中,最深的体会是:嵌入式开发必须硬件和软件协同调试。有时候软件逻辑看似正确,但硬件的一个小问题(如接触不良、电源噪声)就会导致整个系统异常。建议每完成一个功能模块就进行实测验证,不要等到全部代码写完才开始调试。