← 返回教程库

直流电机与驱动:为什么不能直接接单片机

最后更新 2026-06-22
L2 · 传感与交互 ⏱ 约 16 分钟 🟡 涉接线/强电
你将学到
  • 理解为什么电机要经驱动板(大电流 + 感性负载反电动势)
  • 用 gpio_set_level 输出方向真值表控制正转/反转/刹车/滑停
  • 用 ESP-IDF 的 LEDC 给使能脚发 PWM 调速
  • 记住 TB6612 的 STBY 必须拉高、电源分离 + 共地这两条红线
  • 选对 L298N 还是 TB6612
🛒 器材清单
器材数量参考
直流减速电机1
TB6612 或 L298N 驱动板1

价格随渠道波动,以购买页实时为准。

你想让一辆小车跑起来。手头有一颗直流减速电机、一块 ESP32-S3、照着点灯的经验,很自然会想:把电机的两根线接到一个 GPIO 和 GND 上,给个高电平,它不就转了?

别接。真这么接,轻则板子重启,重则烧引脚。直流电机和 LED 是两个量级的负载,中间必须隔一块「电机驱动板」。这一节把为什么讲透,再给你一份能直接跑的正反转加调速 ESP-IDF 代码,最后用一张排查表收尾——读完你能让小车真正动起来,而且知道每一步在防什么坑。

读这篇前,你最好已经会用 LEDC 发 PWM 调亮度。还没搞懂占空比是怎么回事?先看 PWM 调光那节,那里配 LEDC 定时器和通道的套路,这里给电机使能脚调速时会原样复用。


为什么不能直接接 GPIO

这不是「最好别」,是「不能」。两个硬道理,分开讲清楚。

一、电流太大,引脚扛不住

一个 GPIO 引脚能安全输出的电流只有几十毫安——ESP32-S3 单脚典型在 20mA 上下,这是给 LED 这种「吃几毫安就亮」的小负载设计的。

而一颗普通的直流减速电机,空转就要一两百毫安,带载或堵转的瞬间能冲到几百毫安甚至上安培。你拿一个只能给 20mA 的引脚去喂一个要几百 mA 的负载,结果只有一个:引脚被过流烧掉。这跟 LED 必须串限流电阻是同一类问题,只是电机这边的胃口大了一个数量级,靠电阻限流已经没意义——电机本来就需要那么大电流才转得动。

二、电机是感性负载,会反咬一口

电机内部是一圈圈线圈,线圈是典型的感性负载。感性负载有个脾气:电流不能突变。当你突然断电、或者电机换向的瞬间,线圈里的电流想维持原样,于是在两端「憋」出一个方向相反的高压尖峰——这叫反电动势(也叫续流电压)。

这个尖峰可能远超 3.3V,顺着线灌回单片机,足以击穿 GPIO 内部的保护结构。哪怕没当场烧掉,反复的电压冲击也会让芯片莫名其妙地重启、死机。LED 是纯电阻性负载,没这个问题;电机有,所以不能裸接。续流二极管就是干这个的——把这个尖峰泄掉。好消息是 TB6612、L298N 这类驱动板的 H 桥内部都集成了续流二极管,你不用自己外接,但要知道它在帮你挡这一刀。

🚧 避坑

很多人第一次是「先接上试试,转起来再说」。问题是电机裸接 GPIO 不一定当场冒烟,可能先能转几下、看着没事,引脚却已经在过流和电压尖峰里慢慢受损,过两天突然就不工作了。这种「延迟性损坏」最难排查。所以不是「能转就行」,是从第一次就走驱动板。


驱动板做的事:弱电控强电

既然引脚既给不出大电流、又扛不住反电动势,思路就清楚了:让单片机只负责「下命令」,让另一块电路负责「出力」。这块电路就是电机驱动板。

它的核心是一组功率开关(H 桥)。单片机给它几个毫安级的小信号——「正转」「反转」「多快」——驱动板就用自己接的独立大电源,按命令把大电流通断到电机上。同时它内部对反电动势做了处理(集成续流二极管),把电机的「反咬」挡在单片机之外。

这就是嵌入式里反复出现的标准套路:弱电控强电。单片机这边永远是干净的小信号,脏活累活(大电流、感性冲击、可能的高压)全交给中间的驱动级。你后面控制继电器、控制大功率灯、控制水泵,都是同一个模式。理解了这一层,半个 L2 的硬件你都通了。

