ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Arduino的自主移动机器人避障实战:从传感器到电机控制

基于Arduino的自主移动机器人避障实战:从传感器到电机控制 最近在手搓一台基于Arduino的自主移动机器人项目代号就叫AARAutonomous Arduino Robot。做了一圈下来发现这个项目远比想象中更能练手——从环境感知、电机控制到决策逻辑全都要自己搞定一整套“感知—决策—执行”的闭环流程跑通之后那种成就感是拼装玩具车给不了的。这篇文章我会把硬件选型、接线、代码实现、整机调试和踩坑过程完整复盘一遍给想自己做移动机器人的朋友一个可以直接抄作业的参考。先说清楚这个项目能做什么一台基于Arduino Uno的差速驱动机器人车头装超声波传感器通过舵机控制传感器左右扫测配合避障算法实现自主行走。不需要遥控器不需要外部计算机上电之后小车自己在屋里跑遇到障碍会自动停下、转弯、重新规划路线。硬件成本控制在150元以内软件部分全部用Arduino IDE开发适合刚入门电子DIY、想从点灯进阶到做实际运动控制项目的玩家。如果你手里已经有一块Arduino开发板剩下的零件加起来也就几十块钱性价比非常高。1. 项目整体设计与思路拆解1.1 为什么选Arduino做自主移动机器人主控很多人在做移动机器人时一上来就纠结该用树莓派还是STM32其实对于避障小车这个级别Arduino Uno是性价比最高的选择。Uno用的是ATmega328P8位AVR单片机主频16MHzFlash 32KB听起来参数很弱但跑一个超声波避障算法绰绰有余。关键优势是开发效率极高不需要配置复杂的交叉编译环境USB插上就能写代码库的生态也完整HC-SR04超声波库、Servo库都是开箱即用。用树莓派做当然也可以但成本高一个数量级启动还要几十秒本质上是把AI算力浪费在了“该不该转弯”这种小学生逻辑上。很多做ROS的坑其实是被应用场景带偏了——一个只靠超声波避障的机器人根本不需要跑Linux。Arduino的实时性反而更好不需要经过操作系统调度中断响应是纳秒级的对于电机控制和传感器采样这种硬实时任务反而更可靠。1.2 感知—决策—执行的闭环架构自主移动机器人最核心的设计框架就是一套闭环控制系统。我这次的AAR项目把整个系统拆成了三个层次感知层负责采集外界环境信息。用的是HC-SR04超声波传感器插在180度舵机上相当于给机器人装了一个能够“转头观察”的眼睛。舵机从最左转到最右可以扫出三个方向的距离数据左、中、右。这里有个设计细节需要注意舵机不是无脑连续转圈而是按角度定位扫描因为后面的避障算法需要的是“某个方向上是否有障碍物”这种离散判定而不是连续的距离图。决策层根据感知层的数据判断下一步动作。最简单的策略是“优先级判断”如果正前方安全距离内无障碍直行如果前方有障碍停下左右测距选择距离更大的一侧转向如果左右都堵死原地掉头。这套逻辑用状态机实现代码结构清晰后续要加功能也容易。执行层就是电机驱动。我用的是L298N驱动板加两路TT减速电机左边两个轮子并联接一路右边两个轮子并联接另一路通过PWM调速和方向引脚控制实现差速转向。这里要理解差速转向的原理左轮转右轮停车体就以左轮为中心原地右转左轮快右轮慢车体就以弧线向右偏转。小车的转向半径完全由左右轮的转速差控制这个原理搞清楚了后面调转向参数就非常顺。1.3 避障策略选型为什么不直接上SLAM有人可能会问现在SLAM、路径规划算法那么成熟为什么还要用这么“原始”的避障方案我当时的考虑是这个项目定位就是入门级的自主移动机器人目标是用最简单的方案跑通闭环逻辑而不是一步到位上激光雷达建图导航。SLAM至少要接激光雷达或者深度摄像头Uno的算力根本扛不住还得换成树莓派Jetson复杂度完全不是一个量级。更重要的是基础避障逻辑是所有移动机器人的前提。SLAM生成的地图再精确最后一步局部避障还是要靠类似的“测距—判断—转向”来实现。先把这个闭环跑熟理解了传感器误差、电机响应延迟、车体惯性这些物理特性以后再上SLAM很多问题就豁然开朗了。说实话AAR的避障策略虽然简单但改进空间很大——比如加多传感器数据融合、加入历史状态记忆、走记忆化回溯路线这些都是在现在的基础框架上可以做进阶扩展的。2. 硬件选型与参数计算2.1 完整零件清单下面是我实际用到的零件全部是国内电商平台能买到的普通型号零件型号/规格数量参考价格用途主控板Arduino Uno R3135-60元主控电机驱动L298N模块18-15元双路直流电机驱动直流电机TT马达带减速箱44-8元/个驱动车轮车轮65mm橡胶轮42-4元/个移动万向轮小球万向轮13-5元支撑超声波传感器HC-SR0413-6元距离感知舵机SG90 180度15-10元传感器转向扫描电池18650锂电池210-20元/节供电电池盒18650双节串联带线13-5元电源车架亚克力双层板/3D打印底盘1套10-30元安装平台跳线/杜邦线公母、公公若干若干5元连接面包板MB-10213元舵机供电转接电容470uF/25V铝电解11元电源滤波总价算下来在120到150元之间如果用面包板加纸板手搓底盘还能再压到100元左右。注意如果你手头只有Arduino Nano也完全可以引脚定义基本兼容只是供电和引脚数量略有差异我把关键引脚差异写进后续内容。2.2 电源系统的容量与功耗计算电源是整个项目最容易栽跟头的地方我第一版直接把电池接Arduino的Vin口结果舵机一抖主控就复位。这里认真算一遍功耗和供电方案。先看各模块功耗Arduino Uno工作电流约50mA稳压器AMS1117-5V输入电压范围6-20V推荐7-12VL298N驱动板逻辑部分约30mA但板载5V稳压器输出能力有限且发热严重电机空载时单个TT马达电流约150mA堵转时能飙到1.5A以上4个轮子正常巡航时大约600-800mAHC-SR04超声波模块工作电流约15mASG90舵机空闲时约10mA启动瞬间可达500mA堵转时甚至超过1A整体来看系统稳定运行时的平均电流在1A左右瞬间峰值可能到2A以上尤其是小车从静止状态启动时电机启动电流是额定电流的3到5倍。所以电源方案的设计余量至少要按2A来预留。我用的是两节18650锂电池串联标称电压7.4V满电8.4V单节容量约2200mAh。串联后总容量还是2200mAh但电压翻倍。按平均1A电流计算理论续航约2小时实际上因为电机频繁启停、瞬间电流大实测大概续航在1到1.5小时之间这个续航水平对调试和下楼测试来说完全够用。2.3 供电拓扑与稳压方案供电方式我最终采用“双轨供电”方案这是从多轮测试中总结出来的稳定组合第一条线路18650电池组直接进L298N的12V输入端子。L298N内部有电压调节器可以输出5V但我不建议用它给Arduino稳定供电。原因很简单L298N的5V输出电流能力弱带舵机大电流负载时电压跌得厉害而且电机堵转时产生的反电动势会顺着电源线反向冲击主控。实测在电机堵转瞬间Arduino的5V引脚上能看到明显的纹波尖峰。第二条线路L298N的5V输出只给超声波传感器供电。超声波模块是纯数字电路对电压纹波相对不敏感5V经过模块内部稳压后就能正常工作。Arduino和舵机的供电走另一路从电池组引一对线经过一个DC-DC降压模块或者7805线性稳压加散热片降到5V再分别接到Arduino的5V引脚和舵机的电源红线。舵机的瞬时大电流由DC-DC模块承担不会影响主控。这里还有一个关键技巧电池进Arduino之前并联一个470uF电解电容用来吸收舵机和电机带来的电压波动实测加上电容后主控复位的概率下降了80%以上。3. 软件实现与核心算法3.1 Arduino IDE环境与板卡准备代码开发用的Arduino IDE 2.x版本。如果你的IDE是1.8老版本也完全能编译本项目的代码语法保持兼容。需要注意的是Uno板卡驱动Windows 10以上系统通常可以自动识别不用额外装驱动。如果你在设备管理器里看到设备带黄色感叹号手动安装Arduino官方USB驱动即可。这里提醒一个常见的坑Arduino IDE 2.x安装后板卡包和编译缓存默认存在C盘用户目录如果你平时C盘空间紧张装完IDE再安装板卡包会占用不少空间。Uno板卡包很小ESP32和ESP8266的板卡包动辄几百MB甚至超过1GB而且国内下载经常超时失败。如果后续要玩ESP32建议直接把板卡包路径改到D盘。方法是在IDE的File-Preferences里面设置Sketchbook location把Arduino15目录通过符号链接挪到非系统盘。这个坑我在项目后期接ESP32做无线调试时踩过一次提前说给大家避雷。3.2 引脚分配与接线逻辑引脚分配是整个系统的骨架建议先规划好再动手接线否则后面调试时改线会非常痛苦。我的AAR项目接线如下模块信号Arduino引脚超声波Trig输出10us高电平触发D9超声波Echo接收回波高电平脉宽代表距离D8舵机信号线PWM控制角度的方波信号D10L298N IN1左侧电机正转D4L298N IN2左侧电机反转D5L298N IN3右侧电机正转D6L298N IN4右侧电机反转D7L298N ENA左侧电机PWM调速D3L298N ENB右侧电机PWM调速D11这里有个容易踩的坑D3和D11是Uno的PWM引脚但D3对应的是定时器2的OC2B通道D11对应定时器2的OC2A通道这两个通道共用一个定时器。如果你用analogWrite设置这两个引脚的PWM频率会受到delay等操作的影响在做精确调速时会出现左右轮PWM值不完全一致的现象。实际测试下来PWM调速效果还是能用的转速差控制基本可控。如果你要做更精细的速度闭环建议把电机接到D5、D6这样的定时器0引脚上或者后期可以把驱动方案换成TB6612FNGPWM精度会好很多。L298N的IN1到IN4控制电机方向ENA和ENB接收PWM信号控制速度。如果你想让电机转起来必须同时满足IN1和IN2设置成一高一低以决定方向ENA引脚有PWM波形。很多人只设置了IN引脚忘了给ENA设高电平结果电机纹丝不动这个细节千万注意。3.3 超声波测距的精确测量原理HC-SR04测距原理基于声波在空气中的传播速度。触发时Trig引脚需要被拉高至少10微秒模块内部会发出一串40kHz的声波脉冲。声波碰到障碍物反弹回来Echo引脚输出一个高电平高电平持续的时间就是声波往返的时间。距离计算公式是距离(cm) 高电平时间(us) / 58或者写成距离(cm) 高电平时间(us) * 0.034 / 2为什么除以58因为声波在摄氏20度空气中的传播速度约为343m/s也就是0.0343cm/us声波往返所以除以2计算结果再换算成厘米。所以34300cm/s × 1us 0.0343cm0.0343 / 2 0.01715cm/us取倒数约等于58.3。0.034 ÷ 2 × 1000000 / 100 170不对让我重新理清这个公式的推导。声速约343m/s 34300cm/s 0.0343cm/us。声波走一个来回的距离是2d所以时间t 2d / 34300单位秒换算成微秒就是t_us 2d × 1000000 / 34300。整理一下d t_us × 34300 / 2000000 t_us × 0.01715cm。所以其实t_us × 0.01715就是距离如果取0.034的一半就是0.017结果一致但是0.034是单程系数换算时把2放进去就是t_us × 0.034 / 2。两种写法本质一样。平时直接用distance duration * 0.034 / 2;就行温度变化带来的误差在室内环境下可以忽略。实际编码时我用了Arduino的pulseIn函数测量高电平时间这个函数在Uno上精度能到微秒级完全够用。需要注意pulseIn的超时参数默认超时是1秒建议显式设为5000us对应最大测量距离约85cm超过这个范围的“无障碍”情况就不浪费时间等待了。下面是我的超声波测距代码加入了3次采样取中位数的滤波逻辑const int TRIG_PIN 9; const int ECHO_PIN 8; float readDistance() { long duration; float distances[3]; for (int i 0; i 3; i) { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); duration pulseIn(ECHO_PIN, HIGH, 5000); if (duration 0) { distances[i] 200; // 超时表示探测距离很远给个安全值 } else { distances[i] duration * 0.034 / 2; } delay(30); // 每次测量之间的间隔避免回声干扰 } // 冒泡排序取中位数过滤掉瞬时跳变 for (int i 0; i 2; i) { for (int j i 1; j 3; j) { if (distances[j] distances[i]) { float temp distances[i]; distances[i] distances[j]; distances[j] temp; } } } return distances[1]; }3次采样取中位数看起来简单但实际效果很好。HC-SR04在探测到斜面、软质表面或者超声波吸收材料的时候回波信号会变弱导致Echo引脚偶尔输出异常脉宽中位数滤波可以把这种随机跳变过滤掉。缺点是测距速度变慢了一次完整的3次采样加上间隔延时需要约100ms对于低速移动的AAR来说自动驾驶时这个采样频率足够。3.4 舵机扫描与避障决策状态机避障算法的核心是状态机我用两个整型变量来管理状态一个是当前行为状态前进、停止、转向、后退另一个是记忆变量记录上一次转向的方向避免小车在死胡同里反复摇摆。先看舵机扫描函数让超声波传感器依次测量左、中、右三个方向的距离const int SERVO_PIN 10; const int SERVO_LEFT 0; // 舵机0度 朝左 const int SERVO_CENTER 90; // 舵机90度 朝正前方 const int SERVO_RIGHT 180; // 舵机180度 朝右 float distanceLeft, distanceCenter, distanceRight; Servo myservo; void scanAround() { myservo.write(SERVO_LEFT); delay(400); // 等舵机转到目标位置 distanceLeft readDistance(); myservo.write(SERVO_CENTER); delay(400); distanceCenter readDistance(); myservo.write(SERVO_RIGHT); delay(400); distanceRight readDistance(); }这个delay(400)很关键。SG90舵机从0度转到180度大约需要500ms如果采样太快舵机还没停在目标角度测出来的距离是“半路”的距离会导致前方明明没有障碍却误判。400ms是根据我实测的舵机响应时间定的如果你换用更快的MG90S或者金属齿轮舵机可以适当缩短到200ms但安全性起见新换舵机建议从400ms开始试。避障主逻辑是一个大型switch-case状态机核心代码如下const int SAFE_DISTANCE 30; // 安全距离30cm const int TURN_SPEED 160; // 转弯时的PWM值 const int DRIVE_SPEED 200; // 直行时的PWM值 int lastTurnDirection 1; // 记忆上一次转向方向1左转-1右转 void avoidObstacles() { scanAround(); if (distanceCenter SAFE_DISTANCE) { // 前方安全直行 moveForward(DRIVE_SPEED); } else { // 前方有障碍先停车再判断 stopMoving(); delay(200); if (distanceLeft distanceRight distanceLeft 20) { turnLeft(TURN_SPEED); lastTurnDirection 1; } else if (distanceRight 20) { turnRight(TURN_SPEED); lastTurnDirection -1; } else { // 左右都堵死掉头 turnLeft(TURN_SPEED); delay(500); } delay(600); // 保持转向状态确保车体转过足够角度 } }这段代码有几个细节值得展开讲第一转向条件里要求目标方向距离大于20cm避免出现“左边比右边远5cm但左边其实还是墙”的情况。比如左右都是20cm和18cm优先走哪边都一样危险不如直接掉头。20cm这个阈值是我实测得出的经验值因为AAR的车宽约15cm20cm可以保证车体能完整通过。第二转向后delay(600)是为了让车体完成足够的角位移。AAR左右轮距约14cmPWM值160时差速转向转过90度大约需要500到700ms。这个参数和车架宽度、轮子摩擦系数都有关系不同车架组装出来的数据差异很大需要实测调整。可以先设一个值跑两圈看是否撞墙再微调。第三记忆变量lastTurnDirection在后期的避障优化中会用到比如连续两次遇到同样方向堵死时就强制转另一侧防止进入“反复撞墙—转向—再撞墙”的死循环。AAR基础版代码里暂时只是记录扩展版可以实现更智能的惩罚机制。3.5 电机控制函数封装电机控制我封装成几个函数后续调用时思路很清晰const int ENA 3; const int ENB 11; const int IN1 4; const int IN2 5; const int IN3 6; const int IN4 7; void moveForward(int speed) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, speed); analogWrite(ENB, speed); } void turnLeft(int speed) { // 左轮停转右轮正转车体向左转 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); // 左轮制动 digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, 0); analogWrite(ENB, speed); } void turnRight(int speed) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); analogWrite(ENA, speed); analogWrite(ENB, 0); } void stopMoving() { digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); analogWrite(ENA, 0); analogWrite(ENB, 0); }注意turnLeft实现里左轮是让IN1和IN2都拉低L298N在这种状态下是刹车效果比让电机悬空停止不输出PWM更稳因为电机的电极被短接变成发电制动车体不会因为惯性继续滑动。moveForward时用analogWrite输出PWM调速。Uno的analogWrite参数范围是0到255对应占空比0%到100%。但实测下来PWM值低于100时TT马达的扭矩太小小车根本动不起来或者刚启动就卡住。所以我的驱动速度直接设到200留出一些余量给后续PID调速使用。转弯PWM设到160因为转弯时只需要一侧轮子驱动扭矩需求小一些。4. 整机装配与联调复盘4.1 底盘组装与传感器安装位置底盘我用的是双层的亚克力板上层放Arduino和面包板下层放电池盒和L298N。这种层叠结构重心低转弯时不容易翻车。前后各装一个万向球作为支撑点4个电机分别固定在底盘四角电机轴装轮子。这里有一个非常影响操控的细节4个电机的高速轴必须都在同一水平面上如果歪斜车辆行驶时会出现周期性抖动严重时差速转向完全失灵。安装时先把螺丝全部拧入一半位置把车放平在桌面上压一压确认四个轮子同时接触地面后再拧紧。超声波传感器装在舵机摇臂上舵机固定在车头正中央。注意舵机摇臂的长度决定了传感器转动的半径摇臂太长的话传感器转到两侧时会撞到车头边框所以安装前先手动转动舵机摇臂到两个极限位置确认没有干涉。传感器的高度离地面约8到10cm即可太高了测不到矮障碍物比如门槛太低了会被地面杂物误触发。这个高度我调试过很多次才定下来大家可以提前省掉这个摸索过程。4.2 代码上传与串口调试实战代码写完后用USB线把Arduino连接到电脑检查IDE底部状态栏是“Arduino/Genuino Uno在COM3”这样的信息。选好板和端口后点上传按钮第一次上传Uno程序前需要等IDE编译插件的初始化大概需要10到20秒这是正常现象。上传完程序后我强烈建议先别急着装上车跑用串口监视器把传感器数据打出来看看。在代码的loop里加几行调试输出Serial.begin(9600); loop() { scanAround(); Serial.print(L: ); Serial.print(distanceLeft); Serial.print( C: ); Serial.print(distanceCenter); Serial.print( R: ); Serial.println(distanceRight); delay(100); }打开串口监视器波特率选9600用手在传感器前方比划看三个方向的距离数字是否随手的移动而正确变化。这里有几个常见现象要心里有数距离值突然变成0说明传感器没收到有效回波先检查Trig和Echo两根线是否接反距离值稳定但明显偏大比如30cm实际量出来45cm多半是Echo引脚接错到非中断引脚但pulseIn其实不需要中断引脚所以更多还是传感器本身质量问题换一个对比试试。串口调试还有一个好处可以确认舵机扫描的时序。观察状态切换时距离值是否按左、中、右的顺序依次刷新如果顺序乱了说明scanAround函数的调用顺序或者舵机引脚定义有误。4.3 首次上路参数整定的关键过程把调试好的代码重新上传后把小车放在走廊里测试。走廊宽约1.2米两侧是墙前方放一个纸箱当障碍物这个场景非常适合验证AAR的避障逻辑。第一次测试会发生一个经典问题小车直行时略微向右偏跑5米左右就贴到右侧墙上。原因是四个电机的转速不可能完全一致左右两侧的TT马达减速箱摩擦系数有差异再加上电池电量下降也会导致PWM实际转速变化。解决办法有三种我按实用程度排序第一种是软件补偿在moveForward里给偏慢一侧的PWM加值。比如实测左轮慢就把左轮PWM加到210右轮保持200进行直行校准。这个值每次换不同的电池都可能要小幅调整所以我在代码里把它抽成一个常量LEFT_PWM_OFFSET调试时直接改。第二种是硬件层面的轮子对调把转速差大的两个轮子交换位置让左右轮转速差尽量趋近于零。这个方法精度有限但能给软件校准减少压力。第三种是上传感器的修正方案直接在前方加装一个GPS模块或者用OpenMV识别路径方向对于AAR级别的小车来说性价比太低而且增加系统复杂性得不偿失。如果是巡线小车直接用灰度传感器就能解决直行偏斜的问题避障小车没有外部参照物软件补偿已经是最好的平衡点。跑过直行测试后再测试避障转向。我碰到的问题是小车转向角度偏大原本想转90度避让实际转了120度导致转向后车头朝向错误。排查下来原因有两个一个是turnLeft函数里延时设置过长车体转过了头另一个是电机在PWM值160时转速不是理想状态实际角速度比我估算的大。修正方法很简单把延时从600ms缩到450ms再实测转弯角度循环几次就能找到合适的参数。5. 常见问题与排查技巧实录5.1 问题速查表把这几天调试过程中遇到的问题整理成了一张速查表按出现频率排序问题现象原因分析排查与解决电机完全不动L298N电源线未接好或ENA/ENB没设PWM先量L298N的12V和5V输出是否正常再用数字万用表测ENA引脚是否有约1.6V的PWM有效电压只能转向不能直行左右轮有一侧的IN引脚设置错误检查IN1到IN4接线一次只让一个方向的电机转逐个验证测距值跳变剧烈HC-SR04供电不稳定或探头角度偏单独给模块供电模块固定牢固后重新测检查探头是否有灰尘遮挡上电后舵机不停抖动舵机供电电流不足电压跌落单独用DC-DC给舵机供电加470uF电解电容检查舵机摇臂是不是被卡住小车自动复位重启电机堵转时反电动势冲击电源双轨供电给主控加稳压电容避免L298N的5V输出带大负载上传程序失败端口被占用或IDE版本问题换USB线插电脑其他口IDE 2.x文件损坏时重装检查Arduino的RESET引脚是否有电容误触Arduino IDE下载ESP32板卡失败国内网络访问官方服务器超时使用离线安装包或手动下载板卡文件放到Arduino15目录5.2 舵机抖动的“隐形元凶”SG90舵机在这个项目里是最容易被忽视的故障源。第一次测试时我发现舵机在扫描时严重抖动测距数据完全没法用。排查了很久才发现问题出在供电线上舵机电源线直接和超声波传感器共用Arduino的5V引脚舵机启动瞬间电流骤增Arduino板载AMS1117稳压器来不急响应5V被拉低到4.2V左右舵机控制芯片检测到电压异常就不断重启表现就是猛烈抖动。解决办法是把舵机电源从Arduino的5V上挪开接到DC-DC模块的5V输出上并在舵机电源两端并联一个大电容。实测470uF电解电容对舵机抖动改善非常明显。如果你手头只有一个电池组没有DC-DC模块也可以把L298N板载5V输出的GND和Arduino的GND相连然后把舵机电源线接L298N的5V输出。这么做的前提是L298N的供电电压不超过12V否则5V输出会过压损坏舵机。5.3 测距准度问题的深度排查HC-SR04测距不准很多新手第一反应是换传感器但很多时候问题出在结构和算法上。结构上传感器应该安装在车体的最前方探头垂直于前进方向不能有倾斜角。如果探头偏了声波打到障碍物上会反射到别处导致Echo超时测出200的“安全距离”小车就会直接撞上去。算法上我遇到过一种特别坑的情况舵机的金属齿轮在转动时会产生微弱的高频噪声这个噪声频率接近40kHz会被HC-SR04的接收探头误认为是回波信号导致测距结果时远时近。这种情况用中位数滤波能缓解一部分但根治方法是让舵机在测距时保持静止也就是我的代码里在readDistance前先延时等待舵机稳定这是物理层面解决噪声串扰的方法。另外要特别提醒HC-SR04的测量范围是2cm到400cm但说明书上写的最小盲区是2cm实际上在15cm以内测距值就已经明显失真了。所以在避障算法里即使前方距离值为0也要视为“极端危险”而不是“没有障碍物”这个逻辑错误差点导致小车撞墙。5.4 移动场景下的供电跌落问题整车联调时还会注意一个问题电量下降会导致性能突变。两节18650串联满电8.4V时L298N电机驱动口电压充足小车动力充沛电量降到7.2V左右时L298N逻辑部分电压不足会出现电机转动无力、舵机扫描变慢、超声波模块间歇性失灵等现象。最坑的是这些问题不是突然发生的而是随着奔跑时间逐渐加剧容易让人误判为程序bug。应对方案是给系统加一个电压监测功能用Arduino的模拟引脚A0接一个分压电阻把电池电压分压到0到5V范围内可以读取的电压值。在loop里隔一段时间读一次当电压低于设定阈值时让小车主动停车并闪烁板载LED提示。分压公式也很简单比如用两个10k欧电阻分压电池最高8.4V经过分压后到A0引脚的电压是4.2V在安全范围内代码里再用比例换算回真实电压值。5.5 L298N发热与散热问题L298N驱动板的发热问题在这个项目中非常明显。正常运行时我摸了L298N的芯片表面温度大约在50度左右手感发烫但还能摸得住。这主要是因为L298N是线性驱动芯片内部是双H桥结构电机电流通过时芯片上的压降约1.4V乘以电流就是发热功率。4个电机合计800mA电流时芯片发热功率约1.1W加上板载稳压器的发热整体温度偏高是正常的。可以接受的温度边界是芯片表面能连续摸5秒超过这个程度说明驱动板过载。解决办法有几个一是在L298N芯片表面贴一个铝制散热片或者用热熔胶粘一枚5角硬币辅助散热二是把电机的PWM频率降低一些。L298N本身支持20kHz的PWM频率但Uno的analogWrite默认是490Hz这个频率下L298N内部的开关损耗本来就偏大直接用默认值即可不会影响长时间运行三是考虑换TB6612FNG驱动模块它的内阻远低于L298N发热小很多体积也小只是驱动能力稍弱适合四驱这种平均电流不大的场景。6. 进阶扩展方向与我的体会6.1 从避障到巡线再到里程计AAR基础版做完核心闭环已经跑通我觉得最值得做的扩展方向有三个按难度递增排列方向一是巡线功能。在底盘前部加装2到3路灰度传感器阵列用数字量读取黑线或白线位置配合PID算法修正行驶方向。这个项目可以用同一个Uno完成不需要换主控代码量也不大适合想练PID控制的朋友网上叫“Arduino智能小车”的项目大多就是这个形态网上的参考代码和资料非常多。方向二是加OLED显示屏和按键菜单。把当前距离、行驶状态、电池电压实时显示在屏幕通过按键切换调试参数。这个扩展对代码的组织能力要求比较高需要把避障逻辑重构为可配置的状态机我已经在AAR上实现了部分功能效果非常好调参不用反复改代码重新烧录了。方向三是换ESP32做主控加上Wi-Fi远程控制和云端状态上报。ESP32的Arduino Core安装可以参考离线安装包的方式把开发板支持包下载到本地后手动指定目录安装能大幅提升安装成功率。ESP32性能比Uno强几个数量级双核240MHz自带的Wi-Fi和蓝牙可以直接做手机遥控处理传感器数据也更快。不过代码兼容性需要注意引脚编号不同analogRead的分辨率也不同不能直接硬搬AAR的代码。6.2 最终版本代码结构参考把完整的项目代码做一个最终整理结构如下#include Servo.h // 引脚定义 #define TRIG_PIN 9 #define ECHO_PIN 8 #define SERVO_PIN 10 #define ENA 3 #define ENB 11 #define IN1 4 #define IN2 5 #define IN3 6 #define IN4 7 #define VOLTAGE_PIN A0 // 运动参数 #define DRIVE_SPEED 200 #define TURN_SPEED 160 #define SAFE_DISTANCE 30 #define LEFT_PWM_OFFSET 0 Servo myservo; float distanceLeft, distanceCenter, distanceRight; int lastTurnDirection 1; void setup() { pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT); pinMode(VOLTAGE_PIN, INPUT); myservo.attach(SERVO_PIN); Serial.begin(9600); // 上电后先摆正舵机到正前方 myservo.write(90); delay(500); } void loop() { float batteryVoltage readBatteryVoltage(); if (batteryVoltage 6.8) { stopMoving(); Serial.println(LOW BATTERY!); blinkLED(5); return; } avoidObstacles(); delay(50); } float readBatteryVoltage() { int sensorValue analogRead(VOLTAGE_PIN); float voltage sensorValue * (5.0 / 1023.0); // 用分压比例换算回实际电池电压 return voltage * 2.0; }核心的readDistance、scanAround、avoidObstacles、运动控制函数我在前文已经给了实现组合起来就是一个可以直接编译运行的完整项目。6.3 做完项目后的一些实在话最后分享几个没有写在教程里的经验。第一自制机器人的乐趣有很大一部分在于调试参数一次调通反而不如失败几次来得印象深刻。我自己在AAR项目里花了两个晚上调舵机抖动问题最后发现只是电源接错这个教训让我从此养成了“先查供电再查代码”的习惯。第二写代码的时候尽量把参数抽成const常量切身体会到没有一个日志输出和参数配置界面后面的调参过程就是灾难级的痛苦。第三如果测试空间比较小一定要把安全距离设小一点同时把车速调低让小车在室内走廊测试时不至于因为制动距离不足而撞上墙。关于自主移动机器人我个人的体会是别急着堆硬件、上复杂算法一块Uno、一个舵机、四个电机就能让你把机器人最核心的“感知—决策—执行”闭环完整体验一遍。这个基础打牢了以后再往ROS、SLAM方向深入心里的底气是完全不一样的。如果有条件做完AAR基础版之后强烈建议自己再写一版纯状态机实现的代码不加任何库文件你会对底层逻辑有更深的理解。这个项目后续我还在继续扩展等OLED菜单和里程计版本稳定了再来分享第二轮实操记录。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进