一台避障小车,就是一个 2WD 底盘、一块 L298N 驱动板、一颗朝前的 HC-SR04,再加上一个「测距 — 判断 — 行驶」的循环。机械部分不过是一个下午的螺丝活;真正决定你的车是跑得起来还是只会爬的,是电池 —— 因为在马达见到电之前,L298N 已经悄悄吃掉了大约两伏。
做一台避障小车需要哪些零件?
2WD 底盘套件提供的是机械部分 —— 亚克力底板、两颗 TT 减速马达、轮子、一个前万向轮、螺丝,以及一个船型开关。所有电子件都得你自己配齐;而套件里确实附了的那个 AA 电池盒,正是这篇教程要换掉的唯一一样东西。
材料清单 —— 2WD 避障小车
Parts list
4WD 底盘是另一条路线,跑的是同一份程序。TP4056 负责在两次出车之间把两节 18650 充回来;5 针焊接服务覆盖的正是四个马达焊点,如果你不想自己动烙铁;胶带则用来把传感器绑在车头,并且把电池接头包起来。
第一次下单最容易买错的,是杜邦线的公母。L298N 的控制排针和 HC-SR04 的排针都是公针,而 Uno 板边是母座,所以十条信号线全部用公对母。两条电源线是例外:一端是压在 L298N 螺丝端子下的裸露金属针,另一端要插进 Uno 上的母座,所以两头都必须是公头 —— 一条公对母线到了 Uno 那一端带的是母座,两个母座是插不到一起的。这两条要从公对公那一包里拿。面包板硬跳线在这里两种活都不合适。这块 Uno 是用全尺寸 USB-B 烧录的,所以还需要那条 A 转 B 数据线。
接线之前,底盘要怎么装?
2WD 底盘的亚克力板两面都贴着牛皮纸保护膜,所以你拆开包装看到的那块「纸板」并不是底盘。把两面的膜都撕掉,连那几片小小的 T 形马达固定片也一起撕干净,透明亚克力上的每一个孔位和槽口才会显出来。
焊接这一步躲不掉。TT 马达出厂时,后端盖上是两片裸露的金属焊片,另外附一对红黑导线,线头是搪过锡的裸铜:一共四个焊点,每颗马达两个。手法请看我们的焊接教程;如果你宁可把零件寄回来给我们,5 针焊接服务覆盖的正好就是这四个焊点。烙铁停留的时间要短 —— 焊片离塑料端盖只有几毫米。套件里那个船型开关的接脚也是裸露的,把它串进电池盒的正极线上又是两个焊点;不装开关也可以,那样关车的方法就是从电池盒里抠出一节电芯。L298N 那一侧完全不用焊 —— 那边每一根线都是压在螺丝底下的。
剩下的都是机械活 —— 马达卡进亚克力固定片之间、轮子压到白色十字轴上、万向轮装在前缘下方 —— 动手之前先看一遍别人怎么装,会省下不少来回:

