ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Arduino手势控制小车:MPU6050姿态解算与HC-05蓝牙通信实战

Arduino手势控制小车:MPU6050姿态解算与HC-05蓝牙通信实战 简介一款基于Arduino的手势控制小车完整设计方案面向电子爱好者与嵌入式课程设计人群。系统分为手势遥控端与小车执行端遥控端通过MPU6050采集重力感应数据借助HC-05蓝牙模块将手势控制信号无线发送给小车实现前进、后退、转向等动作响应。压缩包共含21个文件约126KB包括8个INO主程序文件、6个H与2个CPP库文件、2个PDE可视化辅助程序、2个JSON配置及1个TXT说明其中INO覆盖MPU6050姿态解析与蓝牙数据收发库文件封装了I2Cdev等底层驱动PDE可用于上位机调试整体结构清晰、便于二次开发。目前已有1204人学习下载适合刚接触Arduino、对手势识别与蓝牙通信感兴趣或有项目选题需求的学习者参考。压缩包内源码可直接启动Arduino IDE编译运行通过实际代码理解传感器数据融合与无线控制链路是一套低成本手势小车实现范例。1. 手势控制小车MPU6050 读姿态、HC-05 传指令核心是两片 Arduino 的协作手势控制小车听上去玄拆开就是三件事MPU6050 读出手部倾斜HC-05 蓝牙把方向字符传出去小车端 Arduino 收到字符后驱动电机。真正决定成败的不是电机和车架而是姿态解算代码和蓝牙握手协议。很多 arduino 智能小车套件都带遥控但重力感应式手势控制需要你同时处理 I2C 数据、互补滤波和 SPP 串口通信。适合正在玩 Arduino Uno/Nano 的爱好者也适合想把手势识别落到具体硬件的嵌入式工程师。这里按硬件链路、源码拆解、滤波调参的顺序讲最后给几个验证技巧。2. 硬件选型与连接MPU6050 的 I2C 地址、HC-05 的 AT 指令与电平匹配2.1 为什么用 MPU6050 而不是单加速度计MPU6050 内部集成了 3 轴加速度计和 3 轴陀螺仪这两个传感器的数据通过 I2C 总线输出默认地址 0x68。加速度计可以测量重力在三个轴上的分量据此能算出静态倾角陀螺仪测量角速度却能捕捉快速转动。手势控制需要的是“手朝哪个方向偏了多少度”单独用加速度计在静止时很好但手一旦晃动线性加速度会混进重力分量里角度值剧烈抖动。陀螺仪不受线性加速度影响但它输出的是角速度要得到角度必须做积分而积分会随时间漂移。所以这个项目选择 MPU6050 而不是单个加速度计目的是用陀螺仪的高频响应去修正加速度计在动态场景下的噪声。这也是 mpu6050 姿态解算这个搜索词背后最常见的动因。2.2 HC-05 与 JDY-31 的蓝牙 SPP 兼容性HC-05 是经典蓝牙串口模块支持 SPP 协议从机模式可以和手机配对主机模式可以主动连接另一个从机。这个项目里需要两块蓝牙一块接在遥控端作主机另一块接在小车端作从机上电后自动配对。HC-05 的关键点是必须先进入 AT 模式才能改角色和波特率按住模块按键再上电或者把 AT 引脚拉高然后发送ATROLE1设为主机ATROLE0设为从机用ATCMODE1允许任意地址连接最后用ATUART9600,0,0统一串口参数。如果你手里不是 HC-05而是网上常见的 JDY-31 底板注意它完全兼容 HC-05/06 从机协议支持同样的 SPP 协议蓝牙 3.0手册里的 AT 指令集与 HC-05 基本一致可以直接替换使用。选择蓝牙模块时不要买只有 BLE 的那个走 GATT和 HC-05 这种透明串口传输不是一个玩法。2.3 最小接线表与供电注意下面是最小接线我一般把遥控端做成手持设备用 Arduino Nano小车端用 Uno 方便调试两款都是 ATmega328P代码通用。MPU6050 模块尽量选带稳压芯片的版本这样可以直接接 5V否则必须接 3.3VHC-05 的 VCC 可以接 5V但其逻辑电平是 3.3V所以 HC-05 的 RX 不能直接接 Arduino 的 5V TX需要串联一个 1kΩ 电阻分压。接线表如下模块引脚遥控端 Arduino小车端 ArduinoMPU6050VCC5V5VMPU6050GNDGNDGNDMPU6050SCLA5A5MPU6050SDAA4A4MPU6050AD0GNDGNDHC-05VCC5V5VHC-05GNDGNDGNDHC-05TXDRXD0RXD0HC-05RXD分压后接 TXD1分压后接 TXD1注意Uno/Nano 的 D0/D1 同时是 USB 串口引脚如果硬件串口连接 HC-05烧录程序时要断开蓝牙 RX 线或者改用 SoftwareSerial 放在 D2/D3 上。另外MPU6050 默认 I2C 地址是 0x68如果 AD0 接高电平会变成 0x69扫描 I2C 设备时要注意。小车电机驱动模块建议使用 L298N 或 L9110S驱动板要和 Arduino 共地但电机电源单独供电避免电机大电流拉低 MPU6050 的参考电压导致姿态角跳变。3. 源码拆解sketch_jun24a 如何把重力感应变成小车动作3.1 遥控端程序设计读取 mpu6050.ino 的原始数据遥控端的 mpu6050.ino 核心是 I2C 读取原始数据常见做法是先用 Wire 库初始化再向 MPU6050 的电源管理寄存器 0x6B 写入 0x00让芯片退出睡眠模式接着配置采样率、陀螺仪量程和加速度计量程。这里贴一段初始化代码#include Wire.h const uint8_t MPU6050_ADDR 0x68; // AD0 接地时默认地址 void mpu6050_init() { Wire.begin(); Wire.beginTransmission(MPU6050_ADDR); Wire.write(0x6B); // 电源管理寄存器 Wire.write(0x00); // 退出睡眠 Wire.endTransmission(); Wire.beginTransmission(MPU6050_ADDR); Wire.write(0x1B); // 陀螺仪配置寄存器 Wire.write(0x08); // ±500°/s灵敏度 65.5 LSB/°/s Wire.endTransmission(); Wire.beginTransmission(MPU6050_ADDR); Wire.write(0x1C); // 加速度计配置寄存器 Wire.write(0x00); // ±2g灵敏度 16384 LSB/g Wire.endTransmission(); }0x6B 寄存器写入 0x00 是把芯片置为正常模式。0x1B 写入 0x08 对应 ±500°/s 量程如果保持默认 0x00 就是 ±250°/s此时换算系数是 131 LSB/°/s。0x1C 写入 0x00 对应 ±2g换算系数 16384。量程越大灵敏度越低手势控制用 ±2g 和 ±500°/s 足够没必要继续加大量程。读完配置后需要从 0x3B 开始连续读 14 字节里面是按顺序排列的加速度 X、Y、Z温度陀螺仪 X、Y、Z。读取函数int16_t ax, ay, az, gx, gy, gz; void mpu6050_read() { Wire.beginTransmission(MPU6050_ADDR); Wire.write(0x3B); Wire.endTransmission(false); Wire.requestFrom(MPU6050_ADDR, 14, true); ax (Wire.read() 8) | Wire.read(); ay (Wire.read() 8) | Wire.read(); az (Wire.read() 8) | Wire.read(); gx (Wire.read() 8) | Wire.read(); gy (Wire.read() 8) | Wire.read(); gz (Wire.read() 8) | Wire.read(); }这里Wire.endTransmission(false)表示不释放 I2C 总线requestFrom一次性请求 14 字节。温度两个字节被丢在gx之前但我们的读取顺序是从ax开始连续赋值的所以温度字节被自动跳过不会串位。Wire.read()返回的是无符号字节通过左移和或运算组合成 16 位有符号数再赋给int16_t。3.2 蓝牙发送协议的设计与串口缓冲遥控端拿到原始数据后不能直接发出去MPU6050 每 10ms 产生一组数据全发会让蓝牙拥塞。常见做法是先判断手势方向然后只发送一个单字节方向字符F前、B后、L左、R右、S停。这样协议简单可靠即使中间偶尔丢包下一次方向数据会覆盖不会导致小车失控。判断代码if (pitch -35) { bt.write(B); // 低头 后退 } else if (pitch 35) { bt.write(F); // 抬头 前进 } else if (roll -35) { bt.write(R); // 左倾 右转 } else if (roll 35) { bt.write(L); // 右倾 左转 } else { bt.write(S); // 静止 停止 }这里的bt建议用 SoftwareSerial因为 Uno/Nano 的硬件串口被 USB 占用。接在 D2/D3 的常见写法#include SoftwareSerial.h SoftwareSerial bt(2, 3); // RX2, TX3 void setup() { Serial.begin(9600); bt.begin(9600); }字符协议注意不要加换行蓝牙串口是透明传输多加\n会在小车端读到空命令。发送频率我控制在 50ms 一次也就是每次判断完方向如果有变化立即发送没有变化则每 500ms 发送一次心跳H用来让小车端知道链路还活着。心跳不是必需品但调试时很有用能区分“没收到数据”和“蓝牙掉了”。3.3 小车端接收控制信号并驱动电机小车端 Arduino 负责接收蓝牙字符并驱动电机。L298N 一般接两路输出PWM 使能口接 5、6 或 9、10。这里用一个驱动函数const int ENA 5; const int IN1 6; const int IN2 7; const int ENB 9; const int IN3 10; const int IN4 11; void motor_run(char cmd, int speed) { int left_speed 0; int right_speed 0; switch (cmd) { case F: left_speed speed; right_speed speed; break; case B: left_speed -speed; right_speed -speed; break; case L: left_speed -speed; right_speed speed; break; case R: left_speed speed; right_speed -speed; break; case S: default: left_speed 0; right_speed 0; break; } analogWrite(ENA, abs(left_speed)); analogWrite(ENB, abs(right_speed)); digitalWrite(IN1, left_speed 0 ? HIGH : LOW); digitalWrite(IN2, left_speed 0 ? LOW : HIGH); digitalWrite(IN3, right_speed 0 ? HIGH : LOW); digitalWrite(IN4, right_speed 0 ? LOW : HIGH); }这个函数把方向字符映射到左右轮 PWM。speed我一般取 180PWM 范围 0255不需要满速因为 MPU6050 的角度读取有延迟满速容易冲出视线。差速转弯时左轮反转、右轮正转车体原地转向。注意电机驱动板和 Arduino 必须共地否则数字信号参考电压不一致车轮会无故抖动。主循环用bt.available()收字符void loop() { if (bt.available()) { char cmd bt.read(); motor_run(cmd, 180); } }这是最朴素的轮询写法没有超时保护。我一般会在收到方向后记录last_cmd_time如果超过 300ms 没收到任何字符强制停车防止蓝牙断链后小车继续冲。4. 姿态解算与滤波从 MPU6050 原始值到稳定的手势指令4.1 加速度计与陀螺仪的互补滤波原理加速度计测的是比力静止时读数就是重力加速度但手晃动时会产生惯性加速度导致角度计算错误陀螺仪测的是角速度积分得到角度但角速度本身有零偏误差会不断累加。互补滤波的思想是让加速度计通过低通滤波器保留低频可信的重力分量让陀螺仪通过高通滤波器保留高频响应的角速度信息两者之和就是估计角度。嵌入式实现只需一阶互补滤波公式为angle alpha * (angle gyro * dt) (1 - alpha) * acc_angle。alpha 一般取 0.950.98alpha 越大越相信陀螺仪角度平滑但跟随慢alpha 越小越相信加速度计响应快但噪声大。对 mpu6050 姿态解算而言关键是先搞清坐标系否则互补滤波方向弄反小车会反向运动。4.2 代码中的一阶互补滤波实现下面是一段完整的姿态更新代码const float alpha 0.98; float pitch 0.0f, roll 0.0f; uint32_t last_ms 0; void update_attitude() { uint32_t now millis(); float dt (now - last_ms) / 1000.0f; last_ms now; // 加速度计角度单位度 float acc_pitch atan2(-ay, sqrt(ax * ax az * az)) * 180.0f / PI; float acc_roll atan2(ax, az) * 180.0f / PI; // 陀螺仪原始值除以灵敏度系数换算成度/秒 float gyro_pitch_rate gy / 65.5f; float gyro_roll_rate gx / 65.5f; // 一阶互补滤波 pitch alpha * (pitch gyro_pitch_rate * dt) (1.0f - alpha) * acc_pitch; roll alpha * (roll gyro_roll_rate * dt) (1.0f - alpha) * acc_roll; }注意这里用的是 ±500°/s 量程所以换算系数是 65.5如果初始化时用的默认 ±250°/s要改成 131.0。atan2的符号取决于 MPU6050 贴装方向我的定义是芯片正面向上、USB 口朝前X 轴往右Y 轴往前Z 轴向上因此 pitch 由ay和az计算roll 由ax和az计算。实际焊接角度不同符号可能相反调程序时先打印原始值手前倾看一眼 pitch 是正还是负再决定是否补负号。dt 单位是秒millis()返回毫秒直接除以 1000。alpha 与 dt 相关如果主循环有阻塞操作滤波截止频率会漂移所以loop里除了姿态更新不要放delay。4.3 阈值判断与死区设置拿到稳定的 pitch/roll 后下一步映射成手势指令。不能简单地拿 0 度作界限手在握持遥控器时本身有自然倾角MPU6050 融合后也有微小波动。我一般取 35 度作为触发角并加入滞后比较。参数表如下手势pitch/roll 条件控制字符备注前进pitch -35°F手向前倾斜后退pitch 35°B手向后仰左转roll 35°L向右翻转手腕右转roll -35°R向左翻转手腕停止|pitch|15° 且 |roll|15°S回中提示左转右转的符号会因握持方向不同而变我的表里是手握遥控器时右手边为 roll 正方向。死区的意义是当角度在 15 度内时强制输出停止避免点头动作被误判。滞后可以用两套阈值实现触发用 35 度释放回到 15 度以下才切换状态。实际调参时我用一组宏常量替代硬编码数字修改TRIGGER_ANGLE和RELEASE_ANGLE两个值就能调整手感不用在多个分支里找数字。5. 调参与验证蓝牙回环、MPU6050 零漂修正与 Wokwi 仿真5.1 蓝牙回环测试先把两块 HC-05 配对好遥控端设为主机小车端设为从机。最简单的验证方式是电脑用 USB-TTL 接一个小车端蓝牙另一片蓝牙接遥控端 Arduino上电后倾斜 MPU6050看串口助手是否输出 F/B/L/R/S。如果输出乱码先检查两边波特率是否一致HC-05 出厂可能是 9600 也可能是 38400用ATUART对齐后再试。注意 USB-TTL 也要设置同样的波特率。5.2 MPU6050 零漂修正陀螺仪零偏会让静止时角度缓慢漂移。常见做法是在 setup 里让模块水平静止采样 100 次求平均作为偏置量在读取后减去。加速度计不要做这种修正因为它要保留重力分量。校准代码float gx_offset 0, gy_offset 0, gz_offset 0; void calibrate_gyro() { const int N 100; long gx_sum 0, gy_sum 0, gz_sum 0; for (int i 0; i N; i) { mpu6050_read(); gx_sum gx; gy_sum gy; gz_sum gz; delay(10); } gx_offset gx_sum / N; gy_offset gy_sum / N; gz_offset gz_sum / N; }校准期间不能移动模块否则偏置会带进动态误差。在正式读取里用gx - gx_offset替换原始gx即可。5.3 用 Wokwi 仿真验证逻辑如果手上没硬件Wokwi 仿真平台支持 Arduino Uno 和 MPU6050可以直接虚拟串口观察输出。先把互补滤波代码放进去手动拖动传感器角度看串口是否输出预期的控制字符。仿真能验证协议逻辑和参数计算但不能替代真实蓝牙配对因为 Wokwi 的 HC-05 只是模型不涉及无线电环境。建议先仿真再上硬件。本文还有配套的精品资源点击获取
返回列表