ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STC双单片机的扫地机器人:电机驱动、避障循迹与人机交互实现

基于STC双单片机的扫地机器人:电机驱动、避障循迹与人机交互实现 简介这份设计报告围绕基于51单片机的扫地机器人展开适合电子、自动化、机器人方向的本科生及竞赛爱好者作为课程设计或毕业设计的参考资料。报告从设计要求入手区分基本指标与拓展功能并系统梳理了微控制器、电机驱动、感知、避障与人机交互等模块的设计思路同时通过方案对比说明各模块的选型依据还覆盖了储存、时钟、LCD及底盘制作等实践细节。资源为单个docx文档共1个文件压缩包大小641KB内容目录完整便于按章节查阅。报告还包含程序流程图、理论分析与计算、整体框架分析等内容从需求到实现层层递进便于学习者直接参考设计流程和论证方法。目前已有78人学习使用尤其适合需要快速搭建扫地机器人整体框架并了解关键模块选型逻辑的读者。1. 双芯片架构与板级选型这样分工的扫地机器人不怕 IO 不够第一次看到这份报告时最吸引我的不是“扫地”功能而是它用了两片单片机STC12C5A60S2 管显示与人机交互STC89C52RC 管电机与传感器。这个选择直接解决了单芯片 IO 口不足的问题——L298N 电机驱动要占 4 个输入红外避障和 TCRT5000 循迹又要占 46 个再挂 LCD12864、DS1302 和独立键盘一片 52RC 的 32 个 IO 根本不够分。方案比较里还提到一个容易被忽略的细节STC12C5A60S2 在相同晶振下运行速度是传统 51 的 812 倍让显示模块单独跑 12864 刷新和按键扫描不会拖累电机的实时响应。这个项目适合两类人一类是准备电子设计竞赛、正纠结“到底用 51 还是 STM32”的在校生另一类是想要快速搭出一台功能完整的小车、又不想一上来就碰复杂库函数的嵌入式入门工程师。下文把这份报告拆开讲重点放在电机驱动、避障循迹、人机交互这三个能直接复现的部分。2. L298N 电机驱动与 PWM 调速前进、后退、转向的实现边界2.1 为什么选 L298N 而不是 TB6612 或自己搭 H 桥报告在模块选型里明确选了封装好的 L298N 模块理由是“高电压大电流全桥驱动、响应频率高、一片能控两个直流电机、带使能端”。从实际使用的角度看L298N 最大的优势是模块上自带 5V 稳压输出能把 612V 的锂电池电压转成 5V 给单片机和传感器供电。这意味着整机只需要一组电源不用再单独给逻辑电路配降压芯片。对比常见替代方案TB6612FNG 的内阻更小、体积更小、效率更高但它的逻辑电源和电机电源是分开的而且最大电机电压只有 13.5V更适合电池电压偏低的小型小车。自己用三极管或 MOS 管搭 H 桥虽然成本低但两个问题很难绕开——一是没有续流二极管保护时电机反向电动势容易打坏单片机 IO二是“死区时间”控制不好上下桥臂直通会直接烧管子。L298N 模块把这些细节都集成好了可靠性优先时选它是对的。2.2 使能端与输入端的电平逻辑L298N 控制直流电机的逻辑表是所有调试的基础搞错这一层后面所有代码都是白写输入 IN1输入 IN2使能 ENAPWM电机 A 状态高电平低电平高电平正转低电平高电平高电平反转低电平低电平高电平刹车任意任意低电平停止报告里提到的“对电机驱动输入不同的使能信号使电机实现正反转动”指的就是给 IN1、IN2 分别赋高低电平而“通过 PWM 信号调节电机转速”则是把使能端当作 PWM 输入脚。这里有一个常见误解很多人把 PWM 接到 IN1 或 IN2 上发现电机根本转不起来——因为 IN1/IN2 只认逻辑电平而 L298N 的使能端才接受 PWM 占空比。下面这段是 STC89 控制电机正反转和调速的核心代码片段对应报告里“小车前进后退”和“车轮转速不同实现转向”的部分// 电机 A 接 L298N 的 OUT1/OUT2使能端 ENA 接 P1.0 sbit ENA P1^0; sbit IN1 P1^1; sbit IN2 P1^2; // 软件 PWM占空比 0~100通过延时实现 void motorA_speed(unsigned char speed, bit direction) { if (speed 100) speed 100; // 限幅防止占空比越界 if (direction 1) { // 正转 IN1 1; IN2 0; } else { // 反转 IN1 0; IN2 1; } // 以 100 为周期输出 PWM for (unsigned char i 0; i 100; i) { if (i speed) ENA 1; // 高电平时间比例 speed/100 else ENA 0; // 延时约 0.1ms整个周期约 10ms频率 100Hz 左右 delay_us(100); } }这段代码的逻辑说明direction位决定 IN1/IN2 的组合speed决定使能端在一个 PWM 周期内高电平的占比。delay_us(100)控制每个电平的保持时间100 次循环后完成一个完整周期。这样做的意义在于——不需要开启定时器中断也可以实现调速代码更容易读懂。但它的缺点是 CPU 被延时占满如果后续要同时处理避障传感器扫描软件 PWM 会堵塞主循环。更稳妥的做法是使用 STC89 的定时器 0 中断产生 PWM或者像这份报告一样让 STC12 用硬件 PCA 模块输出 PWM把 CPU 释放出来。这里特别提醒一点L298N 的 ENA 使能端如果悬空部分模块内部默认是高电平导致电机在上电瞬间就转所以设计 PCB 或接线时一定要给使能端加下拉电阻或者先初始化 IO 为低电平再给电机供电。2.3 双电机差速转向的边界报告测试表里写着“能够转向”但没讲清楚转向是怎么实现的。从电机驱动逻辑看这属于典型的双电机差速转向左轮和右轮各由一个 L298N 通道控制转向时让左右轮速度不同。比如左转时右轮高速正转、左轮低速正转车头就会向左偏。这种转向方式的极限在于——如果两个轮子的 PWM 占空比之差太小小车只会走弧线走不出直角弯如果差太大内侧轮可能会因为负载不均而丢步直流电机表现为转速骤降。实际调试时建议从“左右占空比差 20”起步比如左 80 右 60观察小车转弯半径再逐步调整。3. 红外避障与 TCRT5000 循迹阈值调节和检测距离标定的处理细节3.1 红外避障模块的安装位置与电位器标定报告用于避障的是带电位器的红外传感器模块发射管发射红外线遇到障碍反射回来被接收管接收比较器处理后输出低电平。这类模块常见的型号是 LM393 比较器的“红外避障传感器”输出低电平有效板上有一个蓝色电位器调节检测距离有效范围 230cm。安装位置直接影响避障效果——探测头的高度要和被测障碍物墙壁、纸箱的中部对齐如果装得太低会漏检高于底盘但低于传感器的障碍物如果装得太高又检测不到地面上的小障碍物。报告中的场地是 2m×2m 围墙区域墙面材料是硬质塑料或木板红外反射率较好实际检测距离可以调到 15cm 左右再留出余量。调节方法是把障碍物放在距离小车 20cm 的位置缓慢旋转电位器直到指示灯恰好点亮再把障碍物移远确认指示灯熄灭。这一步必须反复做至少三次因为电位器的灵敏度和环境光线相关白天和晚上调出的结果会不一样。3.2 TCRT5000 循迹模块的反射差异TCRT5000 是反射式光电传感器内部集成了红外发射管和光敏三极管。当发射的红外线被反射回来且强度足够大时光敏三极管饱和模块输出高电平指示灯点亮当红外线被黑色胶布吸收、反射强度不足时输出低电平。这个传感器的工作距离很近一般在 23cm 才稳定所以安装时必须贴近地面。报告拓展要求里“清扫之后回到用黑胶布围成的 30cm×30cm 起点区域”靠的就是这路信号的变化小车扫描到黑线后再前进一小段时间就停车。3.3 避障与循迹的代码冲突避障和循迹其实是逻辑互斥的——避障时读取的是红外避障模块的 IO循迹时读取的是 TCRT5000 的 IO它们都占单片机 IO 口。如果不做逻辑判断就同时扫描可能出现避障传感器误触发导致小车突然转向。一个常用的做法是设置一个“任务状态位”sbit obstacle_sensor P2^1; // 红外避障传感器输出低电平表示检测到障碍 sbit tracking_sensor P2^2; // TCRT5000 输出高电平表示检测到黑线 bit obstacle_flag; // 避障任务标志 bit tracking_flag; // 循迹任务标志 void sensor_task(void) { if (obstacle_sensor 0) { // 障碍信号有效 obstacle_flag 1; // 挂起避障任务 tracking_flag 0; // 禁止循迹避免误判 } if (tracking_sensor 1) { // 检测到黑线 if (obstacle_flag 0) { // 无避障任务时才可以循迹 tracking_flag 1; } } }这段代码的核心逻辑是避障事件拥有更高优先级一旦触发就屏蔽循迹通道。因为小车在清扫过程中如果既遇到墙又遇到黑线比如黑胶布贴在了靠近墙的位置先避障还是先循迹直接决定了能否正常回到起点。报告里“若检测到黑线即会输出电信号单片机识别电信号后行走一小段时间后便停止行动”描述的停车逻辑可以在sensor_task函数里加一个状态计数器来实现比如连续检测到黑线 5ms 才确认防止地面杂色干扰。4. LCD12864 与 DS1302 时钟把时间设置和人机交互界面真正用起来4.1 12864 四线串行接法与初始化代码LCD12864 并口方式需要 8 个数据 IO 加 3 个控制 IO共 11 个口串行方式PSB 接地只需要 SCLK、SDA、RSCS三个信号大幅节省 IO。报告里选择 12864 的理由是“能够显示简单字符和自定义图片、屏幕大、显示信息多”并且明确使用“LCD 自带字库”这意味着不用维护中文字模表直接送 GB2312 编码的汉字内码即可显示。硬件接法上PSB 引脚必须接低电平才能进入串行模式否则模块默认并口模式串行命令全部无效——这是新手最容易踩的坑。下面是典型的 12864 串行写字节与初始化序列// 接线定义SCLKP3^4, SDAP3^5, RS(CS)P3^3, PSB 接 GND sbit LCD_RS P3^3; sbit SCLK P3^4; sbit SDA P3^5; void lcd12864_write_byte(uchar dat) { uchar i; for (i 0; i 8; i) { SCLK 0; if (dat 0x80) SDA 1; // 高位先出 else SDA 0; SCLK 1; // 上升沿锁存数据 dat 1; } } void lcd12864_cmd(uchar cmd) { LCD_RS 0; // RS0 表示传输命令 lcd12864_write_byte(0xF8); // 串行模式命令起始位 lcd12864_write_byte(cmd 0xF0); // 高四位 lcd12864_write_byte(cmd 4); // 低四位 } void lcd12864_init(void) { delayms(50); // 等待模块上电稳定 lcd12864_cmd(0x30); // 基本指令集模式 delayms(2); lcd12864_cmd(0x0C); // 显示开光标关 delayms(2); lcd12864_cmd(0x01); // 清屏 delayms(10); }代码逻辑说明串行模式下每次数据传输需要先发送 5 个字节的起始序列这里用 0xF8 表示命令传输0xFA 表示数据然后分两次传 4 位数据。cmd 0xF0和cmd 4的做法把 8 位命令拆成高四位、低四位各传一次。lcd12864_cmd(0x30)这个参数很关键——0x30 选基本指令集、8 位数据长度0x36 才会切到扩充指令集。如果屏幕上出现乱码或者完全不显示优先检查这行参数是否被写成了 0x34 或 0x36因为 12864 在串行模式下只能用基本指令集写文本。报告提到“初始化函数显示液晶屏固定显示部分”指的是把“扫地机器人”“ON: 00:00:00”“OFF: 00:00:00”“TIME: 00:00:00”这些字符串一次性写入 DDRAM后续只局部刷新时分秒和电量区域而不是每次都全屏重画。4.2 DS1302 的 BCD 码转换与读写时序报告里“DS1302 对时间数据的存储是 16 进制故读取时钟数据时需将其转化为 10 进制再分别取时分秒的十位数和个位数”——这里的“16 进制”其实是 BCD 码。DS1302 的秒、分、时寄存器都按 BCD 编码存储比如十进制 23 秒存成 0x23读出来直接显示会变成 350x23 的十进制值所以必须做转换。读写 DS1302 的基础代码sbit SCLK P3^4; // 时钟线上升沿写入下降沿读出 sbit SDA P3^5; // 数据线双向 sbit RST P3^6; // 片选高电平有效 // 写一个字节从低位开始 void ds1302_write_byte(uchar dat) { uchar i; for (i 0; i 8; i) { SCLK 0; SDA dat 0x01; // 最低位先发 dat 1; SCLK 1; } } // 读一个字节从低位开始 uchar ds1302_read_byte(void) { uchar i, dat 0; for (i 0; i 8; i) { dat 1; if (SDA) dat | 0x80; // 先收低位再移位到高位 SCLK 1; SCLK 0; } return dat; } // 向指定寄存器写入 BCD 码数据 void ds1302_write(uchar cmd, uchar dat) { RST 1; ds1302_write_byte(cmd); // 命令字如 0x80寄存器地址表示写 ds1302_write_byte(dat); // BCD 码时间数据 RST 0; } // 从指定寄存器读取数据命令字最高位为 1 表示读 uchar ds1302_read(uchar cmd) { uchar dat; RST 1; ds1302_write_byte(cmd); dat ds1302_read_byte(); RST 0; return dat; }参数说明DS1302 的命令字格式是 bit7 为方向1 读 0 写bit6 固定为 1bit5 表示操作的是 RAM 还是时钟寄存器。读秒寄存器的命令字是 0x81写是 0x80依次对应 0x83 分、0x85 时。RST 线必须在读写全程保持高电平传输结束后拉低。这里有一个实际调试常见的故障DS1302 的 SDA 线是漏极开路必须外接 4.7kΩ 上拉电阻否则读回来的数据全是 0xFF。另一个坑是 DS1302 需要 32.768kHz 的晶振如果焊的是 12MHz 晶振时间会飞快乱跳。报告还提到“更改时钟数据时需将更改的时钟数据改变为 16 进制再进行储存”对应的反转操作是十进制转 BCD。C 语言里有一个很简洁的写法bcd (dec / 10) 4 | (dec % 10);它把十位放到高 4 位、个位放到低 4 位。按键设置时间时用户的输入是十进制比如设置 23 点必须这样转换后写入 DS1302而显示时再做反向转换dec (bcd 4) * 10 (bcd 0x0F);。报告中“分别取时分秒的十位数和个位数”就是这个逻辑。4.3 单片机自带 EEPROM 做设置掉电保存报告在方案比较里对比了“单片机自带 EEPROM”和“AT24C02 外挂存储”最终选了前者理由是“使用方便可以节省 IO 口操作简单”。STC89C52RC 和 STC12C5A60S2 都集成了 EEPROM通过 ISP/IAP 操作不需要额外 I2C 时序直接用ISP_TRIG触发寄存器就能擦写。下面是报告附录代码里提炼的 EEPROM 写字节和读字节函数// 写一个字节到扇区地址 addr void xcx(uint addr, uchar dat) { ISP_CONTR 0x82; // 打开 ISP/IAP 功能等待 2 个时钟 ISP_CMD 0x02; // 设置为“字节写”命令 ISP_ADDRL addr; // 低 8 位地址 ISP_ADDRH addr 8; // 高 8 位地址 ISP_DATA dat; // 待写入的数据 EA 0; // 关全局中断防止写入被干扰 ISP_TRIG 0x5A; // 触发命令 1 ISP_TRIG 0xA5; // 触发命令 2 delayus(1); // 等待写入完成 Q0(); // 关闭 ISP 功能恢复普通 IO } // 从扇区地址 addr 读一个字节 uchar dcx(uint addr) { uchar dat; ISP_CONTR 0x82; ISP_CMD 0x01; // 字节读命令 ISP_ADDRL addr; ISP_ADDRH addr 8; EA 0; ISP_TRIG 0x5A; ISP_TRIG 0xA5; delayus(1); dat ISP_DATA; // 读取数据 Q0(); return dat; }这段代码的重要参数是ISP_TRIG的 0x5A/0xA5 顺序触发序列这个序列不能颠倒、中间不能插入其他语句这是 STC 系列 ISP/IAP 操作的一个硬性要求。Q0()函数的作用是关闭 ISP 功能并复位相关寄存器——写入后不关闭 ISP会导致后续对同一地址的重复写操作失效。报告中把 ON/OFF 时间数据写入 EEPROM 的思路是用户通过按键设置的开启时间和关闭时间在单片机掉电后仍然保留下次上电时从 EEPROM 读出来直接显示在 12864 上。存储地址的选择要注意STC89C52RC 的 EEPROM 可能只有 4KB并且和程序 Flash 分块使用一般从 0x2000 之后开始分配比较稳妥直接使用 0x0000 可能擦除程序区。5. PWM 背光、AD 电量检测与整机排错清单5.1 用 STC12 的 PCA 模块输出 PWM释放 CPU报告附录第 27 行到 32 行有的一段pwm()函数用的是 STC12C5A60S2 的 PCA 可编程计数器阵列不是普通的定时器中断方式。它初始化 CMOD 寄存器并设置捕获/比较模块让硬件自动产生 PWM 波CPU 只需要在改变占空比时才介入sfr CMOD 0xD9; // PCA 工作模式寄存器 sfr CCAPM0 0xDA; // PCA 模块 0 控制寄存器 sfr CCAP0H 0xFA; // PWM 高电平时间寄存器 sfr CCAP0L 0xEA; void pwm(uchar x) { CCON 0; // 清零 PCA 控制寄存器 CL 0; // 清零 PCA 计数器基础值保证占空比从 0 开始算 CH 0; CMOD 0x02; // 选择 PCA 时钟源0x02 表示系统时钟/2 CCAP0H CCAP0L x; // 设置 PWM 占空比8 位方式下 0~255 对应 0~100% CCAPM0 0x42; // 使能 PWM 输出模式禁止中断 CR 1; // 启动 PCA 计数器 }参数说明x是 0255 的占空比值设为 255 时输出常高设为 128 时约 50% 占空比设为 0 时输出常低。CMOD 0x02把 PCA 时钟源配置为系统时钟 2 分频。CCAP0H CCAP0L x这一句是 8 位 PWM 的典型用法CCAP0L 先装载低 8 位当计数器溢出时自动加载 CCAP0H这样避免在运行中占空比突变产生毛刺。报告里“由 STC12IO 口输出一定的 PWM 信号可调节背光灯的亮度”就是靠这个函数按键扫描到s2时设置pwm(128)亮度为中间档pwm(255)为最亮pwm(0)为熄灭。对比第 2 章里的软件延时 PWM硬件 PWM 的好处是——背光调节不需要占用主循环CPU 可以同时去扫描按键和刷新 12864。5.2 电池电压分压采样与电量百分比映射报告里的电量显示设计很巧妙AD 读到的电压是电源电压的一半所以代码里VAL_AV * 2.0。硬件上大概率是电池正极经过两个等值电阻分压后接入 STC12 的 ADC 通道分压比 1:1。既然这里分了压那 5V 供电下的 ADC 满量程电压就是 5VVAL_AV * 5.0 / 1024就是把 10 位 ADC 的数字量换算成实际分压点电压再乘 2 还原真实电池电压。Ad_AV函数做了 100 次采样取平均这对电池电压这种缓慢变化的量非常有必要——电机启动瞬间电流大电池电压会瞬时跌落只看单次采样会得到抖动很大的电量数值float Ad_AV(uchar channel) { float VAL_AV 0; uchar num; for (num 100; num 0; num--) { // 100 次采样 VAL_AV GetADCResult(channel); // 读取单次 AD 结果 } VAL_AV / 100.0; // 取平均滤除电机引入的波动 VAL_AV * 2.0; // 还原分压前的电池电压 VAL_AV VAL_AV * 5.0 / 1024; // 10 位 ADC 转实际电压值 return VAL_AV; }逻辑说明GetADCResult(channel)返回 01023 的 ADC 原始值100 次累加取平均后除以 1024 再乘以 5V 参考电压就是分压点的电压乘 2 后得到电池端电压。比如读到 1.85V分压点还原为 3.70V乘 2 得 7.40V对应两节 18650 锂电池约 50% 电量。报告附录里powershow()函数中那一长串if-else判断就是从 7.16V 到 8.30V 之间划分电量百分比。这个区间对应的是两节 18650 串联标称 7.4V满电 8.4V截止 6.0V 左右但报告从 7.16V 开始显示说明为了保护锂电池没有把电量压榨到截止电压。有一个细节值得注意dianya 7.450523这种精确到小数点后五位的阈值实际上是通过实测电池在不同负载下的电压值标定出来的不是理论计算值。如果你想复现这个功能建议先用万用表记录“满电空载 8.4V”“电机运转时 7.8V”“接近没电 7.2V”三个关键点再分配中间档位比照抄这套阈值更有意义。串联一个疑问为什么显示“00%”时小车还能走因为 7.16V 只是电量显示的低位阈值并没有真正切断电源——电池其实还有余量。想要做到真正的低电量保护需要加一个比较器或利用 STC12 的掉电检测功能在电压跌到 6.8V 时直接关断电机使能。5.3 整机调试排错清单综合报告的测试方法和测试数据把最容易出问题的环节整理成下面这张核对表方便直接对照排查故障现象可能原因排查方法电机不转L298N 使能端悬空或未拉高用万用表量 ENA 对地电压至少 2V 以上确认 IN1/IN2 电平组合有效小车直线走偏左右电机 PWM 占空比不一致同一占空比下测量两轮转速将快的一侧占空比降 510红外避障不灵敏电位器调节不当或安装高度偏差按 3.1 节方法重新标定确认障碍物反射面正对传感器TCRT5000 检测不到黑线安装高度超过 3cm 或黑胶布反光贴近地面重新固定用黑色哑光胶布替换亮面胶带LCD12864 白屏PSB 未接地或初始化时序不对检查 PSB 电压是否为 0V用示波器确认 SCLK/SDA 有脉冲DS1302 时间乱跳缺少 32.768kHz 晶振或 SDA 无上拉补焊晶振SDA 外接 4.7kΩ 上拉到 VCC电量显示一直 99%AD 采样通道悬空或分压电阻虚焊用万用表量 ADC 引脚电压确认分压点在 05V 内整机联调时建议按这个顺序走先把 L298N 和电机单独调通不接单片机用跳线短接 IN1/IN2 验证正反转再接入单片机用固定 PWM 验证调速随后把红外避障模块接到代码里只保留避障功能跑 2m×2m 区域确认避障稳定后再加入 TCRT5000 循迹回起点最后才移植 LCD12864 显示和 DS1302 时钟。这样分步的好处是——每个阶段只有一个变量出了问题能立刻锁定是哪一层。比如小车能走但避障不转弯问题一定在传感器到 IO 的读值逻辑而不是电机驱动部分。本文还有配套的精品资源点击获取
返回列表