这个基于Arduino的自动跟随机器人底盘项目,是我最近完成的一个很有意思的实践。它采用了6.5寸无刷轮毂电机作为动力核心,配合超声波传感器实现基础的跟随功能。整个系统成本控制在500元以内,却实现了相当不错的性能表现。
这个底盘最吸引我的地方在于它的"黄金比例"设计。6.5寸(约165mm)的轮径配合30cm的轮距,形成了一个非常理想的平衡点:足够大的轮径可以轻松越过2cm以下的障碍,而紧凑的轮距又保证了在狭小空间的机动性。在实际测试中,这个底盘可以承载5kg的负载,最高速度达到0.8m/s,完全满足大多数室内应用场景的需求。
这款轮毂电机是我从某宝上淘来的工业级产品,单价约150元/个。它的参数非常漂亮:
特别提醒:选购时一定要注意电机轴径。市面上常见的6.5寸轮毂电机有12mm和16mm两种轴径,我们项目选用的是16mm轴径版本,承重能力更强。
电机的安装出奇地简单,直接用四个M4螺丝固定在底盘两侧即可。由于是轮毂电机,省去了传统电机+轮子+联轴器的复杂装配过程。不过要注意两点:
我测试了三种常见的超声波模块:
最终选择了HY-SRF05作为主力传感器,主要考虑:
传感器布局很有讲究。单传感器方案只能安装在前方正中位置,而双传感器方案我推荐采用30°夹角安装,这样可以在不增加成本的情况下将水平探测范围从30°提升到60°。
整个系统的电源设计是项目成功的关键。我采用了三级供电方案:
code复制12V锂电池
├─ 第一级:1000μF电解电容(吸收电机启动冲击)
├─ 第二级:DC-DC降压模块(12V→5V,给Arduino供电)
└─ 第三级:LC滤波电路(给传感器供电)
这个设计解决了最让人头疼的电机干扰问题。之前尝试直接用Arduino的5V输出给传感器供电,结果电机一启动超声波读数就乱跳。加入LC滤波后(我用的是100μH电感+100μF电容),传感器工作非常稳定。
轮毂电机需要专门的BLDC驱动器。我测试了几种方案:
考虑到成本,最终选择了基于STM32的简易FOC驱动器,核心是DRV8313芯片。虽然比不上商业级产品,但配合SimpleFOC库已经能实现不错的效果。
接线时特别注意:
最基础的跟随算法其实很简单,就是保持与目标的固定距离。我用的是比例控制:
code复制error = 当前距离 - 目标距离
输出速度 = 基础速度 + Kp × error
但实际测试发现这种简单算法会导致机器人"抽搐式"移动。后来加入了两个改进:
完整的控制逻辑如下:
cpp复制float followControl(float currentDistance) {
const float targetDist = 50.0; // 50cm跟随距离
const float Kp = 1.2;
const float baseSpeed = 80;
float error = currentDistance - targetDist;
// 死区处理
if(fabs(error) < 5) return baseSpeed;
// 比例控制
float speed = baseSpeed + Kp * error;
// 限幅处理
speed = constrain(speed, 30, 150);
return speed;
}
单传感器只能测距,无法感知目标方位。加入第二个超声波传感器后,可以计算出角度偏差:
cpp复制float angleError = (leftDist - rightDist) / sensorSpacing;
其中sensorSpacing是两个传感器的安装间距(我的是15cm)。
实际实现时,我用了移动平均滤波来平滑传感器数据:
cpp复制class MovingAverage {
private:
float buffer[5];
int index = 0;
public:
float filter(float value) {
buffer[index] = value;
index = (index + 1) % 5;
float sum = 0;
for(int i=0; i<5; i++) sum += buffer[i];
return sum / 5;
}
};
基本的避障逻辑可以用有限状态机实现:
cpp复制enum State { FOLLOWING, OBSTACLE_AVOID, BACKING };
State currentState = FOLLOWING;
void loop() {
float frontDist = readUltrasonic();
switch(currentState) {
case FOLLOWING:
if(frontDist < 30) {
currentState = OBSTACLE_AVOID;
avoidStartTime = millis();
}
break;
case OBSTACLE_AVOID:
if(millis() - avoidStartTime > 500) {
currentState = BACKING;
}
break;
case BACKING:
// 后退逻辑
if(frontDist > 50) {
currentState = FOLLOWING;
}
break;
}
}
整个程序采用分层设计:
code复制└─ 主循环(10Hz)
├─ 传感器读取层
├─ 数据处理层(滤波、融合)
├─ 决策控制层
└─ 电机驱动层
关键代码结构:
cpp复制void setup() {
initSensors();
initMotors();
initCommunication();
}
void loop() {
static uint32_t lastControl = 0;
if(millis() - lastControl > 100) { // 10Hz控制频率
float distance = readFilteredDistance();
float speed = calculateSpeed(distance);
setMotorSpeed(speed);
lastControl = millis();
}
}
SimpleFOC库极大简化了BLDC控制:
cpp复制#include <SimpleFOC.h>
BLDCMotor motor = BLDCMotor(7);
BLDCDriver3PWM driver = BLDCDriver3PWM(9,10,11,8);
void setup() {
driver.voltage_power_supply = 12;
driver.init();
motor.linkDriver(&driver);
motor.init();
motor.initFOC();
}
void loop() {
motor.loopFOC(); // 必须实时运行
motor.move(target_velocity);
}
当加入IMU后,需要进行传感器融合:
cpp复制float fusedDistance = 0.7*ultrasonicDist + 0.3*imuPredictedDist;
这里使用了互补滤波,权重系数需要根据传感器精度调整。
调试PID参数时,我推荐以下步骤:
实测的参考参数:
cpp复制// 距离控制PID
float Kp_dist = 1.2;
float Ki_dist = 0.05;
float Kd_dist = 0.3;
// 角度控制PID
float Kp_angle = 0.8;
float Ki_angle = 0.02;
float Kd_angle = 0.15;
我开发了一个简单的调试协议,通过串口实时监控关键参数:
cpp复制void debugOutput(float dist, float speed) {
static uint32_t lastDebug = 0;
if(millis() - lastDebug > 200) { // 5Hz调试输出
Serial.print("Dist:");
Serial.print(dist);
Serial.print("cm Speed:");
Serial.print(speed);
Serial.println();
lastDebug = millis();
}
}
在串口绘图器中可以直观看到系统响应曲线。
症状:距离数据偶尔跳变很大
解决方法:
症状:电机一上电,整个系统重启
解决方法:
症状:机器人像喝醉酒一样左右摇摆
解决方法:
可以用OpenMV或ESP32-CAM实现简单的颜色跟踪:
python复制# OpenMV简单颜色跟踪示例
import sensor, image, time
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
while(True):
img = sensor.snapshot()
blobs = img.find_blobs([(0, 100, -128, 127, -128, 127)]) # 红色检测
if blobs:
largest = max(blobs, key=lambda b: b.pixels())
img.draw_rectangle(largest.rect())
使用RPLIDAR A1配合ROS可以构建简单的室内地图:
bash复制# ROS下启动建图
roslaunch slam_toolbox online_sync.launch
加入ESP8266实现WiFi控制:
cpp复制#include <ESP8266WiFi.h>
void setup() {
WiFi.begin("SSID", "password");
while(WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
if(WiFiClient client = server.available()) {
String req = client.readString();
// 解析控制指令
}
}
这个6.5寸轮毂电机底盘项目从硬件选型到算法实现,每一个环节都充满了工程实践的乐趣。它最吸引我的地方在于,用如此低的成本就实现了一套完整的移动机器人系统,而且性能完全超出预期。特别是在加入了IMU传感器融合后,跟随的平滑度有了质的提升。