1. 项目概述
去年夏天,我在自家车库捣鼓出了一个能自动避障、远程控制的智能小车。这个看似简单的玩具项目,实际上涉及了嵌入式开发、无线通信、传感器融合等多个技术领域的交叉应用。今天就来聊聊这个无线智能小车的软件设计全过程,从架构设计到代码实现,再到实际调试中踩过的那些坑。
这种基于STM32的智能小车平台,不仅适合作为电子爱好者的DIY项目,更是学习嵌入式系统开发的绝佳载体。通过手机APP就能实时控制小车运动,配合超声波和红外传感器实现自动避障,整套系统在200元预算内就能搭建完成。下面我会详细拆解每个模块的软件实现细节。
2. 硬件平台选型与搭建
2.1 核心控制器选择
经过对比Arduino和STM32两种方案,最终选择了STM32F103C8T6作为主控芯片。这款Cortex-M3内核的MCU具有72MHz主频、64KB Flash和20KB RAM,性能足够处理多传感器数据融合,价格却只要十几块钱。更重要的是,STM32的HAL库提供了完善的PWM、ADC、USART等外设驱动,大大降低了开发门槛。
注意:初学者常犯的错误是直接使用寄存器操作,建议先从HAL库入手,等熟悉架构后再尝试LL库或寄存器级编程。
2.2 电机驱动方案
采用L298N双H桥驱动模块控制两个直流电机,这种经典方案虽然效率不如TB6612等新型驱动芯片,但胜在价格低廉(约15元)且驱动能力强(单路2A持续电流)。软件上需要配置TIM定时器产生PWM波,通过改变占空比实现调速:
c复制// PWM初始化示例
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 999; // 1MHz/(999+1)=1kHz PWM
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
2.3 传感器配置
避障系统采用HC-SR04超声波模块(检测距离2cm-400cm)和红外对管(检测近距离障碍物)的组合。超声波模块通过GPIO触发测距,利用输入捕获测量回波时间:
c复制float Get_Distance(void) {
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
delay_us(10);
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
while(!HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin));
uint32_t start = TIM2->CNT;
while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin));
uint32_t end = TIM2->CNT;
return (end-start)/58.0; // 时间(us)/58=距离(cm)
}
红外对管则直接读取GPIO电平状态,当检测到障碍物时输出低电平。
3. 无线通信系统设计
3.1 蓝牙模块配置
采用HC-05蓝牙模块实现手机与小车通信,模块工作在从机模式,波特率设置为9600。通过USART接收手机APP发送的控制指令,协议设计如下:
| 指令字节 | 功能说明 |
|---|---|
| 0x01 | 前进 |
| 0x02 | 后退 |
| 0x03 | 左转 |
| 0x04 | 右转 |
| 0x05 | 停止 |
| 0x11 | 设置速度等级1 |
| 0x12 | 设置速度等级2 |
在STM32中配置USART中断接收:
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
switch(rxBuffer[0]) {
case 0x01: Motor_Forward(); break;
case 0x02: Motor_Backward(); break;
// 其他指令处理...
}
HAL_UART_Receive_IT(&huart1, rxBuffer, 1);
}
}
3.2 WiFi视频传输方案(可选)
对于需要视频监控的场景,可以添加ESP32-CAM模块。通过WiFi将摄像头画面传输到手机,使用MJPG-streamer实现视频流传输。需要注意的是,这会显著增加系统复杂度,建议在完成基础功能后再考虑添加。
4. 运动控制算法实现
4.1 电机PID调速
为实现平稳的运动控制,需要对电机转速进行PID调节。首先通过编码器获取实际转速:
c复制// 编码器接口模式初始化
TIM_Encoder_InitTypeDef encoderConfig = {0};
encoderConfig.EncoderMode = TIM_ENCODERMODE_TI12;
encoderConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
encoderConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
encoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
encoderConfig.IC1Filter = 0;
HAL_TIM_Encoder_Init(&htim4, &encoderConfig);
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
然后实现PID控制器:
c复制typedef struct {
float Kp, Ki, Kd;
float integral, prev_error;
} PID_Controller;
float PID_Update(PID_Controller* pid, float setpoint, float measurement) {
float error = setpoint - measurement;
pid->integral += error;
float derivative = error - pid->prev_error;
pid->prev_error = error;
return pid->Kp*error + pid->Ki*pid->integral + pid->Kd*derivative;
}
4.2 自动避障逻辑
结合超声波和红外传感器的数据,实现分级避障策略:
- 当红外检测到近距离障碍物(<10cm),立即停止并后退
- 超声波检测到中距离障碍物(10-50cm),减速并转向
- 无障碍物时保持设定速度
c复制void Obstacle_Avoidance(void) {
float distance = Get_Distance();
uint8_t ir_status = HAL_GPIO_ReadPin(IR_GPIO_Port, IR_Pin);
if(!ir_status) {
Motor_Stop();
Motor_Backward();
HAL_Delay(500);
Motor_Turn_Left();
HAL_Delay(300);
}
else if(distance < 50) {
Motor_Set_Speed(distance/100.0 * max_speed);
if(rand()%2) Motor_Turn_Left();
else Motor_Turn_Right();
}
}
5. 手机控制端开发
5.1 Android APP设计
使用Android Studio开发控制APP,核心功能包括:
- 通过蓝牙发送运动指令
- 实时显示传感器数据
- 速度调节滑块
关键代码片段:
java复制// 蓝牙连接
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = bluetoothAdapter.getBondedDevices().iterator().next();
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
OutputStream outputStream = socket.getOutputStream();
// 发送控制指令
public void sendCommand(byte cmd) {
try {
outputStream.write(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
5.2 控制界面优化
为提高操作体验,采用虚拟摇杆控件替代传统按钮。通过计算摇杆偏移角度和幅度,实现更精细的速度和方向控制:
java复制joystickView.setOnMoveListener((angle, strength) -> {
byte speed = (byte)(strength * 255 / 100);
byte direction = (byte)(angle / 45); // 将360°分为8个方向
byte[] command = new byte[2];
command[0] = direction;
command[1] = speed;
sendCommand(command);
});
6. 系统集成与调试
6.1 电源管理优化
调试中发现电机启动时会导致电压骤降,引发MCU复位。解决方案:
- 为数字电路和电机驱动使用独立的LM2596降压模块
- 在电源输入端添加4700μF电解电容
- 软件上实现电机软启动:
c复制void Motor_Soft_Start(uint8_t target_speed) {
for(int i=0; i<target_speed; i++) {
Motor_Set_Speed(i);
HAL_Delay(20);
}
}
6.2 无线通信抗干扰
实测中发现蓝牙在电机运行时通信不稳定,采取以下措施:
- 为蓝牙模块添加磁珠滤波
- 通信协议增加校验和重传机制
- 降低PWM频率至1kHz以减少谐波干扰
改进后的数据帧格式:
| 字节位置 | 内容 |
|---|---|
| 0 | 帧头(0xAA) |
| 1 | 指令类型 |
| 2 | 数据 |
| 3 | 校验和 |
6.3 传感器数据融合
单一传感器存在误检测问题,采用加权投票算法融合多传感器数据:
c复制#define ULTRASONIC_WEIGHT 0.7
#define IR_WEIGHT 0.3
uint8_t Detect_Obstacle(void) {
float us_dist = Get_Distance();
uint8_t ir_left = !HAL_GPIO_ReadPin(IR_LEFT_GPIO_Port, IR_LEFT_Pin);
uint8_t ir_right = !HAL_GPIO_ReadPin(IR_RIGHT_GPIO_Port, IR_RIGHT_Pin);
float left_score = (100.0 - us_dist)/100.0 * ULTRASONIC_WEIGHT
+ ir_left * IR_WEIGHT;
float right_score = (100.0 - us_dist)/100.0 * ULTRASONIC_WEIGHT
+ ir_right * IR_WEIGHT;
if(left_score > 0.5 || right_score > 0.5) {
return (left_score > right_score) ? OBSTACLE_LEFT : OBSTACLE_RIGHT;
}
return OBSTACLE_NONE;
}
7. 项目优化与扩展
7.1 OTA远程升级
通过蓝牙实现固件无线更新,需要在Bootloader中实现以下功能:
- 接收并校验新固件
- 擦写Flash
- 跳转到应用程序
Bootloader关键流程:
c复制void Bootloader_Main(void) {
uint8_t buffer[256];
uint32_t address = APP_ADDRESS;
while(1) {
if(UART_Receive(buffer, 256) == [HAL](https://taotoken.net/?utm_source=hardware)_OK) {
if(Verify_Checksum(buffer)) {
FLASH_Erase(address);
FLASH_Program(address, buffer);
address += 256;
}
} else if(Check_Jump_Command()) {
JumpToApp();
}
}
}
7.2 添加IMU实现姿态控制
接入MPU6050六轴传感器,实现更精准的运动控制:
c复制void IMU_Init(void) {
I2C_Write(MPU6050_ADDR, PWR_MGMT_1, 0x01); // 唤醒
I2C_Write(MPU6050_ADDR, SMPLRT_DIV, 0x07); // 采样率1kHz
I2C_Write(MPU6050_ADDR, CONFIG, 0x06); // 低通滤波
I2C_Write(MPU6050_ADDR, GYRO_CONFIG, 0x18); // ±2000°/s
I2C_Write(MPU6050_ADDR, ACCEL_CONFIG, 0x18);// ±16g
}
void Read_IMU_Data(float* accel, float* gyro) {
uint8_t data[14];
I2C_Read(MPU6050_ADDR, ACCEL_XOUT_H, data, 14);
accel[0] = (int16_t)(data[0]<<8 | data[1]) / 2048.0;
accel[1] = (int16_t)(data[2]<<8 | data[3]) / 2048.0;
accel[2] = (int16_t)(data[4]<<8 | data[5]) / 2048.0;
gyro[0] = (int16_t)(data[8]<<8 | data[9]) / 16.384;
gyro[1] = (int16_t)(data[10]<<8 | data[11]) / 16.384;
gyro[2] = (int16_t)(data[12]<<8 | data[13]) / 16.384;
}
7.3 引入RTOS实现多任务管理
对于复杂功能需求,可以移植FreeRTOS实现任务调度:
c复制void Task_Control(void *pvParameters) {
while(1) {
Motor_Control();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void Task_Sensor(void *pvParameters) {
while(1) {
Update_Sensor_Data();
vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
xTaskCreate(Task_Control, "Control", 128, NULL, 2, NULL);
xTaskCreate(Task_Sensor, "Sensor", 128, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
8. 常见问题与解决方案
8.1 电机响应迟缓
可能原因及解决方法:
- PWM频率过低 - 提高到1kHz以上
- PID参数不合适 - 先设Ki=0,调整Kp使系统快速响应但不振荡,再加入少量Ki消除静差
- 电源供电不足 - 检查电池电压,确保驱动模块得到足够电流
8.2 蓝牙连接不稳定
排查步骤:
- 检查天线是否完全展开
- 用示波器观察电源纹波,添加滤波电容
- 降低通信波特率测试
- 避免与WiFi设备共用2.4GHz频段
8.3 超声波测距不准
校准方法:
- 在已知距离测量10次取平均值
- 根据实测误差调整声速参数(原公式使用340m/s)
- 添加温度补偿(声速=331.4+0.6*T℃)
- 确保检测面与超声波模块平行
8.4 小车跑偏问题
解决方案:
- 使用编码器反馈实现闭环控制
- 在平整地面校准电机PWM-速度曲线
- 添加陀螺仪进行航向校正
- 定期检查轮胎磨损情况
9. 项目总结与改进方向
经过三个版本的迭代,目前这个小车系统已经能够稳定实现远程控制、自动避障等基础功能。实测在10米范围内蓝牙控制响应延迟小于100ms,自动避障反应时间约200ms。整个项目硬件成本约180元,软件开发耗时约40小时。
几个值得改进的方向:
- 引入简单的SLAM算法实现地图构建
- 改用2.4GHz无线模块提高控制距离
- 添加机械臂扩展执行简单抓取任务
- 移植TensorFlow Lite实现图像识别
在实际开发过程中,最大的收获是对嵌入式系统实时性处理有了更深理解。比如最初没有考虑电机干扰导致蓝牙断连的问题,后来通过添加硬件滤波和软件重传机制才解决。这也提醒我们,嵌入式开发必须同时考虑硬件和软件的协同设计。
