STM32HAL驱动L298N电机驱动模块(PWM调速+双电机控制)保姆级教程

⭐ 前言

在智能小车、机械臂、小型机器人等嵌入式项目开发中,L298N电机驱动模块是最常用的直流电机驱动器件,它支持双路直流电机独立控制,搭配STM32的PWM功能可实现精准调速。

本文基于STM32F407 芯片,使用TIM2定时器输出两路PWM波,实现L298N双电机的正转、反转、停止、无级调速功能,全程代码可直接移植,避开硬件引脚冲突,适合嵌入式新手快速上手电机驱动开发。

本文目标:

  1. 掌握L298N模块的硬件接线与工作原理
  2. 学会STM32F4定时器PWM输出配置
  3. 实现双电机独立控制与PWM调速
  4. 提供完整可运行的工程代码,直接移植使用

📌 一、L298N模块与硬件原理

1.1 L298N模块简介

L298N是一款双全桥电机驱动芯片,最大支持2路直流电机独立驱动,工作电压5~35V,驱动电流可达2A,具备过流保护功能,完美适配小型直流减速电机。

1.2 核心引脚功能

引脚名称 功能说明
IN1/IN2 电机A方向控制引脚
IN3/IN4 电机B方向控制引脚
ENA 电机A使能+PWM调速引脚
ENB 电机B使能+PWM调速引脚
12V 电机电源输入(外接电池)
GND 电源地(必须与STM32共地)
5V OUT 模块输出5V(可给单片机供电)

1.3 控制逻辑

  • 方向控制:通过IN1/IN2、IN3/IN4高低电平控制电机正反转
  • 速度控制:ENA/ENB输入PWM波,通过占空比调节转速
  • 停止控制:方向引脚拉低,PWM占空比设为0

⚙️ 二、硬件接线设计(STM32F407 + L298N)

2.1 引脚分配(避开冲突,最优方案)

本设计使用TIM2_CH1(PA0)、TIM2_CH2(PA1) 输出PWM,GPIO控制电机方向:

STM32引脚 L298N引脚 功能
PA0 ENA 电机A PWM调速
PA4 IN1 电机A方向控制1
PA5 IN2 电机A方向控制2
PA1 ENB 电机B PWM调速
PA6 IN3 电机B方向控制1
PA7 IN4 电机B方向控制2
GND GND 共地(核心!必须连接)
外部电源12V 12V 电机供电

2.2 接线注意事项

  1. STM32与L298N必须共地,否则电机无法正常工作
  2. 电机电源建议使用独立电池,避免干扰单片机
  3. PWM引脚必须配置为定时器复用功能

💻 三、STM32工程代码实现

3.1 驱动头文件 l298.h

c 复制代码
#ifndef __L298N_H
#define __L298N_H

#include "stm32f4xx_hal.h"

// 函数声明
void Motor_Init(void);
void Tim2_PWM_Init(void);
void MotorA_Set(int speed, uint8_t direction);
void MotorB_Set(int speed,uint8_t direction);
void Motor_PWM_Start(void);

#endif

3.2 驱动源文件 l298.c(核心代码)

c 复制代码
#include "l298.h"
#include "tim.h"  

// 声明外部定时器句柄
extern TIM_HandleTypeDef htim2;

// ==================== 电机引脚定义 ====================
// 电机A:ENA=PA0(TIM2_CH1),IN1=PA4,IN2=PA5
#define MOTOR_A_ENA_PIN     GPIO_PIN_0
#define MOTOR_A_IN1_PIN     GPIO_PIN_4
#define MOTOR_A_IN2_PIN     GPIO_PIN_5
#define MOTOR_A_GPIO_PORT   GPIOA

// 电机B:ENB=PA1(TIM2_CH2),IN3=PA6,IN4=PA7
#define MOTOR_B_ENB_PIN     GPIO_PIN_1
#define MOTOR_B_IN3_PIN     GPIO_PIN_6
#define MOTOR_B_IN4_PIN     GPIO_PIN_7
#define MOTOR_B_GPIO_PORT   GPIOA

/**
 * @brief 初始化电机GPIO
 */
void Motor_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitTypeDef  GPIO_InitStruct={0};
    
    // 配置方向控制引脚
    GPIO_InitStruct.Pin = MOTOR_A_IN1_PIN | MOTOR_A_IN2_PIN | 
                          MOTOR_B_IN3_PIN | MOTOR_B_IN4_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    HAL_GPIO_Init(MOTOR_A_GPIO_PORT, &GPIO_InitStruct);
    
    // 配置电机A PWM引脚 PA0
    GPIO_InitStruct.Pin = MOTOR_A_ENA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(MOTOR_A_GPIO_PORT, &GPIO_InitStruct);
    
    // 配置电机B PWM引脚 PA1
    GPIO_InitStruct.Pin = MOTOR_B_ENB_PIN;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(MOTOR_B_GPIO_PORT, &GPIO_InitStruct);
}

/**
 * @brief TIM2 PWM初始化
 */
void Tim2_PWM_Init(void)
{
    TIM_OC_InitTypeDef sConfig={0};
    
    // 定时器基础配置
    htim2.Instance=TIM2;
    htim2.Init.Period=999;          // PWM周期 1000份
    htim2.Init.Prescaler=0;         // 不分频
    htim2.Init.CounterMode=TIM_COUNTERMODE_UP;
    htim2.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;
    
    if (HAL_TIM_PWM_Init(&htim2)!=HAL_OK)
    {
        Error_Handler();
    }
    
    // PWM通道1配置(电机A)
    sConfig.OCMode=TIM_OCMODE_PWM1;
    sConfig.Pulse=0;
    sConfig.OCPolarity=TIM_OCPOLARITY_HIGH;
    HAL_TIM_PWM_ConfigChannel(&htim2,&sConfig,TIM_CHANNEL_1);
    
    // PWM通道2配置(电机B)
    HAL_TIM_PWM_ConfigChannel(&htim2,&sConfig,TIM_CHANNEL_2);
}

