掌控板mPython驱动L298N直流电机:从PWM调速到智能小车实战
2026/7/28 11:58:41 网站建设 项目流程

1. 项目缘起:为什么是L298N与掌控板的组合?

如果你玩过Arduino,大概率听说过L298N这个“红色战神”。它几乎是每个创客入门电机控制时绕不开的经典模块。而掌控板,作为一款面向国内教育市场的开源硬件,以其集成的丰富传感器和友好的图形化编程环境(如mPython)而闻名。当我把这两者结合起来,用mPython去驱动L298N控制直流电机时,我发现了一个有趣的现象:网上关于Arduino+ L298N的教程铺天盖地,但深入到掌控板,特别是用其配套的mPython(基于MicroPython)进行文本代码级编程的详细指南,却少之又少。大多数资料停留在图形化积木块的拖拽,一旦你想实现更复杂的逻辑,比如精准的PWM调速、多电机协同或者加入传感器反馈形成闭环,就不得不深入代码层面。

这正是我写这篇内容的动机。我手头有一个智能小车项目,需要精确控制两个直流电机的转速和转向,L298N模块是现成的选择,而掌控板因为其内置的Wi-Fi、蓝牙和传感器,非常适合作为主控。整个过程从硬件连接到软件调试,我踩了不少坑,也总结了一套稳定可靠的方案。这篇文章,我就来拆解如何用mPython(X版本)对掌控板进行编程,彻底驯服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 用于电机A;ENB, 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)刹车/快速停止

电机B(IN3, 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等。

这里我设计一个双电机小车的接线示例:

  • 电源连接:

    1. 准备一个独立的电池盒(如4节AA电池,约6V),正极接L298N的12V输入,负极接L298N的GND
    2. 用一根杜邦线,将L298N的GND与掌控板的GND引脚连接起来(共地!)。
  • 信号连接:

    • 电机A:
      • 掌控板P13(PWM) ---> L298NENA
      • 掌控板P14(Digital) ---> L298NIN1
      • 掌控板P15(Digital) ---> L298NIN2
      • L298NOut1Out2---> 直流电机A两根线
    • 电机B:
      • 掌控板P16(PWM) ---> L298NENB
      • 掌控板P0(Digital) ---> L298NIN3
      • 掌控板P1(Digital) ---> L298NIN4
      • L298NOut3Out4---> 直流电机B两根线

注意:务必确保L298N模块上的“5V使能”跳线帽是插上的。这样模块的逻辑电路才工作。接线时最好断开所有电源,接完检查无误后再上电。

3. mPython编程环境搭建与基础驱动函数封装

硬件连接妥当后,我们进入软件部分。mPython有图形化编程和代码编程两种模式。对于电机控制这种需要精细调参的操作,我们直接使用代码模式。

3.1 初始化PWM与引脚

在mPython中,我们使用machine模块中的PWMPin类。首先,我们需要导入必要的模块并初始化所有控制引脚。

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.3V),value(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) # 正转逻辑:IN1=1, IN2=0 IN2.value(0) ENA.duty(speed_duty) # 设置速度 def motor_a_backward(speed_duty): """电机A反转,并设定速度 Args: speed_duty (int): PWM占空比,范围0-1023 """ IN1.value(0) # 反转逻辑:IN1=0, IN2=1 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_stopmotor_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%占空比)可能因为死区导致电机实际转速远低于预期的一半。这个映射函数让用户控制更符合直觉。你需要根据自己电机的特性,调整死区阈值(这里的10150)来获得最佳手感。

4.2 实现平滑加速与减速

突然的启停会让小车颠簸,对机械结构也有冲击。我们可以利用循环和time.sleep()来实现简单的软启动和软停止。

def motor_a_smooth_start(target_speed_duty, duration_ms=500): """电机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_ms=300): """电机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_ratio=0.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_rightcar_spin_right)的函数与之对称。

5.2 通过串口接收指令控制小车

我们可以让掌控板通过USB串口接收来自电脑的简单字符指令,从而遥控小车。这为后续扩展为蓝牙或Wi-Fi遥控打下了基础。

from machine import UART # 初始化串口,使用掌控板的USB串口,波特率115200 uart = UART(2, baudrate=115200, tx=17, rx=16) # 注意: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 电机不转或抖动啸叫

