1. 从"满家找插头"到"插座自己跑过来":小明项目的缘起与定位
家里最烦人的事,其实不是扫地,而是找插头。手机没电了、笔记本要充电、想在客厅放电影却发现投影仪被固定在了卧室的墙上——这些零零碎碎的痛点,就是我启动"小明"这个项目的全部理由:做一台能在家里自由移动的服务机器人,把移动充电、媒体播放、智能家居控制三件事,装进同一个模块化平台。
这个项目最核心的思想是"模块化"。我说的不是软件里拆几个函数就算完,而是从硬件到软件,把机器人拆成底盘、电源、媒体、中枢几块可以独立插拔和升级的模块。对一个人维护全栈软硬件的开发节奏来说,模块化意味着故障隔离。举个例子:上一周媒体模块里的投影散热风扇坏了,我直接把整个媒体模块抽出来,换上一块测试板,底盘导航实验照常跑,根本不用为了一个小风扇把整机拆到螺丝堆里。
"小明"能解决什么问题,要先说清楚边界。它不是一块可以无限输出的"充电宝",而是把能量带到你身边的"移动插座"。它能给手机、耳机、遥控器这类小功率设备反复补能,能把一台60W的笔记本从20%顶到80%,还能在停电时顶着路由器工作好几个小时。媒体功能上,它既能当一块带触摸屏的家庭信息屏,也能在你想看电影时自行开到一面空白墙前,把720p画面投上去。智能中枢层面,它不只是一个怼在家里角落的网关,而是一个会自己转动方向、甚至走到空调正前方去发射红外码的"长腿中枢"。
如果你正好在做机器人DIY、智能家居网关、移动电源类的嵌入式产品,或者计划用Python加ROS2搭一套能跑起来的多模块机器人系统,这篇文章应该能帮你少走不少弯路。我会从需求定位一路讲到联调排障,把每个设计决策背后的"为什么"尽量讲透,而不是只丢给你一张接线图。
我先把整机架构摆出来,后面再一层层说细节:
| 子系统 | 组成 | 关键设计点 |
|---|---|---|
| 运动底盘 | 4×麦克纳姆轮、4×有刷减速电机、STM32F407 | 全向移动、100Hz速度环 |
| 电源系统 | 6S2P 21700电池、BMS、多路DC-DC | 216Wh总能量,移动充电输出 |
| 主控 | 树莓派4B 8GB | ROS2主节点、Python各模块 |
| 媒体模块 | 8寸触摸屏、720p投影、扬声器、4麦阵列 | 双模式显示、语音交互 |
| 中枢模块 | IR码发射、Zigbee协调器、MQTT/Home Assistant | 可移动的智能家居控制 |
| 扩展背板 | 4个标准插槽 | 可热插拔式模块化设计 |
这块背板是我在整个项目里投入最多、也最值的一步,后面第5节会专门展开。
2. 底盘、电源和运动控制:机器人先得"走得稳"
2.1 底盘选型:四轮麦克纳姆还是两轮差速?
机器人先得能走,才谈得上服务。项目早期我对比了三种底盘方案:
| 底盘类型 | 过门能力 | 窄道横移 | 控制复杂度 | 成本 |
|---|---|---|---|---|
| 两轮差速+万向轮 | 好 | 不能横移 | 低 | 低 |
| 四轮麦克纳姆 | 好 | 可以横移/斜移 | 中 | 中 |
| 四轮舵轮全向 | 极好 | 可以横移/斜移 | 高 | 高 |
我选了四轮麦克纳姆,理由非常直接:家里的过道和门厅通常很窄,机器人经常要横着从沙发和茶几之间"挤过去",或者原地掉头后贴着墙停靠。麦克纳姆轮的横向平移能力,把"停靠充电桩""正对投影墙"这类动作的路径规划难度直接降了一档。
麦克纳姆轮的安装方向有个坑:四个轮子的辊子必须按"X"型交错排列,左前轮和右后轮一组、右前轮和左后轮一组,辊子朝向相反。装错一个轮子,原地旋转的时候车体会直接"扭麻花"。我第一次装配时差点误判成电机相序问题,后来对着轮毂上的箭头标记一个一个核对才发现只是装反了一只轮子。
电机选择上,我用了四个24V带霍尔编码器的有刷减速电机,单只额定功率45W,减速比30:1,轮径80mm。为什么不用无刷?一是成本,四套有感FOC驱动板加调试时间不是单个爱好者愿意承担的;二是低速噪声,有刷电机在低速区的安静程度更可控,扭矩脉动则交给PID和速度曲线消掉。
2.2 电池包与电源树:216Wh怎么分配
"移动充电"的底座是车上的能量。我用12节三星50E 21700电芯组成6S2P,标称电压21.6V,充满25.2V,容量10Ah,总能量约216Wh。这个容量不是随手定的,它刚好满足我三个使用场景:
- 给手机、耳机等小功率设备反复补能,一次充电任务耗电不超过25Wh;
- 给一台60W的笔记本从20%充到80%,需要大约50Wh,尚在可接受范围;
- 停电时顶着路由器加光猫(约10W)工作8到10小时,能撑过绝大多数夜晚。
BMS板带均衡和UART通信,STM32可以实时读到每一串电芯电压。电源树按"功能隔离"的原则设计,这是决定整机稳定性的关键:
| 电压轨 | 来源 | 用途 |
|---|---|---|
| 24V电池主轨 | 电池 | 电机驱动、充电输出、投影仪 |
| 12V | 24V→12V降压 | 无线充电模块、扬声器功放 |
| 5V | 24V→5V降压 | 树莓派、触摸屏、USB Hub |
| 3.3V | 5V→3.3V LDO | STM32、IMU、编码器接口 |
电机大电流轨和逻辑媒体轨必须物理分离,这一点几乎决定了整机EMC表现。开发早期我偷懒把投影仪和电机驱动直接挂在同一个24V节点上,结果画面全是横纹,具体排查过程放在第6节。如果你现在正要画电源树,建议直接按这个分层走。
充电桩方面,我在底座上做了两个大电流pogo pin加一个信号pin,配合磁铁做机械对中。机器人回到充电桩的最后5厘米使用二维码视觉对准,定位精度可以做到±2厘米,保证pogo pin可靠接触。充电参数是24V/4A恒流,充满25.2V截止,从空电到满电大约需要2.5小时。
2.3 运动控制的心跳:STM32与PID手记
运动控制MCU用的是STM32F407VET6,168MHz主频,负责四路编码器读取、电机PWM输出、电压电流采样、IMU读取,以及和树莓派的UART通信。我在STM32上跑了两层控制:速度环100Hz,位置环20Hz。
PID调试的经验法则:先只调P,让车直行时在目标速度附近轻微震荡,再加D压住超调,最后加I消稳态误差。我最终在瓷砖地面上收敛出的一组参数如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| Kp | 0.22 | 速度误差到PWM占空比的增益 |
| Ki | 0.08 | 积分项,消掉低速爬行时的稳态误差 |
| Kd | 0.03 | 微分项,抑制车轮加减速时的震荡 |
注意这组参数是归一化到PWM占空比上的,换到别的车架需要重新标定。我还在STM32里加了安全看门狗:如果200ms没收到树莓派的运动指令,立即刹停所有电机。这个冗余保护在ROS2节点crash的时候救了我好几次。
里程计方面,轮式编码器在短毛地毯上的表现还可以,但到了长毛地毯上会因为轮子打滑而严重失真。我引入了IMU陀螺仪做航向融合:短时间用陀螺仪积分修正航向漂移,长时间用编码器里程计修正陀螺仪零漂。ROS2里用robot_localization的EKF做融合,调通之后直线率从"肉眼可见的歪"变成了"跑10米偏角小于2度"。
2.4 导航和停靠:不是能跑就行
导航部分用的是ROS2 Nav2,传感器是一颗LDROBOT LD19激光雷达,360度扫描,12米测距。先用SLAM Toolbox把家里地图建好,再把每个房间命名并绑定到地图坐标,这样"去卧室""去客厅"就变成了一个可调用的action。
地图上要注意膨胀半径的设置:家里走廊宽度一般只有60到90厘米,机器人本体直径38厘米,膨胀半径设太大就直接把走廊堵死了。我在客厅和走廊分别用了15厘米和10厘米两套参数,通过代价地图的分层配置实现。控制器选DWA而不是MPPI,原因是DWA的参数更直观,在门框进出这样的场景下调起来更快。
自动停靠是导航里最麻烦的环节。最初的版本只靠里程计逼近充电桩坐标,实际接触时pogo pin经常错位。后来我在充电桩上贴了一张ArUco标记,机器人靠近到0.5米时切换为视觉伺服,用摄像头实时解算相对位姿,最后5厘米以每秒5厘米的速度缓慢靠近,配合磁铁的机械引导,充电接触成功率从不到70%提高到99%以上。
3. 移动充电不是"拖个充电宝":能源模块的设计细节
3.1 三种充电输出:无线、PD、直流
移动充电如果只做一个USB-A口,那和拖个充电宝没区别。我在"充电甲板"上设计了三种输出方式:
- Qi无线充电:15W,放手机、耳机盒这类支持无线充电的设备。这个位置在机器人顶部甲板的中央,做成了下沉式托盘。
- USB-C PD输出:100W,由24V降压到20V后再经过PD协议芯片(LDR6282)提供5V/9V/12V/15V/20V多档电压,给笔记本、平板这类大功率设备用。
- 12V直流输出:10A,用一个"万能口"引出,给车载吸尘器、充气泵、小型冰箱等12V设备供电。
这三种输出共用一套"可插拔充电甲板",也就是说未来如果Qi2磁吸标准普及了,我只需要换掉整个充电甲板模块,不用动底盘和主控。这正是模块化设计在时间维度上的回报。
3.2 功率预算:到底能"借"出去多少电
这是整个项目里最容易被低估的问题。216Wh总能量,BMS在SOC到20%时强制停止放电,可用能量是172.8Wh。但这里面还要再扣掉机器人自己的保底电量:
- 底盘运动余量:预留60Wh,相当于连续运动1小时,足够覆盖绝大多数任务和回桩路程;
- 主控加屏幕常驻功耗:约12W,使用2小时需要24Wh;
- 这样真正能"外借"的电量大约在90到100Wh之间。
| 场景 | 设备电池容量 | 从机器人取电估算 | 可用次数/电量 |
|---|---|---|---|
| iPhone 15 Pro无线充电 | 17.3Wh | 约25Wh(含无线效率损耗) | 约4次满电 |
| 14寸MacBook Pro PD充电 | 70Wh | 约82Wh(含转换损耗) | 约1次满电 |
| 停电顶路由器 | 10W功耗 | 90Wh可用 | 约9小时 |
设计初期如果没算清这笔账,做出来发现"充了两台手机就喊回家"会很尴尬。小明的定位从来不是"电源墙",而是"最后一米的能量搬运工":你懒得动,它就过来;你忙得走不开,它把电送到手边。
3.3 无线充电的对准与防滑设计
无线充电的用户体验陷阱在"对准"。手机随手一放,线圈偏移3到5毫米,充电功率就可能从15W掉到5W甚至断连。我在托盘上做了三个物理提示:
- 环形凹槽设计,手机底部卡进去自然定位;
- 四角各埋一颗小钕磁铁,配合手机背面的磁吸引磁片,靠近时"咔哒"一下自动对齐;
- 线圈周围一圈LED:绿色表示对准且正常充电,蓝色表示充电中,红色表示异物检测或过热。
防滑问题也踩过坑。麦克纳姆轮在侧向移动时产生的横向加速度比普通两轮差速底盘大得多,急转弯时手机在托盘上滑出去的概率极高。对策是三管齐下:托盘换成高摩擦橡胶纹面、边缘加了可拆卸的半包围边框、导航参数里增加"充电输出中"模式——最大线加速度压到0.1m/s²,最大角速度压到0.2rad/s²。虽然慢了一点,但换来了可靠。
3.4 充电协议与安全
安全设计上,所有输出口都通过负载开关受软件控制,MCU用采样电阻实时读取每个口的电流。温度保护是硬指标:PD降压模块的散热片温度到60℃就降功率,65℃直接切断输出。反向电压保护用了P沟道MOS管做理想二极管电路,防止用户拿正负极接反的线缆插进12V口。
充电桩接触可靠性是个容易忽视的点。pogo pin用久了表面会氧化,接触电阻变大,导致充电电流上不去。我给机器人加了一个"停靠擦拭"动作:每次回桩到位后,以1Hz频率给充电回路施加小电流脉冲,同时控制底盘做±5毫米的微小往返运动,利用机械滑动把pin针表面的氧化层磨掉。这个小动作让充电回路的平均接触电阻从最初的30毫欧稳定到5毫欧以下。
4. 媒体播放与智能中枢:让机器人"有脑子也有面子"
4.1 显示与投影:日常模式和影院模式
媒体模块承担两个完全不同场景的任务,因此我把显示拆成了两块。
日常模式是一块8寸1024x600的触摸屏,安装在顶部甲板靠前的位置,倾斜约45度。这块屏是机器人的"脸":显示时间天气、日历提醒、家庭监控画面、视频通话窗口。用户直接点按操作,语音助手也在这块屏上出字幕反馈。
影院模式是一台720p的LED微型投影机,标称亮度320 ANSI流明,HDMI输入,整机功耗约30W,安装在一个带电动俯仰角的云台上。想看视频时,机器人会自动导航到房间内离空白墙或者天花板合适的位置,旋转机身让投影光轴垂直于墙面,用电机云台微调俯仰角,最后才开始播放。固定投影仪的梯形校正可以靠数字算法硬掰,但小明靠物理定位直接规避了这个问题。
320流明在白天客厅里基本不可用,必须拉窗帘。这是电池供电微型投影的物理限制,我在设计阶段就接受了这个妥协。使用场景集中在晚上:睡前投在天花板上看剧、客厅关灯后投大白墙看电影、周末早上小朋友看动画片。
媒体文件放在机器人内部一块256GB的NVMe固态盘里,用ext4文件系统,走Samba共享给局域网。播放由Python调用libvlc完成,我封装了一个极简HTTP接口,其他设备可以直接向机器人推流:
POST /media/play {"url": "/nas/movies/interstellar.mkv", "device": "projector"}4.2 音频与麦克风阵列
语音交互是这台机器人的核心入口,我用了4麦克风线性阵列配合本地唤醒词引擎,整条链路可以完全离线运行。唤醒词是"小明小明",用sherpa-onnx做流式关键词检测,在树莓派4B上CPU占用约8%。唤醒后录制4秒音频,通过HTTP送到家里一台旧笔记本上跑faster-whisper做离线语音识别,再送回机器人进行意图解析。TTS用的是piper,本地合成,延迟在300毫秒左右,听起来有一点机械感,但胜在全离线、免费、响应快。
音频输出是一块TAS5805M立体声D类功放,驱动两只5W全频扬声器加一个底盘上贴装的低音辐射器。低音辐射器装在底盘底板上,相当于把整个车体变成音箱箱体的一部分,听感比同等大小的塑料音箱好不少。音量控制接入了主控,语音命令"声音大一点"直接调节I2S数字音量,不会引入模拟电位器的底噪。
4.3 智能中枢:让"中心"会走路
智能家居部分,我直接在树莓派上用Docker跑了Home Assistant,机器人通过MQTT discovery注册为它的一个设备。这样做的好处是海量现成的设备集成可以直接复用,我不需要自己写一堆智能插座的驱动。
但小明和固定智能音箱的本质区别在于:它有一双"腿"。传统红外网关最大的痛点是,客厅的空调接收头可能在电视柜遮挡的死角,固定位置的红外发射模块根本照不到。小明可以在收到"打开空调"指令后,先导航到空调正前方1.5米处,把红外LED阵列对准空调接收窗,再执行NEC协议码,实测成功率接近100%。同理,全屋设备控制里如果某个Zigbee路由节点信号弱,它可以走过去充当临时中继。
Zigbee这边用了Sonoff Zigbee 3.0 USB Dongle Plus(EFR32MG21),Home Assistant直接通过ZHA协议接入。目前挂了不少温湿度传感器、人体存在传感器和智能插座,数据汇聚到Home Assistant后,小明可以在语音查询时调用:"客厅温度多少?"会从HA数据库读最新值再合成回答。
BLE做了个实验性的"跟随模式":用户的手机蓝牙信标信号被机器人上的BLE网关收到后,根据多天线信号强度粗略判断方位,机器人会朝信号最强的方向慢速跟进。这个功能目前只是个玩具,精度只能到房间级,但在朋友来家里演示时效果很唬人。
4.4 语音交互与技能插件机制
语音识别出来的文本怎么变成机器人的动作?我用了一套轻量级的技能插件机制。每个技能是一个Python目录,里面有一个handler.py,声明自己负责的意图关键词和参数槽位。比如"投影"技能的意图规则:
intents = [ { "name": "play_movie", "patterns": ["播放{title}"], "require": ["title"], "action": "xiaoming.media.project" } ]主控收到文本后,先用正则规则做匹配,匹配不到再走本地服务器上的大模型做开放式语义解析。规则覆盖了日常80%的指令,大模型兜底剩下的20%。这种"规则优先、模型兜底"的架构开发成本低、可调试性好,不会出现一问三不知的尴尬。
语音交互的完整链路是:唤醒词触发 -> 录音上传 -> ASR识别文本 -> 意图解析 -> 调用对应模块服务 -> TTS播报并执行。树莓派上每个环节都有独立进程,通过MQTT通信,任何一个环节挂了都不会拖垮整个系统。
5. 模块化落地:Python接口、PCB复用和消息协议
5.1 为什么主逻辑用Python
这个项目的主控软件选型是Python,核心原因是生态和迭代速度。OpenCV做视觉、paho-mqtt做消息、libvlc做播放、bleak做蓝牙——全部都有成熟库,不用自己造轮子。ROS2的Python接口对20Hz以内的导航控制完全够用,真正的硬实时部分(100Hz速度环)已经下沉到STM32里了,树莓派上跑的都是非实时业务逻辑。
用Python跑机器人控制,最大的坑是垃圾回收抖动。我处理的办法是:每个功能模块独立进程,循环体内避免高频内存分配,需要复用的消息缓冲区在模块启动时一次性创建。另外所有模块的循环频率都控制在50Hz以下,规避Python解释器加ROS2通信层的调度抖动。
模块基类的设计借鉴了工业上模块化编程的思路,类似TIA Portal里把每个工艺功能封装成功能块,主程序只做拼接调度:
class XiaomingModule: def on_start(self): pass def on_stop(self): pass def on_message(self, topic, payload): pass def report_status(self): pass每个业务模块继承这个基类,然后实现自己的逻辑。主控有一个ServiceManager负责加载、启停、监控所有模块,某模块异常退出时会自动重启并上报日志。日常开发中我改一个模块的代码,只需要重启那一个进程,不用整个机器人重启。
5.2 模块间接口怎么定
接口定义的粒度决定了系统是否好维护。我定了这样一张接口表:
| 模块 | 发布话题 | 提供服务 | 频率/说明 |
|---|---|---|---|
| base | /odom, /imu, /battery_raw | /cmd_vel, /docking_control | 控制频率30Hz |
| power | /power_charge_status | /power/set_output | 状态1Hz |
| media | /media_playback_status | /media/play, /media/project | 状态1Hz |
| hub | /ha_state, /presence | /hub/send_ir, /hub/send_zigbee | 事件触发 |
| voice | /stt_text, /tts_text | /voice/volume_set | 事件触发 |
消息格式统一用JSON,调试时哪怕直接用mosquitto客户端手工发消息都能操作机器人。例如手动开启无线充电:
{ "topic": "/power/set_output", "payload": { "port": "qi", "enable": true, "current_limit_a": 2.0 } }接口抽象层带来的好处是,底层硬件换了,上层代码不用改。我把无线充电模块从一块杂牌Qi板换成了支持FOD的商用模块时,只改了power模块内部一个类,导航、语音、Home Assistant集成一行没动。
5.3 PCB模块化复用:把最小系统做成积木
PCB设计里,我用的是和Allegro"器件模块化复用"同样的思路:把常用的电路单元做成可复用的块,在不同板卡之间复制布局布线。我这个项目里的复用单元包括:
- STM32F407最小系统:含25MHz晶振、复位电路、SWD调试口、启动引脚配置、3.3V LDO、滤波电容。
- DC-DC降压单元:24V转12V和24V转5V的电路,电感电容参数已经过我和PCB实物验证。
- 负载开关单元:用于控制各充电输出的通断,带电流采样电阻和过流保护。
- 电机驱动单元:单片BTS7960半桥,两个拼成一个全桥,靠近电机接口放置。
背板设计了4个标准插槽,每个插槽使用一个20针的2.54mm双排排针,定义如下:
| 引脚组 | 定义 |
|---|---|
| 电源 | VIN(24V)、12V、5V、GND |
| 低速通信 | UART、I2C、CAN(复用) |
| 高速信号 | SPI或USB 2.0 |
| 控制 | 2×GPIO、1×ADC输入 |
| 模块识别 | 1×电阻分压ID |
模块ID通过插入一个特定阻值的电阻来标识,主控上电时读取电压,自动加载对应驱动。这个机制有点像PCIe的设备枚举,只是实现成本低得多。
实测下来,模块化增加了约15%的物料成本,但把调试效率提升了至少三倍。尤其是遇到疑难bug时,我可以把嫌疑模块拆下来放到桌面测试台上跑压力测试,而机器人主体继续干别的事,这种"并行debug"能力对单人项目极其宝贵。
5.4 硬件扩展槽与拆装体验
物理结构上,每个模块通过两颗M3螺丝加滑轨固定在背板上,甲板表面做了快拆卡扣。整个媒体模块可以在一分钟内拿下来,不需要动任何线束。充电甲板模块更简单,是一个抽屉式结构,抽出来就能换。
这个设计在维护时特别有幸福感。电机驱动板上一次烧了一个电容,我把整块运动控制板取下来,在桌面上一顿补焊测试,确认没问题后插回去,前后半小时。要是传统一体化设计,估计得把机器人拆个底朝天,光排线就是十几根。
模块化的另一个好处是并行迭代。我一边在树莓派上调语音识别准确率,一边在另一张桌子上调试新版充电甲板的PD协议,互不阻塞。对于只有一个人加班到深夜的业余项目来说,这种节奏真的救命。
6. 联调联试里那些"翻车现场"与排障链路
6.1 媒体U盘"无媒体容量为0变成MBR格式":存储模块的教训
这个坑必须单独写一节,因为它的表象极具迷惑性。某天晚上机器人播放电影中途突然断电(低电量保护触发了,但我还没写优雅关机的通知脚本),第二天把媒体存储U盘插到电脑上,Windows磁盘管理显示"无媒体",容量变成0,建议格式化,格式还是MBR——一个128GB的盘变成一个看似废掉的空盘。
我当时的排查链路是这样的:
- 先判断物理损坏:换读卡器、换电脑,现象一致,容量依然为0。当时差点就直接扔垃圾桶了。
- 换到Linux上看:
lsblk显示sdb,Size为0,显然固件层出了问题。 - 用
fdisk -l /dev/sdb查看,没有任何分区表信息。 - 抱着死马当活马医的心态,用
testdisk对/dev/sdb做Quick Search,几秒钟后扫出一条"FAT32 128GB"的分区记录。 - 重建分区表并导出数据,128GB的数据完整无损,文件都在。
根因是断电瞬间正在写FAT32的文件目录项,引导扇区被截断。廉价U盘的主控对"系统区异常"这种故障处理非常粗暴,直接向主机报告容量0,导致操作系统默认它是一个空盘。这和"手机屏幕摔碎了不亮,先急着判断主板坏"是同一个逻辑陷阱。
我后续做了三层加固:
- 存储介质换成USB转NVMe的移动固态盘,支持UASP协议,异常断电容错能力远强于普通U盘;
- 文件系统换成ext4,挂载参数加上
noatime,errors=remount-ro,并定期执行fsck; - 在树莓派5V电源轨上加了一块5.5V 1F超级电容,低电量自动关机脚本里先执行
sync和umount,再切断各模块电源,相当于给系统一个"优雅断电"的缓冲期。
6.2 电机大电流把投影画面拉出横纹
症状很典型:机器人只要在地毯上走起来,投影画面就出现横向滚动条纹,底盘架空让电机空转时画面完全干净。这说明不是机械振动引起的成像问题,而是电气干扰。
用示波器点投影仪的12V供电输入端,发现底盘带载运行时纹波达到200mV@20kHz,而空转时纹波不到20mV。20kHz正好是电机PWM频率。根因很清楚:电机H桥的大电流回路和投影仪的DC-DC输入共用了同一个24V节点,电机连续换向的电流尖峰把公共地电位抬高了,干扰顺着地回路耦合进了投影仪电源。
这一轮改动的完整清单:
- 电机H桥直接从电池正负极单独取电,每块H桥旁边就近放470uF/50V电解电容加0.1uF瓷片电容;
- 媒体模块改用独立24V转12V DC-DC,输入端加π型滤波,10uH功率电感加100uF电容;
- 所有串口、I2C信号线加磁珠,树莓派与STM32之间的UART线两端的磁珠都加了;
- 电源地采用星型接地,所有高电流回路和信号地单点汇聚到电池负极。
改完之后,把投影仪挂在示波器上连续跑各种速度曲线,12V纹波始终控制在10mV以内。这件事让我彻底相信了"电源分区大于事后滤波"这句话。
6.3 无线充电托盘上的手机在转弯时"飞"出去
第一次测试横向移动时,手机从无线充电托盘上滑了出去,还差点摔到地上。问题是麦克纳姆轮的横向移动天生会产生比普通底盘大得多的侧向加速度,急转弯时手机和硅胶垫之间的静摩擦根本扛不住。
我用加速度计记录急转弯时托盘平面的加速度,峰值到0.35g。按手机200g计算,侧向力大约0.7N,听起来不大,但接触面积小、硅胶垫沾灰后摩擦系数下降,就很容易滑移。
最终解决方案是一个组合拳:
- 低摩擦硅胶垫换成了高摩擦橡胶纹面,无线充线圈区域做成了下沉0.5mm的凹槽;
- 托盘边缘增加了可拆卸的半包围挡框,日常使用就是一圈矮边,不放手机时也不碍事;
- 导航层增加"充电输出中"状态:只要无线充电在工作,最大线加速度压到0.1m/s²,最大角速度压到0.2rad/s²;
- 增加磁吸引磁片辅助定位,手机靠近托盘时被四角磁铁吸住,基本不会位移。
改完之后连续做了几十组急转弯和麦克纳姆斜移测试,手机始终稳稳地待在凹槽里。
6.4 PID在毛毯和地砖之间"变脸"
同样的PID参数,在瓷砖地面上跑直线很稳,换到长毛地毯上就开始走偏,转弯还会过冲抖动。我一开始以为是编码器坏了,拆下来检查发现轴承里缠了一圈地毯绒毛,清理干净后有所好转,但问题依旧。
进一步排查时,我对比了编码器里程计横摆角速度和IMU陀螺仪的横摆角速度,发现地毯上两者差异非常大——轮子和地面在打滑,编码器"以为"自己在转,实际上车体没怎么动。固定PID把这种打滑误差当成外部扰动猛调,结果越调越震荡。
解决思路是让系统"认识"地面:
- 增加增益调度:当线性速度误差和横摆角误差同时超过阈值时,自动调低角速度环的Kp、调高Kd;
- 速度曲线从梯形改成S形,限制最大加加速度为0.6m/s³,降低电机起步时对地面附着力的冲击;
- 用电机电流均值和IMU震动能量做简单的地面分类,检测到地毯模式时切换到保守加速度参数。
另外每周清理一次轮子辊子轴承里的毛发和灰尘,这是养宠家庭的家用机器人必备保养项。调参调到最后你会发现,运动控制的另一半天赋其实在机械维护上。
最后再分享一点项目节奏上的体会
这个项目从立项到跑通全部核心功能,我个人的感受是:模块化的收益在前期不明显,甚至拖慢了起步速度——光背板和模块接口的协议就设计了两周。但越往后,它的价值越凸显,尤其是当你需要同时改媒体播放和运动控制的时候。
如果准备复刻一台类似的小明机器人,我的建议是先砍功能:第一版只做底盘加移动充电,跑通"自动回桩+给手机充电"这个最小闭环;第二版再上投影和语音;第三版才考虑Home Assistant和全屋中枢。一上来就全功能并行,排障难度会指数级上升。硬件接口多留一点冗余没坏处,软件模块之间把消息协议定义清楚,后面加功能就像插积木。