/**
 * @brief 电机A控制
 * @param speed: 0~999  direction:1正转 2反转 0停止
 */
void MotorA_Set(int speed,uint8_t direction)
{
    speed = (speed > 999) ? 999 : (speed < 0) ? 0 : speed;
    
    switch(direction)
    {
        case 1: // 正转
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN1_PIN, GPIO_PIN_SET);
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN2_PIN, GPIO_PIN_RESET);
            break;
            
        case 2: // 反转
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN1_PIN, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN2_PIN, GPIO_PIN_SET);
            break;
            
        default: // 停止
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN1_PIN, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(MOTOR_A_GPIO_PORT, MOTOR_A_IN2_PIN, GPIO_PIN_RESET);
            speed = 0;
            break;
    }
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, speed);
}

/**
 * @brief 电机B控制
 */
void MotorB_Set(int speed,uint8_t direction)
{
    speed = (speed > 999) ? 999 : (speed < 0) ? 0 : speed;
    
    switch(direction)
    {
        case 1:
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN3_PIN, GPIO_PIN_SET);
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN4_PIN, GPIO_PIN_RESET);
            break;
            
        case 2:
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN3_PIN, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN4_PIN, GPIO_PIN_SET);
            break;
            
        default:
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN3_PIN, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(MOTOR_B_GPIO_PORT, MOTOR_B_IN4_PIN, GPIO_PIN_RESET);
            speed = 0;
            break;
    }
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, speed);
}

/**
 * @brief 启动PWM输出
 */
void Motor_PWM_Start(void)
{
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
}

3.3 定时器配置文件 tim.h / tim.c

c 复制代码
// tim.h
#ifndef __TIM_H
#define __TIM_H
#include "stm32f4xx_hal.h"
extern TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void);
#endif

// tim.c
#include "tim.h"
TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 83;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 65535;
  HAL_TIM_Base_Init(&htim2);
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
  if(tim_baseHandle->Instance==TIM2)
  {
    __HAL_RCC_TIM2_CLK_ENABLE();
  }
}

🚀 四、代码使用说明(主函数调用)

4.1 初始化代码

main.c中添加初始化代码:

c 复制代码
#include "l298.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();  // 系统时钟配置
    MX_GPIO_Init();
    MX_TIM2_Init();
    
    // 电机初始化
    Motor_Init();
    Tim2_PWM_Init();
    Motor_PWM_Start();  // 启动PWM
    
    while(1)
    {
        // 电机A:50%速度正转
        MotorA_Set(500, 1);
        // 电机B:80%速度反转
        MotorB_Set(800, 2);
        HAL_Delay(2000);
        
        // 停止所有电机
        MotorA_Set(0, 0);
        MotorB_Set(0, 0);
        HAL_Delay(1000);
    }
}

4.2 函数参数说明

  1. MotorA_Set(speed, dir) / MotorB_Set(speed, dir)
    • speed:转速,范围0~999,数值越大转速越快
    • dir:1=正转,2=反转,0=停止

❌ 五、常见问题与解决方案

问题现象 解决方案
电机不转 1. 检查共地 2. 检查PWM是否启动 3. 检查电源电压
电机抖动 PWM占空比过低,提高初始速度
只有一个电机工作 检查对应引脚接线与通道配置
电机发热严重 降低PWM占空比,检查电机是否堵转

📝 六、总结

本文完整实现了STM32F407驱动L298N双电机的功能,核心知识点:

  1. L298N模块的硬件接线与共地要求
  2. STM32定时器PWM输出配置(TIM2_CH1/CH2)
  3. 双电机独立控制、正反转、无级调速逻辑
  4. 标准化驱动代码,可直接移植到其他STM32芯片

本代码适配智能小车、机器人等项目,修改引脚定义即可适配不同硬件平台,非常适合嵌入式学习与项目开发。


👨‍💻 作者简介

作者:嵌入式开发者,专注于STM32、机器人、嵌入式Linux开发

主页:@开发者-曼亿点

原创不易,欢迎点赞👍、收藏⭐、关注✅,持续更新嵌入式干货!

相关推荐
筱谙2 小时前
BES 芯片跨核通讯与共享内存设计原理
嵌入式硬件·音频·蓝牙
思为无线NiceRF2 小时前
高空线路安装智能安全帽全双工组网对讲系统(含优先级管控)应用方案
嵌入式硬件·物联网
独小乐4 小时前
012.整体框架适配SDRAM|千篇笔记实现嵌入式全栈/裸机篇
c语言·汇编·笔记·单片机·嵌入式硬件·arm·gnu
不吃鱼的羊4 小时前
ADC扫描组Scan Group
单片机
海砥装备HardAus5 小时前
飞控算法中双环串级PID深度解析:角度环与角速度环的协同机制
stm32·算法·无人机·飞控·串级pid
LCMICRO-133108477465 小时前
长芯微LPC556D1完全P2P替代DAC8830,是引脚兼容的16位数模转换器,该系列产品为单通道、低功耗、缓冲电压输出型DAC
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·电压输出型dac
forAllforMe5 小时前
如何用定时器PWM产生SPWM?--电机驱动控制
嵌入式硬件
charlie1145141915 小时前
嵌入式C++教程实战之Linux下的单片机编程(9):HAL时钟使能 —— 不开时钟,外设就是一坨睡死的硅
linux·开发语言·c++·单片机·嵌入式硬件·c
钿驰科技5 小时前
水泵无刷电机驱动板如何实现恒压控制?
单片机·嵌入式硬件