以 TB6612 为例,它对每个电机给你三根控制脚,外加一根全局使能脚:

  • 两根方向脚(A 路叫 AIN1 / AIN2):高低组合决定正转、反转、刹车、还是滑停。
  • 一根速度脚(A 路叫 PWMA):给 PWM 占空比,决定转多快——这里就是你在 PWM 那节学的东西。
  • 一根 STBY(待机)脚:整块芯片的总开关,必须拉成高电平芯片才工作。这是新手最爱漏的一根线,下面会专门强调。

完整代码:TB6612 控正反转 + 调速(ESP-IDF)

下面以 TB6612 驱动板的一路电机为例,全程用 ESP-IDF:方向脚走普通 gpio_set_level,速度脚走 LEDC PWM。先看接线,再看代码。

接线(一路电机):

ESP32-S3 GPIO4  ──→ 驱动板 AIN1   (方向脚 1)
ESP32-S3 GPIO5  ──→ 驱动板 AIN2   (方向脚 2)
ESP32-S3 GPIO6  ──→ 驱动板 PWMA   (速度脚,走 LEDC)
ESP32-S3 GPIO15 ──→ 驱动板 STBY   (待机脚,必须拉高)
驱动板 AO1 / AO2 ──→ 电机两根线
电机电源(电池组) ──→ 驱动板 VM 和 GND
ESP32-S3 GND ──→ 驱动板 GND        ← 必须共地
驱动板 VCC ──→ ESP32-S3 3.3V        (给芯片逻辑供电)

这些控制脚(AIN1/AIN2/PWMA/STBY)按你手上的板子可自由换,避开 strapping 脚(0/3/45/46)、USB 脚(19/20)和 26~37 段的 flash/PSRAM 脚即可,这里选的是 ESP32-S3 上安全的通用 IO。记住两条:①最后那根 GND 一定要把单片机的地和驱动板的地连到一起(共地),漏了它信号没有共同参考点,电机会乱转或干脆不动;②STBY 不拉高,电机一动不动,你会以为代码错了,其实是芯片在睡觉。

代码(放进工程 main/main.c):

// TB6612 单路电机:正转 → 停 → 反转 → 停,循环
// 方向脚用 gpio_set_level,速度脚用 LEDC PWM
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "motor";

#define AIN1  GPIO_NUM_4       // 方向脚 1 → 驱动板 AIN1
#define AIN2  GPIO_NUM_5       // 方向脚 2 → 驱动板 AIN2
#define PWMA  GPIO_NUM_6       // 速度脚   → 驱动板 PWMA(走 LEDC)
#define STBY  GPIO_NUM_15      // 待机脚   → 驱动板 STBY(必须拉高)

#define PWM_CH    LEDC_CHANNEL_0
#define PWM_TIMER LEDC_TIMER_0
#define PWM_MODE  LEDC_LOW_SPEED_MODE

// 配方向脚、STBY 为普通输出;速度脚交给 LEDC
static void motor_init(void)
{
    // 方向脚 + 待机脚:普通数字输出
    gpio_config_t io = {
        .pin_bit_mask = (1ULL << AIN1) | (1ULL << AIN2) | (1ULL << STBY),
        .mode         = GPIO_MODE_OUTPUT,
    };
    gpio_config(&io);

    // LEDC 定时器:8 位分辨率(占空比 0~255),与 PWM 调光那节同款
    ledc_timer_config_t timer = {
        .speed_mode      = PWM_MODE,
        .duty_resolution = LEDC_TIMER_8_BIT,
        .timer_num       = PWM_TIMER,
        .freq_hz         = 5000,            // 5kHz,电机听不出啸叫
        .clk_cfg         = LEDC_AUTO_CLK,
    };
    ledc_timer_config(&timer);

    // 把 PWMA 那根 GPIO 绑到一个 PWM 通道
    ledc_channel_config_t ch = {
        .gpio_num   = PWMA,
        .speed_mode = PWM_MODE,
        .channel    = PWM_CH,
        .timer_sel  = PWM_TIMER,
        .duty       = 0,
        .hpoint     = 0,
    };
    ledc_channel_config(&ch);

    gpio_set_level(STBY, 1);   // 关键:唤醒 TB6612,不拉高电机不转
}

// 设速度:占空比 0~255,0=停转
static void set_speed(uint32_t duty)
{
    ledc_set_duty(PWM_MODE, PWM_CH, duty);
    ledc_update_duty(PWM_MODE, PWM_CH);
}

void forward(uint32_t speed)               // 正转
{
    gpio_set_level(AIN1, 1);
    gpio_set_level(AIN2, 0);
    set_speed(speed);                      // speed: 0~255,越大越快
}

