☰
用STM32做会听话的智能垃圾桶:语音识别+避障+差速驱动全攻略
2026/10/6 5:59:18 网站建设 项目流程

我一个做嵌入式开发的朋友,前阵子跟我说想给家里老人做个“听话”的垃圾桶,不用弯腰、不用推着走,喊一声就能自己过来,扔完垃圾能自己开盖。本来以为就是个哄老人的小玩具,结果我越做越觉得这项目太适合拿来练手了——语音识别、电机控制、传感器避障、嵌入式状态机,一圈下来几乎把单片机开发的常见知识点全过了一遍,而且成品是真能用的,不是那种做完就吃灰的“实验室项目”。

这项目说白了就是一台“会听口令、会自己跑、会开盖”的移动垃圾桶。核心主控我用的是 STM32F103C8T6,语音部分用了离线识别模块,底盘是双直流减速电机差速驱动,前方挂一个超声波模块负责避障,盖子由一个舵机控制开合。整体成本压在200块以内,代码和硬件设计全部开源,你在 GitHub 或 Gitee 上都能拉下来照着做。这篇文章就把我从零开始做这台垃圾桶的完整过程写下来,包括硬件选型为什么这样做、语音指令怎么对接、电机和避障怎么调、还有我调试时踩进去的几个大坑,给想动手的朋友一条能直接抄的路径。

1. 项目定位与整体架构

1.1 为什么做这个项目:从痛点说起

垃圾桶本身没什么技术含量,但“移动垃圾桶”就有意思了。传统垃圾桶固定在一个位置,你得走过去、弯腰、开盖、扔垃圾,这对行动不便的老人、刚做完手术的病人,或者纯粹想在沙发上瘫着的人都很不友好。我最初的需求很朴素:能听懂“过来”“回去”“开盖”“关盖”,能在家里自己走,别撞到桌腿和墙就行。

这个定位决定了项目的技术路线不会太难,但也绝不是“点亮一个LED”那种入门级练手。它牵扯到几个独立的子系统:语音识别子系统、运动控制子系统、避障测距子系统和执行机构。任何一个子系统单独拎出来,都对应嵌入式开发里一个重要的知识点。把它们拼成一个完整的闭环产品,这才是我觉得这个项目最有价值的地方——不是学某个孤立的功能,而是学会怎么把多个模块组织成一个能稳定运行的系统。

对于想入坑嵌入式但又不知道做什么项目的朋友,我的建议是优先选这种“整机类”项目。面包板点灯、跑个串口打印都是片段,做一台能动的机器,你才会真正理解什么叫“系统级联调”,什么叫“电源完整性”,什么叫“软件状态机”。

1.2 系统拓扑与数据流:谁在听、谁在算、谁在走

整个系统的数据流并不复杂,但搞清楚每个模块之间怎么通讯,是后面写代码和排查问题的基本功。我按信息流向把这个系统分成四层:

第一层是“感知层”,负责接收外界信息。语音识别模块(SU-03T)负责接收人的口令,超声波模块负责探测前方的障碍物距离。这两个模块用的通讯方式不一样——语音模块走串口(USART),超声波走 GPIO 电平时序。第二层是“决策层”,也就是 STM32 主控。它把所有感知信息汇总,运行一个状态机,决定当前该前进、后退、转向、停止还是开盖。第三层是“执行层”,电机驱动板接收 STM32 的 PWM 和方向信号来驱动两个直流电机,舵机根据 PWM 脉宽带动垃圾桶盖开合。第四层是“电源层”,电池组给所有模块供电,中间通过降压模块分出不同电压轨。

打个比方,这就像一个外卖配送系统:超声波和语音模块是“眼睛”和“耳朵”,STM32 是“大脑”,电机和舵机是“手和脚”,电池是“心脏”。任何一个环节出问题,整台机器就会“失明”“耳聋”或者“瘫痪”。做系统级项目时,你花在排查这些问题上的时间,往往比写代码的时间还多。

2. 硬件选型与电路搭建

2.1 主控选型:为什么用 STM32F103C8T6 而不是 Arduino

先说我踩过的一个认知误区。很多教程一上来就让你用 Arduino,因为库多、语法简单。但如果你有了一点点编程基础,我更推荐直接上 STM32F103C8T6 这颗芯片,也就是俗称的“BluePill”开发板。原因有几个:

首先是性能余量。Arduino UNO 用的是 ATmega328P,8 位 MCU,16MHz 主频,2KB RAM。而 STM32F103C8T6 是 32 位 Cortex-M3 内核,72MHz 主频,20KB RAM,64KB Flash。虽然做这个项目的负载并不大,但性能余量意味着你可以在同一个平台上继续扩展功能——比如加个 OLED 屏显示状态、挂个 ESP8266 做物联网远程报警。如果一开始就用 8 位单片机,后期升级的空间非常小。

其次是外设丰富度。STM32 有多个定时器可以输出互相独立的 PWM 通道,这对控制双电机加一个舵机非常顺手。Arduino Uno 只有 6 个 PWM 脚,也能勉强做,但还得考虑超声波测距要用的输入捕获功能。STM32 的 TIM 定时器可以很优雅地实现超声波回波高电平时间测量,精度比 Arduino 的 pulseIn() 高不少。

最后是成本和学习曲线。BluePill 板子在主流电商平台十几块钱一片,比 Arduino 便宜太多。虽然 STM32 的开发门槛比 Arduino 高,国内市场 STM32 的资料和教程海量,只要愿意花半天时间装好 Keil 或者 STM32CubeIDE、跑通一个点灯程序,后面的路就顺了。

2.2 语音识别方案对比:LD3320、SU-03T 还是云端识别

语音识别有好几条路线可以走,这里我对比一下三种常见方案,也说明为什么最终选了 SU-03T。

第一种是 LD3320 这类专用语音识别芯片。它的特点是“非特定人识别”,也就是说谁来说都能识别。但代价是需要额外外挂 Flash 来存储词条,而且识别率对安静环境要求比较高,家里开着电视或油烟机的时候,误识别率会让你抓狂。

第二种是 ESP32 + 云端语音识别(比如百度、讯飞的在线 API)。识别率最高,几乎接近智能音箱的水平,但缺点也很致命——必须联网。垃圾桶一旦断网就成了“聋子”,而且涉及将语音上传云端,很多 DIY 玩家对隐私有顾虑,另外免费额度用完还要付费,对一个本来想长期平稳运行的家用小机器来说并不划算。

第三种就是我这台用的 SU-03T 离线语音识别模块。它最大的优势是“本地识别,无需联网”,支持自定义唤醒词和最多几十条命令词,识别结果通过串口或 GPIO 输出。它在设计上把音频采集、前端降噪、识别算法都集成到模块内部了,你只需要通过配套的网页工具把命令词烧录进去,然后通过串口接收识别结果。实际测试下来,在安静环境下识别率在95%以上,开电视时的识别率能到80%左右,对这个应用场景来说足够用了。

2.3 电机驱动与供配电:差速底盘的能量基础

移动底盘我选择了双直流减速电机加万向轮的方案,两个驱动轮分别由左右两个电机控制,通过两个轮子的转速差实现前进、后退和转向,这就是“差速驱动”。这种方式的优势是结构简单、控制逻辑直观,特别适合室内平地环境。

电机驱动芯片我选的是 TB6612FNG,而不是很多人熟悉的大块头 L298N。TB6612 是 MOSFET 结构的驱动芯片,内部压降小、发热低,连续驱动电流 1.2A,峰值 3.2A,驱动这类小型直流减速电机绰绰有余。L298N 用的是三极管,压降大,发热严重,用一会儿摸散热片就烫手,而且体积巨大,放在垃圾桶里非常占地方。同样是驱动两个电机,TB6612 的板子只有拇指大小,能轻松塞进底盘里。

供电是整个项目里最容易出问题的地方,我必须多说几句。整台机器用了两节 18650 锂电池串联,电压约 7.4V。这个电压直接给电机驱动板供电,驱动两个直流电机。控制逻辑部分则需要更低的电压:STM32 需要 3.3V,语音模块和超声波模块需要 5V,舵机需要 5V。所以我用两路降压——先用一颗 AMS1117-5.0 把 7.4V 降到 5V,给舵机和传感器供电,再用一颗 AMS1117-3.3 从 5V 降压到 3.3V,给主控和语音模块供电。唯一的注意点是 7.4V 输入到 AMS1117-5.0 压差有 2.4V,如果舵机频繁堵转,功耗上来后芯片会发热比较大,后面我会说怎么解决这个问题。

2.4 模块接线总览与引脚规划

