简介:这份资源围绕德州仪器DRV8701电机驱动芯片展开,面向智能车竞赛中C车、F车方向的参赛者与嵌入式电机控制初学者,帮助解决H桥驱动电路设计、电流检测与闭环调速等实际问题。压缩包共6个文件,约462KB,包含3个json工程文件、2个zip压缩包和1个txt说明文档,json对应原理图与PCB工程数据,zip内为驱动电路工程及Gerber制板文件,txt提供使用说明,便于直接查看电路结构与复现方案。资源涵盖DRV8701的栅极驱动、过流与短路保护、霍尔电流检测、PWM与SPWM控制方式及电源与控制接口设计,可帮助读者理解高效低噪声驱动的实现思路,并对照原理图和PCB完成硬件搭建与调试。目前已有2263人学习下载,适合需要快速获取驱动电路参考设计、缩短智能车硬件开发周期的读者。
1. DRV8701 电机驱动:为什么它成了智能车圈里的“低调狠货”
如果你拆过最近两年的智能车或者小型机器人底盘,大概率会在板子上看到一颗丝印为 DRV8701 的芯片。它不像 TB6612 那样被新手教程翻来覆去地讲,也不像 L293D 那样古老到几乎成了历史文物,但在真正跑起来、要拼响应速度和电流能力的场合,DRV8701 出现的频率越来越高。原因很直接:它是一颗单通道 H 桥栅极驱动器,自己不集成 MOSFET,而是靠外置 MOS 管来决定功率上限。这意味着你可以用同一颗芯片驱动一个几安培的小电机,也可以换成大功率 MOS 去推几十安培的负载,灵活性远超那些把功率管封在里面的模块。
我第一次接触它是在一台需要快速启停的循迹小车上,当时用 TB6612 总觉得加速肉、刹车软,换了 DRV8701 加一对低内阻 MOS 之后,同样的电机和电池,起步明显更干脆,刹车时电机两端能迅速短接,那种“说停就停”的手感是集成模块给不了的。这篇文章就围绕这颗芯片,把电路怎么搭、参数怎么算、代码怎么写、坑在哪里,按我实际调试的顺序讲一遍。适合已经会点单片机、想从模块级驱动往分立方案走一步的人,也适合手头有 DRV8701 板子但一直没调通的同行。
2. DRV8701 内部结构与外围电路:从框图到能焊的板子
2.1 它到底集成了什么、没集成什么
DRV8701 的内部框图可以拆成三块:逻辑输入与保护、电荷泵、栅极驱动。逻辑输入侧兼容 3.3V 和 5V 电平,接受 PWM 和方向信号,内部带死区控制,防止上下管直通。电荷泵负责把栅极驱动电压抬到比电源正极还高,这样才能驱动高侧 N 沟道 MOS。栅极驱动输出就是 GATE 引脚,分高侧和低侧,直接连到外置 MOS 的栅极。
它没有集成的是:功率 MOSFET、电流采样电阻、续流二极管(如果 MOS 体二极管够用也可以不加)。所以选型时第一件事就是确定电机的工作电压和堵转电流,然后去挑一对耐压足够、内阻够低的 N 沟道 MOS。常见做法是选耐压 30V 以上、内阻几毫欧到十几毫欧的管子,比如 IRF3205 这类虽然老但皮实的型号,或者更现代的贴片 MOS。电流采样电阻一般取几毫欧到几十毫欧,功率要算够,不然发热会让你怀疑人生。
2.2 最小系统电路怎么连
下面这张表是我在面包板验证阶段用的连接清单,后来画 PCB 也是按这个逻辑走的。注意 VM 是电机电源,VCC 是逻辑电源,两者要共地。
| 引脚 | 连接对象 | 说明 |
|---|---|---|
| VM | 电池正极 | 电机供电,范围 5.9V 到 45V |
| VCC | 3.3V 或 5V | 逻辑供电,必须加 0.1uF 去耦 |
| GND | 电池负极和逻辑地 | 单点共地,避免大电流串扰 |
| IN1/IN2 | 单片机 GPIO | 方向或 PWM 输入,具体看模式 |
| nSLEEP | 上拉或 GPIO | 低电平休眠,悬空可能不工作 |
| GATE_HS | 高侧 MOS 栅极 | 串一个小电阻,典型 10 到 47 欧 |
| GATE_LS | 低侧 MOS 栅极 | 同样串电阻 |
| SH | 高侧 MOS 源极 | 接电机一端和采样电阻 |
| SL | 低侧 MOS 源极 | 接采样电阻和地 |
| VREF | 参考电压 | 设定电流限制阈值 |
| SO | 电流反馈输出 | 接单片机 ADC,可做堵转检测 |
实际焊接时,电荷泵电容和 VM 去耦电容要尽量靠近芯片引脚,栅极走线要短而粗。我见过有人把 MOS 放在板子另一头,栅极线拉了五六厘米,结果开关波形振铃严重,MOS 发热明显。这不是玄学,是走线电感在作怪。
2.3 电流限制和死区时间怎么设
DRV8701 的电流限制是通过 VREF 引脚电压和采样电阻共同决定的。内部比较器会把 SH 引脚上的电压和 VREF 比较,一旦超过就关断输出,进入衰减模式。计算公式常见形式是:
I_limit = VREF / (R_sense × A)
其中 A 是内部电流镜增益,具体值查手册。我一般先选 R_sense,再反推 VREF。比如想要 5A 限流,R_sense 取 0.01 欧,假设增益为 10,那 VREF 就是 0.5V。这个电压可以用单片机 PWM 加 RC 滤波产生,也可以用 DAC,甚至两个电阻分压。用 PWM 的好处是可以在运行中动态调整限流值,比如起步时放宽,堵转时收紧。
死区时间方面,DRV8701 内部已经固定了,不需要外部设置。但外置 MOS 的开关速度会影响实际死区效果,栅极电阻太小会导致开关太快、振铃大,太大又会让死区相对变长、效率下降。我一般从 22 欧开始试,用示波器看 SH 引脚波形,振铃能接受、发热不严重就定下来。
3. 用单片机驱动 DRV8701:PWM 模式与代码落地
3.1 两种控制模式:PWM 模式和相位模式
DRV8701 支持两种输入模式,通过引脚配置或者型号后缀区分。一种是 PWM 模式,IN1 给 PWM,IN2 给方向;另一种是相位模式,IN1 和 IN2 都给 PWM,但相位相反。智能车场景里我更喜欢 PWM 模式,因为方向信号和速度信号分开,代码逻辑清晰,调速时只动一个定时器通道。
以 ESP32-C3 为例,它没有传统的 LEDC 之外的高级定时器,但 LEDC 足够用。下面是一段初始化代码,把 GPIO 配置成 PWM 输出和方向输出。
// ESP32-C3 PWM 初始化,用于 DRV8701 调速 #include "driver/ledc.h" #define PIN_PWM 4 // 接 DRV8701 IN1 #define PIN_DIR 5 // 接 DRV8701 IN2 #define PIN_SLEEP 6 // 接 nSLEEP,高电平使能 void drv8701_init(void) { // 配置 PWM 定时器 ledc_timer_config_t timer_cfg = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_10_BIT, // 0-1023 .timer_num = LEDC_TIMER_0, .freq_hz = 20000, // 20kHz,超出人耳范围 .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&timer_cfg); // 配置 PWM 通道 ledc_channel_config_t ch_cfg = { .gpio_num = PIN_PWM, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .timer_sel = LEDC_TIMER_0, .duty = 0, .hpoint = 0 }; ledc_channel_config(&ch_cfg); // 方向引脚和使能引脚 gpio_set_direction(PIN_DIR, GPIO_MODE_OUTPUT); gpio_set_direction(PIN_SLEEP, GPIO_MODE_OUTPUT); gpio_set_level(PIN_SLEEP, 1); // 退出休眠 } void motor_set(int speed) { // speed 范围 -1023 到 1023 if (speed >= 0) { gpio_set_level(PIN_DIR, 0); ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, speed); } else { gpio_set_level(PIN_DIR, 1); ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, -speed); } ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); }这段代码里,PWM 频率选 20kHz 是为了避开听觉范围,同时开关损耗也能接受。占空比分辨率 10 位,对应 0 到 1023,够精细了。方向引脚在正转时拉低、反转时拉高,注意 DRV8701 的 IN1 和 IN2 逻辑要对照手册,不同模式可能相反。nSLEEP 必须拉高,否则芯片不输出,这个坑我后面还会提。
3.2 电流反馈怎么读、怎么用
SO 引脚输出的是和负载电流成比例的电流,一般需要外接一个电阻转成电压,再送 ADC。以 ESP32-C3 的 ADC 为例,读回来的值可以用来做堵转保护。下面是一个简单的堵转检测逻辑。
// 读取 SO 引脚电压,判断是否堵转 #define PIN_SO 3 #define ADC_CHANNEL ADC_CHANNEL_3 int read_current_raw(void) { int raw = 0; adc_oneshot_read(ADC_UNIT_1, ADC_CHANNEL, &raw); return raw; // 12 位,0-4095 } // 在主循环里调用,超过阈值就停 void check_stall(void) { static int over_count = 0; int raw = read_current_raw(); // 假设 3.3V 对应 4095,电流阈值折算成 raw 约 2500 if (raw > 2500) { over_count++; if (over_count > 10) { // 连续 10 次超限 motor_set(0); gpio_set_level(PIN_SLEEP, 0); // 休眠保护 } } else { over_count = 0; } }这里的关键是阈值标定。我一般先用可调电源限流,让电机堵转,记录 SO 电压对应的 ADC 值,再留 20% 余量作为阈值。连续多次超限才动作,是为了避开启动瞬间的电流尖峰。这个逻辑不复杂,但能救电机和驱动板。
3.3 加减速曲线:让启停不突兀
直接给阶跃 PWM,电机会猛冲一下,齿轮和轮胎都受罪。我习惯加一个简单的斜坡函数,每次调用增加或减少固定步长,直到达到目标值。下面是一个 10ms 调用一次的斜坡。
// 斜坡控制,每 10ms 调用一次 static int current_speed = 0; static int target_speed = 0; void ramp_update(void) { int step = 20; // 每次变化 20,约 0.2 秒从 0 到 1023 if (current_speed < target_speed) { current_speed += step; if (current_speed > target_speed) current_speed = target_speed; } else if (current_speed > target_speed) { current_speed -= step; if (current_speed < target_speed) current_speed = target_speed; } motor_set(current_speed); } void motor_set_target(int speed) { target_speed = speed; }步长和调用周期决定了加速快慢。智能车循迹时我一般用 0.2 到 0.5 秒的加速时间,太快会打滑,太慢跟不上弯道。这个参数没有标准答案,得在赛道上试。
4. 避坑与排查:DRV8701 调试中常见的五个翻车现场
4.1 上电后电机不转,芯片发烫
现象:接好线、程序也跑了,电机纹丝不动,摸芯片有点烫。 原因:最常见的是上下管直通。可能是 IN1 和 IN2 同时给了高电平,或者死区被外部干扰破坏,也可能是 MOS 栅极接错。 解决:先断开电机,用示波器看 GATE_HS 和 GATE_LS,确认没有同时为高的时刻。再检查 IN1/IN2 逻辑是否符合所选模式。如果 MOS 发烫而芯片不烫,查 MOS 引脚是否焊反。
4.2 nSLEEP 悬空导致时好时坏
现象:有时候上电正常,有时候完全没反应,动一下板子又好了。 原因:nSLEEP 内部有下拉,悬空时可能被拉低进入休眠,也可能受干扰。 解决:直接上拉 10k 到 VCC,或者用单片机 GPIO 控制,初始化时拉高。别省这个电阻。
4.3 电流限制误触发,电机没劲
现象:电机能转但力量很小,一加载就停,SO 电压也不高。 原因:VREF 设得太低,或者采样电阻太大,导致限流阈值远低于电机正常工作电流。 解决:重新计算 VREF 和 R_sense,用示波器看 SH 波形,确认正常工作时峰值有没有超过阈值。也可以临时把 VREF 拉到 VCC 排除限流因素。
4.4 PWM 频率太低导致啸叫和抖动
现象:电机低速时嗡嗡响,转速不稳。 原因:PWM 频率落在人耳敏感区,比如 1kHz 到 5kHz,同时低频率下电流纹波大。 解决:把 PWM 频率提到 15kHz 以上,常见 20kHz。注意频率提高后栅极驱动损耗会增加,摸一下 MOS 温度,必要时减小栅极电阻。
4.5 电源地线处理不当,单片机复位
现象:电机一启动,单片机就重启或者 ADC 读数乱跳。 原因:电机大电流经过地线,在地线电阻上产生压降,导致单片机供电地浮动。 解决:电机电源地和逻辑地单点连接,走线尽量短粗。VM 和 VCC 的去耦电容不能省,最好再并一个 100uF 以上的电解。ADC 采样线远离电机线。
5. 进阶技巧:用 SO 反馈做闭环和故障记录
5.1 把 SO 变成电流环
前面用 SO 只做了堵转保护,其实它可以做更细的电流闭环。思路是:设定目标电流,读 SO 换算实际电流,用 PI 控制器调整 PWM 占空比。这样即使电池电压下降,电机输出力矩也能保持稳定。下面是一个简化的 PI 实现。
// 电流 PI 控制,每 1ms 调用一次 float target_current = 2.0f; // 目标 2A float kp = 0.5f, ki = 0.1f; float integral = 0.0f; void current_loop(void) { float actual = read_current_raw() * 3.3f / 4095.0f / 0.01f / 10.0f; // 折算成安培 float error = target_current - actual; integral += error * 0.001f; // 1ms 积分 float output = kp * error + ki * integral; // 限幅到 0-1023 if (output > 1023) output = 1023; if (output < 0) output = 0; ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (int)output); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); }参数 kp 和 ki 需要根据实际电机和采样电路整定。我一般先把 ki 置零,加大 kp 直到电流开始振荡,再退回一半,然后加 ki 消除静差。这个过程有点玄学,但用示波器看电流波形会直观很多。
5.2 故障记录:让每次翻车都有据可查
调试阶段最怕的是偶发故障,过去了就复现不了。我的习惯是在 RAM 里开一个环形缓冲区,记录每次电流超限时的 PWM 值、SO 读数、时间戳。一旦触发保护,就把这些数据通过串口打出来。这样即使现场翻车,也能回看当时发生了什么。下面是一个简单的记录结构。
typedef struct { uint32_t tick; int pwm; int current_raw; } fault_log_t; #define LOG_SIZE 32 static fault_log_t log_buf[LOG_SIZE]; static int log_idx = 0; void log_fault(int pwm, int current_raw) { log_buf[log_idx].tick = xTaskGetTickCount(); log_buf[log_idx].pwm = pwm; log_buf[log_idx].current_raw = current_raw; log_idx = (log_idx + 1) % LOG_SIZE; } void dump_log(void) { for (int i = 0; i < LOG_SIZE; i++) { printf("tick=%lu pwm=%d cur=%d\n", log_buf[i].tick, log_buf[i].pwm, log_buf[i].current_raw); } }这个技巧不限于 DRV8701,任何电机驱动调试都用得上。从那以后我每次焊新板子,都先把日志和限流保护跑一遍,再上电机全速。希望这些经验能帮你少烧几个 MOS,早点让轮子转起来。
本文还有配套的精品资源,点击获取