掌控板mPython驱动L298N直流电机:从PWM调速到智能小车实战

📅 2026/7/28 11:58:47
掌控板mPython驱动L298N直流电机:从PWM调速到智能小车实战
1. 项目缘起为什么是L298N与掌控板的组合如果你玩过Arduino大概率听说过L298N这个“红色战神”。它几乎是每个创客入门电机控制时绕不开的经典模块。而掌控板作为一款面向国内教育市场的开源硬件以其集成的丰富传感器和友好的图形化编程环境如mPython而闻名。当我把这两者结合起来用mPython去驱动L298N控制直流电机时我发现了一个有趣的现象网上关于Arduino L298N的教程铺天盖地但深入到掌控板特别是用其配套的mPython基于MicroPython进行文本代码级编程的详细指南却少之又少。大多数资料停留在图形化积木块的拖拽一旦你想实现更复杂的逻辑比如精准的PWM调速、多电机协同或者加入传感器反馈形成闭环就不得不深入代码层面。这正是我写这篇内容的动机。我手头有一个智能小车项目需要精确控制两个直流电机的转速和转向L298N模块是现成的选择而掌控板因为其内置的Wi-Fi、蓝牙和传感器非常适合作为主控。整个过程从硬件连接到软件调试我踩了不少坑也总结了一套稳定可靠的方案。这篇文章我就来拆解如何用mPythonX版本对掌控板进行编程彻底驯服L298N驱动模块实现直流电机的启动、停止、正反转以及无级调速。无论你是想制作一台循迹小车、一台遥控坦克还是任何需要精准电机控制的项目这里的思路和代码都能直接拿去用。2. 硬件核心L298N模块的引脚逻辑与接线实战在写任何一行代码之前我们必须像了解老朋友一样了解L298N。这块红色板子看似简单但接错线轻则电机不动重则烧毁模块或主板。它的核心是L298N这颗双H桥驱动芯片可以同时驱动两个直流电机或者一个步进电机。2.1 L298N模块引脚全解析我们拿到一块典型的L298N模块其引脚可以分为三大部分电源部分、控制部分和输出部分。电源部分12V供电端 (VCC / 12V输入)这是给电机供电的电源正极。电压范围很宽根据你的电机额定电压来常见的有6V、9V、12V。重要原则电机功率越大所需电流越大这个接口的电源必须独立、足额。绝对不能仅从掌控板取电。GND (地线)电源地线。这里有一个关键连接必须将此外部电源的GND与掌控板的GND连接在一起即“共地”。这是保证信号电平基准一致的基础否则控制信号会紊乱。5V输出/输入 (5V)这个引脚容易让人困惑。当模块上的“5V使能”跳线帽接通时此引脚作为5V输出可以给逻辑电路如掌控板供电但通常不建议供电能力有限。当拔掉跳线帽且需要驱动更高电压电机时此引脚可作为5V输入单独为L298N内部的逻辑电路供电。对于我们的项目一个稳妥的做法是保留跳线帽但掌控板依然使用自身的USB或电池供电不从这个引脚取电。这样最简单。控制部分 (连接掌控板IO口)每个电机需要三个控制信号ENA, IN1, IN2 用于电机AENB, IN3, IN4 用于电机B。ENA / ENB (使能端)这是调速的关键。它接受PWM脉冲宽度调制信号。输入PWM信号电机就能调速如果直接接高电平3.3V电机则全速运行。我们将用掌控板的PWM引脚连接这里。IN1/IN2 与 IN3/IN4 (方向控制端)这两组引脚控制电机的正反转和刹车。它们只需要数字信号高电平3.3V或低电平0V。逻辑如下表所示IN1IN2电机A状态低电平(0)低电平(0)停止/自由停止高电平(1)低电平(0)正转低电平(0)高电平(1)反转高电平(1)高电平(1)刹车/快速停止电机BIN3, IN4的控制逻辑完全相同。输出部分Out1 Out2连接电机A的两根线。Out3 Out4连接电机B的两根线。电机接线不分正负调换线序只会改变我们定义的“正转”方向。2.2 掌控板与L298N的接线方案掌控板我以常见的掌控板2.0为例其引脚兼容Micro:bit。我们需要选择支持PWM输出的引脚来连接ENA/ENB。掌控板上很多引脚都支持PWM例如P0、P1、P2、P8、P9、P12、P13、P14、P15、P16等。这里我设计一个双电机小车的接线示例电源连接准备一个独立的电池盒如4节AA电池约6V正极接L298N的12V输入负极接L298N的GND。用一根杜邦线将L298N的GND与掌控板的GND引脚连接起来共地。信号连接电机A掌控板P13(PWM) --- L298NENA掌控板P14(Digital) --- L298NIN1掌控板P15(Digital) --- L298NIN2L298NOut1和Out2--- 直流电机A两根线电机B掌控板P16(PWM) --- L298NENB掌控板P0(Digital) --- L298NIN3掌控板P1(Digital) --- L298NIN4L298NOut3和Out4--- 直流电机B两根线注意务必确保L298N模块上的“5V使能”跳线帽是插上的。这样模块的逻辑电路才工作。接线时最好断开所有电源接完检查无误后再上电。3. mPython编程环境搭建与基础驱动函数封装硬件连接妥当后我们进入软件部分。mPython有图形化编程和代码编程两种模式。对于电机控制这种需要精细调参的操作我们直接使用代码模式。3.1 初始化PWM与引脚在mPython中我们使用machine模块中的PWM和Pin类。首先我们需要导入必要的模块并初始化所有控制引脚。from machine import Pin, PWM import time # 初始化电机A的控制引脚 ENA PWM(Pin(13)) # P13引脚作为PWM输出用于电机A调速 IN1 Pin(14, Pin.OUT) # P14作为数字输出控制方向 IN2 Pin(15, Pin.OUT) # P15作为数字输出控制方向 # 初始化电机B的控制引脚 ENB PWM(Pin(16)) # P16引脚作为PWM输出用于电机B调速 IN3 Pin(0, Pin.OUT) # P0作为数字输出 IN4 Pin(1, Pin.OUT) # P1作为数字输出 # 设置PWM频率。频率太低电机会啸叫太高则可能驱动不了。对于L298N和直流电机50Hz-1000Hz之间常用这里设500Hz。 PWM_FREQ 500 ENA.freq(PWM_FREQ) ENB.freq(PWM_FREQ)关键点解释PWM对象在初始化时就需要绑定引脚和设置频率。duty方法用于设置占空比范围是0-1023对应0%-100%。Pin对象用于数字输出value(1)为高电平3.3Vvalue(0)为低电平。3.2 封装基础电机驱动函数为了代码清晰和复用我们绝对应该把控制电机的操作封装成函数。下面我封装了四个最核心的函数motor_stop,motor_forward,motor_backward,motor_speed。这里以控制电机A为例电机B的逻辑完全一致。def motor_a_stop(): 停止电机A自由停止 IN1.value(0) IN2.value(0) # 也可以将PWM占空比设为0双重保障 ENA.duty(0) def motor_a_forward(speed_duty): 电机A正转并设定速度 Args: speed_duty (int): PWM占空比范围0-1023 IN1.value(1) # 正转逻辑IN11, IN20 IN2.value(0) ENA.duty(speed_duty) # 设置速度 def motor_a_backward(speed_duty): 电机A反转并设定速度 Args: speed_duty (int): PWM占空比范围0-1023 IN1.value(0) # 反转逻辑IN10, IN21 IN2.value(1) ENA.duty(speed_duty) # 设置速度 def motor_a_brake(): 电机A刹车快速停止 IN1.value(1) IN2.value(1) ENA.duty(1023) # 使能端保持高占空比实现快速制动 time.sleep_ms(100) # 保持刹车状态一小段时间 ENA.duty(0) # 然后释放 IN1.value(0) IN2.value(0)实操心得motor_stop和motor_brake有区别。stop是让电机惯性滑行停止比较柔和brake是让电机两端短接产生反向力矩迅速停止适用于需要急停的场景。在小车项目中通常用stop即可brake可能会让小车抖动。对于电机B只需创建一套名称不同的函数如motor_b_forward并将操作对象IN1/IN2/ENA替换为IN3/IN4/ENB即可。更高级的做法是封装一个Motor类将引脚作为属性但为了初学者理解函数形式更直观。4. PWM调速原理与在mPython中的精细控制PWM调速是直流电机控制的核心。其原理不是调节电压大小而是通过高速开关例如每秒500次来控制电源接通的时间比例占空比。占空比100%相当于全速50%相当于半速0%则停止。L298N的ENA/ENB引脚就是接收这个PWM信号。4.1 占空比与速度的非线性关系在mPython中PWM.duty()的取值范围是0-1023。但电机的转速与占空比通常不是完美的线性关系。存在一个“死区”即占空比低于某个阈值例如100时电机可能无法启动只是嗡嗡响。一旦超过这个阈值电机开始转动并且低速时转速增长较慢中段接近线性高速时可能因电源功率限制而提升有限。因此我们需要做一个简单的“校准”或“映射”。例如我们希望用一个0-100的整数speed_percent来控制速度可以写一个映射函数def speed_map(percent): 将百分比速度(0-100)映射到实际的PWM占空比(0-1023)并处理死区 if percent 0: return 0 elif percent 10: # 假设10%以下为死区直接给一个启动阈值 return 150 # 一个能让大多数小型直流电机启动的值需要实测调整 else: # 将10-100线性映射到150-1023 return int(150 (percent - 10) * (1023 - 150) / 90) # 使用示例让电机A以50%的速度正转 motor_a_forward(speed_map(50))为什么这么做直接使用duty(512)即50%占空比可能因为死区导致电机实际转速远低于预期的一半。这个映射函数让用户控制更符合直觉。你需要根据自己电机的特性调整死区阈值这里的10和150来获得最佳手感。4.2 实现平滑加速与减速突然的启停会让小车颠簸对机械结构也有冲击。我们可以利用循环和time.sleep()来实现简单的软启动和软停止。def motor_a_smooth_start(target_speed_duty, duration_ms500): 电机A平滑加速到目标速度 Args: target_speed_duty (int): 目标占空比 duration_ms (int): 加速过程总时间毫秒 steps 20 # 分20步加速 step_duty target_speed_duty / steps step_time duration_ms / steps motor_a_forward(0) # 先设置为正转方向速度为0 current_duty 0 for i in range(steps): current_duty step_duty ENA.duty(int(current_duty)) time.sleep_ms(int(step_time)) def motor_a_smooth_stop(duration_ms300): 电机A平滑减速停止 current_duty ENA.duty() # 获取当前占空比 if current_duty 0: return steps 15 step_duty current_duty / steps step_time duration_ms / steps for i in range(steps): current_duty - step_duty ENA.duty(int(current_duty)) time.sleep_ms(int(step_time)) motor_a_stop() # 最后完全停止经验之谈平滑控制不仅提升了体验在电池供电下还能减少瞬间大电流对电路的冲击。steps步数和duration_ms持续时间可以根据需要调整步数越多越平滑但控制响应会变慢。5. 典型应用场景与综合代码示例遥控小车掌握了单电机控制后我们将两个电机组合起来实现一个可以通过串口命令控制的简易双电机小车。这里我们假设电机A控制左轮电机B控制右轮并且两个电机的“正转”方向都设定为让小车前进。5.1 差速转向原理与函数封装小车的转向依靠左右轮的速度差。例如左轮慢、右轮快小车就左转左轮反转、右轮正转小车就原地旋转。我们封装几个基本动作函数# 假设我们已经有了 motor_a_forward, motor_b_forward 等函数 def car_stop(): 小车停止 motor_a_stop() motor_b_stop() def car_forward(speed_percent): 小车前进 Args: speed_percent (int): 速度百分比0-100 duty speed_map(speed_percent) motor_a_forward(duty) motor_b_forward(duty) def car_backward(speed_percent): 小车后退 duty speed_map(speed_percent) motor_a_backward(duty) motor_b_backward(duty) def car_turn_left(speed_percent, turn_ratio0.5): 小车左转差速 Args: speed_percent (int): 基础速度百分比 turn_ratio (float): 转向系数0-1。1为原地旋转0.5为一侧半速。 duty speed_map(speed_percent) left_duty int(duty * (1 - turn_ratio)) right_duty duty # 左轮减速右轮全速 if left_duty 50: # 确保左轮有足够动力 motor_a_forward(left_duty) else: motor_a_stop() # 如果速度太低直接停止左轮转向更灵敏 motor_b_forward(right_duty) def car_spin_left(speed_percent): 小车原地左旋左轮后退右轮前进 duty speed_map(speed_percent) motor_a_backward(duty) motor_b_forward(duty)右转car_turn_right和car_spin_right的函数与之对称。5.2 通过串口接收指令控制小车我们可以让掌控板通过USB串口接收来自电脑的简单字符指令从而遥控小车。这为后续扩展为蓝牙或Wi-Fi遥控打下了基础。from machine import UART # 初始化串口使用掌控板的USB串口波特率115200 uart UART(2, baudrate115200, tx17, rx16) # 注意tx/rx引脚号需根据掌控板具体型号调整此处为示例 # 更简单的方式是使用REPL串口但为了独立性我们使用硬件UART。 def uart_control_car(): 主循环监听串口指令并控制小车 print(小车控制就绪。指令) print(w: 前进 s: 后退 a: 左转 d: 右转) print(q: 原地左旋 e: 原地右旋 x: 停止) print(1-9: 设置速度等级 (1最慢9最快)) current_speed 50 # 默认速度50% while True: if uart.any(): # 检查是否有数据 cmd uart.read(1).decode() # 读取一个字符 print(收到指令:, cmd) if cmd w: car_forward(current_speed) elif cmd s: car_backward(current_speed) elif cmd a: car_turn_left(current_speed, 0.7) elif cmd d: car_turn_right(current_speed, 0.7) elif cmd q: car_spin_left(current_speed) elif cmd e: car_spin_right(current_speed) elif cmd x: car_stop() elif cmd in 123456789: # 将字符1-9映射为速度30%-90% speed_level int(cmd) current_speed 20 speed_level * 8 # 1-28%, 9-92% print(当前速度设置为:, current_speed, %) # 保持当前运动状态但更新速度这里需要根据当前运动方向重新设置 # 为了简化我们这里仅打印实际需要更复杂的状态机来更新。 time.sleep_ms(50) # 短暂延时防止CPU占用过高 # 在主程序中调用 if __name__ __main__: # 初始化所有电机控制引脚代码如前所述 # ... # 启动串口控制 uart_control_car()避坑指南串口读取数据时使用uart.read(1)是读取一个字节。在实际无线通信中如蓝牙数据可能是连续发送的可能会一次性读到多个指令字符如”w\n“所以更健壮的做法是设置一个小的缓冲区并按行\n或特定分隔符来解析指令。上面的简化版适用于手动在串口工具里一次发送一个字符的场景。6. 进阶话题常见问题排查与性能优化项目做到这里基本功能已经实现。但在实际制作中你几乎一定会遇到下面这些问题。我把我的排查经验和优化技巧分享出来。6.1 电机不转或抖动啸叫这是最常见的问题。请按照以下顺序排查电源问题占90%以上现象电机不转或只“哒哒”响一下。L298N模块指示灯暗淡或不亮。排查首先检查给L298N的12V供电是否足够。用万用表测量电机供电端的电压在电机试图转动时电压是否被拉得很低如从6V掉到3V以下如果是说明电池电量不足或电池内阻太大无法提供电机启动所需的大电流。务必使用动力电池如18650锂电池组或大容量碱性电池避免使用普通的干电池或旧电池。解决更换容量更大、放电能力更强的电源。确保所有电源连接线接触牢固。共地问题现象控制信号似乎正常但电机无反应。排查确认L298N的GND和掌控板的GND是否用导线可靠连接。这是必须的PWM频率问题现象电机发出尖锐的啸叫声但可以转动。排查PWM频率不在电机和驱动芯片的舒适区。频率太低如50Hz以下人耳可闻电机会“唱歌”。频率太高如超过20kHzL298N的开关损耗可能增大导致发热甚至无法有效驱动。解决将PWM频率调整在500Hz到2kHz之间我通常用500Hz或1kHz这是一个比较通用的值。使能端ENA/ENB问题现象方向控制IN1/IN2变化时电机有反应比如用手轻轻拨动轮子能感觉到阻力变化但就是不能主动转。排查检查ENA/ENB是否接到了掌控板的PWM引脚并且代码中是否正确初始化了PWM对象并设置了duty大于0。一个快速测试方法是用一根杜邦线将ENA引脚短暂接到掌控板的3.3V引脚上模拟100%占空比如果电机全速转了那就肯定是程序里PWM设置的问题。6.2 电机只能一个方向转或转向相反逻辑电平错误现象发送正转指令电机反转。解决检查代码中IN1/IN2的值是否与硬件接线匹配。如果不匹配要么修改代码中的逻辑要么在硬件上直接调换电机的两根线。我建议修改代码保持硬件接线整洁。例如如果发现“前进”指令让小车后退只需将car_forward函数中的motor_a_forward和motor_b_forward互换为motor_a_backward和motor_b_backward即可。只有一个电机转排查分别测试两个电机。单独给电机A发送指令再单独给电机B发送指令。如果只有一个不转则集中排查该电机的三条控制线ENx, INx1, INx2的连接、代码初始化以及对应的电机本身是否完好。6.3 优化功耗与发热L298N模块在工作时尤其是驱动负载较重或堵转时发热会非常严重。芯片上那个银色的散热片有时都烫手。避免长时间堵转在软件设计中应避免让电机在遇到障碍物时长时间处于高占空比输出状态。可以加入电流检测或软件计时超过一定时间后自动停止或降低功率。使用合适的电源电压在满足电机额定电压的前提下尽量使用较低的电压。例如额定6V的电机用7.2V两节锂电池比用12V发热小得多。因为L298N是线性驱动原理输入输出压差越大芯片消耗的功率以热量形式散失就越多。考虑升级驱动模块如果项目对效率和发热要求高可以考虑使用更先进的驱动方案如TB6612FNG或DRV8833。这些芯片采用MOSFET桥效率更高发热极小体积也更小但电流承载能力通常比L298N小一些。6.4 代码结构优化建议当项目复杂后上面的全局函数会显得混乱。更好的做法是使用面向对象编程封装一个DCMotor类和一个Car类。class DCMotor: def __init__(self, en_pin, in1_pin, in2_pin, freq500): self.en PWM(Pin(en_pin)) self.in1 Pin(in1_pin, Pin.OUT) self.in2 Pin(in2_pin, Pin.OUT) self.en.freq(freq) self.speed 0 self._stop() def _stop(self): self.in1.value(0) self.in2.value(0) self.en.duty(0) def forward(self, speed_duty): self.in1.value(1) self.in2.value(0) self.en.duty(speed_duty) self.speed speed_duty # ... 其他方法 backward, brake等 class Car: def __init__(self, motor_left, motor_right): self.motor_left motor_left self.motor_right motor_right def move(self, left_speed, right_speed): # 根据正负值判断方向 if left_speed 0: self.motor_left.forward(abs(left_speed)) else: self.motor_left.backward(abs(left_speed)) # 对右电机做同样处理 # ...这样主程序会变得非常清晰my_car Car(motor_left, motor_right); my_car.move(500, 300)。这种结构在后期增加红外避障、超声波跟随等功能时扩展性会好得多。从硬件连接到软件封装从基础驱动到综合应用再到问题排查这套基于L298N和掌控板的直流电机控制方案已经相当完整。关键在于理解每个引脚的作用、PWM调速的实质以及通过封装让代码易于管理和扩展。动手做一遍遇到问题对照第六部分排查你的电机控制项目一定能顺利跑起来。