简介:《智能扫地机器人课程设计》面向自动化、电子信息、嵌入式及相关专业学生,适合正进行课程设计、毕业设计选题或机器人入门实践的读者。内容围绕 AT89S52 主控展开,涵盖电机驱动、红外与超声波避障、充电站检测、路径规划、无线通信、TFT 液晶显示和独立按键交互,并对主控、驱动芯片与无线模块进行方案论证,可帮助梳理系统框图、硬件电路原理和程序设计思路。资源包共 1 个 PDF 文件,约 464KB,结构紧凑,便于阅读与打印。目前已有 390 人学习浏览,说明该题目在课程设计场景中具有一定关注度。内容对 L298、ULN2003、RF903 等选型分析,以及避障、自动回充、清扫模式切换等功能的讲解,能辅助读者形成从需求分析到电路设计、再到算法实现的完整设计框架。
1. 智能扫地机器人课程设计:从“能动”到“扫得干净”的分水岭
答辩现场最常见的画面,是一台小车在桌面上绕着矿泉水瓶转圈,转够三分钟老师喊停——因为它除了“会动”,没有任何可量化的指标。智能扫地机器人课程设计真正的分水岭不在电机能不能转,而在三个能被当场验证的数字:固定场地内的覆盖率、单位时间内的碰撞次数、低电量回充的成功率。它把一个综合题目压缩进一台小车:机械与底盘、嵌入式实时控制、传感器融合、路径规划与决策,一层都不能省。
近几年“具身智能机器人”成为热词,很多题目 PDF 里也开始出现感知—决策—执行分层的字眼。但对课程设计这个尺度来说,具身智能的落点很具体:底盘是本体,超声波与红外是感知,状态机与覆盖算法是决策,双轮差速是执行。这篇文章面向做智能机器人课程设计的学生和带课教师,按硬件最小系统、运动闭环、覆盖路径、联调验证的顺序,把每一步的参数怎么定、代码怎么写、坑在哪说清楚。
2. 智能扫地机器人的硬件最小系统:主控、传感器与电机驱动怎么定
选型阶段最容易犯的错是先买激光雷达再想主控,结果算力和供电都撑不住。合理的顺序是:先看题目 PDF 里写了哪些必做功能,再倒推主控和传感器。绝大多数课程设计的功能清单是“自主避障清扫 + 沿墙行走 + 低电回充”,这个清单用裸机 MCU 就能完成,不需要上 Linux。
2.1 主控与电机驱动的三类方案对照
| 方案 | 典型配置 | 算力与资源 | 适合的功能清单 | 主要短板 |
|---|---|---|---|---|
| 单片机裸机 | STM32F103C8T6 或同级 MCU | 72MHz,64KB Flash,无操作系统 | 避障、弓字形、回充 | 上激光建图基本无望 |
| 带无线 MCU | ESP32-S3 一类 | 双核 240MHz,带 PSRAM 与无线 | 需要上位机/手机联调 | ADC 抖动大,无线功耗高 |
| MCU + Linux 协处理 | STM32 负责执行,树莓派负责决策 | 应用处理器 1GHz 级 | 要跑 ROS、要建图 | 供电与调试链路复杂,成本翻倍 |
电机驱动比主控更容易被忽略。L298N 这类老模块压降大、发热明显,两节 18650 供电时电机端往往只剩 6V 上下,跑一会儿就掉速;TB6612 或 DRV8833 的导通电阻低得多,同样电压下爬坡和越障表现差别很大。驱动模块的供电必须与主控分开走一路,否则电机启动瞬间的浪涌会把 MCU 拉复位。
提示:如果题目里写了“具身智能”或“分层架构”,最省事的做法是把决策层放到协处理器上,MCU 只保留实时执行循环,两边用串口协议通信,答辩时分层讲得清,实现量也不爆炸。
2.2 传感器清单、安装高度与关键参数
避障传感器的安装高度直接决定误报率,这一栏比型号更重要。下表的参数是常见落地值,实际以实物实测为准。
| 用途 | 常见器件 | 安装位置 | 关键参数 | 常见坑 |
|---|---|---|---|---|
| 前方避障 | 超声波测距模块 | 车头中线,离地 120~150mm | 量程 2~400cm,需 60ms 冷却 | 对窗帘、斜面回波弱,连续触发要限频 |
| 侧向沿墙 | 红外测距(模拟输出) | 右侧,离地约 100mm | 有效 10~80cm,输出非线性 | 黑色踢脚线吸红外,读数虚高 |
| 悬崖检测 | 反射式光电对管 | 底盘前缘 4 个,离地 8~12mm | 数字或模拟输出 | 离地过高测不到地面,过低刮地毯 |
| 姿态与转角 | 六轴陀螺仪加速度计 | 车体几何中心,保持水平 | 量程 ±250~±2000 dps 可选 | 零偏随温度漂移,必须上电标定 |
| 里程计 | 霍尔或光电编码器 | 减速箱输出轴或电机尾轴 | 11~13 PPR | 只用单轮累计航向会越跑越偏 |
| 碰撞检测 | 微动开关或碰撞条 | 车头左右前角 | 常开触点 | 不做去抖会连续触发 |
轮径、轮距、编码器线数这三个机械参数必须在装配完成后实测,不要直接抄电机规格书。用游标卡尺量轮子外径,用卷尺量两轮中心距,误差 2% 就能让弓字形在 4 米长的房间里跑出明显的斜线。
2.3 上电自检代码:把故障挡在跑之前
每次改完接线都先跑自检,比跑起来再排查省时间。下面这段覆盖了悬崖误报、超声波无回波、电机接线反相三类高频故障。
// 上电自检:悬崖、避障、电机依次验证,任何一项失败就停在这一步 #define PIN_MOTOR_L_PWM 9 #define PIN_MOTOR_R_PWM 10 #define PIN_TRIG 7 #define PIN_ECHO 8 #define PIN_CLIFF_L A0 #define CLIFF_THRESHOLD 520 // 12 位 ADC:白地面实测约 720,悬空约 300 void setup() { Serial.begin(115200); pinMode(PIN_MOTOR_L_PWM, OUTPUT); pinMode(PIN_MOTOR_R_PWM, OUTPUT); pinMode(PIN_TRIG, OUTPUT); pinMode(PIN_ECHO, INPUT); selfTest(); } float readDistanceCm() { digitalWrite(PIN_TRIG, LOW); delayMicroseconds(3); digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG, LOW); // 20ms 超时约等于 3.4m,避免无回波时死等 unsigned long us = pulseIn(PIN_ECHO, HIGH, 20000UL); return us == 0 ? -1.0f : us * 0.0343f / 2.0f; } void selfTest() { if (analogRead(PIN_CLIFF_L) < CLIFF_THRESHOLD) { Serial.println("FAIL: cliff sensor on flat ground"); while (1); } float d = readDistanceCm(); if (d < 0 || d > 200) { Serial.println("FAIL: ultrasonic no echo"); while (1); } analogWrite(PIN_MOTOR_L_PWM, 90); // 8 位 PWM,约 35% 占空比,低于死区电机不转 analogWrite(PIN_MOTOR_R_PWM, 90); delay(600); // 这 600ms 用人眼确认两轮同向转动 analogWrite(PIN_MOTOR_L_PWM, 0); analogWrite(PIN_MOTOR_R_PWM, 0); Serial.println("SELFTEST PASS"); }pulseIn的第三个参数是超时时间,单位微秒,不设的话在无回波时会把主循环卡住,表现就是“车突然不动了”。系数 0.0343 是声速的 cm/us 换算值,除以 2 是因为声波走了往返。CLIFF_THRESHOLD必须现场标定:把车放在实际地面上读一次,再抬起来读一次,取两者中间偏地面一侧的值,取太靠中间会在地毯上误报。PWM 的 90 是试探性起点,具体值取决于电机减速比和电池电压,如果 90 不转就往上加,加到手能明显感觉到转动为止。
3. 运动控制与避障闭环:编码器里程计、PID 调速与沿墙清扫
避障和路径规划都建立在“底盘能按指令走准”这个前提上。很多课程设计卡在这一层:传感器没问题、算法也写对了,但车走不直,弓字形就变成锯齿形,覆盖率自然上不去。这一章把里程计、速度环 PID、避障状态机三件事按依赖顺序做完。
3.1 差速底盘的里程计模型与标定
差速底盘的运动学很简单:左右轮各走一段距离,用两轮差值算转角,用平均值算位移。难点全在标定上。
// 里程计更新:10ms 定时中断里调用,dTickL/dTickR 由编码器中断累加得到 const float WHEEL_DIA = 0.065f; // 轮径 65mm,卡尺实测 const float PPR = 13.0f; // 编码器每转脉冲数 const float GEAR = 30.0f; // 减速比 1:30 const float TICK_MM = (3.1415926f * WHEEL_DIA * 1000.0f) / (PPR * GEAR * 4.0f); const float WHEEL_BASE = 0.145f; // 两轮中心距 145mm float x = 0, y = 0, th = 0; // 位姿,单位 m 与 rad void updateOdometry(long dTickL, long dTickR, float gyroZ) { float dL = dTickL * TICK_MM / 1000.0f; float dR = dTickR * TICK_MM / 1000.0f; float dTheta = (dR - dL) / WHEEL_BASE; // 编码器与陀螺仪加权融合,抑制单侧打滑造成的航向漂移 float fused = 0.4f * dTheta + 0.6f * (gyroZ * 0.01f); th += fused; float dS = (dL + dR) * 0.5f; x += dS * cosf(th); y += dS * sinf(th); }TICK_MM里乘 4 是四倍频计数,如果代码只在单相上升沿计数就改成乘 1,否则里程会差 4 倍。融合权重 0.4/0.6 不是理论推导值,是跑五次 2 米直行后按末端横向误差调出来的经验值,打滑严重的场地可以再往陀螺仪一侧偏。下表是三项必做标定的判据。
| 标定项 | 方法 | 合格判据 | 不合格时的调整 |
|---|---|---|---|
| 轮径系数 | 直行 2000mm | 末端横向偏差小于 80mm | 按比例微调 TICK_MM |
| 轮距 | 原地转 10 圈 | 航向误差小于 5° | 调 WHEEL_BASE,每次 ±5% |
| 陀螺仪零偏 | 上电静置 2s 取 200 次均值 | 零偏小于 0.5 dps | 写入 Flash,开机直接加载 |
3.2 双轮速度环 PID:三个参数从零怎么调起来
调速环的输入是目标线速度,输出是 PWM 增量。课程设计里最容易出问题的是积分项:长直道上积分一路累积,一遇到障碍转向就“回冲”,车会猛地窜一下。
// 双轮速度环 PID,20ms 调用一次;目标速度单位 mm/s typedef struct { float kp, ki, kd; float i, lastErr, outMax; } PID; float pidStep(PID *p, float target, float actual, float dt) { float err = target - actual; p->i += err * dt; // 积分限幅,防止长直道结束后电机回冲 float iMax = p->outMax / (p->ki > 0.0001f ? p->ki : 1.0f); if (p->i > iMax) p->i = iMax; if (p->i < -iMax) p->i = -iMax; float d = (err - p->lastErr) / dt; p->lastErr = err; float out = p->kp * err + p->ki * p->i + p->kd * d; if (out > p->outMax) out = p->outMax; if (out < -p->outMax) out = -p->outMax; return out; }| 参数 | 起始值 | 作用 | 现象与改法 |
|---|---|---|---|
| kp | 0.8 | 决定响应速度 | 车轮啸叫抖动就减半;转向迟钝每次加 0.2 |
| ki | 0.15 | 消除左右轮稳态速度差 | 直线越跑越偏就加;来回摆动就减 |
| kd | 0.02 | 抑制超调 | 起步顿挫就加;噪声敏感先给速度加一阶滤波 |
调参顺序固定:先把 ki、kd 置零,kp 加到轮子刚开始振荡,退回该值的 60%;再加 ki 直到稳态误差消失;最后补一点 kd 压制起步超调。实际速度由编码器在固定 20ms 窗口内的脉冲数换算,不要用 PWM 值代替速度做反馈,电池电压下降时两者差异很大。
3.3 沿墙清扫与悬崖检测的状态机实现
避障逻辑用状态机写比用一堆 if 嵌套清晰得多。关键是把优先级排对:悬崖最高,碰撞次之,前方避障再次,沿墙跟随最后。
enum State { GO_STRAIGHT, FOLLOW_WALL, BACK_OFF, TURN }; State st = GO_STRAIGHT; void controlLoop() { float dFront = readDistanceCm(); bool cliff = (analogRead(PIN_CLIFF_L) < CLIFF_THRESHOLD) || (analogRead(PIN_CLIFF_R) < CLIFF_THRESHOLD); if (cliff) { st = BACK_OFF; return; } // 悬崖优先级最高,先退再转向 switch (st) { case GO_STRAIGHT: if (dFront > 0 && dFront < 250) st = FOLLOW_WALL; // 25cm 内进入沿墙 else setWheelSpeed(220, 220); // 220mm/s 直行 break; case FOLLOW_WALL: { float dSide = readSideIrCm(); float err = 120.0f - dSide; // 目标贴墙 12cm setWheelSpeed(180 + 1.2f * err, 180 - 1.2f * err); // 比例纠偏 if (dFront > 350) st = GO_STRAIGHT; // 前方开阔,回到直行 break; } case BACK_OFF: setWheelSpeed(-150, -150); delay(350); st = TURN; break; case TURN: setWheelSpeed(-160, 160); delay(500); st = GO_STRAIGHT; break; } }沿墙目标距离取 12cm,是因为侧向红外在 10~30cm 区间线性度最好,太近进入盲区。比例系数 1.2 是“毫米误差到毫米每秒”的映射,系数大了会贴着墙来回蹭,小了贴不上。delay写法的阻塞问题在答辩时一定会被问,标准回答是把延时改成定时器计时的非阻塞状态迁移,代码量增加不多。
注意:红外测距的输出是非线性的,直接用
analogRead值线性换算距离误差很大,稳妥做法是取 5~8 个标定点做分段线性或查表。
4. 覆盖路径与回充逻辑:弓字形、螺旋、分区在智能扫地机器人上的落地
避障解决的是“不撞”,覆盖解决的是“扫全”。这两件事在课程设计里经常被混在一起写,结果代码里到处是“遇到障碍就随机转个角度”,覆盖率自然只有六成。
4.1 三种覆盖策略的取舍
| 策略 | 20㎡ 空场 10 分钟覆盖率 | 重复率 | 代码量 | 适用场景 |
|---|---|---|---|---|
| 随机碰撞 | 60%~75% | 高且不可预测 | 约 50 行 | 家具密集的窄小空间 |
| 弓字形 | 88%~95% | 低 | 200~300 行 | 空旷、接近矩形的房间 |
| 螺旋加沿边 | 80% 左右 | 中 | 约 150 行 | 从房间中央起步 |
选哪种取决于题目要求。如果 PDF 里明确写了“覆盖率不低于 85%”,那就只能做弓字形,随机碰撞靠时间堆不出稳定结果。如果只要求“能避障清扫”,随机碰撞加碰撞后随机转向反而是最省事且不容易翻车的方案,答辩时如实说明策略与适用场景,比硬写一个跑不通的规划算法得分高。
4.2 弓字形路径的生成与换行逻辑
弓字形的本质是“沿主方向走直线,到边界后水平平移一个行距,反向再走”。行距设置是唯一需要仔细算的参数:大于机身宽度会漏扫,小于机身宽度重复率上升、耗时增加,取机身宽度的 0.8~0.9 倍比较合适。
// 弓字形覆盖:主方向直行,到达边界后平移一个行距再反向 const float LANE_PITCH = 300.0f; // 行距 300mm,机身宽约 340mm void boustrophedon() { while (true) { // 1) 沿主方向直行,直到前方 25cm 内有障碍或检测到悬崖 while (readDistanceCm() > 250 && !cliffDetected()) { straightByGyro(); // 陀螺仪锁航向,抵消左右轮差异 } // 2) 侧移一个行距:右转 90° → 前进 LANE_PITCH → 左转 90° turnToHeading(90); moveDistance(LANE_PITCH); turnToHeading(0); // 3) 侧向遇到长边障碍时,先沿墙走完这条边再回到弓字形 if (obstacleOnSide()) followBoundaryOnce(); } }straightByGyro是内层闭环,每 20ms 读一次陀螺仪航向,偏差超过 2° 就用左右轮差速纠回;turnToHeading用角度积分判断到位,而不是靠固定的delay,因为电池电压变化会让同样的延时转出不同角度。LANE_PITCH建议做成可调变量,联调时从 250mm 试到 350mm,用覆盖率数据挑最优值。实际房间里一定会有椅子腿这类障碍,撞到后局部补扫再回到主方向,比强行重规划整张图的工程代价低得多。
4.3 回充对接与电量阈值策略
回充是课程设计里最容易被砍掉的功能,也是答辩最容易加分的一项。电量分三档管理,思路是先保命再谈回充。
| 电量区间 | 动作 | 关键参数 |
|---|---|---|
| 大于 60% | 正常清扫 | 风机全速,行进速度 220mm/s |
| 20%~60% | 继续清扫,每到一个区域边界探测一次充电座信号 | 探测间隔约 30s |
| 小于 20% | 中止清扫,转入回充流程 | 关闭吸尘风机,限速 80mm/s |
// 红外回充对接:充电座发两束 38kHz 调制红外,左右接收头幅度相同时判定对准 int irL = readDockIr(LEFT); int irR = readDockIr(RIGHT); if (irL > 200 || irR > 200) { // 阈值按实测定,典型 150~400 float err = (float)(irL - irR); if (fabsf(err) < 30) { // 已对准,低速直行对接 setWheelSpeed(80, 80); } else { setWheelSpeed(60 - 0.4f * err, 60 + 0.4f * err); // 差速纠偏 } if (bumpDetected()) stopAndCharge(); // 碰到充电触点即为到位 }调制红外必须用定时中断连续采样取峰值,主循环里直接analogRead会被室内灯光和阳光干扰,表现就是白天对不上、拉上窗帘就好了。对准窗口取 30 是左右 ADC 的差值,调太小在远处永远进不了窗口,调太大又会以歪斜姿态撞上去导致接触不良。对接阶段关掉风机和边刷,一是省电,二是避免边刷把车头顶偏。
5. 联调与答辩:用日志和覆盖率数据把智能扫地机器人课程设计做实
跑通不等于能交付。答辩时老师最常问的两句是“你怎么知道覆盖率是多少”和“出故障你怎么定位”。这两件事都靠同一套东西解决:位姿日志加一套可复现的验证流程。
先给串口输出定格式,每 100ms 打印一行时间, x, y, 航向,同时把状态机当前状态和几个关键传感器读数一起打出来。跑完一轮清扫,把日志存成 CSV,用下面这段脚本按 30cm 网格离散化,算出真实覆盖率。
# 覆盖率统计:把位姿日志按 0.3m 网格离散化,统计被覆盖的格子比例 import sys CELL = 0.3 grid = set() for line in sys.stdin: # 每行格式: t,x,y,th parts = line.strip().split(',') if len(parts) != 4: continue x, y = float(parts[1]), float(parts[2]) grid.add((int(x / CELL), int(y / CELL))) # 机身按 30cm 网格计入 room_cells = 20.0 / (CELL * CELL) # 20 平方米房间的总格数 print("coverage = %.1f%%" % (100.0 * len(grid) / room_cells))网格边长取 0.3m 是折中,接近一般课程设计小车的机身宽度,取太细会把机身宽度带来的覆盖算漏。同一段日志还能反过来验证里程计精度:把日志里的终点坐标和卷尺实测坐标对比,偏差超过 150mm 就说明轮径系数或陀螺仪零偏没调好。
下面这张表是联调阶段最高频的五类故障,建议直接抄进课程设计报告的“调试记录”一节。
| 现象 | 大概率原因 | 验证方法 |
|---|---|---|
| 弓字形越跑越斜 | 陀螺仪零偏未标定或轮径系数偏差 | 静置读零偏值,直行 2m 量横向偏差 |
| 沿墙贴不上或撞墙 | 红外受深色踢脚线影响 | 用黑胶带贴在传感器前对比读数 |
| 回充在 30cm 处打转 | 红外阈值被阳光或灯光干扰 | 遮光后重测阈值,给接收头加遮光筒 |
| 白地毯上突然停车 | 悬崖反射率低于阈值 | 实拍地面 ADC 值,重新定阈值 |
| 电机一给 PWM 主控复位 | 电机启动电流拉低主控电压 | 主控与电机分开供电,就近加 470µF 电容 |
答辩陈述里加一句分层说明会很占便宜:感知层是超声波、红外与陀螺仪,决策层是状态机加覆盖策略,执行层是双轮差速与风机。这套说法正好对应具身智能机器人的经典分层,讲清楚每层的输入输出和调试手段,比展示一堆代码截图有说服力。最后留一个可现场演示的细节:把陀螺仪零偏在每次上电静置 2 秒内取 200 次采样求均值并写入 Flash,下次开机直接加载,弓字形跑偏的概率会明显下降,这一条改动量不到二十行,却是评委最容易验证的改进点。
本文还有配套的精品资源,点击获取