前段时间收拾工作室,翻出两块吃灰很久的Arduino Uno和一盒零散的乐高积木,我突然想到一个很久以前就想试试的点子:用乐高搭机械结构,用Arduino Uno当大脑,再用一块电机驱动Shield把两者连接起来,做一台能跑、能避障的小车。这个组合听起来有点跨界,但实际做下来效果意外的好。这篇文章就把整个项目的完整过程记录下来,从硬件选型到烧录引导,再到避障程序调参,每一步的坑和心得都摊开来说,给打算用Arduino Uno和乐高动手做东西的朋友一个可以直接参考的路线。
先说清楚这个项目是干什么的:用乐高积木搭建车体骨架和传动结构,用Arduino Uno作为主控板,通过叠插式Shield扩展板连接两个直流减速电机和一个超声波传感器,最终实现一台具备自动避障功能的智能小车。它适合刚接触Arduino但已经会点亮LED、想往运动控制方向进阶的玩家,也适合手里正好有闲置Uno板和乐高零件、想低成本做个实物项目的人。
1. 为什么把Arduino Uno和乐高放在一起
很多人觉得Arduino和乐高是两个世界的东西,一个是电子开发板,一个是塑料积木。但实际上把它们组合使用的逻辑特别通顺:乐高负责解决"机械结构怎么搭"的问题,Arduino Uno负责解决"运动逻辑怎么走"的问题,Shield负责解决"引脚怎么接、功率怎么带"的问题。三者各管一段,互相不抢戏。
我见过不少新手做小车,第一步就卡在车体上,切割亚克力板、打孔、装铜柱,折腾一整天还没通电。用乐高搭底盘就不一样了,拆装都以秒为单位,轴距、轮距、重心位置都可以随时改。这个优势在做运动控制类项目时尤其明显,因为你需要反复调整机械结构来配合程序逻辑,用胶水和螺丝固定的车架改一次就想哭。
再来说Shield。Arduino Uno的引脚一排排裸露着,用杜邦线跳线做实验可以,但做成一个要跑起来的设备就不靠谱了,线松了、接反了都是家常便饭。Shield的意义在于把"通用引脚"变成"专用接口",电机盾直接给你螺丝端子,接两根电机线就能跑,超声波模块也有现成的排针接口,不用再去查引脚定义表。这就是为什么这个项目要把Uno、Shield和乐高三者绑定在一起,而不只是拿Uno裸板加几个传感器去搭。
选型逻辑也要说清楚:为什么主控一定选Arduino Uno而不选Nano或Mega。Uno在这个项目里的优势是扩展资源多,Shield生态最丰富,网上随便一搜就能找到几十种针对Uno设计的扩展板。而且Uno的ATmega328P在5V逻辑下运行稳定,16MHz时钟频率驱动两个直流电机绰绰有余。用Nano的话虽然也能塞进乐高结构里,但Shield兼容性会差很多,很多电机盾和传感器盾都是按Uno的引脚布局做的。
2. 硬件选型与物理连接:Uno、Shield和乐高的组合细节
2.1 主控板与Shield选型搭配
先列一下我这次用的硬件清单,供参考:
- Arduino Uno兼容板一块(主控)
- L298N电机驱动Shield一块(电机控制)
- HC-SR04超声波传感器一个(避障测距)
- 两个直流减速电机加配套轮子
- 一节7.4V锂电池组或者6节AA电池盒(供电)
- 乐高积木若干,包括梁、销、齿轮、轴、车轮
- 杜邦线若干,扎带几个
电机驱动Shield这里要重点说一下。市面上常见的Arduino电机盾核心芯片一般是L298N或者TB6612FNG,我给新手的建议是优先选TB6612方案的板子,工作电压范围宽、压降小、发热低。L298N是经典老芯片,驱动能力大但功耗高,本身压降能达到2V以上,对电池电压要求高。如果只是驱动两个130电机或者TT电机,TB6612完全够用还省电,而且很多TB6612电机盾上还集成了超声波或者蓝牙接口,扩展性更强。我用的是双H桥电机盾,一路接左电机,一路接右电机,每个电机的正反转都可以独立控制,这正好是差速转向的基础。
2.2 乐高积木与电子模块的固定方法
把乐高和电子模块固定在一起,是很多人第一感觉最麻烦的地方。其实方法有好几种,我这次用了两个组合策略。
第一个策略是"利用乐高本身的孔洞结构"。乐高科技系列(Technic)的梁上有大量圆孔和十字孔,用扎带穿过孔绑扎电路板、电机和传感器是非常好用的方法,也方便拆卸。L298N电机盾叠在Uno上之后,Uno本身的安装孔可以用M3螺丝固定在3D打印的底座上,再把这个底座用乐高销钉固定到车架。不过没有3D打印机也没关系,直接用扎带把Uno板绑在乐高横梁上,实测也很稳。
第二个策略是"用橡皮筋和双面泡棉胶辅助固定"。超声波传感器要朝前安装,位置一般挂在车头,用两根扎带固定在乐高梁上正好。直流电机因为个头小,直接用橡皮筋捆在乐高横梁上,配合乐高轴和传动齿轮,比打胶干净多了。我这里踩过一个坑:一开始用热熔胶固定电机,跑两圈就震脱了,换扎带加固后再没出问题。
2.3 电源系统的接线逻辑
电源是整个项目最容易翻车的地方。Arduino Uno板载稳压器支持DC 7-12V输入,电机驱动Shield一般也从Uno的DC接口取电。我把7.4V锂电池接Uno的DC电源插座,Uno板载稳压输出5V给超声波传感器供电,电机盾则从电池正负极直接取电,这是标准的"双电源思想":逻辑控制部分用小电流5V,运动执行部分用大电流直供。千万别让电机电流全部流过Uno的5V稳压器,那颗芯片撑不了多久就会过热烧掉。
如果只有一节7.4V电池,也可以把电池正负极同时接入电机盾的电源输入端,电机盾再输出5V给Uno和传感器供电,这也是一种常见的接法。但要注意部分电机盾的板载稳压器输出电流有限,带两个电机加一块Uno会比较吃力,实测可能出现复位重启的现象。我的建议还是各供各的,控制归控制,驱动归驱动,问题最少。
3. 动手搭建:从零拼出一台乐高Arduino小车
3.1 底盘设计与传动方案
乐高底盘我用了标准的4轮方案,但实际上真正的驱动轮只有两个后轮,前轮是随动万向轮。后轮驱动、前轮从动的好处是悬挂简单、重心容易控制,差速转向时左右轮的转速差能直接决定转弯半径,逻辑清晰,适合做避障这种基础运动控制。
底盘拼装时先搭两根平行的横梁作为主框架,间距和Uno板的宽度保持一致,50毫米左右,这样后续把控制板固定在正中间时不会歪。驱动电机安装在主框架后部,两个电机对称放置。这里有一个关键点:两个直流电机最好选带减速箱的类型,空载转速一般在200-300转每分钟左右,直接驱动车轮扭矩就够。如果电机没有减速箱,就必须用乐高齿轮组做减速,否则扭矩不够,车会原地打滑。
传动上我用的是直接轴传动,电机输出轴通过联轴器或摩擦配合直接卡进乐高车轮,省去齿轮组,效率高、程序也简单。选轮子的时候要注意轮毂和电机轴的孔径匹配,我手头这组乐高轮子正好能插进电机六角轴,属于运气好,如果你的轮子孔径不匹配,可以用小尺寸的乐高齿轮套在电机轴上,再让齿轮驱动乐高轮子,也能实现传动。
3.2 控制板与驱动盾叠插安装
Uno和电机盾的叠插没有任何难度,对准插针按下去就行,唯一要留意的是检查板子的焊盘有没有虚焊,以及插紧后是否有引脚短路。装好之后,把Stack层从上往下数:电机盾在顶层,Uno在中间,最底下是电池仓。
控制板固定在车架上我用了一个很土但很稳妥的办法:Uno板底部贴一块泡棉双面胶,粘在底盘的中间横梁上,再用两根扎带穿过Uno板的安装孔和横梁孔拉紧。这样做的好处是缓冲减震,电机震动不会直接传导到主板上,坏处是换板子要重新撕泡棉胶,不过对一次成型的小项目来说完全能接受。
3.3 接线表:一目了然的引脚分配
电机盾接线的坑最多,尤其是左右电机容易接反。我最后确定的接线关系如下,按这个接就不会乱:
- 电机盾左电机输出OA1、OA2接左侧电机的两根线
- 电机盾右电机输出OB1、OB2接右侧电机的两根线
- 电机盾供电输入端接7.4V电池正负极
- 超声波传感器TRIG接D9,ECHO接D10
- 电机盾的VCC排针对应5V输出,接传感器VCC
- 地线统一共地,接电机盾GND
超声波传感器的引脚要特别说明:VCC接5V,GND接地,TRIG是触发脚,给一个高电平脉冲就发射超声波,ECHO是回波检测脚,返回高电平的时间就是声波往返时间。D9和D10在Uno上都是数字引脚,支持普通读写,接这两个口不会和电机盾占用的PWM引脚冲突。我第一次接的时候把ECHO接到了模拟口A0,程序里读脉冲用的还是digitalRead,结果读数永远是0,后来查了两小时才发现问题不在代码而在引脚定义上。
4. 给Uno板烧录引导:最容易翻车的一步
4.1 什么是引导程序,为什么有时候要重新烧录
说件很现实的事:新买的Arduino Uno兼容板,出厂时引导程序基本都是已经烧好的,接上USB线就能通过IDE直接上传程序。但如果你像我一样遇到二手板、板子被折腾过、或者IDE报出"avrdude stk500_getsync not in sync"这类经典错误,那大概率就是引导程序丢了或者乱了,这时候就得手动给Uno板重烧引导。
引导程序(业界叫Bootloader)是固化在单片机Flash里的一段小程序,作用是在上电时短暂接管串口,检查电脑端有没有发来新的程序文件,有就写入,没有就跳到应用区运行用户代码。可以把它理解成电脑的BIOS,负责"启动时先看看有没有要更新的系统",没了它,Uno就无法通过USB串口烧录程序。
4.2 把另一块Arduino当作烧录器的完整步骤
给Uno烧引导最典型的做法,是用另一块好的Arduino板当ISP下载器(在Arduino IDE里叫"Arduino as ISP")。完整流程如下,每一步都有对应目的:
第一步,准备好工作正常的Arduino板(我用的是一块Uno),把它作为主机,连接电脑。在Arduino IDE里打开示例菜单中的"ArduinoISP"程序,直接烧录进这块主机板。这块板子接下来扮演的角色就是用GPIO模拟SPI时序去给目标芯片写Flash。
第二步,接线。主机板的D10、D11、D12、D13分别对应目标板的RESET、MOSI、MISO、SCK。这个映射关系是ArduinoISP示例中约定好的,不要改。具体连接:主机D10接目标板RESET,主机D11接目标板11脚(D11),主机D12接目标板12脚(D12),主机D13接目标板13脚(D13),同时目标板的5V和GND要与主机板连在一起供电。因为目标Uno在烧录引导时不需要USB连接电脑,它完全由主机板供电和喂数据。
第三步,在IDE中选择目标板型号为"Arduino Uno",然后选择"烧录引导程序"菜单项,烧录器选择"Arduino as ISP"。点击烧录后,IDE会调用avrdude把ATmega328P对应的bootloader镜像通过SPI刷进目标芯片。等输出窗口出现"烧录完成"提示,目标Uno就重新获得了通过USB串口烧录用户程序的能力。
实测下来的时间大约是十几秒,比我想象的快很多。烧录完成后,拔掉ISP接线,把目标Uno用USB线单独连电脑,再通过IDE上传一个Blink程序,只要LED按每秒一次的节奏闪烁,就说明引导程序已经正常工作了。
4.3 烧录失败的常见原因与排查链路
如果烧录中途报错,我总结过几类高频原因,你按这个顺序排查基本能定位:
第一,查接线。MISO、MOSI接反是最大的嫌疑,尤其D12和D11在主机和目标板之间必须严格对应,有个很诡异的坑是主机板也要选"Arduino as ISP"菜单项下面的"Arduino as ISP"协议,如果选成了其他编程器协议,时序会完全不匹配。
第二,查目标板供电。有些目标板板载稳压器在断电状态下如果麦斯吉.COM 3.3V和5V引脚之间有焊桥就会热,目标板只连主机板的VCC和GND就能确认是不是电源问题。
第三,查芯片型号。Arduino Uno兼容板用的芯片绝大多数是ATmega328P,但有个别盗版板子用Atmega8或者贴片版本,选错了芯片型号烧录也会失败。可以在目标板芯片表面直接看丝印,确认是328P再做处理。
还有一点容易忽略:烧录引导时要把目标板上的其他外设接线全部断开,特别是如果接了电机盾,它会占用SPI引脚,干扰烧录过程。我第一次就是在电机盾还插着的时候尝试烧录,折腾了半小时没成功,拔掉盾板一次通过。
5. 避障程序设计与电机控制逻辑
5.1 先写电机控制底层的正确姿势
电机控制是整个软件部分的第一层地基。我用的电机盾是TB6612方案,芯片提供了IN1、IN2、PWM1、IN3、IN4、PWM2这样的控制引脚,其中IN1和IN2控制左电机的转向逻辑,PWM1控制左电机速度,右侧同理。启动程序前,先做一个最简单的前进测试,代码贴在下面:
const int leftPWMPin = 5; const int leftIn1 = 4; const int leftIn2 = 7; const int rightPWMPin = 6; const int rightIn1 = 8; const int rightIn2 = 12; void setup() { pinMode(leftPWMPin, OUTPUT); pinMode(leftIn1, OUTPUT); pinMode(leftIn2, OUTPUT); pinMode(rightPWMPin, OUTPUT); pinMode(rightIn1, OUTPUT); pinMode(rightIn2, OUTPUT); } void loop() { // 左电机正转,速度150 digitalWrite(leftIn1, HIGH); digitalWrite(leftIn2, LOW); analogWrite(leftPWMPin, 150); // 右电机正转,速度150 digitalWrite(rightIn1, HIGH); digitalWrite(rightIn2, LOW); analogWrite(rightPWMPin, 150); delay(2000); // 停止2秒 analogWrite(leftPWMPin, 0); analogWrite(rightPWMPin, 0); delay(2000); }第一次跑这个程序,重点观察两个轮子是不是都朝前转,转速是否一致。如果车跑斜了,说明左右电机转速有差异,这非常正常,直流电机的个体差异加上轮子摩擦力不同,会让车头偏向某一侧,后面需要在避障逻辑里做死区修正。
转向测试也很重要:让左轮正转、右轮反转,小车就会原地旋转。我用下面这段代码验证差速转向:
void turnLeft() { digitalWrite(leftIn1, HIGH); digitalWrite(leftIn2, LOW); digitalWrite(rightIn1, LOW); digitalWrite(rightIn2, HIGH); analogWrite(leftPWMPin, 180); analogWrite(rightPWMPin, 180); } void turnRight() { digitalWrite(leftIn1, LOW); digitalWrite(leftIn2, HIGH); digitalWrite(rightIn1, HIGH); digitalWrite(rightIn2, LOW); analogWrite(leftPWMPin, 180); analogWrite(rightPWMPin, 180); }这里的逻辑是:左转时左轮正转、右轮反转,车绕几何中心原地旋转;右转时反过来。你可能会想,为什么不让车像汽车一样转前轮,而是原地转圈?因为乐高搭建的这种小车转向方式就是差速转向,机器人领域叫"ACV"(Ackermann其实是汽车转向结构,差速转向是履带车和轮式机器人最常用的方案),它结构简单,在低速场景下完全够用。
5.2 超声波测距的中断式读取与哨兵防御逻辑
超声波传感器读取距离的原理是:TRIG脚拉高10微秒以上,模块连续发射8个40kHz的脉冲波,遇到障碍物返回后,ECHO脚输出一个和往返时间成正比的高电平。计算距离的公式是:距离 = 高电平持续时间 × 声速 / 2。
代码层面有两种常见写法。一种是直接用pulseIn函数,这个函数会阻塞等待高电平结束,好处是简单,坏处是程序在等待期间什么都干不了。另一种是用中断或者时间戳轮询,但小车避障这个场景对实时性要求不高,pulseIn足够,代码可读性也更好。我用下面的方式组织整个避障主循环:
const int trigPin = 9; const int echoPin = 10; long readDistance() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration = pulseIn(echoPin, HIGH); return duration * 0.034 / 2; // 单位:厘米 }读到的距离是厘米,接下来就要根据这个值做运动决策。避障逻辑我用的是经典状态机:直行、左转、右转三种状态。默认直行,当检测到前方障碍物距离小于25厘米时,进入避障状态。先右转350毫秒(这个时间是靠实测标定的,转完大约90度),再重新探测,如果前方仍然有障碍,就左转更大的角度,直到前方通畅后恢复直行。
这里有个容易被忽略的坑:超声波传感器的探测是有死角的,正前方障碍物距离读数是准的,但如果障碍物在车的斜前方,可能已经撞上了传感器还没触发。所以我在主循环里每隔50毫秒循环读取,不做阻塞,而且规定如果连续三次读数都小于阈值才进入避障状态,防止单次尖峰误触发。调试时我用串口打印实时距离,方便调整阈值。 ### 5.3 调参与标定的实际记录 标定转向时间是个体力活。我把小车放在地上,手按着车尾让它原地转,用秒表看它转90度需要多久。第一次设了300毫秒,实测转了大约半圈,明显过头;改成200毫秒又只转了45度不到。最后稳定在280毫秒左右,转90度误差在正负10度以内。这个参数会因为电池电量变化而有浮动,电池电压高时电机转速快,同样的时间内转过的角度就大,所以程序里我用前几次测距的均值动态决定转向时长,实测比固定值更稳定。 避障距离阈值也要根据车体长度和电机响应速度调。车长大约18厘米,如果阈值设置成5厘米,根本来不及反应就撞上了。我初步定在20厘米,实测发现因为电机启动有延迟,从发出停止指令到车轮真正停稳还有一小段滑行,所以最终把阈值提高到25厘米,牺牲一点机动性换来绝不撞车的稳定度。 ## 6. 实测表现与踩坑总结 ### 6.1 实际跑起来的观察记录 整台车拼好并烧录程序后,我在客厅铺了一张2米乘1.5米的区域,摆了三四个纸箱当障碍。实测结果挺让我满意:小车能持续避障绕行,大部分情况下能在纸箱前停下来完成转向,偶尔会出现一次转向时间不够撞到箱子侧面,但不会卡死,检测到侧面障碍后又会自动调整方向。 整个过程中电机盾发热在正常范围,连续跑了五分钟,芯片表面温度大约40摄氏度上下,手摸微温。Uno板温度更不明显,说明双电源供电策略起了作用,电机大电流没有经过板载稳压器。电池用的是两节18650串联的7.4V锂电池组,连续运行半小时没什么问题,电机的耗电比我想象的低。 ### 6.2 几个印象深刻的坑 第一个坑:电机接线正负极接反。左电机接反后小车不是后退,而是左轮反转右轮正转,原地打圈。排查方法是写一段最简单的"两轮都正转"测试代码,用左右轮转向方向判断接线极性,这个测试要坚持用,别一上来就写完整避障程序,否则出问题根本分不清是程序逻辑错还是接线错。 第二个坑:电池电压瞬间跌落导致Uno复位。最初用了一节9V叠层电池供电,电机一启动Uno就重启,这是因为电机启动电流峰值接近1A,9V电池内阻大带不动。换成7.4V锂电池后问题消失。也验证了不用双电源只靠板载稳压是行不通的,至少要保证电机驱动和逻辑供电的电流容量充足。 第三个坑:乐高齿轮打滑。刚开始用乐高小齿轮直接咬合大齿轮传动,结果电机一转齿轮就打滑跳齿。原因是轴向间隙太大,齿轮咬合深度不够。解决办法是在轴上多垫几个乐高垫片,把齿轮挤到合理位置,咬合深度达到齿轮齿高的大约三分之二就稳定了。 第四个坑:超声波传感器的测量盲区。HC-SR04的最小测量距离只有2厘米左右,低于这个距离就永远读回最大值,传感器会认为前方没有障碍。有一次小车贴墙太近,读到的距离反而显示为很大,导致它一直往前顶墙。后来我在代码里对小于3厘米的距离做强制处理,视为障碍物,才算把这个问题绕过去。 ### 6.3 后续还能把这个项目往哪些方向扩展 做完整台车之后,扩展空间其实很大,简单列几个方向。 第一个方向是加蓝牙Shield,手机遥控。在现有结构上叠一块HC-05蓝牙盾,写个串口指令解析程序,就能把避障模式切换成遥控模式,这样既保留了自动避障的代码,又多了人为操控的玩法。 第二个方向是换成带编码器的电机加PID闭环。乐高在市面上有兼容的编码器电机,配合Arduino的中断接口,可以读到每个时刻的车轮转速,然后用PID算法实时修正左右轮速差,这样小车就能走直线,转向精度也会大幅提升。避障逻辑从"开环盲走"变成"闭环规划",这是质变。 第三个方向是加一块OLED显示屏或者小喇叭,把当前状态、距离、模式显示出来,调试时不用再依赖串口,跑起来更有产品的感觉。乐高车壳上留一块屏幕的位置很合适,机械上也不用大改。 第四个方向是搞一个巡线传感器,让小车沿着黑色胶带贴的路线走,这就变成循迹小车了。原理不复杂,查一下数字灰度传感器的输出,再嵌套进现有的状态机,大概半小时就能调通。 ## 7. 想抄作业的人,我最后的建议 整套项目做下来,给我最大的体会就是:Arduino Uno加乐高这个组合,是真的适合折腾。乐高解决的是"结构怎么搭"的低成本快速试错,Uno解决的是"控制逻辑怎么跑"的成熟稳定底座,Shield解决的是"电气连接怎么不踩坑"的工程化问题。三者叠加,让一个脑子里的想法变成地上能跑的小车,整个周期只需要一个周末。 如果你也要做类似项目,我总结几条保命建议: 一是先测部件再组装整机。电机、驱动盾、传感器各自单独测试通过后,再合体调试,一旦出问题你至少知道问题出在哪个模块。 二是不要把代码一次写完。哪怕只是一个简单的避障小车,也建议分成"电机前进测试"、"转向测试"、"超声波测距测试"、"状态机全流程测试"四步走,每步验证无误再合并,排错时间能省一半以上。 三是重视供电。所有运动控制项目的崩溃根源九成在电源,电流不够、电压跌落、共地不良,症状五花八门,查到最后全是供电问题。优先备一个7.4V锂电池组,电机和控制分开供电,能省去大量时间。 四是接线做记号。电机盾和传感器的每一根线都用标签纸或者不同颜色杜邦线区分,防止插错。我回看自己的操作笔记,踩的所有坑里,至少一半是接线混乱造成的。 五是乐高结构的紧固程度要适中。太紧会导致齿轮卡死,太松会抖动松脱,标准是轻轻摇动车架,零件不脱落但又能感觉到微小旷量即可。 最后说一句关于买件的话:如果你手里还没有Arduino Uno,我个人建议直接买兼容板加TB6612电机盾的基础套装,性价比最高。乐高零件不用专门买,散件里有几个梁、几个齿轮、四个轮子就够了,大多数人家里的乐高散件都能凑齐。 这个项目我目前还在继续演进,下一步打算加编码器电机做闭环控制,让小车跑得更直、转弯更准。等我调完再来写一篇更深入的。如果你也把车做出来了,遇到问题欢迎交流,我踩过的坑也许能给你省点时间。