void backward(uint32_t speed)              // 反转
{
    gpio_set_level(AIN1, 0);
    gpio_set_level(AIN2, 1);
    set_speed(speed);
}

void motor_stop(void)                      // 停(滑停)
{
    gpio_set_level(AIN1, 0);
    gpio_set_level(AIN2, 0);
    set_speed(0);
}

void app_main(void)
{
    motor_init();

    while (1) {
        ESP_LOGI(TAG, "正转 1.5s");
        forward(180);  vTaskDelay(pdMS_TO_TICKS(1500));
        ESP_LOGI(TAG, "停 0.5s");
        motor_stop();  vTaskDelay(pdMS_TO_TICKS(500));
        ESP_LOGI(TAG, "反转 1.5s");
        backward(180); vTaskDelay(pdMS_TO_TICKS(1500));
        ESP_LOGI(TAG, "停 0.5s");
        motor_stop();  vTaskDelay(pdMS_TO_TICKS(500));
    }
}
⚠️ 安全

上面的 GPIO 编号、占空比、PWM 频率是参考实现,需按你的实际接线和电机自行验证。第一次跑务必让电机空载(别装到小车上),确认方向和调速正常再装轮子。

编译、烧录、看日志一条命令搞定:

idf.py build flash monitor

(第一次先 idf.py set-target esp32s3 选好芯片。)

你应该看到什么

  • 串口里滚动打印 正转 1.5s / 停 0.5s / 反转 1.5s / 停 0.5s,对应电机正转约 1.5 秒 → 停半秒 → 反转约 1.5 秒 → 停半秒 → 无限循环。
  • forward(180) 里的 180 改大(比如 240),电机明显转得更快;改小(比如 90),慢下来甚至带载就转不动了——这就是 LEDC 占空比在控速度。
  • 如果你接的两根电机线方向反了,「正转」看起来是反着转。没关系,把电机那两根线对调一下,或者把代码里 forwardbackward 的 AIN1/AIN2 互换即可,不影响安全。
  • 如果电机纹丝不动、日志却照常打印——八成是 STBY 没拉高,或者那根线没接好。回头检查 gpio_set_level(STBY, 1) 和接线。

AIN1 / AIN2 / PWM 方向真值表

把方向脚和速度脚的组合记成一张表,所有动作都从这里查:

AIN1 AIN2 PWM 占空比 电机状态
1 0 1~255 正转,占空比越大越快
0 1 1~255 反转,占空比越大越快
0 0 任意 停(自由滑停,电机靠惯性滑停)
1 1 任意 停(刹车,两端短接,立刻停住)
任意 任意 0 停(无驱动)

要点:方向由两根方向脚(AIN1/AIN2)用 gpio_set_level 输出的高低组合决定,速度由 PWMA 上的 LEDC 占空比决定,两者独立。AIN1 == AIN2 时电机停:0,0 是滑停(撒手让它自己慢慢停),1,1 是刹车(两端短接,急停)。代码里 motor_stop() 用的是滑停;想急停就把两根脚都置 1。


STBY 与供电共地:两条红线

电机这边的电、和单片机这边的电,处理方式完全不同;再加上 TB6612 那根容易漏的 STBY——这一段是整节最不能省的地方。

⚠️ 安全

电机用独立电源供电(电池组 / 适配器),接到驱动板的 VM 脚,不要从单片机引脚或 USB 取电——电机的大电流会瞬间拉垮单片机供电,直接重启。驱动板电源地要和单片机共地(GND 连到一起),否则信号没有共同参考。电机启停有大电流冲击,建议在电源端加大电容稳压(H 桥内部已集成续流二极管,无需外接),进一步避免尖峰干扰单片机。

再强调四条,照做能避开八成翻车:

  • STBY 必须拉高:TB6612 的 STBY 是芯片总开关,代码里 gpio_set_level(STBY, 1) 一句没有,整块芯片就在待机,电机怎么都不转。这是最隐蔽的坑——日志一切正常,电机就是死的。
  • 电源分两路:逻辑电(给 ESP32-S3)走 USB 或稳压,功率电(给电机)走电池组接 VM,两路各管各的,只在 GND 共地;驱动板的 VCC(逻辑供电)从单片机 3.3V 取。
  • 先确认电压匹配:你的电机标称几伏,驱动板 VM 就给几伏(常见 6V/7.4V/12V),别拿 3.3V 去推 12V 电机,也别拿 12V 灌进只标 6V 的电机。
  • 第一次上电先空载:电机别装到小车上,先让它空转确认方向和调速正常,再装上轮子,避免接错时小车直接窜出去。