这是最常见的问题。请按照以下顺序排查:

  1. 电源问题(占90%以上):

    • 现象:电机不转,或只“哒哒”响一下。L298N模块指示灯暗淡或不亮。
    • 排查:首先检查给L298N的12V供电是否足够。用万用表测量电机供电端的电压,在电机试图转动时,电压是否被拉得很低(如从6V掉到3V以下)?如果是,说明电池电量不足或电池内阻太大,无法提供电机启动所需的大电流。务必使用动力电池(如18650锂电池组)或大容量碱性电池,避免使用普通的干电池或旧电池。
    • 解决:更换容量更大、放电能力更强的电源。确保所有电源连接线接触牢固。
  2. 共地问题:

    • 现象:控制信号似乎正常,但电机无反应。
    • 排查:确认L298N的GND和掌控板的GND是否用导线可靠连接。这是必须的!
  3. PWM频率问题:

    • 现象:电机发出尖锐的啸叫声,但可以转动。
    • 排查:PWM频率不在电机和驱动芯片的舒适区。频率太低(如50Hz以下),人耳可闻,电机会“唱歌”。频率太高(如超过20kHz),L298N的开关损耗可能增大,导致发热甚至无法有效驱动。
    • 解决:将PWM频率调整在500Hz到2kHz之间,我通常用500Hz或1kHz,这是一个比较通用的值。
  4. 使能端(ENA/ENB)问题:

    • 现象:方向控制(IN1/IN2)变化时电机有反应(比如用手轻轻拨动轮子能感觉到阻力变化),但就是不能主动转。
    • 排查:检查ENA/ENB是否接到了掌控板的PWM引脚,并且代码中是否正确初始化了PWM对象并设置了duty(大于0)。一个快速测试方法是:用一根杜邦线,将ENA引脚短暂接到掌控板的3.3V引脚上(模拟100%占空比),如果电机全速转了,那就肯定是程序里PWM设置的问题。

6.2 电机只能一个方向转或转向相反

  1. 逻辑电平错误:

    • 现象:发送正转指令,电机反转。
    • 解决:检查代码中IN1/IN2的值是否与硬件接线匹配。如果不匹配,要么修改代码中的逻辑,要么在硬件上直接调换电机的两根线。我建议修改代码,保持硬件接线整洁。例如,如果发现“前进”指令让小车后退,只需将car_forward函数中的motor_a_forwardmotor_b_forward互换为motor_a_backwardmotor_b_backward即可。
  2. 只有一个电机转:

    • 排查:分别测试两个电机。单独给电机A发送指令,再单独给电机B发送指令。如果只有一个不转,则集中排查该电机的三条控制线(ENx, INx1, INx2)的连接、代码初始化以及对应的电机本身是否完好。

6.3 优化功耗与发热

L298N模块在工作时,尤其是驱动负载较重或堵转时,发热会非常严重。芯片上那个银色的散热片有时都烫手。

  1. 避免长时间堵转:在软件设计中,应避免让电机在遇到障碍物时长时间处于高占空比输出状态。可以加入电流检测或软件计时,超过一定时间后自动停止或降低功率。
  2. 使用合适的电源电压:在满足电机额定电压的前提下,尽量使用较低的电压。例如,额定6V的电机,用7.2V(两节锂电池)比用12V发热小得多。因为L298N是线性驱动原理,输入输出压差越大,芯片消耗的功率(以热量形式散失)就越多。
  3. 考虑升级驱动模块:如果项目对效率和发热要求高,可以考虑使用更先进的驱动方案,如TB6612FNG或DRV8833。这些芯片采用MOSFET桥,效率更高,发热极小,体积也更小,但电流承载能力通常比L298N小一些。

6.4 代码结构优化建议

当项目复杂后,上面的全局函数会显得混乱。更好的做法是使用面向对象编程,封装一个DCMotor类和一个Car类。

class DCMotor: def __init__(self, en_pin, in1_pin, in2_pin, freq=500): 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调速的实质,以及通过封装让代码易于管理和扩展。动手做一遍,遇到问题对照第六部分排查,你的电机控制项目一定能顺利跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询