手把手教你用Arduino制作自动清扫机器人:避障与状态机实战
最近在整理一个自动清扫小车项目时发现网上很多教程要么只讲单个传感器模块要么直接给你一大段代码却不解释为什么这么写。真正能从头到尾把清扫机器人讲清楚、照着做就能跑起来的完整教程并不多。这篇文章就把我整理的一套自动清扫机器人实战方案完整分享出来从硬件选型、工作原理、代码实现到调试排错全部走一遍。本教程适合三类读者一是正在做嵌入式课程设计的同学二是想入门 Arduino 和传感器开发的爱好者三是在做智能小车相关创新项目的开发者。跟着文章走完你能掌握电机驱动、PWM 调速、红外避障、状态机清扫策略等核心技能并得到一台可以自动避障、自动清扫的机器人原型。1. 项目背景与需求拆解1.1 智能清扫机器人是什么智能清扫机器人可以理解为“会自己跑的吸尘器”。它不需要人工遥控而是通过传感器感知周围环境自主决定前进、后退、转向同时带动底部清扫刷或吸尘机构把地面垃圾收集起来。它的核心并不是“清扫”这个动作而是“决策”。清扫刷谁都会转难的是让小车知道什么时候该直行、什么时候该转弯、遇到障碍物怎么绕开、怎样避免在同一个地方反复打转。这些决策逻辑才是清扫机器人项目的学习重点。1.2 本项目要完成的功能本文实现的清扫机器人原型包含以下功能功能项说明自动前进小车默认向前直线行驶红外避障检测前方障碍物自动停车并转向自动清扫底盘安装清扫刷随车轮转动持续工作掉头规避遇到墙面或家具时自动后退并转弯状态指示通过 LED 显示当前运行状态从功能上讲它已经具备一个成品扫地机器人的最基本行为逻辑。后续可以在此基础上增加遥控、电量检测、灰尘传感器联动等功能。1.3 技术选型为什么用 Arduino清扫机器人的主控方案很多常见的有Arduino 系列上手快资料多适合学习和原型验证。STM32 系列性能强适合产品级开发但调试门槛高。树莓派可以跑 Linux 和视觉算法成本高功耗大。ESP32 / ESP8266自带 Wi-Fi适合做联网设备但模拟引脚和 PWM 资源相对有限。对于第一次动手做清扫机器人的开发者Arduino 依然是最合适的方案。它的生态成熟库函数封装完善遇到问题能搜到大量解决方案。本教程以 Arduino 开发板为例重点讲解逻辑实现和硬件接线代码可以直接复制编译。2. 硬件组成与工作原理2.1 完整硬件清单先列出一份完整的硬件清单。所有型号均为常见规格采购时按名称搜索即可品牌不限参数以实际到手为准。模块规格示例数量用途主控板Arduino Uno / Nano1控制核心电机驱动板L298N 或 TB6612FNG1驱动直流电机直流减速电机3-6V 带减速箱2左右轮动力万向轮金属/塑料万向轮1支撑车身小车底盘亚克力智能小车底盘1承载所有模块红外避障模块HC-SR501 或 TCRT5000注意区分2前方和左/右侧避障超声波模块HC-SR041可选精确测距清扫刷组件直流电机 毛刷/叶片1清扫执行机构电池18650 电池组或 7.4V 锂电池1供电面包板与跳线若干1套电路连接LED 电阻5mm 红/绿 LED2状态指示注意区分红外避障模块和红外循迹模块。本教程使用的是“红外避障”模块它能输出高低电平检测前方是否有障碍物循迹模块则是检测黑线用的功能不同不要买错。2.2 电机驱动与 PWM 调速原理直流电机的转速控制不能直接通过 Arduino 的数字引脚完成原因有两个Arduino 引脚输出电流有限无法直接驱动电机。数字引脚只能输出高或低电平无法精确控制转速。因此需要电机驱动板来完成“功率放大”和“方向控制”。以 L298N 为例它的逻辑控制端连接到 Arduino输出端连接电机。Arduino 通过控制 IN1、IN2 的电平组合来决定电机正转、反转还是停止通过 ENA 引脚的 PWM 信号来控制转速。PWM 的全称是脉冲宽度调制简单理解就是通过快速切换高电平和低电平改变“在一个周期内高电平占的时间比例”这个比例叫占空比。占空比越高电机平均电压越高转速越快。// 设置数字引脚为PWM输出 pinMode(ENA, OUTPUT); // 用模拟写入设置占空比取值范围0-255 analogWrite(ENA, 150); // 大约59%的占空比在代码里analogWrite虽然后缀是 analog但它输出的并不是真正的模拟电压而是一串固定频率的方波。这一点初学者容易弄混在这里先记住结论analogWrite(引脚, 数值)用于控制 PWM 占空比值越大电机转得越快。2.3 红外避障模块的工作方式红外避障模块的核心是一对红外发射管和接收管。发射管持续发出红外光当障碍物靠近时红外光被反射回来接收管收到信号后模块上的比较器电路会输出一个低电平或高电平信号。不同模块的电平逻辑可能相反。有的模块是“检测到障碍物输出低电平”有的则是“输出高电平”。因此接线前先看模块背面的说明标签或者在串口监视器里打印数字引脚的值来判断。本教程示例代码以“检测到障碍物输出低电平”为默认逻辑如果你的模块逻辑相反把代码里读取引脚后的判断条件取反即可。2.4 清扫机构的设计思路清扫机构不复杂核心是一个小直流电机带动毛刷或叶片旋转。课程设计阶段可以直接用一个玩具电机加上一片圆形毛刷固定在底盘前部或中部。电机的一端接电池正极另一端通过三极管或电机驱动板的一个通道来控制。为了让清扫刷和整机电源联动建议把清扫电机也接到电机驱动板上这样 Arduino 可以独立控制它的启停。如果不想占用驱动板通道也可以用一个继电器模块由 Arduino 数字引脚控制继电器通断。3. 环境准备与开发工具3.1 开发环境本教程使用 Arduino IDE 进行开发。版本方面Arduino IDE 1.8.x 和 2.x 均可用界面略有差异但不影响操作。安装完成后在“开发板管理器”中选择你手头的主控型号。项目说明操作系统Windows / macOS / Linux 均可开发工具Arduino IDE 1.8.19 或 2.x主控板Arduino Uno / Nano / Mega编程语言Arduino C基于 C/C如果你的 IDE 语言环境不是中文不影响代码编写但建议把语言切换为中文方便阅读串口输出。切换方式是在 IDE 的 Preferences 或首选项里选择语言重启后生效。本文的代码没有使用第三方库全部基于 Arduino 标准库函数因此不需要额外安装库文件复制代码即可编译。3.2 示例项目目录结构一个完整的 Arduino 项目并不复杂。通常我们把所有代码放在一个.ino文件里也可以拆分为多个选项卡。对于清扫机器人这种中等复杂度的项目推荐把代码拆成几个文件便于阅读和修改SmartSweeper/ ├── SmartSweeper.ino // 主文件包含 setup 和 loop ├── motor.h // 电机驱动头文件 ├── motor.cpp // 电机驱动实现 ├── sensor.h // 传感器头文件 ├── sensor.cpp // 传感器读取实现 └── cleaner.h // 清扫策略实现不过Arduino IDE 对多文件项目的管理有一点特殊放在同一个文件夹下的所有.ino文件会被自动合并为一个主文件.h和.cpp文件需要手动添加。为了降低初学者的理解成本本文先用单文件的方式给出完整代码后续扩展部分再说明如何拆分。3.3 接线表接线是新手最容易出错的地方。下面给出 Arduino Uno 与各模块的接线对照表模块引脚Arduino 引脚L298N IN1左电机D8L298N IN2左电机D9L298N IN3右电机D10L298N IN4右电机D11L298N ENA左电机调速D5L298N ENB右电机调速D6L298N 12V 电源输入电池组正极L298N GND电池组负极和 Arduino GND 共地红外避障模块 1 OUTD2红外避障模块 2 OUTD3红外避障模块 VCC5V红外避障模块 GNDGND清扫电机控制D4LED 状态灯绿D12LED 状态灯红D13接线时最重要的一条原则是“共地”。电机驱动板的 GND 必须和 Arduino 的 GND 连接在一起否则传感器读取的电压信号没有统一的参考点会出现电平读取错乱、电机不受控等问题。很多初学者接完线发现传感器数值乱跳大概率就是共地没做好。4. 核心代码实现4.1 电机控制模块先写一个电机控制的基础函数。定义左右电机的控制引脚和 PWM 引脚并封装前进、后退、左转、右转、停止五个操作。// 文件路径SmartSweeper/SmartSweeper.ino // 左电机 #define LEFT_PWM 5 #define LEFT_IN1 8 #define LEFT_IN2 9 // 右电机 #define RIGHT_PWM 6 #define RIGHT_IN3 10 #define RIGHT_IN4 11 // 清扫电机 #define SWEEP_MOTOR 4 // 红外避障传感器 #define IR_LEFT 2 #define IR_RIGHT 3 // LED指示灯 #define LED_GREEN 12 #define LED_RED 13 // 默认速度0-255 #define BASE_SPEED 150 #define TURN_SPEED 120 void setupMotor() { pinMode(LEFT_PWM, OUTPUT); pinMode(LEFT_IN1, OUTPUT); pinMode(LEFT_IN2, OUTPUT); pinMode(RIGHT_PWM, OUTPUT); pinMode(RIGHT_IN3, OUTPUT); pinMode(RIGHT_IN4, OUTPUT); pinMode(SWEEP_MOTOR, OUTPUT); } void motorForward(int speedValue) { digitalWrite(LEFT_IN1, HIGH); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, HIGH); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorBackward(int speedValue) { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, HIGH); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, HIGH); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorTurnLeft(int speedValue) { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, HIGH); digitalWrite(RIGHT_IN3, HIGH); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorTurnRight(int speedValue) { digitalWrite(LEFT_IN1, HIGH); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, HIGH); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorStop() { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, 0); analogWrite(RIGHT_PWM, 0); }这里需要解释一下左右轮的差速转向原理。普通小车的转向并不是像汽车那样靠前轮偏转而是靠左右两个轮子的速度差来实现的。左轮停转、右轮继续转时小车会向左转弯左轮反转、右轮正转时小车可以原地转圈。上面代码中motorTurnLeft让左轮反转、右轮正转小车就会原地向左旋转这种方式在狭窄空间里非常实用。4.2 传感器读取与状态判断红外避障模块的逻辑比较简单读取数字引脚的值判断前方是否有障碍物。void setupSensor() { pinMode(IR_LEFT, INPUT); pinMode(IR_RIGHT, INPUT); } // 默认模块检测到障碍物输出LOW bool isLeftBlocked() { return digitalRead(IR_LEFT) LOW; } bool isRightBlocked() { return digitalRead(IR_RIGHT) LOW; }如果模块在你手上表现相反也就是无障碍物时输出 LOW、有障碍物时输出 HIGH那么把判断条件改成 HIGH就好。在正式编写清扫逻辑之前建议先用一个简单的测试代码把传感器输出打印到串口监视器确认逻辑方向void testSensorRead() { Serial.begin(9600); pinMode(IR_LEFT, INPUT); pinMode(IR_RIGHT, INPUT); } void loop() { Serial.print(Left: ); Serial.print(digitalRead(IR_LEFT)); Serial.print( Right: ); Serial.println(digitalRead(IR_RIGHT)); delay(200); }把这段代码单独上传到开发板用手在传感器前晃动观察串口输出。这一步能帮你省下后面排查逻辑错误的大量时间。4.3 清扫策略设计状态机清扫机器人最核心的部分不是电机函数而是决策逻辑。初学者最常见的写法是“一路 if-else 堆到底”比如if (左边有障碍) { 右转; } else if (右边有障碍) { 左转; } else { 前进; }这种写法在简单场景下能用但问题不少小车遇到障碍后会频繁左右抖动没有“后退一步再转向”的过程容易卡在墙角。更严重的是代码无法表达“当前正在执行什么动作”后续扩展会很痛苦。推荐的做法是引入状态机。状态机就是把机器人的行为划分为几个互斥的状态例如状态含义触发条件FORWARD正常前进清扫无障碍物BACKWARD后退脱离障碍检测到障碍后先执行TURN_LEFT左转躲避右侧或前方有障碍TURN_RIGHT右转躲避左侧或前方有障碍STOP停止收到停止指令状态机的好处是每个状态都有明确的进入条件和执行内容从一个状态切换到另一个状态时逻辑清晰不容易出现“左右轮打架”的情况。enum RobotState { STATE_FORWARD, STATE_BACKWARD, STATE_TURN_LEFT, STATE_TURN_RIGHT, STATE_STOP }; RobotState currentState STATE_FORWARD;4.4 状态机主循环逻辑下面完成主循环。为了让机器人动作更平滑这里加入一个简单的时间控制进入后退和转向状态后持续执行一段时间再切回前进状态。unsigned long stateStartTime 0; const unsigned long BACKWARD_DURATION 500; // 后退500ms const unsigned long TURN_DURATION 600; // 转向600ms void setup() { setupMotor(); setupSensor(); Serial.begin(9600); pinMode(LED_GREEN, OUTPUT); pinMode(LED_RED, OUTPUT); // 启动时保持绿灯表示正常运行 digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_RED, LOW); // 启动清扫电机 digitalWrite(SWEEP_MOTOR, HIGH); currentState STATE_FORWARD; stateStartTime millis(); } void loop() { bool leftBlocked isLeftBlocked(); bool rightBlocked isRightBlocked(); switch (currentState) { case STATE_FORWARD: motorForward(BASE_SPEED); if (leftBlocked rightBlocked) { // 两边都有障碍先后退再左转 currentState STATE_BACKWARD; stateStartTime millis(); } else if (leftBlocked) { // 左侧障碍右转 currentState STATE_TURN_RIGHT; stateStartTime millis(); } else if (rightBlocked) { // 右侧障碍左转 currentState STATE_TURN_LEFT; stateStartTime millis(); } break; case STATE_BACKWARD: motorBackward(TURN_SPEED); digitalWrite(LED_RED, HIGH); if (millis() - stateStartTime BACKWARD_DURATION) { currentState STATE_TURN_LEFT; stateStartTime millis(); } break; case STATE_TURN_LEFT: motorTurnLeft(TURN_SPEED); if (millis() - stateStartTime TURN_DURATION) { currentState STATE_FORWARD; digitalWrite(LED_RED, LOW); } break; case STATE_TURN_RIGHT: motorTurnRight(TURN_SPEED); if (millis() - stateStartTime TURN_DURATION) { currentState STATE_FORWARD; digitalWrite(LED_RED, LOW); } break; case STATE_STOP: motorStop(); break; } delay(50); }这里的关键函数是millis()。它返回开发板从开机到现在经过的毫秒数常用于非阻塞延时。不使用delay()的原因是delay()会阻塞整个程序在延时期间无法读取传感器也无法响应状态变化。用millis()做时间判断程序在等待的同时仍然可以处理其他逻辑。4.5 完整代码合并把上面的函数整合到一个.ino文件中就构成一个完整的可运行程序。完整代码如下// 文件路径SmartSweeper/SmartSweeper.ino // 自动清扫机器人完整代码 enum RobotState { STATE_FORWARD, STATE_BACKWARD, STATE_TURN_LEFT, STATE_TURN_RIGHT, STATE_STOP }; // 左电机 #define LEFT_PWM 5 #define LEFT_IN1 8 #define LEFT_IN2 9 // 右电机 #define RIGHT_PWM 6 #define RIGHT_IN3 10 #define RIGHT_IN4 11 // 清扫电机 #define SWEEP_MOTOR 4 // 红外避障传感器 #define IR_LEFT 2 #define IR_RIGHT 3 // LED指示灯 #define LED_GREEN 12 #define LED_RED 13 #define BASE_SPEED 150 #define TURN_SPEED 120 RobotState currentState STATE_FORWARD; unsigned long stateStartTime 0; const unsigned long BACKWARD_DURATION 500; const unsigned long TURN_DURATION 600; void setupMotor() { pinMode(LEFT_PWM, OUTPUT); pinMode(LEFT_IN1, OUTPUT); pinMode(LEFT_IN2, OUTPUT); pinMode(RIGHT_PWM, OUTPUT); pinMode(RIGHT_IN3, OUTPUT); pinMode(RIGHT_IN4, OUTPUT); pinMode(SWEEP_MOTOR, OUTPUT); } void setupSensor() { pinMode(IR_LEFT, INPUT); pinMode(IR_RIGHT, INPUT); } bool isLeftBlocked() { return digitalRead(IR_LEFT) LOW; } bool isRightBlocked() { return digitalRead(IR_RIGHT) LOW; } void motorForward(int speedValue) { digitalWrite(LEFT_IN1, HIGH); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, HIGH); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorBackward(int speedValue) { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, HIGH); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, HIGH); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorTurnLeft(int speedValue) { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, HIGH); digitalWrite(RIGHT_IN3, HIGH); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorTurnRight(int speedValue) { digitalWrite(LEFT_IN1, HIGH); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, HIGH); analogWrite(LEFT_PWM, speedValue); analogWrite(RIGHT_PWM, speedValue); } void motorStop() { digitalWrite(LEFT_IN1, LOW); digitalWrite(LEFT_IN2, LOW); digitalWrite(RIGHT_IN3, LOW); digitalWrite(RIGHT_IN4, LOW); analogWrite(LEFT_PWM, 0); analogWrite(RIGHT_PWM, 0); } void setup() { setupMotor(); setupSensor(); Serial.begin(9600); pinMode(LED_GREEN, OUTPUT); pinMode(LED_RED, OUTPUT); digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_RED, LOW); digitalWrite(SWEEP_MOTOR, HIGH); currentState STATE_FORWARD; stateStartTime millis(); } void loop() { bool leftBlocked isLeftBlocked(); bool rightBlocked isRightBlocked(); switch (currentState) { case STATE_FORWARD: motorForward(BASE_SPEED); if (leftBlocked rightBlocked) { currentState STATE_BACKWARD; stateStartTime millis(); } else if (leftBlocked) { currentState STATE_TURN_RIGHT; stateStartTime millis(); } else if (rightBlocked) { currentState STATE_TURN_LEFT; stateStartTime millis(); } break; case STATE_BACKWARD: motorBackward(TURN_SPEED); digitalWrite(LED_RED, HIGH); if (millis() - stateStartTime BACKWARD_DURATION) { currentState STATE_TURN_LEFT; stateStartTime millis(); } break; case STATE_TURN_LEFT: motorTurnLeft(TURN_SPEED); if (millis() - stateStartTime TURN_DURATION) { currentState STATE_FORWARD; digitalWrite(LED_RED, LOW); } break; case STATE_TURN_RIGHT: motorTurnRight(TURN_SPEED); if (millis() - stateStartTime TURN_DURATION) { currentState STATE_FORWARD; digitalWrite(LED_RED, LOW); } break; case STATE_STOP: motorStop(); break; } delay(50); }5. 编译烧录与运行验证5.1 编译与上传步骤代码写好后按以下步骤编译上传用 USB 线连接 Arduino 开发板和电脑。在 Arduino IDE 的“工具 - 开发板”中选择正确的型号。在“工具 - 端口”中选择开发板对应的串口号。Windows 下通常是 COM 开头macOS/Linux 下通常是 /dev/cu.usbmodem 开头。点击 IDE 左上角的“对勾”按钮编译代码确认没有语法错误。点击“右箭头”按钮上传代码到开发板。上传过程中开发板上的 RX/TX 指示灯会闪烁属于正常现象。如果上传失败常见的报错是“avrdude: stk500_recv(): programmer is not responding”这种问题通常和端口选择错误或开发板型号不匹配有关。5.2 供电注意事项清扫机器人有两个电源问题需要特别留意。第一是逻辑电源与电机电源的区分。Arduino 板上的 5V 引脚输出电流有限能带动的电流通常在 500mA 左右直接给 L298N 逻辑电路供电可以但直接给电机供电是不行的。电机必须由电池组通过驱动板供电。第二是电源必须“共地”。如果电池组给 L298N 供电Arduino 用 USB 供电两套电源之间没有连接 GND电机驱动板的逻辑信号就会不稳定。正确做法是L298N 的 GND 与 Arduino 的 GND 用一根跳线连起来。推荐使用 7.4V 锂电池组或两节 18650 串联给 L298N 供电L298N 的 5V 输出引脚再给 Arduino 的 5V 引脚供电这样整机只需要一组电池。接线关系如下电池正极 ---- L298N 12V 电池负极 ---- L298N GND ---- Arduino GND L298N 5V ---- Arduino 5V这种方式下开发板、传感器、电机驱动逻辑部分都由 L298N 的稳压输出供电结构更简洁。5.3 预期运行表现启动后小车应该保持绿灯亮直行前进。手放在小车前方约 15-20cm 处小车检测到障碍物后会执行以下动作红色 LED 亮起表示进入避障状态。小车后退约 500ms。原地左转约 600ms。红色 LED 熄灭恢复绿灯继续前进。如果障碍物只出现在左侧小车直接右转只出现在右侧小车直接左转。如果两侧同时有障碍小车后退后左转。整个过程不需要人工干预。这时候可以把一些小纸团、碎纸片放在小车前方观察清扫刷是否把垃圾拨到两侧或收集到集尘盒中。清扫效果取决于毛刷长度和底盘高度建议把清扫刷固定在离地面 5-10mm 的位置既能接触地面垃圾又不会被卡住。6. 功能扩展与算法进阶基础版本做完后可以继续从以下几个方向进行扩展难度循序渐进。6.1 增加遥控模式在代码中加入一个模式切换开关通过蓝牙模块HC-05/HC-06或 2.4G 遥控接收模块让小车在“自动清扫”和“手动遥控”两种模式之间切换。手动模式下使用手机蓝牙串口助手发送字符控制小车前后左右移动代码实现并不复杂void loop() { if (Serial.available() 0) { char cmd Serial.read(); switch (cmd) { case F: motorForward(BASE_SPEED); break; case B: motorBackward(BASE_SPEED); break; case L: motorTurnLeft(TURN_SPEED); break; case R: motorTurnRight(TURN_SPEED); break; case S: motorStop(); break; } } }注意蓝牙模块波特率通常出厂设置为 9600和Serial.begin(9600)保持一致。接收端要用软串口SoftwareSerial接蓝牙模块避免占用硬串口影响调试输出。6.2 增加灰尘检测功能给清扫机器人加一个灰尘检测传感器可以实现“检测到垃圾才启动清扫刷”的节能效果。灰尘传感器的原理很简单内置红外发射管和接收管当灰尘颗粒经过传感器凹槽时红外光被遮挡或反射输出信号发生变化。每种灰尘传感器的输出形式不同有的是模拟电压有的是脉冲频率需要查阅你手里的传感器手册确认读取方式。void loop() { int dustValue analogRead(DUST_SENSOR_PIN); if (dustValue DUST_THRESHOLD) { digitalWrite(SWEEP_MOTOR, HIGH); // 检测到灰尘启动清扫 } else { digitalWrite(SWEEP_MOTOR, LOW); // 没有灰尘暂停清扫 } }这种方式能减少清扫刷的磨损和电池消耗也更接近真实扫地机器人的工作逻辑。6.3 从固定路线到随机覆盖算法本教程基础版本采用的是一种简单的“遇障转向”策略这种方法的清扫覆盖率不够高小车可能在房间一部分区域反复转圈而其他区域扫不到。真实扫地机器人通常会结合“随机碰撞 弓字形规划”或“激光 SLAM 建图”来完成清扫。在 Arduino 原型阶段可以先实现一个改良版的随机路径策略每次转向的角度不是固定值而是在一定范围内随机选取。增加一个运行计时器每运行 30 秒执行一次原地旋转 360°改变清扫方向。记录每个方向的直行时间避免长时间朝同一个方向行驶后偏离清扫区域。// 随机转向角度示例 int randomTurnTime random(400, 1000); // 400ms到1000ms的随机转向时间使用random()时需要先调用randomSeed(analogRead(A0))设置随机种子否则每次上电产生的随机序列是相同的。6.4 代码模块化重构当代码越来越长时单文件方式会让维护变得困难。这时可以按照 3.2 节中的目录结构进行重构。Arduino IDE 对多文件项目的支持方式如下.h文件声明函数和全局变量。.cpp文件函数实现。.ino文件主逻辑。例如motor.h内容#ifndef MOTOR_H #define MOTOR_H #include Arduino.h void setupMotor(); void motorForward(int speedValue); void motorBackward(int speedValue); void motorTurnLeft(int speedValue); void motorTurnRight(int speedValue); void motorStop(); #endif拆分文件后主文件只需要#include motor.h即可调用电机控制函数。这种结构在课程设计的代码审查中也会加不少分。7. 常见问题与排查思路7.1 问题汇总表问题现象常见原因解决思路电机不转供电不足、接线错误、PWM引脚不对用万用表测电机两端电压检查IN1/IN2逻辑组合确认PWM引脚在支持列表内传感器数值一直不变模块供电异常、OUT引脚接触不良用串口打印数字值单独给传感器供5V电检查共地小车不走直线左右电机转速不一致在代码中为两侧设置不同速度补偿值检查轮胎磨损和电池电压红外检测距离过短或过长模块上的电位器没有调好用小螺丝刀旋转模块电位器调节检测阈值上传代码失败端口选择错误或开发板型号错误重新拔插USB线确认COM口号和板型电机转动时Arduino重启电机反电动势干扰电源在电机两端并联104电容确保电源线和信号线分开走线转向幅度不够TURN_DURATION太短加大转向时间或调低TURN_SPEED清扫刷不转清扫电机驱动通道未使能检查SWEEP_MOTOR引脚连接的电路确认驱动板对应使能引脚7.2 典型问题分析电机不转先排查软件再排查硬件这是调试的基本顺序。软件层面检查代码中电机控制引脚是否定义了 PWM 功能。Arduino Uno 上支持 PWM 的引脚是 3、5、6、9、10、11如果误用了其他引脚analogWrite()不会报错但不会输出 PWM 波形。硬件层面检查 L298N 的 12V 是否有输入电压。L298N 上有一个 12V 跳线帽如果拔掉了驱动板上的 5V 稳压输出会失效Arduino 就无法通过 L298N 的 5V 接口供电。另外留意 L298N 的 ENA 和 ENB如果跳线帽没有插在使能端PWM 信号无法到达电机驱动输出端。7.3 典型问题分析小车原地打转小车一直转圈说明两个传感器中至少有一个持续输出“有障碍物”信号。先通过串口打印判断传感器是否被外部物体挡住然后检查传感器安装位置。红外避障模块的有效检测距离一般在 3cm 到 30cm 之间如果安装角度朝下对着地面地面也可能被误判为障碍物。解决方法是调整模块的安装角度让红外发射方向水平朝前并避开驱动轮和底盘金属部分的反射。8. 工程实践与安全建议8.1 硬件安全注意事项锂电池供电时要防止正负极短路。建议在电池组输出端串联一个带保险丝的电池座或者在电源回路上加一个自恢复保险丝。接线之前先断开电源所有线路接好后再上电避免带电插拔导致驱动板烧毁。电机是感性负载在断电瞬间会产生反向电动势可能反向击穿驱动芯片。标准的做法是在每个电机两端并联一个 104 瓷片电容极性没有要求。如果驱动板本身带续流二极管则不需要额外处理。8.2 代码健壮性建议清扫机器人运行在真实环境中会面临传感器抖动、电机电压波动等问题。代码层面可以做以下增强传感器读取采用滤波处理避免瞬间误判。最简单的方法是连续读取几次以多数结果为准bool isLeftBlocked() { int count 0; for (int i 0; i 5; i) { if (digitalRead(IR_LEFT) LOW) { count; } delay(2); } return count 3; }主循环的delay(50)是为了让电机驱动有一定响应时间同时也起到简单的滤波作用。实际调节时可以根据传感器灵敏度和电机反应速度调整这个值。8.3 测试与验收方法课程设计或项目交付时建议制定一份简单的测试清单测试项操作预期结果直行测试开机不放障碍物小车直线前进不跑偏左侧避障测试在左侧挡一块硬纸板小车右转绕开右侧避障测试在右侧挡一块硬纸板小车左转绕开墙角脱困测试把小车放在墙角小车后退后转出墙角清扫效果测试地面撒碎纸屑清扫刷持续旋转并清扫垃圾续航测试充满电持续运行记录连续工作时长测试环境建议选择平整地面避免地毯和门槛。真实房间环境复杂可以先在围栏围出的测试区域内验证再放到开放区域跑。8.4 从原型到产品的差距本文完成的是清扫机器人的原型离产品还有一段距离。真实产品会考虑更多工程问题电池管理系统、充电底座对接、跌落传感器、尘盒容量检测、噪音控制、路径规划算法、OTA 固件升级等。但作为学习和开发入门来说理解传感器读取、执行器控制、状态机决策这一套完整流程已经足够了。后续无论是转 STM32 嵌入式开发还是学习 ROS 机器人操作系统这套基础逻辑都是通用的。9. 总结与下一步学习方向这个清扫机器人项目看起来代码不长但包含的知识点非常密集PWM 调速、电机驱动逻辑、红外传感原理、非阻塞延时、状态机思想。把这些点吃透再去看任何小车项目都会轻松很多。下一步你可以按自己的兴趣继续深入如果对硬件感兴趣尝试用 STM32 替代 Arduino自己写寄存器级别的 PWM 配置。如果对算法感兴趣研究扫地机器人路径规划从随机覆盖算法起步逐步接触栅格地图和 Dijkstra 寻路。如果对 IoT 方向感兴趣换上 ESP32把清扫机器人的状态上报到 MQTT 服务器用手机远程查看。动手做一台自己的清扫机器人比只看教程理解深得多。搭建好之后多跑几种场景把遇到的问题记下来再回来调整参数你会发现每次修改都是对这套系统更深一层的理解。