L298N 还是 TB6612

两块都是常见的双路直流电机驱动板,选哪个:

L298N TB6612
驱动技术 双极型晶体管,压降大(满载约掉 2V 多) MOSFET,压降小、效率高
发热 大,常需散热片
体积 小巧
持续电流 单路约 2A,能扛更大电机 单路约 1.2A(峰值约 3A)
控制脚 IN1/IN2 + ENA(PWM 走 ENA) AIN1/AIN2 + PWMA + STBY
价格 老、便宜、好买 略贵一点
推荐 大电机/图便宜够用 小车轻负载更推荐

简单说:做小车、做轻负载,直接上 TB6612——它用 MOSFET、压降小、发热小、效率高、个头小,同样电池更耐用。L298N 是老方案,内部用双极型晶体管,压降大、发热明显,但能扛更大电流,如果你要驱动比较大的电机、或者手头只有它,也完全够用。

注意两点差异:①L298N 没有 STBY 脚,省掉 STBY 那根线和那句拉高;②L298N 的速度脚叫 ENA(使能),把上面代码里 LEDC 绑定的 PWMA 换成接 ENA 的 GPIO 即可,方向脚换成 IN1/IN2,其余 gpio_set_level + LEDC 的逻辑一模一样。照驱动板丝印对脚名就行。


故障排查:电机不对劲,按这张表查

现象 最可能的原因 怎么办
电机完全不转,日志正常 STBY 没拉高 / 没共地 / VM 没接电源 / 占空比是 0 先确认 gpio_set_level(STBY, 1) 有执行且 STBY 接好;确认单片机 GND 和驱动板 GND 连到一起;确认 VM 接了电池;确认 set_speed 不是 0
只能往一个方向转 一根方向脚(AIN1 或 AIN2)没接好或代码写死 量一下 AIN1/AIN2 是否都接到位;核对 backward 分支确实切换了两根脚的 gpio_set_level
一上电单片机就重启 电机从单片机取电 / 没独立电源 / 缺稳压电容 电机改走独立电池组接 VM;电源端加大电容;别让电机和单片机抢同一路电
驱动板或电机发烫 电压给高了 / 长时间堵转 / 电流超出驱动板能力 核对电机标称电压与 VM 是否匹配;别让电机长时间堵转;负载太大就换 L298N 或更大驱动
转速调不动,给多少都一样 LEDC 没绑到 PWMA / 绑错了 GPIO / 没调 ledc_update_duty 确认 ledc_channel_configgpio_num 是接 PWMA 的脚;确认 set_speed 里调了 ledc_update_duty

动手挑战:让小车前进和转向

会控一个电机,控两个就能做小车了。试试这个:

  1. 接第二路电机:TB6612 是双路,另一路用 BIN1 / BIN2 / PWMB(STBY 共用同一根)。给 LEDC 再配一个通道(LEDC_CHANNEL_1)绑到 PWMB,照样写一组 forward/backward/stop,参数换成第二个电机的脚。
  2. 前进:两个电机同向同速——小车直走。
  3. 转向:让一边电机比另一边慢(差速转向),或者一边正转、一边反转(原地打转)。试着写一个 turn_left():左电机慢、右电机快。

卡住了?把你的接线、想要的动作、和现在的 ESP-IDF 代码一起发给 AI,让它帮你补出双电机的转向函数和第二个 LEDC 通道——你已经掌握了单电机这一半,剩下的是复制和组合。


小结 · 你现在掌握了什么

  • 你彻底搞懂了电机为什么不能裸接 GPIO:电流太大会烧引脚,感性负载的反电动势会击穿单片机,必须靠驱动板的 H 桥和续流二极管挡着。
  • 你能用 ESP-IDF 控正反转和调速——方向靠 AIN1/AIN2 的 gpio_set_level 真值表,速度靠 PWMA 上的 LEDC 占空比,两者独立。
  • 你记住了 STBY 必须拉高独立电源 + 共地 这两条红线,知道电机电和逻辑电要分开走。
  • 你会选驱动板:小车轻负载上 TB6612(MOSFET、压降小、发热小),要大电流或图便宜用 L298N。

这套「单片机出小信号、驱动级出大力气」的弱电控强电思路,下一节会原样用到——只不过那次碰的不是电机,而是真正的强电。

下一步:学继电器,用同样的套路去控制 220V 这类强电负载。那一节的安全提示比这节还要紧,务必认真读。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明