1. 项目概述
去年夏天,我在自家车库捣鼓出了一个能通过手机遥控的智能小车。这个看似简单的玩具项目,实际上涉及了嵌入式开发、无线通信、传感器融合等多个技术领域的知识整合。今天就来详细聊聊这个无线智能小车的软件架构设计和实现过程。
这种智能小车在创客教育、智能家居和工业巡检等领域都有广泛应用场景。通过手机APP就能实时控制小车移动、采集环境数据,还能实现自动避障等智能功能。整个系统由硬件层、通信层和应用层三大部分组成,其中软件设计是连接各硬件模块的"大脑"。
2. 系统架构设计
2.1 整体架构
整个系统采用分层设计,自下而上分为:
- 硬件驱动层:负责电机控制、传感器数据采集等底层操作
- 通信协议层:处理无线数据传输和协议解析
- 应用逻辑层:实现业务逻辑和用户交互
这种分层设计使得各模块耦合度低,便于后期功能扩展和维护。比如要新增一个超声波避障功能,只需在硬件层添加驱动,应用层增加相应逻辑即可。
2.2 硬件选型
核心控制器选用STM32F103C8T6,这款ARM Cortex-M3内核的MCU性价比极高,72MHz主频完全能满足实时控制需求。无线模块采用ESP8266,既可以通过WiFi与手机通信,又能通过AT指令方便地集成到系统中。
电机驱动使用L298N双H桥芯片,可同时驱动两个直流电机实现前进、后退和转向。传感器方面配备了HC-SR04超声波模块用于测距,MPU6050六轴传感器用于姿态检测。
3. 关键模块实现
3.1 电机控制
电机驱动采用PWM调速技术,通过改变占空比来调节电机转速。在STM32中配置TIM定时器产生PWM波,关键代码如下:
c复制// PWM初始化
void PWM_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
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_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
注意:PWM频率不宜过高,一般控制在1-10kHz范围内。频率太低会导致电机运转不平稳,太高则可能引起驱动芯片过热。
3.2 无线通信
ESP8266模块工作在STA模式,手机作为TCP客户端连接小车。通信协议设计如下:
| 指令类型 | 数据格式 | 说明 |
|---|---|---|
| 运动控制 | $M,方向,速度# | 方向:F/B/L/R,速度:0-255 |
| 传感器查询 | $S,类型# | 类型:D(距离)/A(姿态) |
| 系统命令 | $C,命令# | 命令:R(重启)/S(停止) |
在STM32端通过串口中断接收数据,使用状态机解析协议:
c复制void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
char ch = USART_ReceiveData(USART1);
switch(parse_state)
{
case WAIT_START:
if(ch == '$') parse_state = WAIT_TYPE;
break;
case WAIT_TYPE:
cmd_type = ch;
parse_state = WAIT_DATA;
break;
case WAIT_DATA:
if(ch != '#') buffer[index++] = ch;
else {
buffer[index] = '\0';
process_command(cmd_type, buffer);
parse_state = WAIT_START;
index = 0;
}
break;
}
}
}
3.3 传感器数据融合
为了提高测距准确性,对超声波传感器采用滑动窗口滤波算法:
c复制#define WINDOW_SIZE 5
uint16_t distance_window[WINDOW_SIZE];
uint8_t window_index = 0;
uint16_t filter_distance(uint16_t new_val)
{
distance_window[window_index] = new_val;
window_index = (window_index + 1) % WINDOW_SIZE;
uint32_t sum = 0;
for(int i=0; i<WINDOW_SIZE; i++) {
sum += distance_window[i];
}
return sum / WINDOW_SIZE;
}
MPU6050的数据通过DMP库解算得到欧拉角,再通过互补滤波融合加速度计和陀螺仪数据:
c复制void update_attitude()
{
float accel_angle = atan2(accelY, accelZ) * RAD_TO_DEG;
gyro_rate = gyroX / 131.0; // 转换为度/秒
// 互补滤波
current_angle = 0.98 * (current_angle + gyro_rate * dt)
+ 0.02 * accel_angle;
}
4. 手机端APP设计
4.1 控制界面
使用Android Studio开发控制APP,主要功能包括:
- 虚拟摇杆控制
- 传感器数据显示
- 自动模式切换
- 参数设置
关键控制逻辑:
java复制public void onJoystickMoved(float xPercent, float yPercent) {
int leftSpeed = (int)(yPercent * 255);
int rightSpeed = (int)(yPercent * 255);
if(xPercent > 0) { // 右转
rightSpeed = (int)(rightSpeed * (1 - xPercent));
} else { // 左转
leftSpeed = (int)(leftSpeed * (1 + xPercent));
}
sendCommand("$M," + leftSpeed + "," + rightSpeed + "#");
}
4.2 视频传输
通过ESP32-CAM模块实现实时视频传输,使用MJPG-streamer将视频流通过WiFi传输:
bash复制./mjpg_streamer -i "input_uvc.so -d /dev/video0" -o "output_http.so -p 8080"
在APP中使用VideoView组件显示视频流:
xml复制<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="200dp" />
java复制String videoUrl = "http://" + ipAddress + ":8080/?action=stream";
videoView.setVideoURI(Uri.parse(videoUrl));
videoView.start();
5. 系统优化与调试
5.1 电源管理
实测发现电机启动时会导致电压骤降,影响控制器稳定性。解决方案:
- 在电机电源输入端并联大容量电解电容(1000μF以上)
- 采用独立的LDO为控制电路供电
- 软件上实现电机软启动:
c复制void motor_soft_start(int target_speed)
{
int current_speed = 0;
while(current_speed < target_speed) {
current_speed += 5;
if(current_speed > target_speed)
current_speed = target_speed;
set_motor_speed(current_speed);
delay(50);
}
}
5.2 通信可靠性
无线通信易受干扰,采取以下措施提高可靠性:
- 增加数据校验和重传机制
- 使用心跳包检测连接状态
- 重要指令采用应答模式
改进后的通信协议:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 1字节 | 固定为0xAA |
| 序列号 | 1字节 | 用于匹配请求和应答 |
| 数据长度 | 1字节 | 数据域长度 |
| 数据域 | N字节 | 有效载荷 |
| CRC8 | 1字节 | 校验和 |
5.3 常见问题排查
-
电机不转:
- 检查L298N使能引脚是否接高电平
- 测量电机两端电压是否正常
- 确认PWM信号是否输出
-
WiFi连接不稳定:
- 检查天线是否完好
- 尝试更换信道(1/6/11干扰最小)
- 降低传输速率提高稳定性
-
传感器数据异常:
- 检查电源电压是否稳定
- 确认I2C/SPI接口接线正确
- 添加适当的滤波算法
6. 功能扩展思路
- SLAM建图:添加RPLIDAR实现环境地图构建
- 视觉识别:使用OpenCV实现物体识别跟踪
- 语音控制:集成语音识别模块实现声控
- 云端监控:通过MQTT协议接入物联网平台
- 编队控制:多车协同完成复杂任务
实现视觉巡线功能的示例代码:
python复制import cv2
import numpy as np
def process_frame(frame):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_yellow = np.array([20, 100, 100])
upper_yellow = np.array([30, 255, 255])
mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) > 0:
max_contour = max(contours, key=cv2.contourArea)
M = cv2.moments(max_contour)
cx = int(M['m10']/M['m00'])
return cx
return None
这个项目从最初的基础遥控功能,逐步扩展出自动避障、视频传输等高级功能,整个过程让我深刻体会到嵌入式系统开发的乐趣和挑战。特别是在资源受限的MCU上实现复杂功能时,需要充分考虑实时性、稳定性和功耗的平衡。