资讯动态

FPGA驱动L9110H电机控制:H桥原理、死区与Verilog实现详解

发布时间:2026/9/28 16:56:37 来源:尧图企业网站定制
搞嵌入式这么多年凡是做过小车、云台、风扇这类运动控制项目的几乎没有不知道 L9110H 的。这颗芯片便宜、封装小、外围电路简单是很多入门级电机驱动板的核心。但这颗芯片的玩法远不止“接两根线就能转”——尤其是当你打算用 FPGA 而不是单片机去驱动它时时序、占空比、死区、逻辑电平这些平时容易被忽略的细节全部会浮出水面。这篇文章我把 L9110H 从芯片内部电路到 FPGA 代码实现完整梳理一遍。你会知道 H 桥到底是怎么工作的、模块上那些电阻电容起什么用、FPGA 的 IO 能不能直连、Verilog 代码怎么写才安全可靠以及上板调试时最容易栽的几个坑。适合正在做 FPGA 小车/云台项目的同学也适合想从电路层面彻底搞懂电机驱动原理的嵌入式爱好者。1. 模块拆解L9110H 这颗芯片到底集成了什么1.1 引脚与封装L9110H 最常见的封装是 SOP-8一共 8 个引脚功能非常直白。先看引脚定义引脚号名称功能说明1OAA 通道输出接电机一端2VCC电源正极2.5V~12V 均可3GND电源地4OBB 通道输出接电机另一端5IBB 通道控制输入6IAA 通道控制输入7GND芯片内部地与引脚 3 直通8GND同 3、7散热也会走这里很多淘宝模块引出的接口其实是把 IA、IB 标成了 IN_A、IN_B把 OA、OB 接到电机端子。你看到的“4 线接口 2 线电机输出”拆开看就是这么一颗 8 脚芯片。需要注意一点L9110H 的两路输出 OA 和 OB 并不是独立的双 H 桥而是组成了一路完整 H 桥的两半。想同时驱动两个电机需要两颗芯片。1.2 H 桥电路原理四个开关如何让电机正反转L9110H 内部本质上是一个 H 桥驱动器。为什么叫 H 桥把电机当作 H 中间那一横四个开关管分别在四个角上形成字母 H 的形状。四个开关管在不同组合下导通电机的端电压方向就会改变开关组合电机两端电压方向电机状态左上右下导通AB-正转右上左下导通A-B反转全部关断无电压停止惯性滑行同侧两个导通两端短路刹车能耗制动你可能会想既然控制信号只有两个输入 IA、IB四个管子怎么分配答案在内部逻辑。IA、IB 的电平组合直接决定输出对 OA、OB 的状态IA 电平IB 电平OAOB电机状态高低高低正转低高低高反转低低高阻高阻停止滑行高高低低刹车制动注意最后一行IA 和 IB 同时为高时电机两端被拉到近似同电位产生刹车效果。这个组合在单片机项目里用得少但如果你想让小车迅速停下来而不是靠惯性滑行这个状态非常有价值。1.3 模块板载电路为什么还要加电容和电阻市面上 L9110H 模块的板载电路差别不大基本就是这几个元件电源端一颗 100uF ~ 470uF 的电解电容吸收电机启停瞬间的电流冲击电机端或电源端并联一颗 104100nF瓷片电容滤高频噪声有些模块会加一颗稳压二极管或防反接二极管保护芯片不受电源反接损坏控制输入端的限流电阻有些板子有有些没有用于保护前级 IO。这些元件不是摆设。直流电机是感性负载换向瞬间会产生反电动势。如果没有电容吸收尖峰电压可能直接打坏芯片内部功率管。实测中我用示波器看过电机换向瞬间的波形电压尖峰能超出 VCC 两倍以上没有 104 电容的话驱动芯片寿命会明显缩短。2. 选型对比L9110H、L298N、TB6612谁更适合 FPGA 项目2.1 三款驱动器的核心参数对照很多人在选电机驱动时会纠结尤其当主控从单片机换成 FPGA 以后接口和电平逻辑都要重新评估。我直接列一张对比表参数L9110HL298NTB6612工作电压2.5V~12V5V~35V2.5V~13.5V峰值输出电流约 1.5A~2A2A/通道约 3.2A 峰值输出导通压降较小较大约 1.5V~2V较小控制逻辑2 路 TTL 电平使能 两路 IN使能 两路 INPWM 频率范围较宽实验常用 1k~20kHz建议 1k~5kHz较宽外围电路极少需外部二极管需外部电容典型应用小型直流电机、玩具小车大功率减速电机、机器人底盘智能小车、云台FPGA 项目里我建议优先用 L9110H 或 TB6612。L298N 的导通压降太大满速时电机会明显感觉“没劲”而且逻辑部分用的是 TTL 电平FPGA 的 3.3V 输出虽然电平兼容但对高电平阈值的要求要仔细确认。2.2 逻辑电平匹配FPGA 的 IO 能不能直连FPGA 的 IO 标准常见有 LVCMOS333.3V、LVCMOS252.5V、LVCMOS181.8V等。L9110H 的逻辑输入接口按芯片手册理解高电平范围通常是 VCC 的 0.7 倍以上。如果你给 L9110H 的 VCC 供电是 5V那么 0.7×5V 3.5V而 FPGA 的 3.3V 高电平输出可能不够。这就有意思了FPGA 的 3.3V LVCMOS 输出VOH 典型值大约 3.2V~3.3V而芯片要求的 VIH 是 3.5V信号的容差非常紧张。虽然实际电机驱动模块内置了电平均衡电路多数情况下能正常跑但这是有风险的。更稳妥的方式是给 L9110H 的 VCC 也供 3.3V这样 VIH 降到 2.31VFPGA 输出完全满足或者加一路电平转换芯片如 TXS0108E、74LVC245把 FPGA 的 3.3V 逻辑提升到 5V或者选择逻辑部分供电与 FPGA 一致的模块版本。我个人的习惯是FPGA 项目里的 L9110H 直接 VCC 供 3.3V因为小型电机在 3.3V 下输出扭矩足够而且完全避免了电平匹配问题。如果你的电机是额定 6V 以上那就老老实实加电平转换别硬直连。2.3 按项目场景选择小车、云台、风扇、四轴跑 FPGA 项目的场景不同驱动选型逻辑也不一样小型轮式小车、两轮平衡车实验L9110H 足够峰值 1.5A 左右重量轻的小车完全跑得动云台、舵机替代场景直流电机 编码器TB6612 更合适它的 PWM 频率响应更好适合闭环控制大功率底盘、搬运机器人L298N 或专门的 BTN7971 等大电流驱动L9110H 的电流余量不够实验用风扇、水泵L9110H 的 PWM 调速范围宽100Hz 到 20kHz 都能胜任。注意L9110H 的峰值电流标称看起来不高但不同批次芯片的散热能力有差异。长时间满负荷运行芯片温度会明显上升。选型时如果不是做一次性演示我建议电流余量至少留 50%。3. 从电路到 FPGA控制信号需要什么样的时序3.1 PWM 调速的底层逻辑占空比与平均电压FPGA 没法像单片机那样直接在 GPIO 上输出模拟电压驱动电机调速的标准做法是 PWM——脉冲宽度调制。电机的转速取决于两端平均电压。一个周期 T 内高电平持续时间为 Ton占空比 D Ton / T。平均电压 Vavg D × VCC。PWM 频率要选得合适频率太低比如 50Hz电机会一抖一抖地转动噪音大频率太高比如 50kHz 以上功率管开关损耗上升驱动芯片发热明显对于 L9110H 驱动小型直流电机我实测 10kHz ~ 20kHz 比较舒服听不到明显啸叫芯片发热也能接受。在 FPGA 里生成 PWM本质就是一个计数器。我经常用这个思路计数器周期 时钟频率 / PWM 频率比较值 占空比 × 周期计数器小于比较值时输出高否则输出低。举个例子如果 FPGA 板载时钟是 50MHz想要 10kHz 的 PWM周期的计数值就是 5000。也就是计数器从 0 数到 4999一个周期正好 0.1ms。占空比如果是 30%比较值就是 1500。3.2 直通风险与死区插入这是很多人第一次用 FPGA 写电机驱动代码容易踩的坑。H 桥的正反转切换不能瞬间完成。比如电机正在正转此时左上和右下管子导通。你突然要反转需要切换到右上和左下导通。如果切换瞬间四个管子同时导通了一小段VCC 和 GND 之间就变成了一条低阻路径电流会瞬间飙升这叫直通shoot-through。短路电流会造成什么后果轻则驱动芯片发热严重重则直接击穿功率管。在 FPGA 代码里解决直通的办法叫“死区插入”——在切换方向时先让所有管子关断一小段时间再开通另一组管子。具体在多长时间对 L9110H 来说几十纳秒到几百纳秒就能满足管子关断时间但考虑到 FPGA 信号经过引脚延迟、模块上电阻电容的影响我一般插入 1us ~ 2us 的死区。如果你的时钟是 50MHz也就是 50~100 个时钟周期的全关断时间。3.3 FPGA 侧的状态机设计思路把电机控制抽象成状态机代码写起来会清晰很多。我常用的状态集合IDLE停机两个控制输入都拉低对应滑行或都拉高对应刹车FWD正转A 给 PWMB 拉低REV反转A 拉低B 给 PWMBRAKE刹车A 和 B 都拉高或者都拉低并配合使能短路。状态切换时插入死区从 FWD 到 REV先进入一个短暂的“全关断”状态等计数器走完死区时间再进入 REV。这个状态机可以直接用 Verilog 的 always case 实现代码下面细讲。4. Verilog 实现可综合的电机驱动控制器4.1 顶层模块与端口定义我先给一个顶层模块的框架。假设你用的是常见的 FPGA 开发板系统时钟 50MHz按键控制方向拨码开关或串口调占空比四个输出分别接 L9110H 的两路控制输入。module l9110h_driver_top ( input wire clk_50m, input wire rst_n, input wire dir_switch, // 1: 正转, 0: 反转 input wire brake_en, // 1: 刹车 input wire [7:0] duty_in, // 0~255, 占空比 output wire motor_ia, // L9110H IA output wire motor_ib // L9110H IB );端口不多但背后有几个设计点dir_switch 和 brake_en 建议在模块内部先同步打两拍因为外部按键输入是异步信号直接进来会有亚稳态风险duty_in 的值代表占空比8 位共 256 级对电机控制来说精度够了输出 motor_ia、motor_ib 是直接连到 FPGA 引脚的信号IO 标准要在约束文件里设置。4.2 PWM 生成模块PWM 生成的核心代码并不复杂我直接写一个可综合的版本// 50MHz 时钟, PWM 频率 10kHz 计数周期 5000 localparam PWM_PERIOD 5000 - 1; reg [12:0] pwm_cnt; reg pwm_out; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin pwm_cnt 13d0; pwm_out 1b0; end else begin if (pwm_cnt PWM_PERIOD) begin pwm_cnt 13d0; end else begin pwm_cnt pwm_cnt 13d1; end // duty_in 范围 0~255, 映射到占空比 0~100% // 计算: pwm_cnt duty_in * (PWM_PERIOD1) / 256 - 1 if (pwm_cnt ((duty_in * (PWM_PERIOD1)) / 256)) begin pwm_out 1b1; end else begin pwm_out 1b0; end end end这里有个细节乘法duty_in * (PWM_PERIOD1)的结果最大是 255 × 5000 1,275,000需要用足够宽的寄存器保存。如果直接在条件下写乘法综合器通常也能正确处理但我建议把比较值单独算出一个寄存器代码更清晰reg [19:0] pwm_threshold; always (*) begin pwm_threshold (duty_in * (PWM_PERIOD1)) / 256; end always (posedge clk_50m or negedge rst_n) begin ... if (pwm_cnt pwm_threshold) begin pwm_out 1b1; end else begin pwm_out 1b0; end end4.3 方向与刹车控制逻辑有了 PWM 输出接下来就是把 PWM 分配到 IA 和 IB 上。这里有个重要原则正反转切换时PWM 和方向信号不能同时变化。我在代码里用了一个方向寄存器和死区状态机reg [1:0] ctrl_state; localparam S_IDLE 2d0; localparam S_FWD 2d1; localparam S_REV 2d2; localparam S_BRAKE 2d3; reg [5:0] dead_cnt; localparam DEAD_TIME_CNT 50; // 1us 50MHz always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin ctrl_state S_IDLE; motor_ia 1b0; motor_ib 1b0; dead_cnt 6d0; end else begin case (ctrl_state) S_IDLE: begin motor_ia 1b0; motor_ib 1b0; if (dir_switch !brake_en) ctrl_state S_FWD; else if (!dir_switch !brake_en) ctrl_state S_REV; else if (brake_en) ctrl_state S_BRAKE; end S_FWD: begin if (brake_en) begin ctrl_state S_BRAKE; end else if (!dir_switch) begin // 先进入全关断死区 dead_cnt DEAD_TIME_CNT; ctrl_state S_IDLE; end else begin motor_ia pwm_out; motor_ib 1b0; end end S_REV: begin if (brake_en) begin ctrl_state S_BRAKE; end else if (dir_switch) begin dead_cnt DEAD_TIME_CNT; ctrl_state S_IDLE; end else begin motor_ia 1b0; motor_ib pwm_out; end end S_BRAKE: begin motor_ia 1b1; motor_ib 1b1; if (!brake_en) begin dead_cnt DEAD_TIME_CNT; ctrl_state S_IDLE; end end default: ctrl_state S_IDLE; endcase end end这段代码里方向切换时先把状态置回 S_IDLE但 S_IDLE 里我留了一个死区计数的位置。上面其实简化了死区等待逻辑完整做法是在 S_IDLE 的 case 分支里先判断 dead_cnt 是否减到 0再允许进入新的方向状态。4.4 死区插入与安全保护完整的死区处理我习惯用一个独立的小模块// 死区模块: 进入 FWD/REV 前强制输出全低 reg [5:0] idle_cnt; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin idle_cnt 6d0; end else begin if (ctrl_state S_IDLE) begin if (idle_cnt DEAD_TIME_CNT) idle_cnt idle_cnt 6d1; else idle_cnt idle_cnt; end else begin idle_cnt 6d0; end end end always (*) begin if (idle_cnt DEAD_TIME_CNT) begin motor_ia 1b0; motor_ib 1b0; end end这个做法的意思是哪怕你状态机已经跳去了 FWD只要死区计数没走完输出依旧保持全低确保 H 桥上下管子不会同时导通。除了死区还有两个保护机制值得加占空比上限通过寄存器duty_max把 duty_in 钳位在例如 240避免满占空比下电机启动电流过大掉电检测如果 FPGA 检测到系统复位默认输出全低滑行而不是保持原来的方向输出防止上电瞬间电机猛转。wire [7:0] duty_safe (duty_in 8d240) ? 8d240 : duty_in;这个小改动非常有用。我之前见过一个项目FPGA 加载 bit 文件瞬间由于所有寄存器初始化为 0电机没动但换了另一次编译版本某个状态寄存器上电是随机值电机在上电瞬间猛转一下。加上钳位和复位默认低电平输出就能消除这类问题。5. 上板调试波形、转速与常见坑5.1 用逻辑分析仪和示波器看什么Verilog 写完不是直接接电机就完事了先看波形再上电机这是排错的基本原则。我调试时一般分三步第一步空载看逻辑。不接电机直接在 FPGA 引脚上测 IA、IB 的波形。先用逻辑分析仪看 PWM 频率和占空比是否和设计值一致再看方向切换时死区是否生效。逻辑分析仪抓触发条件设为 IA 上升沿抓一组方向切换的数据。第二步接电机看供电。电机接上后用示波器探头测 L9110H 的 VCC 和 GND 之间波形。如果看到明显的电压跌落说明电源功率不足或电容容量不够。空载和带载的电压差最好控制在 0.5V 以内。第三步看电流波形。如果有电流探头直接看 VCC 的电流波形。正常情况下电流有规律波动如果出现尖峰异常大概率是 PWM 频率不对或者死区没插好。5.2 引脚分配与板级约束实例以常见的 Xilinx Artix-7 开发板为例引脚约束文件里要明确设置 IO 标准和驱动强度set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports clk_50m] set_property -dict { PACKAGE_PIN T17 IOSTANDARD LVCMOS33 } [get_ports rst_n] set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports dir_switch] set_property -dict { PACKAGE_PIN M21 IOSTANDARD LVCMOS33 } [get_ports brake_en] set_property -dict { PACKAGE_PIN L22 IOSTANDARD LVCMOS33 } [get_ports motor_ia] set_property -dict { PACKAGE_PIN L23 IOSTANDARD LVCMOS33 } [get_ports motor_ib]有几个细节输出引脚建议选在 FPGA 边缘或靠近对应 bank 的电源引脚位置减少 PCB 走线过长带来的毛刺驱动强度Drive Strength不用刻意加大默认 8mA 或 12mA 就够驱动 L9110H 的逻辑输入如果电机调速有不流畅感优先检查是否把 PWM 输出脚误配成了 LVCMOS25。电平不对时逻辑分析仪看起来波形正常但芯片根本不识别。5.3 电机带来的干扰与误复位问题这是实际项目里最常见也最头疼的问题。电机一转向FPGA 就复位重启现象表现为 LED 重新闪烁、串口重新打印启动信息。原因通常是电机启停瞬间的大电流导致电源电压跌落FPGA 的内核电压低于复位阈值触发了上电复位。要解决这个问题从几个角度同时下手措施原理效果电机电源和 FPGA 电源分开避免功率回路电流冲击逻辑电源最有效强烈建议电机电源端加大电容吸收瞬态电流有效尤其是电解电容FPGA 电源入口加磁珠隔离高频噪声辅助有效逻辑地和功率地单点连接减小地弹噪声有效需注意布局降低 PWM 频率减少电流瞬变斜率有一定帮助我用过一款开发板电磁干扰严重到一接电机串口就乱码。后来在电机电源并联了两个 470uF 电容再加上 100nF 高频电容问题基本消失。这类问题不是芯片本身的问题而是整个系统回路设计的问题。5.4 调速不线性感知映射与占空比的关系还有一个容易被 FPGA 初学者忽略的问题PWM 占空比和转速不是线性关系。电机存在启动死区。占空比很低的时候比如小于 10%电压不足以克服静摩擦和转子惯性电机根本不动。一旦超过某个阈值转速会突然跳上来。中段占空比和转速大致线性接近满占空比时又趋于饱和。所以调试时不要直接“占空比 10% 就预期很慢”要找你的电机和负载的启动占空比阈值。我的做法是写一个上位机脚本或直接用按键微调占空比从 0 逐步加到电机刚转起来记下这个值把它作为软件里的启动下限。6. 几个容易忽略的电路细节我最后补上6.1 电源去耦与电容摆放L9110H 模块上的电容其实不太够。模块面积小电解电容容量有限而且很多廉价模块用的电容质量一般。如果你指望模块自带电容搞定一切往往会失望。我长期使用的做法是在模块的电源输入口额外并联一颗 100uF 电解电容和一颗 100nF 陶瓷电容并且尽量将电容放在电机端子附近。原因是电机换向时产生的反电动势尖峰是高频分量陶瓷电容能有效吸收而启动电流的大能量只能靠电解电容扛。6.2 逻辑地与功率地的处理FPGA 板的 GND 和电机驱动的 GND 之间一定存在地电位差。电机电流越大地线上的噪声越高。如果 FPGA 的逻辑信号参考地和驱动器的功率地之间是细长走线噪声会直接通过地线耦合到 FPGA。正确的做法是单点接地FPGA 板的 GND 和 L9110H 模块的 GND 在电源入口处汇合功率回路的电流不要穿过 FPGA 板的地平面。如果实在无法避免在信号线上串联 33Ω~100Ω 的电阻限制地弹电流同时也能保护 FPGA 引脚。6.3 防反接、防堵转这个不算 L9110H 特有但任何电机驱动项目都该考虑防反接模块电源入口加一个低压差二极管或者用 PMOS 防反接电路。L9110H 没有内部反接保护接反了大概率直接烧芯片防堵转电机堵转时电流会是正常工作电流的好几倍持续几秒钟就可能让 L9110H 过热。FPGA 里可以用计数器检测 PWM 输出不变且电流持续过大的状态实现超时停机热管理L9110H 满负荷工作一段时间后芯片表面温度会到 60℃ 甚至更高。如果项目要求长时间运行建议增加散热焊盘或选用更宽裕的驱动器。6.4 上电顺序问题FPGA 项目里FPGA 的 bit 文件加载需要时间而电机驱动模块如果先上电电位可能悬空。悬空的 IA、IB 在 L9110H 内部会被不确定电平解释电机可能在上电瞬间抖动。解决思路有两种在 FPGA 配置完成前把 IA、IB 接到 GND 的下拉电阻约 10kΩ~100kΩ或者把 L9110H 的 VCC 由 FPGA 板上的控制信号通过 MOSFET 开关供电FPGA 配置完成后才让电机驱动得电。第二种方案虽然多了几个器件但彻底解决了上电乱转的问题也方便做软件急停。做产品原型时很值得采用。一下我自己的实操体会L9110H 看起来只是一颗 8 脚小芯片真正和 FPGA 配合起来涉及的层面一点都不少。电路上要懂 H 桥开关逻辑和死区概念代码上要会写 PWM 生成和状态保护调试时还要能辨别波形异常和电源噪声。把这些细节全部处理好你会发现即便是一个“最简单”的电机动起来背后也有扎实的工程逻辑在支撑。如果非要我再给一条建议不要跳过死区插入哪怕你的电机很小、转速不高。省了这一小块逻辑等哪天你在现场切换方向烧掉模块再去查直通问题付出的时间成本一定远大于写死区代码的成本。按照上面这套设计流程走一遍我是踩过了所有坑才总结出的结论希望能帮你少绕几圈弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