为了让后面讲代码的时候不糊涂,这里先把我实际使用的引脚分配整理成一张表。注意,STM32 的 GPIO 很多脚可以复用为定时器通道,接 PWM 的设备必须挂在能输出 PWM 的引脚上,这一点在画原理图的时候一定要提前规划好。

模块信号线STM32F103C8T6 引脚
SU-03T 语音模块TXPA10 (USART1_RX)
SU-03T 语音模块RXPA9 (USART1_TX)
SU-03T 语音模块VCC / GND5V / GND
HC-SR04 超声波 TrigGPIO 输出PA6
HC-SR04 超声波 EchoGPIO 输入PA7
TB6612 左电机 PWMAPWMPA8 (TIM1_CH1)
TB6612 左电机 AIN1 / AIN2GPIOPB14 / PB15
TB6612 右电机 PWMBPWMPA11 (TIM1_CH4)
TB6612 右电机 BIN1 / BIN2GPIOPB12 / PB13
SG90 舵机信号线PWMPA0 (TIM2_CH1)

接线时有三个关键点,新手容易踩坑:第一,所有模块的 GND 必须和 STM32 共地,否则串口数据全是乱码,超声波测距完全不工作;第二,TB6612 的逻辑电源和电机电源必须分别接,逻辑电源一般接 3.3V 或 5V,电机电源接电池正极;第三,SU-03T 和 STM32 之间是串口对接,要让模块的 TX 接 STM32 的 RX,模块的 RX 接 STM32 的 TX,交叉连接,不要接成直连。

3. 会“听”:语音模块的接入与词条训练

3.1 SU-03T 的识别流程与硬件接口

SU-03T 是一颗语音识别专用芯片,模块上集成了麦克风、Flash 存储和功放电路。用它的第一步不是写代码,而是去它的配套网页平台配置“唤醒词”和“命令词”,配置好之后把固件下载下来,放到 TF 卡里插到模块上启动烧录,或者直接用 USB 烧录器下载,这一步做完,模块才“知道”自己该听什么。

它的工作方式分两个阶段。第一阶段是“待唤醒”,模块持续在低功耗状态下监听音频信号,只有检测到唤醒词(我设置的是“小桶小桶”)才进入“识别”状态。第二阶段是“听命令”,这时你再说“过来”“回去”“开盖”等命令词,模块会把识别结果通过串口发送给主控。这个“唤醒词”机制非常重要,没有它,模块会一直处理环境中的各种声音,误触发率会高到没法用。

我选 SU-03T 还有一个理由是它的串口协议很灵活。识别结果可以配置成直接输出一句话的内容,也可以输出一个自定义指令。为了简化主控解析,我配置成指令输出模式——比如识别到“过来”,串口就发一个固定的十六进制帧。这样 STM32 那边根本不用处理字符串,只需要匹配指令码,代码写起来干净利落。

3.2 自定义唤醒词和命令词训练配置

训练配置的完整流程如下:

  1. 打开模块厂商的在线配置平台(一般是浏览器里操作的网页工具),注册登录,新建一个产品,选择“智能家居”或“自定义”品类。
  2. 在“唤醒词”一栏填写“小桶小桶”,旁边会有合成的语音示例,你也可以自己录制。
  3. 在“命令词”列表里,把需要的口令和对应的“回复语”都填进去。我配置的完整命令词表如下:
    • 小桶过来 → 回复“好的,我过来了”,指令码 0x01
    • 小桶回去 → 回复“好的,我回去了”,指令码 0x02
    • 小桶停 → 回复“好的,我停了”,指令码 0x03
    • 打开盖子 → 回复“盖子打开啦”,指令码 0x04
    • 关闭盖子 → 回复“盖子关上啦”,指令码 0x05
    • 小桶干活 → 回复“开始巡逻”,指令码 0x06
  4. 配置完成后,平台会生成一个固件。把它下载下来,烧录进 SU-03T 模块。
  5. 上电后模块会播报“设置已生效”,此时叫一声“小桶小桶”,它回应“我在”,就说明唤醒成功。

平台上的语音合成效果虽然有点机械味,但一句“好的,我过来了”从垃圾桶里传出来,家里的体验感一下就上来了。如果想更有温度,也可以自己录音替换回复语。

3.3 串口协议对接:从语音到指令

SU-03T 默认的串口波特率是 9600,8 位数据位,1 位停止位,与 STM32 的 USART1 对接。我把 USART1 配成中断接收,每次收到一帧数据,就放到一个环形缓冲区里,主循环再解析。