套件里没有任何东西是用来固定 HC-SR04 的,所以趁甲板还空着先想好它的位置。传感器要装在最前端,摆平、正对前方,两颗探头都探出亚克力边缘 —— 缩在车头后面,它听到的是自己的底板,不是房间。「摆平」比看上去重要得多:那束超声波是一个大约 15 度的锥形,所以稍微朝下一点,它就会在前方二三十厘米处找到地板,并且当成一堵永远不会消失的墙。两条扎带穿过底板的槽口,或者一段绝缘胶带沿着板缘缠住,都够牢了;Uno 和 L298N 装在它后面的铜柱上,每一排排针都要留得到手。
为什么电池的选择决定了车子动不动得了?
L298N 用的是老式的双极型达林顿桥,不是 MOSFET 桥。马达电流要先穿过高侧一组导通的三极管,再穿过低侧一组,而饱和状态下的双极型三极管并不像一个闭合的开关,它身上始终顶着一段固执的压降。ST 的 L298 规格书把这两段合起来列为 1 A 时最小 1.8 V、最大 3.2 V 的总压降,而它的 Figure 1 画出这个压降随电流一路往上爬 —— 所以下面这些用 1.8 V 下限算出来的账,已经是最厚道的一种情况。这一份电永远到不了马达,它变成的是黑色散热片上的热。
再看套件自带的那个电池盒。四节新的 AA 电池是 6 V;减掉桥的那一份,马达大约只看到 4.2 V,而碱性电池的内阻会在两颗马达同时起动的一瞬间把它拉得更低。TT 马达的额定是 3–6 V、空载连续电流 150 mA ±10%,轮子被按住时是这个数字的好几倍 —— 所以一个带载就往下掉的 4.2 V,在瓷砖上是爬,到了地毯上就直接停住。6 V 也没办法通过 VIN 给 Uno 供电,VIN 要的是 7–12 V。
两节 18650 电芯装进双槽电池盒时是头尾相接,也就是串联:标称 7.4 V,充满 8.4 V。减掉桥的那一份,标称电压下马达大约看到 5.6 V,稳稳落在它们的额定区间里;而程序里前进用的 PWM 是 255 分之 200,刚充满的电池组平均下来仍在 5.2 V 上下。这 7.4 V 同时也高过 L298N 板载 5 V 稳压器的压差要求,而那个 +5V 螺丝端子正是用来给 Uno 供电的 —— 一块板加一颗传感器,连十分之一安培都用不到。
板上那颗小小的 5V-EN 跳线帽请留着:它把这颗稳压器接到 +12V 输入上,只有输入超过大约 12 V 才需要拔掉。另外,不要拿四槽 18650 电池盒来代替 —— 那一款是给更高电压的作品准备的:多槽电池盒同样是把电芯串起来,四节就是 14.8–16.8 V,即使先让桥分掉一份,也远远超出一颗 3–6 V 马达能承受的范围。

