1. 项目概述
这个手势控制无线遥控车项目,是我最近完成的一个嵌入式系统综合实践。它通过两块STM32开发板实现了基于重力感应的手势遥控功能,整个系统分为小车端和控制端两部分,通过WiFi模块进行无线通信。
作为一个经常玩嵌入式开发的工程师,我发现市面上的遥控车大多还是传统按键或摇杆控制。这次尝试用重力感应来实现手势控制,不仅操作更直观,而且实际玩起来确实很"炫酷"——你可以像魔法师一样,通过倾斜控制板就能让小车做出各种动作。
1.1 系统组成
小车端硬件配置:
- 主控:STM32F103C8T6最小系统板(72MHz主频,64KB Flash,20KB SRAM)
- 通信:ESP8266 WiFi模块(支持STA/AP模式)
- 驱动:L298N电机驱动模块(最大2A驱动电流)
- 电源:18650锂电池组(7.4V)+ TP4056充电模块
- 辅助:DC-DC升压模块(将电池电压稳定在5V)
控制端硬件配置:
- 主控:同款STM32F103C8T6最小系统板
- 传感器:ADXL345三轴加速度计(±16g量程,I2C接口)
- 显示:1.44寸TFT彩屏(128x128分辨率,SPI接口)
- 按键:两个轻触开关(校准键K1和模式切换键K2)
- 通信:同款ESP8266 WiFi模块
提示:选择STM32F103C8T6是因为它性价比高,且完全能满足本项目需求。ADXL345的采样率设置为100Hz,既保证了响应速度又不会给MCU带来太大负担。
2. 系统工作原理
2.1 手势识别原理
ADXL345加速度计会实时测量控制板在X、Y、Z三个轴上的加速度值。当控制板静止时,Z轴会检测到1g的重力加速度,X/Y轴接近0g;当倾斜控制板时,重力在各轴上的分量会发生变化。
我们通过以下公式计算倾斜角度:
code复制X轴角度 = atan2(Y, Z) * 180/PI
Y轴角度 = atan2(X, Z) * 180/PI
其中atan2是四象限反正切函数,能输出-180°~+180°的角度值。
2.2 控制逻辑设计
系统状态机包含以下几个主要状态:
- 初始化状态:显示欢迎界面,等待WiFi连接
- 校准状态:记录当前姿态作为基准(零位)
- 待机状态:显示"停止控制",防止误操作
- 控制状态:根据倾斜角度控制小车运动
角度与速度的映射关系采用分段线性函数:
code复制if 角度 < 阈值: 速度=0
else if 角度 < 中值: 速度=低速档
else: 速度=高速档
2.3 通信协议设计
控制端和小车端通过UDP协议通信,数据包格式如下:
| 字节 | 内容 | 说明 |
|---|---|---|
| 0 | 0xAA | 帧头 |
| 1 | 命令类型 | 0x01:校准 0x02:控制 |
| 2 | X轴角度 | 有符号字节(-90~+90) |
| 3 | Y轴角度 | 有符号字节(-90~+90) |
| 4 | 0x55 | 帧尾 |
注意:实际通信中还加入了简单的校验和,防止数据错误导致小车失控。
3. 硬件搭建详解
3.1 小车端电路设计
电机驱动部分:
L298N模块的IN1~IN4接STM32的PA0~PA3,ENA/ENB接PA4/PA5(PWM输出)。电机电源直接接锂电池(7.4V),逻辑电源接5V稳压输出。
电源管理:
- TP4056充电模块负责给两节18650充电
- DC-DC升压模块将电池电压升至5V给STM32和WiFi模块供电
- 建议在电源输入端加入1000μF电容滤波
3.2 控制端电路设计
ADXL345连接:
- SDA → PB7
- SCL → PB6
- INT1 → PA8(用于中断唤醒)
- VCC接3.3V,注意I2C上拉电阻(4.7kΩ)
TFT屏幕连接:
- SCK → PA5
- MOSI → PA7
- CS → PA2
- DC → PA1
- RESET → PA0
实操技巧:焊接前先用杜邦线测试所有连接,确认无误后再固定。ADXL345要尽量安装在控制板中心位置,避免偏心导致测量误差。
4. 软件实现关键点
4.1 加速度计数据处理
c复制// 读取ADXL345原始数据
void ADXL345_ReadData(int16_t *x, int16_t *y, int16_t *z) {
uint8_t data[6];
HAL_I2C_Mem_Read(&hi2c1, ADXL345_ADDR, 0x32, 1, data, 6, 100);
*x = (data[1]<<8)|data[0];
*y = (data[3]<<8)|data[2];
*z = (data[5]<<8)|data[4];
}
// 转换为角度值
void GetAngles(float *roll, float *pitch) {
int16_t x,y,z;
ADXL345_ReadData(&x, &y, &z);
*roll = atan2f(y, z) * 180/M_PI;
*pitch = atan2f(x, z) * 180/M_PI;
}
4.2 电机控制算法
c复制void Motor_Control(float pitch, float roll) {
// 前进/后退控制
if(fabs(pitch) > 15) { // 死区阈值
int speed = (int)((fabs(pitch)-15)/45 * 100); // 0-100%映射
speed = constrain(speed, 0, 100);
if(pitch > 0) {
// 前进
PWM_SetDuty(MOTOR_R, speed);
PWM_SetDuty(MOTOR_L, speed);
} else {
// 后退
PWM_SetDuty(MOTOR_R, -speed);
PWM_SetDuty(MOTOR_L, -speed);
}
}
// 转向控制
if(fabs(roll) > 15) {
int delta = (int)((fabs(roll)-15)/45 * 50);
if(roll > 0) {
// 右转
PWM_SetDuty(MOTOR_L, PWM_GetDuty(MOTOR_L) + delta);
PWM_SetDuty(MOTOR_R, PWM_GetDuty(MOTOR_R) - delta);
} else {
// 左转
PWM_SetDuty(MOTOR_L, PWM_GetDuty(MOTOR_L) - delta);
PWM_SetDuty(MOTOR_R, PWM_GetDuty(MOTOR_R) + delta);
}
}
}
4.3 WiFi通信实现
c复制// UDP数据发送
void WiFi_SendControlData(float pitch, float roll) {
uint8_t buf[5] = {0xAA, 0x02,
(uint8_t)(constrain(pitch, -90, 90)+90),
(uint8_t)(constrain(roll, -90, 90)+90),
0x55};
HAL_UART_Transmit(&huart1, buf, 5, 100);
}
// 接收处理线程
void WiFi_ReceiveThread(void const *argument) {
uint8_t rxBuf[32];
while(1) {
if(WiFi_Receive(rxBuf, 32) > 0) {
if(rxBuf[0] == 0xAA && rxBuf[4] == 0x55) {
if(rxBuf[1] == 0x01) { // 校准确认
LCD_ShowString(10, 50, "Calib OK!");
}
}
}
osDelay(10);
}
}
5. 调试与优化经验
5.1 加速度计校准
ADXL345出厂时存在零点偏移,需要先进行校准:
- 将控制板水平放置在桌面上
- 读取100次采样取平均值作为零点
- 计算各轴的缩放系数(理论值256 LSB/g)
校准代码示例:
c复制void ADXL345_Calibrate() {
int32_t x_sum=0, y_sum=0, z_sum=0;
for(int i=0; i<100; i++) {
int16_t x,y,z;
ADXL345_ReadData(&x, &y, &z);
x_sum += x; y_sum += y; z_sum += z;
HAL_Delay(10);
}
offset_x = x_sum / 100;
offset_y = y_sum / 100;
offset_z = z_sum / 100 - 256; // Z轴应有1g重力
}
5.2 运动控制平滑处理
原始角度数据会有抖动,需要进行滤波:
c复制// 一阶低通滤波
float LowPassFilter(float newVal, float oldVal, float alpha) {
return alpha * newVal + (1-alpha) * oldVal;
}
// 在角度计算后调用
filtered_pitch = LowPassFilter(pitch, filtered_pitch, 0.2);
filtered_roll = LowPassFilter(roll, filtered_roll, 0.2);
5.3 常见问题排查
-
WiFi连接不稳定:
- 检查天线是否接触良好
- 尝试修改ESP8266的WiFi信道(避开拥挤信道)
- 增加重连机制,当超过3秒未收到心跳包时主动重连
-
小车响应延迟:
- 降低ADXL345的采样率(从400Hz降到100Hz)
- 优化UDP发送间隔(从50ms增加到100ms)
- 检查电机驱动电源是否充足(电压低于6V时性能下降)
-
角度测量不准:
- 重新校准加速度计
- 检查控制板是否有振动干扰
- 尝试改用卡尔曼滤波算���
6. 项目扩展方向
在实际使用过程中,我发现这个系统还有不少可以改进的地方:
-
增加手势动作识别:
- 双击控制板启动/停止
- 画圈动作实现360°旋转
- 甩动控制板实现急刹
-
加入更多传感器:
- 陀螺仪实现更精确的姿态检测
- 超声波避障模块
- 摄像头实现视觉跟随
-
优化电源管理:
- 加入低功耗模式(摇动唤醒)
- 电池电量显示
- 充电状态指示
这个项目最让我满意的是将理论知识与实际应用完美结合。从电路设计、嵌入式编程到通信协议,每个环节都需要仔细考量。特别是在调试手势控制灵敏度时,反复调整死区阈值和速度映射曲线,最终找到了既灵敏又不易误触发的参数组合。