不同固件版本默认的帧协议可能不同,如果拿到模块先用串口助手看一下模块上电时有没有主动发数据、喊命令时发的是什么格式。我自己这套的帧格式是 5 字节:帧头(0xFF)+ 数据长度(0x01)+ 命令码 + 校验位 + 帧尾(0xFE)。比如识别到“小桶过来”时,串口收到的数据是 FF 01 01 02 FE,其中命令码是 0x01,校验位是前面几个字节的累加和取低 8 位。

STM32 端解析代码的写法大致是这样的:

// 收到语音指令:FF 01 CMD SUM FE void Voice_ParseFrame(uint8_t *buf, uint8_t len) { if (len < 5) return; if (buf[0] != 0xFF || buf[len - 1] != 0xFE) return; // 帧头帧尾校验 uint8_t sum = 0; for (uint8_t i = 0; i < len - 2; i++) sum += buf[i]; if (sum != buf[len - 2]) return; // 校验和检查失败则丢弃 uint8_t cmd = buf[2]; switch (cmd) { case 0x01: Robot_SetState(STATE_GO); break; case 0x02: Robot_SetState(STATE_BACK); break; case 0x03: Robot_SetState(STATE_STOP); break; case 0x04: Servo_SetAngle(LID_OPEN); break; case 0x05: Servo_SetAngle(LID_CLOSE); break; case 0x06: Robot_SetState(STATE_PATROL); break; default: break; } }

这个解析逻辑的核心是“校验”。串口通讯在电机大量启动时容易受到干扰,出现数据错误,如果没有帧头帧尾和校验位,一个误码就会让主控跑飞。很多新手直接读一个字节就执行动作,结果机器经常“抽风”,源头就在这。

4. 会“走”:电机控制与超声波避障

4.1 差速驱动原理与双电机运动模型

差速驱动是所有轮式机器人最基础的运动模型。两个驱动轮分别由独立电机控制,当两个轮子转速相同方向相同时,机器人直线前进;当一个轮子转另一个不转时,机器人原地转向;两个轮子方向相反时,机器人原地旋转。

实际控制中我不直接给电机电压,而是通过 STM32 定时器输出 PWM 波,改变占空比就是改变电机的平均电压,进而改变转速。TB6612 的每个通道有两根方向引脚(AIN1/AIN2)和一根 PWM 使能脚(PWMA)。比如想让左轮正转,就 AIN1=1、AIN2=0,PWMA 给一个 0% 到 100% 的占空比;想让左轮反转,就交换 AIN1 和 AIN2 的电平。右轮同理。

差速转向的时候,我常用的计算方式是这样的:假设目标速度是 V,转向系数是 D(范围 -1 到 1),那么左轮速度 = V * (1 - D),右轮速度 = V * (1 + D)。D 为 0 时两轮同速,直线前进;D 为正时左慢右快,机器人右转;D 为负时相反。这套公式虽然简单,但做后续扩展时很管用——如果你想接 PS2 手柄或者 App 遥控,摇杆数据映射成 V 和 D 就能复用同一套底盘代码。

4.2 超声波测距的原理与代码实现

超声波模块用的是 HC-SR04,原理说起来像“蝙蝠回声定位”:Trig 脚给一个 10us 以上的高电平触发信号,模块内部就会发出 8 个 40kHz 的超声波脉冲,同时把 Echo 脚拉高。声波遇到障碍物反射回来,模块检测到回波后把 Echo 脚拉低。所以 Echo 高电平持续的时间,就是声波从发出到返回的来回时间。距离公式就是:

距离(cm)= 高电平时间(us)/ 2 × 0.034

0.034 是常温下声速 340m/s 换算成 cm/us 的值。除以 2 是因为时间是往返的。举个例子,如果 Echo 高电平持续了 580us,那障碍物距离大约是 580 / 2 × 0.034 ≈ 9.86cm。

代码实现有两种思路。一种是用延时“死等”,就是拉高 Trig 后轮询读取 Echo 引脚电平,再用定时器计算高电平持续了多少。这种方法简单,但会阻塞 CPU,测距期间干不了别的事。另一种是用定时器输入捕获,Capture 上升沿开始计数,捕获到下降沿读出计数值,主循环里异步读取结果。我这台用的是第二种,因为测距期间 STM32 还要处理串口语音指令,如果一直死等超声波,语音指令的响应就会卡顿。