L298N、传感器和 Uno 要怎么接?
L298N 出厂时在 ENA 和 ENB 上各套着一个黑色跳线帽,把两个使能端永久拉高 —— 全速运转,没有调速可言。把它们拔掉,让露出来的针接到 Arduino 的 PWM 输出上;程序里那两个微调常数能起作用,靠的就是这一步。想再往下走,请看我们的 L298N 调速教程。
| 从 | 接到 | 为什么 |
|---|---|---|
| 电池盒红线,经过套件那个船型开关 | L298N +12V 螺丝端子 | 这组 7.4 V 同时喂桥和板载的 5 V 稳压器 |
| 电池盒黑线 | L298N GND(中间那颗螺丝) | 马达电流的回流路径 |
| L298N +5V 螺丝端子 | Uno 5V 引脚 | 模块稳压出来的 5 V 供 Uno 和传感器用 —— 不必再加第二组电池 |
| L298N GND | Uno GND | 没有共地,IN 引脚就没有参考电平,车子的行为会变得毫无规律 |
| L298N OUT1 / OUT2 | 左边马达的两根线 | 那个轮子转反了就把这两根对调 —— 代码一个字都不用改 |
| L298N OUT3 / OUT4 | 右边马达的两根线 | 另一侧同理 |
| L298N IN1 / IN2 | Uno D9 / D8 | 一高一低,决定左边马达的转向 |
| L298N IN3 / IN4 | Uno D7 / D6 | 右边马达的同一组 |
| L298N ENA / ENB(跳线帽已拔) | Uno D10 / D11 | 各自一侧的 PWM 转速,每颗马达的微调就住在这里 |
| HC-SR04 Vcc | L298N +5V 螺丝端子,和给 Uno 的那根并排 | Uno 只有一个 5V 引脚,而模块那根线已经占着了;螺丝端子能同时夹住两根。传感器大约吃 15 mA |
| HC-SR04 Gnd | Uno GND(板上一共有三个) | 第二个 Uno 地脚,让传感器和 Arduino 共用同一个参考 |
| HC-SR04 Trig_RX_SCL | Uno D3 | 那个 10 µs 的脉冲,用来发射声波 |
| HC-SR04 Echo_TX_SDA | Uno D2 | 声音在空中飞多久,它就高多久 |
两节串联的 18650 和它们碰到的任何东西之间,是没有保险丝的:一节健康的 2200 mAh 电芯短路时能倒出几十安培,热到足以熔掉绝缘皮、烫伤手指。电池盒引线的绝缘皮只剥到螺丝端子需要的长度,螺丝要压在裸铜上而不是压在绝缘皮上,并且在电池组落进满是螺丝的底盘之前,把每一处裸露的接头都包好胶带。每一节电芯都要按照槽位上模压出来的极性放进去 —— 弹簧那一端永远是负极 —— 并且请用同型号、电量状态也相近的两节,免得强的那一节一路拖着弱的那一节走。
HC-SR04 是怎么把回声换算成厘米的?
Trig 被拉高 10 µs 时,HC-SR04 会从标着「T」的那颗探头发出八个周期的 40 kHz 声波,用「R」那颗去听,然后把 Echo 拉高,高电平持续的时间正好等于声音在空中飞行的时间。室温空气里声速大约 343 m/s —— 每微秒 0.0343 cm —— 而这趟路是去再回,所以厘米数就是微秒数 ÷ 58。单是这一块,我们另有一篇传感器教程。
我们发的这个版本用的是多模式丝印:Vcc / Trig_RX_SCL / Echo_TX_SDA / Gnd。如果你在找一支干脆写着「Trig」的脚,它就是 Trig_RX_SCL —— 多出来的那些名字属于这个作品用不上的其他模式。
接下来是那个让小车在空房间里搁浅的陷阱。Arduino 的 pulseIn() 超时之后返回的是 0,而不是一个错误 —— AVR 内核的 wiring_pulse.c 在没测到脉冲时返回的就是零,而不是一段时长。零除以 58 得到 0 cm,一份没有防备的程序会把它读成「有一堵墙贴在传感器上」,于是车子对着空气刹车。斜着的墙面、软性家具、空旷的地板,都会把声波散射到别处去。正确的做法是把零当成最大量程:没有回声,意味着前面什么都没有。
避障小车的完整源代码是什么?
HC-SR04 和 L298N 都不需要任何库。开发板选 Arduino Uno,用 A 转 B 数据线上传,再把串口监视器开在 9600 baud,就能一边把车拿在手上一边看读数。这份程序编译出来 3,194 字节,大约是 Uno 闪存的 9%。上传时电池开关要关着,然后先拔掉 USB,再开电池。这两路供电绝对不能同时带电:L298N 的 +5V 是直接进 Uno 的 5V 引脚的,既绕过了板上自己的稳压器,也绕过了守着 USB 口的那颗保险丝,所以这条电源轨前面什么都没有 —— 一旦它同时和笔记本的 5 V 接在一起,电压高的那一边就会往另一边灌电流。第一次跑之前,把车架在一本书上,让轮子悬空,这样一个写错的引脚号,代价只是一个空转的轮子,而不是从桌上摔下去。
// 2WD 避障小车:Arduino Uno + L298N + HC-SR04。
// 不需要任何库。Trig 和 Echo 就是普通数字脚,每颗马达占两支方向脚
// 外加 L298N 上一支 PWM 使能脚。
// ---- 引脚 ----
const uint8_t TRIG_PIN = 3; // HC-SR04 Trig_RX_SCL
const uint8_t ECHO_PIN = 2; // HC-SR04 Echo_TX_SDA
const uint8_t ENA_PIN = 10; // 左马达转速 (PWM)
const uint8_t IN1_PIN = 9; // 左马达方向
const uint8_t IN2_PIN = 8;
const uint8_t IN3_PIN = 7; // 右马达方向
const uint8_t IN4_PIN = 6;
const uint8_t ENB_PIN = 11; // 右马达转速 (PWM)
// ---- 你要调的就是这两个数 ----
// 没有两颗 TT 马达是一样的。让车在平地上直行,把跑在前面的那一侧
// 每次降 5 个计数,直到它走得笔直为止。
const uint8_t SPEED_LEFT = 200;
const uint8_t SPEED_RIGHT = 200;
const uint8_t SPEED_TURN = 220; // 原地转向要对抗两个轮胎,所以要给得更高
// ---- 判断条件 ----
const uint16_t CLEAR_CM = 25; // 比这个近,车就要作出反应
const uint16_t NO_ECHO_CM = 400; // 一次超时的测距算作什么:前方空旷
const unsigned long REVERSE_MS = 400;
const unsigned long PIVOT_MS = 350;
const unsigned long ECHO_TIMEOUT_US = 25000UL; // 往返约 4.3 m,超过就放弃
// 测一次距离。返回厘米数;什么都没回来时返回 NO_ECHO_CM。
uint16_t pingOnceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10); // 规格书要求的 10 us 触发脉冲
digitalWrite(TRIG_PIN, LOW);
unsigned long echoUs = pulseIn(ECHO_PIN, HIGH, ECHO_TIMEOUT_US);
// pulseIn 超时返回 0。这个零不是零厘米:
// 没有回声,说明前方是空的,而不是有一堵墙贴着传感器。
if (echoUs == 0) {
return NO_ECHO_CM;
}
// 室温下声音走 1 cm 出去再 1 cm 回来,大约要 58 us。
return (uint16_t)(echoUs / 58);
}
// 三次测距取中位数:一次离奇的反射从此再也指挥不动这台车。
uint16_t distanceCm() {
uint16_t a = pingOnceCm();
delay(30);
uint16_t b = pingOnceCm();
delay(30);
uint16_t c = pingOnceCm();
uint16_t swap;
if (a > b) { swap = a; a = b; b = swap; }
if (b > c) { swap = b; b = c; c = swap; }
if (a > b) { swap = a; a = b; b = swap; }
return b;
}
void driveLeft(bool forward, uint8_t speed) {
digitalWrite(IN1_PIN, forward ? HIGH : LOW);
digitalWrite(IN2_PIN, forward ? LOW : HIGH);
analogWrite(ENA_PIN, speed);
}
void driveRight(bool forward, uint8_t speed) {
digitalWrite(IN3_PIN, forward ? HIGH : LOW);
digitalWrite(IN4_PIN, forward ? LOW : HIGH);
analogWrite(ENB_PIN, speed);
}
void stopBoth() {
analogWrite(ENA_PIN, 0);
analogWrite(ENB_PIN, 0);
}
void goForward() { driveLeft(true, SPEED_LEFT); driveRight(true, SPEED_RIGHT); }
void goBackward() { driveLeft(false, SPEED_LEFT); driveRight(false, SPEED_RIGHT); }
void pivotLeft() { driveLeft(false, SPEED_TURN); driveRight(true, SPEED_TURN); }
void pivotRight() { driveLeft(true, SPEED_TURN); driveRight(false, SPEED_TURN); }
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(ENA_PIN, OUTPUT);
pinMode(IN1_PIN, OUTPUT);
pinMode(IN2_PIN, OUTPUT);
pinMode(IN3_PIN, OUTPUT);
pinMode(IN4_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
stopBoth();
Serial.println(F("Obstacle car ready - put it down."));
delay(2000); // 留点时间把车放到地上并松手
}
void loop() {
uint16_t cm = distanceCm();
Serial.println(cm);
if (cm > CLEAR_CM) {
goForward();
delay(40);
return;
}
// 前方有东西。倒车之前先停:把一副带着负载的齿轮箱从前进直接
// 甩进后退,是你能对塑料齿轮和 H 桥提出的最苛刻的要求。
stopBoth();
delay(200);
goBackward();
delay(REVERSE_MS);
stopBoth();
delay(150);
// 左右各看一眼。没有舵机,车只能转动自己的车身来对准传感器,
// 所以每转一次就测一次,然后往宽敞的那一边走。
pivotLeft();
delay(PIVOT_MS);
stopBoth();
delay(150);
uint16_t leftCm = distanceCm();
pivotRight();
delay(PIVOT_MS * 2);
stopBoth();
delay(150);
uint16_t rightCm = distanceCm();
if (leftCm > rightCm) {
pivotLeft(); // 转回来越过中线,回到刚才看左边的角度
delay(PIVOT_MS * 2);
stopBoth();
}
delay(100); // 稳一下,下一圈循环就开进它选中的那个缺口
}
CLEAR_CM 取 25 而不是 5,是因为车子在「思考」的时候仍然在往前走。analogWrite() 把 PWM 交给了硬件定时器,所以整个 distanceCm() 执行期间马达照转不误,而三次测距加上中间的稳定延时大约要花掉十分之一秒 —— 其中若有一次要等到超时,还会更久。这段时间里车子会走出好几厘米,之后还要再多滑一点,因为 stopBoth() 是把使能脚拉低,那等于松开桥,让轮子惯性滑行而不是刹住。近端同样有讲究:HC-SR04 测不了大约 2 cm 以内的距离,落在这个盲区里的障碍物给不出可用的回声 —— 而这份程序按设计会把它读成空地。在 25 cm 就作出反应,等于把墙留在传感器还诚实的那一段量程里。
为什么它会画弧线,不走直线?
两颗 TT 马达从来不会一模一样:注塑齿轮组的摩擦不同,电刷磨合的情况也不同,所以同一个 PWM 值下总有一侧转得快些。这正是 SPEED_LEFT 和 SPEED_RIGHT 要分成两个常数的原因。把车放在平地上,看它往哪边偏:车总是朝快的那一侧的反方向拐,所以一台向左画弧的车,快的是右边那颗马达。把那一侧的常数降五个计数,重新上传,反复几次,直到它走上两三米还保持直线。
两个数都不要压太低。大约低于 100 之后,桥的压降之后剩下的平均电压已经推不动齿轮组的静摩擦,轮子只会嗡嗡响却不转 —— 一个很低的 PWM 值不是「慢的马达」,而是一颗堵转的马达。
这套流程的形状和它的数字一样重要:先左右各看一眼再决定转向,正是破解「墙角陷阱」的关键 —— 一台永远往同一边转的车,会在两面墙之间来回弹。装在同一副底盘上的循线小车解决的则是相反的问题。

