前阵子帮一个刚开始玩Arduino的朋友排查小车不转的问题,他按照教程把直流电机直接接到了UNO的5V和GND上,结果电机纹丝不动,板子还偶尔重启。这个场景其实特别典型——很多初学者一听到"直流电机控制",第一反应就是"给电就转",但实际上要让电机按要求转起来、转得快慢可控、方向可换,里面有一条完整的知识链。这篇内容就围绕这块展开,覆盖Arduino直流电机控制从原理、选型、接线到PWM调速和避坑的完整路径,适合刚接触Arduino、想用直流电机做小项目或智能小车底盘的读者参考。
我尽量把每个环节的"为什么"讲清楚,而不是只扔一段代码让你抄完就跑。毕竟电机的坑往往不在代码里,而在电流、电源、接线这些看起来不起眼的地方。
1. 先搞明白直流电机需要什么样的控制
先把直流电机这个东西的本质说清楚。直流电机靠电刷换向,输入直流电压就会转动,电压方向决定转向,电压大小决定转速。听起来简单,但到了单片机控制这个层面,"给直流电"只是个起点,真正要做到可控,你至少得解决三个问题:开关、换向、调速。
1.1 为什么不能把电机直接接在Arduino引脚上
Arduino UNO的GPIO引脚有两个硬性限制:输出电压只有5V(3.3V板子更低),输出电流一般只能到20mA左右,极限不超过40mA。直流电机启动瞬间的电流往往是额定电流的数倍,一个小型130电机空载电流可能200mA,堵转的时候冲到1A以上都是正常的。直接接引脚的结果只有一个——引脚烧了,或者板子重启。
这就引出两个必需品:外部电源和驱动电路。外部电源负责提供电机真正需要的电流和电压,驱动电路负责用Arduino的小信号去控制大电路的通断和方向。
1.2 控制电机本质上是控制这三件事
- 通断:让电机转还是不转。可以用继电器、三极管、MOS管实现。
- 换向:改变电机正负极连接,让电机正转或反转。继电器阵列或H桥电路实现。
- 调速:改变电机两端的平均电压。最常用的方式是PWM(脉宽调制),这也是本篇的核心。
很多新手把这三件事混在一起想,其实它们的控制手段是独立的。所以成熟的电机驱动模块往往是一个"组合拳":方向引脚管换向,PWM引脚管调速,使能引脚管通断(很多模块里PWM兼作通断)。
1.3 直流电机控制里一个重要概念:惯性滑行与刹车
直流电机断电后,转子不会立刻停下来,而是会因为惯性继续旋转。如果此时把电机两根线短接在一起,电机会因为发电制动效应迅速停转。这就形成了两种停止方式:
- 滑行停止:所有开关断开,电机靠摩擦自然减速,停得慢。
- 制动停止:把电机两端短接,电磁制动加速停车,停得快。
听起来是不是有点像用刹车片和松油门溜车的区别。后面讲代码逻辑表的时候,你会看到驱动模块专门有对应的状态来处理这两种情况。这一点在做小车的时候特别重要,尤其是巡线或避障时,电机反应速度直接决定控制效果。
2. 电机驱动模块选型:L298N和TB6612该怎么选
能在Arduino生态里直接用的直流电机驱动方案不少,最常见的是L298N模块、TB6612FNG模块,还有直接用分立MOS管搭的驱动板。这里只聊前两个,因为覆盖了绝大多数入门场景,也好买好查资料。
2.1 H桥原理:驱动模块内部到底在干什么
先补一个关键电路概念:H桥。所谓H桥,就是四个开关管(三极管或MOSFET)组成了一个像字母H的电路,电机接在H的中间横杠上。
- 打开左上和右下开关:电流从左往右流过电机,正转。
- 打开右上和左下开关:电流从右往左流过电机,反转。
- 四个开关全关:电机两端悬空,惯性滑行。
- 打开同侧两个开关(比如左上和左下):电机两端被短接,制动。
L298N和TB6612内部就是封装好的双H桥,一个模块能独立控制两个电机。你只需要通过逻辑引脚告诉它"往哪边转、用多大力气转",剩下的电流切换交给内部电路完成。
2.2 L298N模块:皮实但耗电大户
L298N是ST的老芯片,几乎是Arduino入门教程的标配。市面上的L298N模块通常长这样:
- 12V输入端(VS):接电机电源,典型值7V-12V
- 5V输出端:板载稳压器从VS降压出来,可以给Arduino供电(但不推荐)
- 使能端ENA、ENB:高电平开启对应通道,也可以接PWM调速
- 逻辑输入IN1、IN2、IN3、IN4:控制电机方向和开关
- OUT1/OUT2、OUT3/OUT4:接两个电机
L298N最大的问题在于内部采用的是饱和型三极管,压降很大。实测在电流稍大的时候,它自己就要吃掉1.5V-2V的压降,这意味着电池电压会被白白消耗掉一部分,模块本身也会明显发热。但好处是结构简单、抗造、接线容错率高,新手拿它学原理非常合适。
2.3 TB6612FNG模块:压降小,性能更好
TB6612是东芝的电机驱动芯片,被用在很多成品小车和机器人套件里。跟L298N模块相比,它的核心优势就是MOSFET管压降低(典型压降0.5V左右),同样电压下电机实际获得的电压更高,发热也小。模块体积比L298N小一大截,引脚有逻辑电源VCC和电机电源VM分离设计,逻辑电源最低2.7V,意味着3.3V的单片机也能直连。
TB6612的引脚功能跟L298N模块大差不差,都有AIN1/AIN2、BIN1/BIN2、PWMA/PWMB、STBY引脚,唯一多出来的是STBY(待机)引脚——拉低整个模块不工作,拉高正常工作。很多人第一次用TB6612发现电机不转,大概率就是STBY悬空导致模块一直处于待机态。
2.4 两个方案的选择对比
| 对比维度 | L298N模块 | TB6612FNG模块 |
|---|---|---|
| 芯片类型 | 双H桥,三极管 | 双H桥,MOSFET |
| 最大电流 | 2A(峰值) | 1.2A(每通道) |
| 逻辑电压 | 5V | 2.7V-5.5V |
| 电机电压 | 最高约12V(VS) | 最高约15V(VM) |
| 压降 | 高(1.5V-2V) | 低(约0.5V) |
| 发热 | 明显 | 较小 |
| 模块体积 | 大 | 小 |
| 适合场景 | 大电流、学习原理、皮实接线 | 智能小车、电池供电、长时间运行 |
如果是做智能小车这种电池续航敏感的项目,我建议优先用TB6612。如果只是桌面实验、手边有大功率电机,或者想把电路原理看得直观一点,L298N也不会让你失望。顺带说一句,这两种模块网上仿制版非常多,质量参差不齐,买的时候尽量挑带散热片、接口印字清楚的版本,能少很多隐性问题。
3. 接线实战:从模块到Arduino和电源的完整链路
接线看起来简单,实际上80%的入门翻车都发生在这一环节。下面以L298N模块为例,走一遍完整接线流程,顺便把电源分配和共地问题讲透。TB6612的接法逻辑类似,只是引脚名称不同,我会在关键处指出来。
3.1 完整的接线步骤
假设用一块Arduino UNO、一个L298N模块、一个直流电机、一个9V电池组(或者6节5号电池盒)。
第一步,接电机和电源到L298N:
- 电机两根线任意接OUT1和OUT2(如果发现转向反了,交换两根线即可,不用改代码)
- 电池组正极接L298N的12V接线柱,负极接GND接线柱
注意L298N的12V输入其实是VS,也就是电机电源输入。它支持的范围是7V-12V,标"12V"只是默认值。如果你的电机额定电压只有3V-5V,不建议直接走L298N的VS供电,因为模块输入电压太高会把电机烧掉。这种情况要么单独给电机供3V/5V电源,要么选一个支持低压的驱动方案。
第二步,接控制信号线:
- L298N的IN1接UNO的数字引脚D5
- L298N的IN2接UNO的数字引脚D6
- L298N的ENA接UNO的PWM引脚D9(要实现调速,ENA必须接可输出PWM的引脚,不是随便一个数字口都行)
- 另外一个通道如果暂时不用,IN3、IN4、ENB可以留着不接
UNO上标了"~"符号的引脚才是硬件PWM输出引脚:3、5、6、9、10、11。如果你用analogWrite输出PWM,这几个引脚才能正常出信号。接普通数字口也能让电机转,但只是全速开/关,没法调速。
第三步,共地:
- UNO的GND和L298N的GND接线柱之间必须连一根线
这一步极其关键,也是最容易被忽略的。Arduino输出的控制信号是对它自己的GND而言的5V高电平,驱动模块内部的逻辑电路也必须以同一个GND作为参考点,信号才有意义。如果不共地,控制信号跟驱动模块之间没有统一的电位基准,你会看到电机乱转、时转时不转、甚至逻辑输入完全无效。
第四步,可选:用L298N板载5V给Arduino供电。
把UNO从USB拔下来,L298N的5V输出接到UNO的5V引脚,就可以让Arduino通过电机电源供电独立运行。但这里有个隐藏风险:L298N板载的稳压器是从VS(可能12V)线性降压到5V,电流稍大就发热严重,烫到怀疑人生。如果你手上有独立的5V电源,尽量不要这样用。我的习惯是:电机用大电流电源,Arduino用USB充电宝或9V电池供电,双方只在GND上相连。
3.2 TB6612接线差异说明
TB6612模块的引脚对应关系:
- AIN1、AIN2对应L298N的IN1、IN2
- PWMA对应ENA
- VM接电机电源正极,GND接电源负极和Arduino的GND(共地)
- VCC接Arduino的5V(或3.3V),给模块逻辑电路供电
- STBY接Arduino的5V,让模块退出待机状态
注意TB6612的逻辑电源和电机电源是分开的,这一点比L298N更合理——L298N的逻辑供电是从VS稳压出来的,而TB6612可以单独给逻辑部分供低电压,适合3.3V的主控板。
3.3 电源分配的本质:把"信号地"和"功率地"在单点汇合
很多帖子只说"记得共地",但没说清楚为什么。这里换个角度看:电机是功率设备,启动电流大,会产生很强的电流变化;Arduino是信号设备,逻辑电平脆弱。如果电机的大电流流经Arduino的GND走线,地线上的压降和噪声就会干扰逻辑信号,轻则按键失灵,重则单片机复位。
共地的正确做法是:在驱动模块的GND接线柱上,把电源负极和Arduino GND汇合,形成"星形接地"。让电机的功率电流尽可能只在电源-驱动模块-电机之间回流,不要把Arduino的GND当成主电流通道。这也是给Arduino的5V引脚并接一个大电容(比如470uF电解电容)能抑制抖动的原因——电容在瞬态电流冲击时可以充当局部能量缓冲,缓解电源跌落,但治本方案还是独立供电加上合理的单点共地。
4. 用PWM调速:占空比、analogWrite和完整代码
现在进入转速控制的部分。Arduino本身有analogWrite()接口可以输出模拟般的PWM波形,但它的背后到底发生了什么,值得展开说一下。
4.1 PWM不是电压,而是"开关的速度和比例"
PWM(脉冲宽度调制)的本质是在一个固定周期内,让引脚在高电平和低电平之间高速切换,通过改变高电平所占的时间比例,也就是占空比,来改变负载上的平均功率。
举个例子:一个5V引脚,如果用50%占空比的PWM输出给LED,LED的亮度大约相当于接在2.5V上;占空比0%就是关,占空比100%就是全开。对于电机,电压是平均化的,50%占空比大致意味着电机的供电电压是电源电压的50%,转速也近似按比例下降。
这里有个新手经常有的误解:以为PWM是把电压变成了连续的模拟值。其实Arduino引脚只有"开"和"关"两个状态,PWM只是让它在两种状态之间快速切换。电机因为有电感和转动惯量,会把这种快速通断"抹平",表现成平滑的转速变化,而不是一顿一顿地转。
4.2 analogWrite的参数范围与频率
Arduino UNO上,analogWrite(pin, duty)的duty范围是0到255,对应占空比0%到100%。但在用之前你需要知道:
- 只有标注了"~"的引脚支持analogWrite,因为它们的定时器硬件支持PWM
- 不同的引脚PWM频率不一样:D5、D6约980Hz,D3、D9、D10、D11约490Hz
- analogWrite的值不是电压,而是占空比
如果你习惯用百分比来控制,需要在代码里做一个映射:
int pwmValue = map(percent, 0, 100, 0, 255); analogWrite(motorPwmPin, pwmValue);L298N的ENA引脚和TB6612的PWMA引脚接上PWM信号后,电机转速就跟着占空比走了。
4.3 完整的直流电机控制代码
直接给一套能跑的代码,一边看注释一边讲逻辑。
// 定义引脚 const int IN1 = 5; const int IN2 = 6; const int ENA = 9; void setup() { pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENA, OUTPUT); // 串口用于调试控制指令 Serial.begin(9600); Serial.println("输入格式: s 速度(0-255), d 方向(0正转 1反转), b 刹车, r 滑行"); } void motorStop() { // 滑行停止:两个方向引脚都输出低电平 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); } void motorBrake() { // 制动停止:两个方向引脚都输出高电平 digitalWrite(IN1, HIGH); digitalWrite(IN2, HIGH); } void motorForward(int speed) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); analogWrite(ENA, speed); } void motorBackward(int speed) { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); analogWrite(ENA, speed); } void loop() { if (Serial.available() > 0) { char cmd = Serial.read(); if (cmd == 'f') { // 加速到200,模拟软启动 for (int i = 0; i <= 200; i += 5) { motorForward(i); delay(30); } Serial.println("正转启动"); } else if (cmd == 'b') { for (int i = 0; i <= 200; i += 5) { motorBackward(i); delay(30); } Serial.println("反转启动"); } else if (cmd == 's') { motorStop(); Serial.println("滑行停止"); } else if (cmd == 'x') { motorBrake(); Serial.println("制动停止"); } } }4.4 L298N逻辑真值表与代码的关系
上面这段代码里的几个动作,对照L298N的逻辑真值表是这样对应的:
| 电机状态 | IN1 | IN2 | PWM/ENA | 实际效果 |
|---|---|---|---|---|
| 正转 | HIGH | LOW | 可调(0-255) | 电机正转,速度随PWM变化 |
| 反转 | LOW | HIGH | 可调(0-255) | 电机反转,速度随PWM变化 |
| 滑行 | LOW | LOW | 任意 | 电机惯性减速 |
| 制动 | HIGH | HIGH | 任意 | 电机短接,快速停转 |
看到没有,制动状态居然是IN1和IN2都为高,而滑行是都为低,这跟很多人的直觉正好相反。如果代码里的高低搞反了,你按"停止"键,电机反而会急刹;按"刹车"键,电机反而会溜溜转。
4.5 调速过程中的两个实际经验
第一,PWM值存在"死区"。因为电机存在静摩擦和启动转矩,PWM值在0到某个阈值(跟电机和电源电压有关,常见在20-50之间)时,电机可能根本不转。你调了半天发现电机纹丝不动,不一定是代码错了,可能只是驱动力根本不够克服摩擦力。
第二,启动时直接给最高PWM值会有浪涌电流。PWM=255的一瞬间,电机的启动电流可能是额定电流的数倍,对电源产生巨大冲击。软启动(从低到高逐步提速)不仅能保护电源,还能让小车起步更平稳,不会"窜出去"。上面代码里用for循环做渐加速就是这个道理。
5. 实测中容易踩的坑与完整的排查链路
这一节专门聊聊那些在实验台上反复出现的问题。我尽量把排查思路讲清楚,而不是只丢一句"接错了"。
5.1 电机一转Arduino就重启
现象:代码上电后一切正常,一调用motorForward()让电机转起来,UNO立刻重启,串口监视器重新初始化,LED闪一下。
原因:电机启动瞬间电流过大,导致电源电压瞬间跌落。Arduino的5V是它内部稳压电路输出的,如果外部供电电压跌落太多,稳压器输出跟着掉,CPU供电低于阈值就会复位。
排查链路:
- 确认电机电源不是从Arduino的5V引出来的(这是最常见的原因)
- 确认电机电源有足够的电流余量,不要用纽扣电池、手机充电线直接怼的那种劣质电池盒
- 在电机电源两端并联一个大电解电容(470uF-1000uF,耐压高于电源电压),吸收启动浪涌
- 检查共地是否可靠,电源负极、驱动模块GND、Arduino GND是否都在同一点
第3条是很多教程里不会细讲的点,但实测非常有效。电容的本质是电压缓冲器,电压跌落瞬间它快速补充电荷,电压回升时又吸收多余尖峰,等于给电源加了个蓄水池。
5.2 电机不转但驱动模块电源灯亮
场景:模块的电源LED亮着,但是电机完全不转,代码在跑,Serial也有输出。
排查链路:
- 先检查IN1、IN2是否处于有效状态。如果两个引脚都是LOW,电机就是滑行状态,不会主动转,但用手拨动电机轴会觉得松松的。
- 用万用表量一下OUT1和OUT2之间的电压。如果PWM输出50%,应该有大约一半的电源电压。
- 检查ENA是否已经接到PWM引脚。如果不接或者接了普通数字口但没有置高,电机也不会转。
- 检查电刷是否接触良好。直流电机也有寿命,碳刷磨损严重会出现转不动的现象,拿手拧一下能转但通电不转,多半是电刷或换向器问题。
- 测一下电机本身有没有断线——用万用表电阻档量电机两根线,正常直流电机电阻很小(几欧姆到几十欧姆),如果读数无穷大,电机内部断路。
我遇到过一次很玄学的情况:电机在桌面上好好的,拧到小车底盘上就转不动了。最后发现是连接电机的杜邦线太长、太细,线阻大,启动电压全被线吃掉了。直流电机对线材其实挺敏感的,尤其是电流大的时候,尽量用粗一点的硅胶线。
5.3 驱动模块过热,甚至冒烟
L298N模块发热是正常的,因为内部三极管压降大,电流越大发热越厉害。但如果到了烫手都不敢摸的程度,就要检查:
- 是否长时间堵转(电机轴被卡住,电流急剧上升)
- 是否接线短路,OUT1和OUT2之间不该直接短接
- 电源电压是否过高,超过了模块允许范围
TB6612本身压降小,正常使用发热不明显。如果它也很烫,大概率是接线错误或者电机已经损坏短路。另外,TB6612的反向电动势吸收能力有限,电机在快速正反转切换时会产生很高的尖峰电压,有的模块上会预留大电容的焊接位置,有条件可以加上。
5.4 同一个电机接同一个模块,怎么换了一根杜邦线就不转了
这是个被低估的问题。杜邦线看起来都一样,但质量和线径差异很大。细线在电机电流下压降大,实测一根JST转杜邦线可能在1A电流下吃掉0.5V以上的电压,电机可能就从"转得动"变成了"转不起来"。做小车时,功率路径(电池到驱动模块、驱动模块到电机)尽量用短而粗的导线,信号路径(Arduino到模块)才用普通杜邦线。
5.5 PWM调速时电机发出高频啸叫
电机在PWM驱动下偶尔会发出"嘶嘶"或"吱吱"声,这其实是PWM频率落进了人耳可听范围(20Hz-20kHz),电机的线圈和铁芯在高频振动。UNO默认的PWM频率是490Hz/980Hz,恰好在这个范围内,所以有声音是正常的,不是板子坏了。
如果嫌吵,可以把PWM频率提高到超声频段(比如20kHz以上)。UNO上实现这个需要改定时器寄存器。以D9/D10为例,它们共用Timer1,可以通过修改TCCR1B寄存器来改变分频系数:
void setup() { // 把Timer1的PWM频率从490Hz提升到约3906Hz TCCR1B = (TCCR1B & 0xF8) | 0x02; }不过改频率会影响这个定时器管理的所有引脚,而且频率高了之后电机内部的开关损耗会增加,电机反而容易发热。所以如果不是特别在意噪音,保持默认就好。
6. 从单电机到差速小车:控制系统怎么自然演进
既然很多人的最终目标是做智能小车,那就从单个电机的控制往前再走一步,看看控制架构是怎么从小实验变成完整的运动系统的。
6.1 双电机差速转向的概念
小车底盘通常左右各一个电机,叫差速驱动。左轮速度和右轮速度不一样的时候,小车就会转弯:
- 左轮转速 > 右轮转速:向右转弯
- 左轮转速 < 右轮转速:向左转弯
- 两轮同速:直行
- 两轮反向等速:原地旋转
所以不管用L298N还是TB6612,只要模块是双通道的,就能控制两个电机来实现完整的平面运动。代码上只需把单电机的控制封装成左右两个电机的函数:
void setMotor(int leftSpeed, int rightSpeed) { if (leftSpeed >= 0) { motorForwardLeft(leftSpeed); } else { motorBackwardLeft(-leftSpeed); } if (rightSpeed >= 0) { motorForwardRight(rightSpeed); } else { motorBackwardRight(-rightSpeed); } }这里用正负号表示方向,正数正转,负数反转,PWM值取绝对值。这种接口设计比单纯的forward/backward函数好用得多,因为控制逻辑关注的是"左右两个轮子的速度差",而不是底层方向引脚的开关组合。
6.2 把控制代码按"方向+速度"分层封装
写小车控制代码,最忌讳的就是把方向引脚和PWM引脚散落在各处,逻辑一复杂根本没法维护。我的习惯是分三层:
- 底层:每个电机的方向引脚和PWM引脚的开关控制,只做最基础的IO操作
- 中间层:将底层封装成每个电机的正向、反向、制动、滑行,等于是"电机驱动API"
- 上层:运动逻辑层,通过"左右两个轮子的速度"这个抽象接口来控制小车前进、后退、转弯
上层代码不需要关心IN1是不是接到了D5,只需要知道"我让小左轮以50%速度正转"。这样不管是换驱动板还是换引脚,都只需要改底层,上层运动逻辑完全不用动。
6.3 关于编辑器、仿真平台和练手路径的补充
有不少人问是先用仿真还是直接买硬件。先说结论:如果条件允许,直接上手硬件是最快的学习路径,因为电机的很多问题(电源跌落、电机啸叫、接线松动)在仿真里根本暴露不出来。但如果手边暂时没有硬件,用Wokwi这类在线仿真平台先把代码逻辑跑通,也是一个不错的过渡——它至少能帮你验证PWM调速的逻辑方向对不对、L298N真值表有没有记反。
编辑器的选择上,Arduino IDE依然是入门最友好的,装驱动、编译上传、串口监视器一步到位。如果你觉得它代码编辑体验太原始,用VSCode装Arduino扩展也可以,本质还是调用Arduino CLI做编译和上传。但新手不建议一上来就折腾VSCode,等遇到工程文件变多、需要看代码结构的时候再迁移也不迟。
6.4 从开环到闭环:编码器电机是下一步
本篇讲的控制方式都是"开环"的——你给多少PWM,就默认电机转到某个速度,但实际上电机的真实转速会受到负载、电池电压、地面摩擦的影响而波动。如果想让小车走一条笔直的线,或者精确走一段距离,光靠PWM和延时是不够的。
这时就需要带编码器的直流电机(也叫带霍尔编码器的电机),它能输出脉冲信号,让单片机知道电机实际转了多少圈。配合PID算法,可以让电机转速精确跟踪目标值,应对负载变化。这个东西是智能小车从入门到进阶的一个坎,如果能先把本篇的PWM调速和差速控制吃透,再去看PID和编码器反馈,会轻松很多。
我自己在玩电机控制的过程中,最大的体会是:电机的控制难题从来不在"让电机转起来",而在"让电机稳定可靠地按预期转动"。电源的余量、地线的连接、线材的粗细,这些看起来微不足道的细节,往往决定了你的小车是在赛道上稳定巡航,还是跑两步就抽风。如果你照着上面的步骤接线、烧录、跑通了第一个直流电机,下一步就可以开始搭底盘、加传感器,把运动控制真正用到完整作品里了。