实测下来 HC-SR04 的有效测距范围是 2cm 到 400cm,对这个垃圾桶场景,我主要关注 0 到 80cm。在干净的走廊环境下数据很稳定,最大误差正负 1cm 左右,够用了。

4.3 避障策略与状态机设计

避障不能只写“遇到障碍物就停”,否则机器人一碰到桌腿就原地发呆。我设计了三级距离响应,用一棵简单的判断树来处理:

  1. 当超声波测距值大于 40cm 时,判断为“安全”,机器人以 60% 占空比前进。
  2. 当测距值在 20cm 到 40cm 之间时,判断为“接近障碍物”,机器人减速到 30% 占空比继续前进,并观看下一步。
  3. 当测距值小于 20cm 时,判断为“危险”,机器人立即停车,进入避让决策。

避让决策我用了一个“两轮扫描”的办法。停车后先原地左转 90 度,测一次前方距离记为 LeftDist,再原地右转 180 度,测一次距离记为 RightDist,然后比较哪边更开阔就往哪边转。如果两边都小于 20cm,说明被困住了,就原地后退 10cm 再做一次判断。

这个决策逻辑看着简单,但实际调试时发现一个现象:如果每次碰到障碍物都比来比去,机器人会走得很“犹豫”,有点像一个消费者在两个商品之间反复横跳。后来我给决策加了一个“惯性因子”——如果上一次是左转避让成功的,那么这次如果左边距离和右边距离差得不多,就优先再左转,这样机器人路径会连贯很多,走起来动作干净利落。

4.4 开盖机构:舵机 PWM 脉宽与角度控制

垃圾桶盖由一个 SG90 舵机控制。舵机的本质是一个闭环伺服机构,你给它一个周期 20ms 的 PWM 信号,脉宽决定了输出轴的转角。标准参数是:0.5ms 脉宽对应 0 度,1.5ms 脉宽对应 90 度,2.5ms 脉宽对应 180 度。SG90 的机械范围是 0 到 180 度,实际拧到极限位置会堵转,所以程序里要做软限位。

我的设计是把舵机臂和桶盖用一根铁丝连杆连接起来。舵机转到 0 度时盖子完全盖住,转到 130 度时盖子完全开启。这样既避开了舵机极限位置的堵转风险,又保证了开盖动作的视觉效果。

STM32 用定时器输出 PWM 时,需要计算自动重装载值和比较值。我配置 TIM2 的时钟源,经过预分频后让它产生 50Hz(周期 20ms)的 PWM,然后比较值每变化 1000 对应 0.5ms 脉宽、2000 对应 1ms、3000 对应 1.5ms,以此类推。想转到 130 度,脉宽大概是 0.5 + (130/180) × 2.0 ≈ 1.94ms,比较值设到 1940 附近,再根据实际机械结构微调。

5. 主控逻辑:STM32 软件框架与核心代码

5.1 状态机模块划分:从“裸奔”到“有组织”

整个固件我没用操作系统,而是用一个“超循环 + 状态机”的结构来组织代码。这个结构对嵌入式初学者来说非常友好,也是很多小型机器人项目的标准范式。

所谓超循环,就是 main 函数里的 while(1) 死循环,循环里周期性地执行几个子任务:读取超声波数据、轮询语音串口缓冲、根据当前状态执行运动逻辑、更新舵机角度。这个循环的周期我用一个 SysTick 定时器来管理,比如每 20ms 跑一次主逻辑,这样每个模块都能得到及时的响应。

状态机是决策层的核心。我定义了以下几个状态:

状态含义进入条件退出条件
STATE_IDLE待机状态,电机停止,盖关闭上电初始化完成收到语音指令或定时巡检触发
STATE_GO向前行进语音指令“小桶过来”到达目标位置(超声波距离小于阈值)
STATE_BACK向后倒车语音指令“小桶回去”持续5秒后停止
STATE_PATROL自动巡逻模式语音指令“小桶干活”语音指令“小桶停”
STATE_AVOID避障转向超声波距离小于20cm且正在行进转向完成

状态切换的关键原则是“原子性”,也就是说,状态机的每个循环只能处理一个状态下的逻辑,状态切换时只修改状态变量,不直接跳出去执行别的动作代码。这样出问题的时候,你只需要看当前状态和导致状态切换的条件,就能很快定位到问题,而不是在乱七八的 if 嵌套里翻车。

