1. 项目概述
这个基于STM32的蓝牙遥控智能小车系统,是我去年带学生做毕业设计时开发的一个典型嵌入式综合项目。它完美融合了STM32微控制器编程、蓝牙通信、电机控制和传感器数据处理等多项核心技术,特别适合想要进阶嵌入式开发的工程师练手。
整个系统的核心功能其实很简单:通过手机APP发送蓝牙指令,控制小车完成前进、后退、转向等基本运动,同时可以扩展添加避障、循迹等智能功能。但麻雀虽小五脏俱全,从硬件选型到软件架构,从通信协议到控制算法,每个环节都藏着不少门道。下面我就把这个项目从设计到实现的完整过程拆解给大家,特别是那些容易踩坑的细节。
2. 硬件系统设计
2.1 核心控制器选型
我选择了STM32F103C8T6作为主控芯片,这款芯片在业内被称为"蓝色药丸",性价比极高:
- 72MHz主频的Cortex-M3内核
- 64KB Flash + 20KB RAM
- 丰富的外设接口(3个USART、2个SPI、2个I2C)
- 价格仅10元左右
注意:市场上存在大量翻新芯片,建议通过正规渠道购买。我曾遇到过因使用翻新芯片导致PWM输出不稳定的问题。
2.2 电机驱动方案
经过对比测试,最终选用L298N双H桥驱动模块:
- 驱动电压:5-35V
- 单路峰值电流:2A
- 内置续流二极管
- 支持PWM调速
实际接线时要注意:
- 电机电源与逻辑电源必须共地
- 每个电机需要占用2个GPIO控制方向,1个PWM通道控制速度
- 建议在电机两端并联0.1μF电容滤除电刷噪声
2.3 蓝牙模块配置
HC-05蓝牙模块是最经济实惠的选择:
- 支持SPP协议(串口透传)
- 工作电压3.3V(需注意电平匹配)
- 默认波特率9600
- 可通过AT命令修改主从模式
配置技巧:
- 进入AT模式需要按住按键上电
- 发送"AT+UART=115200,0,0"可提高通信速率
- 使用"AT+PSWD"修改配对密码
3. 软件架构设计
3.1 系统任务划分
采用前后台系统架构:
- 主循环处理控制逻辑
- 串口中断接收蓝牙数据
- 定时器中断生成PWM信号
关键数据结构:
c复制typedef struct {
uint8_t speed; // 0-100%
int8_t direction; // -100~+100
uint8_t auto_mode; // 自动模式标志
} CarState;
3.2 蓝牙通信协议
自定义简单协议帧格式:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 0xA5 | 帧头 |
| 1 | 指令类型 | 0x01:控制 0x02:配置 |
| 2 | 数据长度N | |
| 3~N+2 | 数据内容 | |
| N+3 | 校验和 | 前面所有字节累加和 |
示例控制指令:
hex复制A5 01 02 50 64 D3
// 解释:速度50%,方向64,校验和0xD3
3.3 电机控制算法
采用差分驱动模型:
c复制void set_motor_speed(int left, int right) {
// 限制输入范围
left = constrain(left, -100, 100);
right = constrain(right, -100, 100);
// 设置左电机
if(left >= 0) {
set_dir(MOTOR_L1, HIGH);
set_dir(MOTOR_L2, LOW);
set_pwm(MOTOR_L_PWM, left);
} else {
set_dir(MOTOR_L1, LOW);
set_dir(MOTOR_L2, HIGH);
set_pwm(MOTOR_L_PWM, -left);
}
// 右电机同理...
}
4. 关键实现细节
4.1 PWM配置要点
使用TIM3产生4路PWM:
c复制void PWM_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 时基配置
TIM_TimeBaseStructure.TIM_Period = 999; // 1kHz
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
4.2 蓝牙数据接收处理
使用DMA+空闲中断提高效率:
c复制void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
USART_ReceiveData(USART1); // 清除标志
DMA_Cmd(DMA1_Channel5, DISABLE);
uint16_t len = BUF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
process_ble_data(rx_buf, len);
DMA_SetCurrDataCounter(DMA1_Channel5, BUF_SIZE);
DMA_Cmd(DMA1_Channel5, ENABLE);
}
}
5. 系统调试与优化
5.1 常见问题排查
-
电机不转但指示灯亮
- 检查使能引脚是否拉高
- 测量电机端子间电压
- 尝试直接短接电机看是否损坏
-
蓝牙连接不稳定
- 检查天线是否完全展开
- 用示波器观察串口信号质量
- 尝试降低波特率到9600
-
PWM输出异常
- 确认时钟配置正确
- 检查GPIO重映射设置
- 验证预分频和自动重装值计算
5.2 性能优化技巧
-
电源处理
- 电机电源与MCU电源分开供电
- 在电源入口处增加大容量电解电容
- 每个芯片的VCC引脚添加0.1μF去耦电容
-
软件优化
- 使用查表法替代实时计算sin/cos
- 将频繁调用的函数声明为static inline
- 开启编译器的优化选项(-O2)
-
控制算法改进
c复制// 加入死区补偿 void update_motors() { static int last_left = 0, last_right = 0; int delta_l = target_left - last_left; int delta_r = target_right - last_right; // 限制加速度 delta_l = constrain(delta_l, -MAX_ACCEL, MAX_ACCEL); delta_r = constrain(delta_r, -MAX_ACCEL, MAX_ACCEL); last_left += delta_l; last_right += delta_r; set_motor_speed(last_left, last_right); }
6. 功能扩展方向
6.1 添加超声波避障
硬件连接:
- 触发引脚 -> PA8
- 回波引脚 -> PA0(TIM2_CH1)
测距实现:
c复制float get_distance(void) {
// 发送10us高脉冲
GPIO_SetBits(GPIOA, GPIO_Pin_8);
delay_us(10);
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
// 等待回波上升沿
while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0));
TIM_SetCounter(TIM2, 0);
// 等待下降沿
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0));
uint16_t cnt = TIM_GetCounter(TIM2);
return cnt * 0.034 / 2; // 单位cm
}
6.2 实现循迹功能
红外对管布局:
- 5路红外传感器呈扇形排列
- 推荐使用TCRT5000模块
循迹算法:
c复制void line_following(void) {
uint8_t sensors = read_line_sensors();
switch(sensors) {
case 0b00100: // 居中
set_motor_speed(50, 50);
break;
case 0b00010: // 轻微右偏
set_motor_speed(40, 60);
break;
// 其他情况类似处理...
}
}
6.3 升级到手机APP控制
使用MIT App Inventor快速开发:
- 设计UI界面:方向摇杆+速度滑块
- 蓝牙组件配对连接
- 定时发送控制指令(建议50ms间隔)
关键代码块:
blocks复制// 当摇杆位置改变时
procedure Joystick.PositionChanged(x y)
// 将坐标(-100~100)转换为电机速度
speed = y
turn = x
// 计算左右轮速差
left = speed + turn/2
right = speed - turn/2
// 发送蓝牙指令
BluetoothClient.WriteBytes(list to bytes(0xA5, 0x01, 0x02, left, right))
end procedure
这个项目最让我有成就感的是看到学生们从最开始的连电路都接不对,到最后能自己调试PID参数让小车平稳循迹。嵌入式开发就是这样,理论看似简单,但每个细节都可能藏着魔鬼。建议大家在复现时,一定要亲手调试每个参数,观察每个信号波形,这才是真正的成长之道。