车子跑起来之后还能加什么?
SG90 舵机是最顺理成章的升级:把 HC-SR04 架在舵机轴上,它自己就能左右张望,不必再靠整台车转身 —— 更快,也远没那么容易在掉头途中撞上桌脚;落到代码里,就是把两次原地转向换成两个舵机角度,而比较的逻辑原封不动,做法可以参考我们的自动垃圾桶教程。支架要有心理准备自己想办法,因为这类舵机是裸装出货的,不带舵盘和螺丝 —— 一小片亚克力加绝缘胶带就搞定了。
4WD 底盘可以原封不动地跑这份程序:把每一侧的两颗马达并联接进 L298N 的同一个通道,一对正常行驶的马达只占掉这个通道 2 A 额定的一小部分,而且在地毯上抓地力更好。例外是堵转 —— 一颗卡住的 TT 马达大约要 1.5 A,所以卡死的一对已经超过通道的额定;请把车弄出来,而不是让它硬顶。不过那是另一套件:没有万向轮、没有开关,也没有 AA 电池盒。最后再配一块 TP4056 充电板把整个方案收尾;动手之前请先读我们的 18650 充电教程。
我们从真实客户那里常看到的错误
线全接好了,但什么都不动。先检查 ENA 和 ENB 的跳线帽,再检查地:跳线帽拔了却没有驱动使能脚,等于两个通道都是关着的;而 L298N 的 GND 如果从来没有接到 Uno 的地上,IN 引脚就是悬空的,它们对着的是一个 Arduino 根本看不见的电池负极。
有一个轮子转反了。什么都没坏 —— 直流马达的转向,无非就是它两根线里哪一根接的是正。把那颗马达在 OUT1/OUT2 或者 OUT3/OUT4 上的两根线对调,代码不用动。
插着 USB 时跑得好好的,一换电池就死。通常是电池没电、电芯装反,或者把电池组接到了 Uno 上而不是 L298N 的 +12V 端子上 —— 先量一下电池组的电压。
一转弯它就自己重启。原地转向是这台车电流最大的动作,而 Uno 靠的是 L298N 上那颗小小的 5 V 稳压器,它的输入要比输出高出大约两伏才行。一组充满电的 7.4 V 电池,这点余量绰绰有余;而内阻偏高的老化电芯、只充了一半的电芯或者拆机回收的电芯就没有,至于套件那组 6 V 的 AA 电池,还没被拉垮就已经低于稳压器想要的电压了。症状非常好认:车子会僵住两秒钟,然后从头再走一遍整套流程 —— 因为那正是 setup() 里的 delay(2000) 又跑了一次。
马达一转,读数就开始乱跳。直流马达内部的电刷打火,就是一个带着两根天线的小型无线电发射机,所以请把每颗马达的两根线绞在一起,并且让它们远离传感器那四根线走。在每颗马达的两个接线端子上焊一颗 0.1 µF 的瓷片电容,是这类套件上的标准解法。
常见问题
前面明明没有东西,车子为什么会停?
一次超时的测距会让 pulseIn() 返回 0,而没有防备的代码会把它变成 0 cm。请把零当成最大量程。
配 L298N 的小车该用什么电池?
两节串联的 18650,不要用 9 V 方块电池:烟雾报警器用的那种电池内阻太高,两颗马达同时起动就能把它的电压拉垮,顺带把 Arduino 重启掉。
该看见的墙,传感器为什么会漏掉?
超声波的反射像光打在镜子上,所以一个斜面会把声音送到旁边去,而窗帘和沙发则直接把它吸收掉。请让它摆平、正对前方。
可以用 ESP32 代替 Uno 吗?
可以,但有一点要注意:Echo 输出会摆到 5 V,而 ESP32 的引脚是 3.3 V 的,所以这一条线需要一个两电阻分压。IN 和 EN 则直接接受 3.3 V 逻辑,不用改。
最后更新:2026年8月。有问题?WhatsApp 联系我们。



2WD 4WD Smart Car Chassis Single Layer Acrylic Arduino Smart Car Chassis Kit With Motor - 2WD CAR KIT
L298N Motor Driver Module Arduino Compatible Dual H Bridge Dual DC Motor Control Arduino Robotic Car
Ultrasonic Sensor HC-SR04 Ultrasonic Range Detection Distance Finder Obstacle avoidance For Arduino Application
Arduino Compatible UNO R3 FT232 Development Board Arduino Compatible CH340 Alternative
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)
18650 Battery Holder 1/2/3/4 Slot 3.7V Battery Cell Housing Battery Case Battery Casing - 18650 BATTERY HOLDER (2)
3.7V 18650 Battery Rechargeable Li-Ion Battery 1200mAh 2200mAh 3200mAh Flat Top - 18650 Li-Ion Battery (2200mAh)
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-F 20CM
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 20CM
TP4056 18650 Battery Charger Module Lithium Ion Battery Li-ion Battery Charger Type C Micro USB - TP4056 CHARGER (TYPEC)
Soldering Service for Circuit Boards & Electronics - 5 Pins Soldering Service
Electrical Insulation Tape 18mm x 10m PVC Color Coded Marking Red White Blue Yellow Green Black - ELECTRICAL TAPE (BLACK)