5.2 核心代码:主循环、语音轮询与避障逻辑

我选 Keil MDK 加标准外设库开发,代码结构分成几个 .c 文件:main.c 负责超循环调度,motor.c 封装电机控制,ultrasonic.c 负责测距,voice.c 解析语音,servo.c 控制舵机,robot.c 是状态机主体。每个模块独立成文件,后续想改成 HAL 库或者移植到别的单片机平台,只需要替换对应驱动文件就行。

主循环和状态机的核心代码思路大致如下:

int main(void) { System_Init(); // 时钟、GPIO、定时器、串口全部初始化 Robot_SetState(STATE_IDLE); while (1) { if (g_flag_20ms) { // SysTick 每20ms置一次标志位 g_flag_20ms = 0; Robot_Task(); // 状态机主任务 Ultrasonic_MeasureTask(); // 异步触发测距 Voice_Poll(); // 检查串口缓冲区是否有新指令 } } } void Robot_Task(void) { switch (g_state) { case STATE_IDLE: Motor_Stop(); Servo_SetAngle(LID_CLOSE); break; case STATE_GO: // 前进,同时检测障碍物 Motor_Forward(BASE_SPEED_PWM); if (Ultrasonic_GetDistance() < DIST_STOP) { Robot_SetState(STATE_AVOID); } break; case STATE_AVOID: // 先原地停下,做两侧扫描 Motor_Stop(); DelayMs(200); uint16_t left = Ultrasonic_ScanDirection(DIR_LEFT); uint16_t right = Ultrasonic_ScanDirection(DIR_RIGHT); if (left >= right) Robot_SetState(STATE_TURN_LEFT); else Robot_SetState(STATE_TURN_RIGHT); break; case STATE_TURN_LEFT: Motor_TurnLeft(TURN_SPEED_PWM); g_turn_count++; if (g_turn_count > TURN_90_DEG_COUNT) { g_turn_count = 0; Robot_SetState(STATE_GO); } break; // 其他状态类似... } }

这里有个关键细节:转向 90 度不是靠角度传感器,而是靠“时间积分”——记录电机以固定 PWM 旋转旋转的时间,乘以预先标定的旋转速度,反推出转过的角度。这在实际工程里叫“开环控制”,优点是便宜可靠,缺点是会受地面摩擦力影响,跑久了会有累计误差。每次转向后重新走直线,误差不会无限放大,对这个场景来说完全够用。想出更好的效果,可以升级成带编码器的电机然后做 PID 闭环,这就是后话了。

5.3 可靠性设计:看门狗、掉电保护和低功耗

做嵌入式项目,功能写完只算完成一半,另一半是可靠性。一个家用垃圾桶,不能跑着跑着就死机,也不能放着三天电就漏光。

首先是看门狗。STM32 内部有一个 IWDG(独立看门狗),它需要主程序每隔一段时间去“喂狗”,也就是刷新一个特殊寄存器。如果主程序因为某种原因卡死,看门狗计数器就会溢出,强制复位整个芯片。这个机制成本极低但价值极高——它保证你的设备永远有一个“最后一根稻草”来把你从死循环中拉出来。我在超循环里每 20ms 喂一次狗,就算语音模块串口数据把缓冲冲爆,程序卡死,一秒钟后系统自己重启恢复,不会变成一块“哑巴砖头”。

其次是掉电保护。锂电池的电压会随着电量下降越来越低,如果不做保护,过放会把电池搞坏。我在电池输出端加了一个电压检测分压电阻,STM32 的 ADC 每 500ms 采样一次电池电压。当电压低于 6.4V(相当于每节 3.2V)时,主控把喇叭播报“电量低,请充电”,然后进入停机状态,不再驱动电机,最大限度保护电池。

最后是低功耗。垃圾桶不是时刻都在跑,大多数时间它都在待机。待机时我给语音模块、主控和舵机供电,但把电机驱动板完全断电,同时让 STM32 进入睡眠模式。实测待机电流从 80mA 降到了 12mA 左右。两节 2600mAh 的 18650 电池,正常使用频率下可以撑三四天,已经够日常生活了。

6. 调试实录:我踩过的那些坑

6.1 问题一:电机一启动,超声波就疯狂误报

这是我调试过程中遇到的第一个也是让我最头疼的问题。现象很奇特:只开超声波模块,测距很准;只转电机,电机也很顺;但电机一转,超声波的数据就开始乱跳,明明前面什么都没有,却测出二三十厘米的距离,机器人就自己原地打转。

后来拿示波器一量就明白了。PWM 斩波驱动电机时,电机两端的电压是高频开关波形,电流突变会在线束上产生严重的电磁干扰。这个干扰通过电源线和地线传导到了 HC-SR04 的供电端,导致超声波模块的参考电平抖动,回波检测阈值被干扰,就会误判。

解决办法分成几步,缺一不可。第一,在所有模块的电源脚并联一个 100uF 电解电容和一个 104 陶瓷电容,做电源去耦;第二,电机电源线和信号线物理上分开走线,不要捆在一起;第三,测距时序和 PWM 错开,超声波测距触发后 14ms 内不要改变 PWM 占空比;第四,软件上加滤波,连续测 5 次取中位数,不是平均值——平均值会被极端的毛刺拉偏,中位数能稳定剔除异常值。

6.2 问题二:舵机一开盖,主控直接复位重启

这个问题比上一个更吓人。现象是叫它“打开盖子”,舵机刚开始转,整个系统就黑屏重启,语音模块重新播报开机提示音,垃圾桶在原地跳了一下,盖子只开了一半。

原因也出在电源上。SG90 舵机启动和堵转瞬间电流非常大,可以达到 1A 以上,而我的 AMS1117-5.0 线性稳压器最大输出电流只有 1A,瞬时压降导致 5V 电压被拉低到 4V 以下,STM32 的 3.3V 也崩了,于是复位。

解决思路是“别把所有怕堵转的东西放在同一个 5V 源上”。我给舵机单独加了一路 DC-DC 降压模块(比如 MP1584 或 mini560),输出 5V/3A,只给舵机供电,同时给舵机电源端并联一个大容量电解电容(我用了两颗 470uF)用来吸收瞬时浪涌。原来那路 AMS1117 只负责给超声波和语音模块供电,负载轻了很多,之后再也没出现过开盖重启的问题。

6.3 问题三:语音识别率忽高忽低,同一个词说十次能错三次

语音模块刚烧录完时识别率很高,用了两天之后就感觉“耳背”了,特别是家里开着电视时,喊好几遍“小桶小桶”都没反应。

查了官方文档才发现一个非常隐蔽的坑:麦克风拾音孔的位置。SU-03T 模块上的麦克风是贴片驻极体麦克风,有方向性,麦克风孔正对的方向拾音效果最好。我把模块装在垃圾桶外壳内部,外壳挡住了一部分声音,而且麦克风孔正好朝着天花板,人站着说话的声音传到它那里时已经衰减得差不多了。后面我把模块重新固定了一个位置,让麦克风孔斜向下 45 度角朝向使用者,识别率立刻回升。

另外还有一个细节,唤醒词和命令词之间要停顿一下。很多人喊“小桶小桶过来”习惯一口气说完,模块还没从唤醒状态切换到识别状态,后半句就丢了。正确做法是“小桶小桶”停半秒,听到模块回应“我在”,再说“过来”。这不算 bug,是这种离线模块的工作方式,在文档里写清楚,使用者就不会以为坏了。

6.4 常见问题速查表

现象可能原因排查与解决
语音模块无反应串口接线接反交叉 TX/RX,确认共地
语音串口数据乱码波特率不对或电平不匹配设置 9600,8-N-1,确认供电 5V
电机转但机器人不走万向轮卡死或减速齿轮打滑检查万向轮轴承,拧紧电机固定螺丝
一侧电机无力TB6612 某路输出虚焊重新焊接 BIN1/BIN2/PWMB
锂电不耐用电机频繁启停 + 舵机待机耗电待机时让驱动板断电,调低空转 PWM
转向角度越来越不准开环控制累计误差每次避障后重新对准,或升级编码器闭环

7. 开源发布与后续扩展

7.1 项目文档结构设计:如何把项目“端”给全世界

代码写完了,机器跑通了,这时候才是开源项目的真正开始。很多人以为开源就是代码往 GitHub 一传就算完了,其实不然。一个没有文档的开源项目,别人根本看不懂你的硬件怎么接,代码怎么烧,就算拉下来也跑不起来,最后只会变成一堆“star”。我自己维护开源项目的经验是,文档的重要性完全不亚于代码本身。

我的开源仓库目录结构大致是这样:

smart-trash-can/ ├── README.md ├── LICENSE ├── docs/ │ ├── hardware_list.md # 元器件清单和购买渠道 │ ├── wiring.md # 接线说明和引脚图表 │ └── getting_started.md # 环境搭建、烧录步骤、常见问题 ├── firmware/ │ ├── Core/ # STM32 工程源码(main.c、robot.c 等) │ └── Drives/ # 各模块驱动代码 ├── hardware/ │ ├── schematic.pdf # 电路原理图 │ └── gerber/ # PCB 制板文件(如果打样了) └── 3dprint/ ├── shell.stl # 桶身外壳模型 └── lid.stl # 桶盖模型

README.md 是门面,我习惯第一屏就放三样东西:一张成品实物图、一句话项目简介、一个“3步跑起来”的快速开始指引。不要上来就是大段原理分析,读者先跑起来才有兴趣看你的设计文档。很多开发者犯的错是把 README 写成“论文”,读三屏还不知道这项目到底干嘛的。

7.2 License 选择与贡献指南

开源不等于放弃版权,选择开源许可证是有讲究的。我的项目分为硬件部分和软件部分,它们适用不同的许可证。

固件代码我选的是 MIT License。这种许可证极其宽松,允许别人拿去随意修改、商用,甚至闭源,只需要保留原始版权声明即可。选择 MIT 是因为我希望这个项目能被更多人拿去二次开发,比如有人想做商业化的桌面垃圾桶,MIT 不会对他造成阻碍,说不定他还会回来提交一些改进代码。

硬件设计文档我选的是 CERN Open Hardware Licence v2 (OHL)。这个许可证是专门为硬件设计的,它要求别人如果使用你的硬件设计文件打样,必须保留你的署名,并对修改后的设计文件沿用相同许可证。几何外壳的 3D 打印 STL 文件我用了 CC BY-SA 4.0,这样既允许他人修改并分享,又要求必须注明原项目来源。

贡献指南方面,我在仓库里放了 issue 模板和 pull request 模板。Issue 模板会提醒提问者说明硬件版本、固件版本、复现步骤和串口日志;PR 模板会要求提交者写明改动目的、自测结果和涉及的文件。这些小细节看起来繁文缛节,但它们能避免开源项目最常见的“失联问题”——用户报了一个怪问题,你问他拿日志,他第二天消失了,问题永远悬着。

7.3 可以继续玩的方向:从语音到视觉

这台机器已经具备“会听”“会走”两个核心能力,但我做完之后觉得它还有很大的扩展空间,这里列几个思路,给后续想二开的朋友参考。

如果你对“视觉感知”感兴趣,可以加一个 OpenMV 或者 ESP32-CAM 摄像头,训练一个简单的 YOLO 目标检测模型,让机器人能识别地上的塑料瓶,自己跑过去捡取。这个升级的本质是把“感知”从超声波这种单点测距升级成图像级的语义理解,整个系统的复杂度会上一个台阶,但挑战也更有意思。

如果你对“自动回充”感兴趣,可以借鉴扫地机器人的方案,在垃圾桶底座装两组充电触点,家里放一个充电桩,用红外信号引导它自动归位。这是移动机器人的一个经典闭环场景:低电量→导航回桩→对接充电→继续工作。

如果只想做软件优化,也可以把电机控制升级成闭环 PID。现在这套开环控制够用,但精度和抗干扰能力有限。加装带霍尔编码器的减速电机,用 STM32 的编码器接口读转速,再用 PID 算法调 PWM 占空比,机器人的直线走得更直、转弯角度更准,对地面摩擦力的变化也没那么敏感。

我个人在整套项目做完之后的体会是:开源硬件项目的真正价值不在做出来的那台机器本身,而在于你把它“开源”出去之后,别人能踩在你的肩膀上继续走。我收到过一条留言,说有个学生在我的电路图基础上改版,加了一个紫外线消毒灯模块,做成了防疫垃圾桶,还拿了学校比赛的一等奖。这种事情给创作者的成就感,比自己闷头玩大得多。

最后再分享一个小习惯:开源项目的文档里,一定不要把“我踩过的坑”藏着掖着。那些电源干扰、舵机复位、麦克风朝向的问题,都是新手一定会重复踩的雷。写在 README 里不仅显得你专业,而且会帮你挡掉一大部分“我的机器人怎么一动就重启”的重复提问。开源本来就是“人人为我,我为人人”的事,别怕暴露自己的调试黑历史——那恰恰是你这个项目最值钱的部分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询