1. 项目概述
作为一名嵌入式开发工程师,我最近完成了一个基于STM32F103的智能小车项目。这个小车不仅可以通过手机APP远程控制,还具备超声波避障和红外循迹功能。这个项目非常适合想要学习嵌入式系统开发的初学者,也适合有一定基础的开发者作为进阶练习。
1.1 功能需求分析
这个智能小车项目主要实现了三大核心功能:
首先是远程控制功能。我们使用HC-05蓝牙模块实现了与手机的通信,通过自定义的Android APP可以发送控制指令,实现小车的前进、后退、左转、右转等基本运动控制。
其次是超声波避障功能。采用HC-SR04超声波模块实时检测前方障碍物距离,当距离小于安全阈值时,小车会自动转向避开障碍物。这个功能让小车具备了基本的自主导航能力。
最后是红外循迹功能。我们使用三个TCRT5000红外传感器组成阵列,可以识别地面上的黑色轨迹线,使小车能够沿着预定路径自动行驶。这个功能在很多自动化场景中都非常实用。
1.2 系统架构设计
整个系统的硬件架构以STM32F103C8T6微控制器为核心,这款芯片属于STM32F1系列,具有72MHz的主频和丰富的外设资源,非常适合这类嵌入式控制应用。
电机驱动部分选用L298N模块,它可以驱动两个直流电机,我们用它来控制小车的四个TT马达。传感器方面,HC-SR04负责距离检测,三个TCRT5000组成红外循迹阵列,HC-05实现蓝牙通信。系统架构简洁明了,各模块分工明确。
提示:在选择STM32型号时,F103C8T6是最具性价比的选择,它有足够的GPIO和定时器资源来满足本项目需求,而且价格便宜,开发资料丰富。
2. 硬件准备
2.1 核心部件清单
制作这个智能小车需要准备以下硬件组件:
| 部件 | 型号 | 数量 | 备注 |
|---|---|---|---|
| 主控芯片 | STM32F103C8T6 | 1 | 最小系统板即可 |
| 电机驱动模块 | L298N | 1 | 可驱动两个直流电机 |
| 直流电机 | TT马达 | 4 | 带减速齿轮和车轮 |
| 超声波模块 | HC-SR04 | 1 | 测距范围2-400cm |
| 红外传感器 | TCRT5000 | 3 | 用于循迹 |
| 蓝牙模块 | HC-05 | 1 | 主从一体蓝牙模块 |
| 电源 | 18650电池 | 2 | 建议使用电池盒 |
2.2 电路连接指南
硬件连接是整个项目的基础,正确的接线可以避免很多调试时的麻烦。下面是各模块与STM32的连接方式:
超声波模块连接:
- TRIG → PA1
- ECHO → PA0
红外循迹传感器连接:
- 左传感器 → PC13
- 中传感器 → PC14
- 右传感器 → PC15
电机驱动连接:
- IN1 → PB6
- IN2 → PB7
- IN3 → PB8
- IN4 → PB9
蓝牙模块连接:
- TX → PA10 (USART1_RX)
- RX → PA9 (USART1_TX)
注意:L298N模块需要单独供电,不能直接从STM32取电。建议使用7-12V的电源为电机驱动供电,同时通过稳压模块为STM32提供5V或3.3V电源。
3. 开发环境搭建
3.1 Keil MDK安装步骤
Keil MDK是STM32开发的主流IDE之一,安装步骤如下:
- 从Keil官网下载MDK-ARM安装包,当前最新版本是uVision5
- 安装过程中选择"Full Version",并勾选所有组件
- 安装完成后,需要单独安装STM32F1的设备支持包
- 新建工程时,选择Device为STM32F103C8
- 在工程选项中配置:
- Target → 勾选"Use MicroLIB"
- Output → 勾选"Create HEX File"
- Debug → 选择ST-Link Debugger
3.2 ST-Link调试配置
ST-Link是ST官方推出的调试工具,配置方法如下:
c复制void Debug_Init(void) {
// 启用AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 禁用JTAG,释放PB3/PB4引脚
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
}
这段代码的作用是配置调试接口,释放被JTAG占用的GPIO引脚。在实际项目中,我们经常需要这些引脚作为普通IO使用。
经验分享:如果使用ST-Link下载程序时遇到问题,可以尝试更新ST-Link的固件。另外,Keil中Debug选项卡下的"Reset and Run"选项非常实用,可以避免每次下载后手动复位。
4. 软件架构设计
4.1 程序模块划分
为了实现功能的模块化和可维护性,我将软件分为以下几个主要模块:
- 系统初始化:配置时钟、GPIO、中断等基础设置
- 外设初始化:初始化USART、定时器等外设
- 任务创建:基于FreeRTOS创建多个并行任务
- 超声波测距任务:周期性测量前方障碍物距离
- 红外循迹任务:实时检测地面轨迹并做出决策
- 蓝牙指令解析:处理来自手机APP的控制命令
- 电机控制任务:根据指令控制电机运动
这种基于RTOS的设计使得各个功能模块可以独立开发和测试,提高了代码的可维护性和可扩展性。
5. 核心代码实现
5.1 电机驱动控制 (motor.c)
电机控制是小车运动的基础,下面是关键代码实现:
c复制#include "stm32f10x.h"
// 定义电机控制引脚
#define IN1_PIN GPIO_Pin_6
#define IN2_PIN GPIO_Pin_7
#define IN3_PIN GPIO_Pin_8
#define IN4_PIN GPIO_Pin_9
#define MOTOR_GPIO GPIOB
// 电机初始化函数
void Motor_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启用GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置电机控制引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MOTOR_GPIO, &GPIO_InitStructure);
}
// 小车前进
void Car_Forward(void) {
GPIO_SetBits(MOTOR_GPIO, IN1_PIN | IN3_PIN);
GPIO_ResetBits(MOTOR_GPIO, IN2_PIN | IN4_PIN);
}
// 小车后退
void Car_Backward(void) {
GPIO_SetBits(MOTOR_GPIO, IN2_PIN | IN4_PIN);
GPIO_ResetBits(MOTOR_GPIO, IN1_PIN | IN3_PIN);
}
// 小车左转
void Car_TurnLeft(void) {
GPIO_SetBits(MOTOR_GPIO, IN3_PIN);
GPIO_ResetBits(MOTOR_GPIO, IN1_PIN | IN2_PIN | IN4_PIN);
}
// 小车右转
void Car_TurnRight(void) {
GPIO_SetBits(MOTOR_GPIO, IN1_PIN);
GPIO_ResetBits(MOTOR_GPIO, IN2_PIN | IN3_PIN | IN4_PIN);
}
// 小车停止
void Car_Stop(void) {
GPIO_ResetBits(MOTOR_GPIO, IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN);
}
注意:L298N的使能端(ENA/ENB)需要接PWM信号才能实现调速功能。如果不需要调速,可以直接接高电平。
5.2 超声波避障算法 (ultrasonic.c)
超声波测距是实现避障功能的关键,下面是具体实现:
c复制#include "stm32f10x.h"
#include "delay.h"
// 定义超声波引脚
#define TRIG_PIN GPIO_Pin_1
#define ECHO_PIN GPIO_Pin_0
#define US_GPIO GPIOA
// 超声波初始化
void Ultrasonic_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启用GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// TRIG引脚配置为推挽输出
GPIO_InitStructure.GPIO_Pin = TRIG_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(US_GPIO, &GPIO_InitStructure);
// ECHO引脚配置为下拉输入
GPIO_InitStructure.GPIO_Pin = ECHO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(US_GPIO, &GPIO_InitStructure);
}
// 获取距离(厘米)
float Get_Distance(void) {
uint32_t start_time = 0, end_time = 0;
// 发送10us高电平触发信号
GPIO_SetBits(US_GPIO, TRIG_PIN);
Delay_us(10);
GPIO_ResetBits(US_GPIO, TRIG_PIN);
// 等待回波高电平开始
while(GPIO_ReadInputDataBit(US_GPIO, ECHO_PIN) == 0);
start_time = SysTick->VAL;
// 等待回波高电平结束
while(GPIO_ReadInputDataBit(US_GPIO, ECHO_PIN) == 1);
end_time = SysTick->VAL;
// 计算距离(声速340m/s)
// 注意:SysTick是递减计数器
float time = (start_time - end_time) * 1.0 / SystemCoreClock;
return (time * 34000) / 2; // 除以2是因为声波往返
}
经验分享:超声波测距容易受到环境干扰,建议在代码中加入滤波算法。我采用的是滑动平均滤波,连续采样5次去掉最大最小值后取平均,效果很好。
5.3 红外循迹逻辑 (infrared.c)
红外循迹功能的实现主要依赖于三个红外传感器的状态判断:
c复制#include "stm32f10x.h"
// 定义红外传感器引脚
#define IR_LEFT_PIN GPIO_Pin_13
#define IR_MID_PIN GPIO_Pin_14
#define IR_RIGHT_PIN GPIO_Pin_15
#define IR_GPIO GPIOC
// 红外传感器初始化
void Infrared_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启用GPIOC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// 配置红外传感器引脚为上拉输入
GPIO_InitStructure.GPIO_Pin = IR_LEFT_PIN | IR_MID_PIN | IR_RIGHT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(IR_GPIO, &GPIO_InitStructure);
}
// 循迹决策函数
uint8_t Track_Decision(void) {
uint8_t left = GPIO_ReadInputDataBit(IR_GPIO, IR_LEFT_PIN);
uint8_t mid = GPIO_ReadInputDataBit(IR_GPIO, IR_MID_PIN);
uint8_t right = GPIO_ReadInputDataBit(IR_GPIO, IR_RIGHT_PIN);
// 中间传感器检测到黑线
if(mid == 0 && left == 1 && right == 1)
return 1; // 直行
// 左侧传感器检测到黑线
else if(left == 0)
return 2; // 左转
// 右侧传感器检测到黑线
else if(right == 0)
return 3; // 右转
// 未检测到黑线
else
return 0; // 停止
}
提示:TCRT5000的红外发射管和接收管之间的距离影响检测效果。实际调试时,建议将传感器安装在距地面1-1.5cm的高度,这个距离对黑色轨迹线的检测最为灵敏。
5.4 蓝牙远程通信 (bluetooth.c)
蓝牙通信实现了手机APP对小车的远程控制:
c复制#include "stm32f10x.h"
#include "usart.h"
#define BT_USART USART1
// 蓝牙模块初始化
void Bluetooth_Init(uint32_t baudrate) {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 启用USART1和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA9为复用推挽输出(TX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PA10为浮空输入(RX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART参数
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(BT_USART, &USART_InitStructure);
// 启用USART
USART_Cmd(BT_USART, ENABLE);
}
// 发送字符串
void BT_SendString(char *str) {
while(*str) {
USART_SendData(BT_USART, *str++);
while(USART_GetFlagStatus(BT_USART, USART_FLAG_TC) == RESET);
}
}
// 接收字符(非阻塞)
uint8_t BT_ReceiveChar(uint8_t *ch) {
if(USART_GetFlagStatus(BT_USART, USART_FLAG_RXNE) != RESET) {
*ch = USART_ReceiveData(BT_USART);
return 1;
}
return 0;
}
注意:HC-05蓝牙模块默认波特率是9600,但可以通过AT命令修改。建议在初始化时先发送"AT+UART=115200,0,0\r\n"将波特率设置为115200,以提高通信速率。
6. 系统调试与优化
6.1 常见问题解决方案
在项目开发过程中,我遇到了不少问题,下面是常见问题及解决方法:
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 电机不转 | 电源供电不足 | 检查电池电压,确保L298N供电充足 |
| 小车运动方向相反 | 电机线序接反 | 交换电机接线极性 |
| 蓝牙无法连接 | 波特率不匹配或未进入AT模式 | 检查波特率设置,确认模块进入正确模式 |
| 循迹不稳定 | 传感器高度不合适 | 调整传感器距地面1-1.5cm |
| 超声波测距不准 | 环境回声干扰 | 增加软件滤波,避开干扰源 |
6.2 PID参数整定流程
为了让小车循迹更平稳,我采用了PID控制算法。下面是参数整定的基本流程:
- 首先将I和D参数设为0,逐渐增大P值直到小车能够快速响应轨迹变化
- 如果出现振荡,适当减小P值,同时增加D值来抑制振荡
- 如果小车响应迟缓,可以适当增加I值来消除稳态误差
- 反复微调三个参数,直到小车能够平稳、准确地跟随轨迹
实际调试中,我发现一组比较合适的参数是:P=0.5,I=0.01,D=0.2。当然,这组参数会根据小车的机械结构、电机特性等因素有所不同,需要根据实际情况调整。
经验分享:PID参数整定是一个需要耐心的过程。建议先用遥控模式手动控制小车运行,观察其运动特性,然后再进行参数调整。同时,可以在代码中加入参数在线调整功能,方便调试。
7. 成果展示与扩展
7.1 功能演示
完成后的智能小车实现了以下功能演示:
-
手机遥控模式:通过自定义Android APP,可以实现对小车的精确控制,包括前进、后退、左右转向等基本动作,以及速度调节功能。
-
自动避障模式:在此模式下,小车会自动前进,当超声波检测到前方障碍物距离小于30cm时,会根据左右两侧的距离测量结果选择转向方向,避开障碍物后继续前进。
-
黑线循迹模式:小车能够稳定地跟随地面上的黑色轨迹线行驶,可以完成8字形、S形等复杂路径的跟踪,转弯时也能保持平稳。
7.2 扩展方向
这个项目还有很大的扩展空间,以下是一些可能的扩展方向:
-
增加WiFi图传功能:添加摄像头和ESP32-CAM模块,实现实时视频传输,让小车具备FPV(第一人称视角)功能。
-
添加机械臂:在小车上安装舵机控制的机械臂,可以实现物品抓取和搬运功能,扩展应用场景。
-
移植ROS系统:将小车平台升级为ROS(Robot Operating System)节点,可以开发更复杂的导航和SLAM功能。
-
多车协同:通过无线通信实现多辆小车的协同工作,可以完成更复杂的���务,如编队行驶、协作运输等。
-
环境监测:添加温湿度、空气质量等传感器,让小车具备移动环境监测功能。
这个STM32智能小车项目不仅让我巩固了嵌入式开发的基础知识,也让我对机器人控制系统有了更深入的理解。在实际开发过程中,解决问题的过程往往比结果更有价值。希望这个分享能给对嵌入式开发感兴趣的朋友一些启发和帮助。
