Microduck这个东西,我第一次在同事工位上见到时,还以为是某个玩具公司出的桌面摆件。等看到底板上的ESPressif logo和飞出来的杜邦线,才意识到这是一台自己攒出来的微型智能设备。再后来查了下资料,发现社区里已经有不少人把microduck当成从零做一台桌面机器人的练手项目:主控用ESP32,驱动用ED330这类小电机板,再配超声波或者IMU,就能实现最简单的感知和运动。这篇文章,就是给那些想自己做一台microduck、但还不知道从哪下手的人准备的完整路线图,从硬件选型讲到第一行代码,照着走基本不会迷路。
我会尽量把每一步的“为什么”也讲清楚。网上教程很多都是“照着插线就能跑”,但跑完第二天就忘光了。换成不知道原理就复现不出来的思路去做,才是真正做出自己的microduck的价值所在。
1. 先想清楚再做:microduck的整体设计思路
1.1 我要做的microduck到底是个什么东西
Microduck并不是一个官方定义的型号,更像是一类“带轮子的小型智能设备”的统称。市面上你能看到很多奇奇怪怪的名字:小车、机器人底盘、桌面助手、创意电子玩具……microduck的特点就是小、便宜、可定制,而且名字带点玩味,让人一听就知道不是那种工业级严肃项目。
硬件上,一套标准的microduck由几个核心部分组成:主控板负责“大脑”,电机驱动模块负责“手脚”,传感器负责“眼睛和耳朵”,再加上电池、底盘结构件,基本就齐了。软件上,它跑的是一个典型的感知—决策—执行闭环:传感器采集数据,主控根据数据判断下一步动作,然后通过驱动模块控制电机转动。
自己做一台microduck,和直接买个成品机器人最大的区别在于,你能完整掌控这个闭环里的每一个环节。成品机器人往往把电路、代码、结构全部封装好了,你按个按钮它就会动,但内部发生了什么你完全不知道。从硬件选型到第一行代码全自己来,才能把嵌入式开发中最基础也最重要的东西——GPIO、PWM、I2C、UART、电源管理——真正串起来。
1.2 五步路线图:从想法到成品
做之前,我建议先在心里把整条路线拆成五步。这不是我凭空想出来的顺序,而是按照依赖关系排的,每一步都是下一步的前提。
| 阶段 | 核心任务 | 关键产出 | 大概耗时 |
|---|---|---|---|
| 1. 功能定义 | 明确microduck要做什么 | 功能清单、预算范围 | 1-2天 |
| 2. 硬件选型 | 确定主控、驱动、传感器、电源型号 | 硬件清单、接线图 | 2-3天 |
| 3. 环境准备 | 配好开发环境和串口驱动 | 能编译、能上传的IDE | 1天 |
| 4. 第一行代码 | 点灯、读传感器、驱动电机 | 分模块的验证程序 | 2-3天 |
| 5. 整机联调 | 把模块拼成完整逻辑 | 能跑的microduck | 3-5天 |
前两步最容易被新手跳过,觉得“随便选个板子先跑起来再说”,结果买回来发现引脚不够、驱动电压不匹配、结构装不上,返工成本远高于当初的决策成本。我自己第一版就吃过这个亏,买了个大号的L298N驱动板,塞进小底盘里半天都装不下,后来只能换成更紧凑的ED330模块。
1.3 为什么不用现成方案直接改
有些朋友可能会问:GitHub上那么多microduck开源方案,直接clone下来改改不香吗?我承认,直接抄作业确实能省时间。如果你只是想要一个能动的玩具,网上十几块钱的遥控小车更直接。
但如果你是为了学习,我强烈建议至少从“读代码”开始,而不是“跑代码”开始。直接烧录别人写好的固件,遇到问题你完全不知道从哪里查起,甚至连串口打印的信息都看不懂。我自己带过几个想入门的同事,凡是“先跑通再研究”的,最后基本都卡在某个莫名其妙的地方。
反过来,如果你自己亲手把每个模块的代码都写一遍,哪怕写得很简单,当你遇到问题时,你至少能判断问题出在硬件还是软件。这种“定位问题”的能力,恰恰是嵌入式开发最值钱的部分,也是自己做microduck最大的收获之一。
2. 硬件选型:把每一个零件都选明白
2.1 主控选型:ESP32为什么最适合新手微缩机器人
主控是整个microduck的大脑,也是航向的第一颗纽扣。我一贯的选型顺序是价低、资料多、外设丰富、上手门槛低,四项全占才是新人首选。按照这个标准,ESP32可以说是目前最适合做microduck的主控,没有之一。
先看价格,ESP32 DevKitC开发板现在十几块钱就能买到,比Arduino Uno便宜一半以上。再看性能,它是一颗双核240MHz的处理器,带Wi-Fi和蓝牙,内存、Flash空间都比传统AVR单片机大一个数量级,跑复杂逻辑甚至做简单的图像处理都有余量。
然后是外设资源。Microduck需要的东西ESP32基本都齐了:PWM输出可以驱动舵机和电机,ADC可以读取模拟量,I2C总线和UART可以挂各种传感器,还有足够的数字GPIO用于按键、LED和超声波模块。最关键的,ESP32支持Arduino框架,新手完全可以用写Arduino代码的方式入门,社区资料又多,遇到问题一搜就有结果。
我用一张表快速对比常见主控选项,大家心里就有数了。
| 主控方案 | 价格 | 上手难度 | 外设丰富度 | 适合microduck吗 |
|---|---|---|---|---|
| Arduino Uno | 中等 | 低 | 一般 | 能跑,但贵且性能弱 |
| STM32 | 中等偏贵 | 高 | 好 | 可以,但对新手不友好 |
| Raspberry Pi Pico | 偏低 | 中 | 一般 | 可以,PWM资源吃紧 |
| ESP32 | 低 | 低 | 很丰富 | 首选 |
2.2 电机驱动与ED330:小体积机器人的动力关键
主控选完了,接下来就是让microduck“动起来”的电机驱动部分。这里有个很多新手都会困惑的问题:明明GPIO已经能输出高电平和低电平了,为什么还要单独用驱动模块?
原因很简单,主控的GPIO只是个“信号源”,它的电流驱动能力非常弱,一般只能输出几十毫安。而电机启动瞬间的电流轻松超过几百毫安,直接接在主控引脚上,轻则电机不转,重则烧毁主控。驱动模块的作用,就是把GPIO的小信号放大成能驱动电机的大电流。
电机驱动模块的核心原理是H桥电路。四个开关管组成一个“H”型结构,通过控制两组开关的通断,就能改变电机两端电压的正负方向,从而实现正转和反转。PWM调速则是通过高速开关电源电压的占空比,让电机“感觉”到的平均电压变化,占空比越高转速越快。
在microduck这种小体型设备上,模块尺寸是硬约束。我第一版用的L298N,功能没问题,但体积实在太大,还废电。后来换成ED330这类紧凑型驱动模块,体积缩小了一多半,却能同时驱动两路电机。它支持电机电源和逻辑电源分离,也就是说,电机可以直接用电池供电,逻辑部分由主控的3.3V或5V供电,互不干扰,对稳定性的提升非常明显。
ED330的参数也要留意几个关键点:供电电压范围、峰值电流、逻辑电平电压、PWM频率兼容性。以常见的小型microduck为例,电机电源接两节18650串联的7.4V,模块完全能承受;逻辑电源接主控的3.3V或5V都行。峰值电流足够覆盖N20减速电机和TT马达正常工作的余量,PWM频率在Arduino框架下默认也能正常工作。
如果是更重的底盘或者需要更大扭矩,可以换成TB6612FNG或者直接上带电流检测的驱动方案,但ED330对入门级别的microduck来说,属于“刚好够用且有余量”的定位。
2.3 传感器模块:让microduck有自己的感觉
感知层是microduck的灵魂,没有传感器,它就是个遥控车玩具。我建议第一版只做最基础的感知能力,不要贪多,把“测距”和“姿态”两个能力做好就够用了。
测距最常用的模块是HC-SR04超声波传感器,工作原理就像蝙蝠:发送一束超声波脉冲,测量它碰到物体后反射回来所需要的时间,再根据声速换算成距离。代码实现逻辑也不复杂,拉高TRIG引脚10微秒,然后等待ECHO引脚变高,用pulseIn函数测出脉冲宽度,距离等于脉冲宽度乘以声速再除以2。它的测量范围在2厘米到4米之间,桌面避障场景完全够用。
姿态检测用MPU6050六轴传感器,能输出加速度计和陀螺仪的数据。加速度计告诉你microduck倾斜了多少度,陀螺仪告诉你它在绕着哪个轴转动。把两者数据通过卡尔曼滤波或互补滤波融合起来,就能得到比较稳定的姿态角。对于带轮子的microduck,这个数据可以用来做倾斜判断、里程积分或者简单的自平衡实验。
传感器选型的核心原则是“够用就好”,别一上来就买一大堆环境光、红外、温湿度传感器堆在板子上。模块越多,接线越乱,排查越难。我建议第一版就超声波加MPU6050,外加一个板载LED做状态指示,足够了。
2.4 结构件与电源:经常被低估的两块
结构件和电源,是microduck项目里最容易翻车的两个隐藏环节。
先说结构件。Microduck不需要多么精密的CNC机加工,最便宜也最灵活的方案是亚克力板激光切割或3D打印。如果你手头什么都没有,两块洞洞板加尼龙柱也能撑起一套底盘。关键是注意三点:一是要轻,底盘越重,需要的电机扭矩越大;二是要稳,重心尽量压低,否则急停或者走斜坡容易翻车;三是留出维修空间,螺丝别全拧死,方便拆装调试。
电源设计是microduck最容易出问题的部分。很多新手直接把USB线插在主控板上给电机供电,结果一启动就重启。原因是电机瞬间启动电流非常大,会让USB口电压瞬间跌落,主控断电重启。正确做法是独立供电:电机用两节18650电池串联供电,输出电压约7.4V,先经过降压模块给主控提供稳定的5V或3.3V,电机电源直接由电池供给驱动模块。
这里还要特别注意“共地”问题。简单说,就是主控的GND、驱动模块的GND、电池的GND必须连在一起,电路才有统一的参考电压。如果不共地,信号高低电平的判断会变得混乱,轻则传感器读数不准,重则驱动模块直接罢工。我在调试第一版microduck时,就遇到过超声波距离读数飘忽不定的情况,最后查了半天,发现就是没共地导致的。
3. 开发环境准备与第一行代码
3.1 开发环境:Arduino IDE搭配ESP32板卡
硬件备齐后,下一步就是搭开发环境。我推荐用Arduino IDE配合ESP32开发板,因为它是目前对新手最友好的组合:安装简单,编译烧录一键完成,串口监视器直接看输出,而且社区教程多到你想踩坑都难。
安装流程大致分三步。第一步,去Arduino官网下载对应系统的IDE,装完打开。第二步,在IDE的“文件—首选项—附加开发板管理器网址”里填入ESP32开发板的支持包地址,然后在“工具—开发板—开发板管理器”里搜索ESP32并安装。第三步,把USB转串口驱动装好。绝大多数ESP32开发板用的是CP2102或CH340芯片,插上电脑后设备管理器里能看到新的COM口,说明驱动已经生效。
很多新手在这一步就卡住了,因为设备管理器里根本找不到COM口。遇到这种情况,我第一反应是先换一根USB线试试。没错,就是看起来很普通的数据线,很多线只有充电功能,没有数据传输能力,这种线插上以后电脑根本识别不到开发板。这是新手翻车率最高的问题之一,没有之一。
3.2 点亮LED:第一行代码怎么写才不出错
开发环境装好之后,第一行代码建议从“点亮LED”开始。不要觉得这太简单,它的意义在于验证一整条链路:代码能写、能编译、能上传、开发板串口通信正常、GPIO输出正常。这条链路通了,后面所有模块都建立在这个基础上。
经典的点灯代码如下,我用的是ESP32开发板的通用写法:
// first_light.ino // 让板载LED每隔500毫秒亮灭一次 #define LED_PIN 2 // 大多数ESP32 DevKit板载LED接GPIO2 void setup() { pinMode(LED_PIN, OUTPUT); // 把GPIO2设置为输出模式 } void loop() { digitalWrite(LED_PIN, HIGH); // 输出高电平,灯亮 delay(500); // 保持500毫秒 digitalWrite(LED_PIN, LOW); // 输出低电平,灯灭 delay(500); // 保持500毫秒 }上传前记得在“工具—开发板”里选择正确的ESP32型号,在“工具—端口”里选择刚才看到的COM口。点击上传按钮后,如果代码编译正常、上传成功,串口监视器会显示“Done uploading”,同时LED开始闪烁。
如果上传过程中出现“Failed to connect”之类的错误,不要慌。绝大多数ESP32开发板需要在上传时按住板子上的BOOT按钮,等串口日志提示“Connecting......”时松开,就能正常烧录了。记住先上传、后松开,这是ESP32新手最容易踩的坑。
3.3 传感器+LED联动:第一次让代码和物理世界互动
点灯成功只能说明IO输出没问题,接下来可以让代码和物理世界做一次真正的互动。我的做法是接上超声波传感器,然后用距离控制LED亮灭:距离小于20厘米时亮灯,否则灭灯。
接线方式很简单,超声波模块的VCC接主控的5V或3.3V,GND接GND,TRIG接GPIO4,ECHO接GPIO5。注意,有些超声波模块的ECHO输出是5V高电平,直接接在ESP32的GPIO上可能有3.3V容忍度的问题,保险起见可以用一个分压电阻或者电平转换模块。对于入门来说,很多模块实际上也能工作,但理论知识还是要知道的。
代码如下:
// distance_led.ino // 通过超声波传感器感知距离,距离小于20厘米时点亮LED #define TRIG_PIN 4 #define ECHO_PIN 5 #define LED_PIN 2 float readDistance() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); long duration = pulseIn(ECHO_PIN, HIGH); float distance = duration * 0.034 / 2; // 声速约340m/s return distance; // 单位:厘米 } void setup() { pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); pinMode(LED_PIN, OUTPUT); Serial.begin(115200); } void loop() { float dist = readDistance(); Serial.print("Distance: "); Serial.println(dist); if (dist < 20) { digitalWrite(LED_PIN, HIGH); } else { digitalWrite(LED_PIN, LOW); } delay(100); }这段代码的关键在readDistance这个函数。先拉低TRIG引脚2微秒,再拉高10微秒,再拉低,让超声波模块产生一个脉冲信号,随后ECHO引脚会保持一段时间的高电平,时间长度和声音从发射到返回的时长成正比。pulseIn就是用来测量这个高电平持续时间的。乘以0.034除以2,是因为声速约340米每秒,换算到厘米要除以10再乘100,而且声音走的是往返路程,所以还要除以2。
这段代码跑通后,你就完成了从“主控到传感器的读取”,还完成了“根据传感器数据控制执行器”的闭环。前面所有模块的验证,都会遵循这个模式:“读数据、做判断、动设备”。
4. 核心环节实现:从点灯到跑起来
4.1 接线:硬件电路连接规范(先看这张表)
从传感器联动到整机跑起来,还差一个电机驱动的核心环节。接线是整个环节里最容易出乱子的地方,我先给出一张标准接线表,以ESP32、ED330驱动模块、两路N20减速电机和电池为例。
| ED330模块引脚 | 连接到 |
|---|---|
| VM(电机电源正) | 电池正极(7.4V) |
| GND(主电源地) | 电池负极,同时连ESP32的GND |
| VLOGIC | ESP32的5V或3.3V |
| IN1 | ESP32 GPIO12 |
| IN2 | ESP32 GPIO14 |
| PWM1 | ESP32 GPIO13 |
| 电机A输出 | 左电机两根线 |
| IN3 | ESP32 GPIO27 |
| IN4 | ESP32 GPIO26 |
| PWM2 | ESP32 GPIO25 |
| 电机B输出 | 右电机两根线 |
接线有几个原则要注意。第一,所有GND必须连在一起,尤其是电池负极、ED330的GND、ESP32的GND,三者之间用一根线串联起来,这能避免很多奇怪的问题。第二,电机电源和逻辑电源要分开,电机电源直接接电池,逻辑电源由主控提供,这样即使电机大电流启动导致电压波动,也不会把主控拖死。第三,信号线尽量短,杜邦线别拉太长,既能减少干扰,也让走线更整洁。
4.2 驱动电机:让轮子先转起来
接线完成后,先在代码里定义电机控制引脚,写一个最简单的电机测试程序。这里我使用的是ESP32的LEDC PWM功能,因为ESP32的analogWrite用法和Arduino Uno不太一样,直接用LEDC更标准,也好调频率和分辨率。
// motor_test.ino // 让两个轮子以80%速度前进,1秒后停止 #define IN1 12 #define IN2 14 #define PWM1 13 #define IN3 27 #define IN4 26 #define PWM2 25 void setupMotor() { pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); ledcSetup(0, 5000, 8); // 通道0,频率5kHz,8位分辨率 ledcAttachPin(PWM1, 0); ledcSetup(1, 5000, 8); ledcAttachPin(PWM2, 1); } void motorForward(int speedA, int speedB) { digitalWrite(IN1, HIGH); // 左电机正转 digitalWrite(IN2, LOW); ledcWrite(0, speedA); digitalWrite(IN3, HIGH); // 右电机正转 digitalWrite(IN4, LOW); ledcWrite(1, speedB); } void motorStop() { ledcWrite(0, 0); ledcWrite(1, 0); } void setup() { setupMotor(); motorForward(204, 204); // 80%左右速度,因为8位分辨率最大255 delay(1000); motorStop(); } void loop() { // 测试一次后暂停 }这段代码里,我故意把两个电机分别用两个PWM通道控制,这样后面可以很方便地做差速转向。如果你想让它转弯,只需要两个电机的速度不一样就行,比如左轮速度200、右轮速度120,microduck就会往右转弯。
PWM的频率我设的是5kHz,这个值对大多数直流电机是合适的。频率太低了,电机会发出明显的啸叫声;太高了,驱动模块的开关损耗会增大,电机反而可能没力气。5kHz属于一个实际调试中很稳妥的中间值。
4.3 简单控制逻辑:带避障的行走骨架
轮子能转了,传感器也能读距离了,接下来就是把两边拼起来,写一个最基础的避障逻辑。思路很简单:microduck正常前进,遇到正前方障碍物距离小于阈值,就停下来,左转一下,再继续前进。
// avoid_obstacle.ino // 超声波避障逻辑的简化骨架 float readDistance() { // ...省略具体读取代码,参考上一节 } void setup() { setupMotor(); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); Serial.begin(115200); } void loop() { float dist = readDistance(); Serial.print("Distance: "); Serial.println(dist); if (dist > 20) { motorForward(180, 180); // 前方通畅,前进 } else { motorStop(); // 先停下来 delay(200); motorForward(150, 100); // 往右转一点,左轮比右轮快 delay(400); motorStop(); } delay(50); }这段代码虽然简单,但包含了状态机控制的基础思想:根据外部输入(距离),不断切换机器人的行为状态(前进、停止、转向)。你可以在这个基础上增加更多状态,比如“后退”“大步绕过”“寻找物体”,每个状态由一组参数和动作组成。
一个实用的调参技巧是,我总是先在串口监视器里观察实时的距离值,再决定转弯时间。桌面上不同材质的障碍物反射面差异很大,有的会被超声波忽略,有的距离读数会跳变。先用代码打印真实数据,再去调转向时间,会比盲猜高效得多。
4.4 烧录与上电调试:观察现象、确认行为
整机联调的时候,上电顺序和检查规范很重要。我先给一个自己一直在用的步骤,按这个节奏来,能把发问题的时间和概率控制在最小范围。
首先是不接电机和传感器,单独给主控上电,确认系统能正常启动。第二步接上传感器,打开串口监视器,手动拿一块纸板在传感器前面晃,看距离数值是否合理。第三步接上驱动模块和电机,但先不装轮子,用手捏住电机轴确认它能转,顺便感受一下扭矩和转速。第四步才是装上轮子,把microduck放到桌面上空跑。
每走一步,都要先确认上一步一切正常,再往后走。千万不要一口气把所有模块接好再上电,不然出问题了你都不知道是电源的问题、接线的问题还是代码的问题。
调试时我还会做一件小事:给microduck写一个“手动测试模式”,用串口输入命令控制运动。这样我不用反复烧录固件,就能快速验证不同转速、不同转向时间的表现。这个习惯帮我省了很多时间,也让你对每个参数的效果有直观的手感。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
做microduck的过程中,会遇到一些典型问题,我把它们整理成了一张速查表,给后面做的人留个参考。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 电脑找不到COM口 | 数据线不支持数据传输、驱动没装 | 换线;装CH340/CP210x驱动 |
| 上传提示Failed to connect | 开发板没进入下载模式 | 按住BOOT按钮再上传,出现Connecting后松开 |
| 超声波一直返回0 | ECHO接线错误、模块供电不足 | 检查接线;用万用表量模块VCC电压 |
| 超声波距离值乱跳 | GND没共地、电源噪声大 | 接好共地;在电源端并一个大电容 |
| 电机上电发出“咔咔”声但不转 | PWM占空比太低、电源电压不足 | 提高PWM值;改用独立电池供电 |
| 电机一转主控就重启 | 电机启动电流把主控电压拉垮 | 独立供电,不要用USB或主控电源驱动电机 |
| 两个电机速度明显不一样 | 电机个体差异、机械阻力不同 | 软件里分别设置两个电机的基础速度偏移 |
这张表看起来简单,但每一条背后都是实打实的翻车经历。尤其是“共地”和“独立供电”这两条,能避免一半以上新手期的诡异问题。
5.2 避坑经验:这些坑我替你踩过了
第一版microduck我用了那种特别细的普通杜邦线,结果超声波信号偶尔会瞬间丢失,而且是随机出现,非常难排查。后来换成质量更好的杜邦线,问题立刻消失了。信号线这种看似不起眼的东西,在2.4GHz主控旁边受干扰的影响比想象中大得多。
第二个坑是电源。我一开始想省事,直接5V变3.3V后再给电机供电,结果microduck跑几步就趴窝。后来一测才发现,电机启动瞬间把电压拉到了4V以下,整个系统都处于“半死不活”的状态。改成电池直接给驱动模块供电后,一切才恢复正常。
第三个坑是功能贪多。我第一版想一口气加上OLED显示屏、蓝牙遥控、超声波避障、巡线传感器,结果代码和接线全挤在一台小车上,出了问题根本不知道是哪一个模块导致的。后来砍到只剩避障一个功能,才顺利跑起来。我的建议是,第一版先做成“能自己绕着桌子走”的microduck,稳定了之后再加新功能,一次只加一个。
我自己的第一台microduck,前前后后折腾了差不多两个周末。第一个周末几乎全耗在环境搭建和电源问题上,真正写代码的时间可能只有几个小时。等到第二周周末,它终于在桌面上遇到障碍物自己拐了个弯,那一下的成就感,是任何买现成模块给不了的。
如果你正在准备做自己的第一台microduck,我的建议是:别急着一次到位,先点亮灯,再动轮子,最后再谈避障算法。等哪一天你看到它自己绕过障碍、继续往前走的时候,你会觉得这条路线图,走得值。