欢迎光临
我们一直在努力

基于 STM32 驱动有刷编码电机:从电源到代码的完整开发流程

目录

  • 前言
  • 一、系统总览:五个模块各管什么
  • 二、电源与电源模块:别让电机把 MCU 拖垮
  • 三、L298N 驱动模块:H 桥的真值表要刻进脑子
  • 四、STM32 控制模块:TIM 资源分配
  • 五、电机模块:编码器原理与接线
  • 六、CubeMX 配置(TIM3 PWM + TIM4 编码器)
  • 七、HAL 库代码
  • 八、标准库代码(STM32F103)
  • 九、开环 → 闭环:开发流程的四个阶段
  • 十、避坑清单(实战干货)
  • 十一、文章收尾:三条可执行结论

前言

本文是一篇面向实战的 STM32 电机控制入门教程,主线是把「电源 → L298N → STM32 → 编码电机」这条链路完整串起来:用 L298N 做功率放大与方向控制,用 STM32 定时器输出 PWM 调速,用编码器接口做硬件计数测速,最后在代码层把「开环 PWM」升级到「速度闭环 PID」。

全文按工程落地顺序展开,从系统总览、双电源设计、H 桥控制逻辑,到 TIM 资源分配、编码器原理、CubeMX 配置,再到 HAL 库与标准库两套可运行代码,最后给出开环到闭环的四阶段开发流程和避坑清单。每一节都对应一个具体的工程动作,语言力求简短,读者可以边看边动手。

阅读本文前,建议先准备好 STM32F103 开发板、L298N 模块、带霍尔编码器的有刷直流电机以及 12V 电源,并安装好 STM32CubeMX 与 Keil(或 STM32CubeIDE)。如果你已经跑通过基础的点灯和 GPIO 例程,上手会更快。

一、系统总览:五个模块各管什么

模块核心器件作用关键约束
电源 12V 电池 / 适配器 给电机提供功率电 电流 ≥ 电机额定电流 2 倍
电源模块 LDO / DC-DC(5V/3.3V) 给 STM32 和编码器逻辑供电 与电机电源共地,建议分开布线
L298N 模块 L298N 双 H 桥 功率放大 + 方向切换 + PWM 调速 逻辑电源 5V,驱动电源 12V,必须共地
STM32 控制模块 STM32F1/F4 等 产生 PWM、读编码器、跑 PID TIM 资源分配:一路 PWM 定时器 + 一路编码器定时器
电机模块 有刷直流电机 + 霍尔编码器 执行机构 + 位置/速度反馈 A/B 相正交输出,Z 相零位(若有)

核心原理一句话:PWM 占空比决定电机两端的平均电压(即转速大小),IN1/IN2 电平组合决定电流方向(即正反转),编码器 A/B 相硬件计数反映实际转过的角度/速度,三者构成"指令—执行—反馈"闭环。

二、电源与电源模块:别让电机把 MCU 拖垮

1. 为什么要"双电源"

电机启动瞬间的浪涌电流会拉低电源轨,若与 STM32 共用同一路 12V,会导致 MCU 复位甚至损坏。推荐方案:

  • 电机电源:12V 电池或适配器,单独给 L298N 的 VM 端供电
  • 逻辑电源:USB 5V 或 DC-DC 降压到 3.3V,给 STM32 和编码器供电
  • 共地:两路电源的 GND 必须连在一起,否则信号无参考

2. L298N 电源引脚

  • VS(VM):电机驱动电源,接 12V
  • VSS(+5V):逻辑电源,接 5V(部分模块可由板载 5V 输出反供 STM32)
  • GND:与 STM32 GND、12V 负极连通

罗姆对直流有刷电机 PWM 驱动的解释很到位:PWM 导通时电压施加到电机两端,关断时电机两端被短路实现电流再生,故电感电流纹波由占空比和周期决定。这正是我们用 L298N 做调速的物理基础。

三、L298N 驱动模块:H 桥的真值表要刻进脑子

1. H 桥控制逻辑(单路电机)

IN1IN2电机状态
H L 正转
L H 反转
H H 刹车(短路制动)
L L 停止(惯性滑行)
ENA=PWM — 调速(ENA 为高时 H 桥工作)

ENA 接 PWM 信号,占空比越大,电机两端平均电压越高,转速越快。

2. 典型接线(以 STM32F103C8T6 为例)

STM32 引脚L298N 引脚功能
PA6 (TIM3_CH1) ENA PWM 调速
PB12 IN1 方向控制 A
PB13 IN2 方向控制 B
PB14 (TIM4_CH3) ENB 第二路 PWM(若驱双电机)
PB15 IN3 第二路方向 A
PA8 (TIM1_CH1) IN4 第二路方向 B
GND GND 共地

3. 输出端

  • OUT1、OUT2 → 电机两极
  • L298N 内部续流二极管已集成在模块上,无需外接

四、STM32 控制模块:TIM 资源分配

关键原则:用两个不同的定时器——一个做 PWM 输出,一个做编码器接口。

推荐分配(F103 为例):

  • TIM3:PWM 输出(通道 1 → ENA),72MHz/72/1000 = 1kHz,或 72MHz/72/100 = 10kHz
  • TIM4:编码器模式(CH3/PB8 = A 相,CH4/PB9 = B 相),4× 倍频
  • 方向控制:PB12/PB13 普通 GPIO 推挽输出
  • PID 周期:TIM2 或 SysTick 产生 10ms 中断,用于速度环计算

五、电机模块:编码器原理与接线

1. 正交编码原理

编码器码盘随电机轴旋转,A、B 两相输出 90° 错相方波:

  • 正转:A 超前 B 90°
  • 反转:B 超前 A 90°

STM32 定时器编码器接口在 TI1 和 TI2 双边沿都计数(4× 倍频),硬件自动识别方向并增减 CNT,零 CPU 开销。

2. 接线

编码器引脚STM32 引脚说明
VCC 5V 编码器供电
GND GND 共地
A 相 PB8 (TIM4_CH3) 编码器输入 1
B 相 PB9 (TIM4_CH4) 编码器输入 2
Z 相(若有) 任意外部中断引脚 零位校准

3. 关键参数

  • PPR(每转脉冲数):如 15 线霍尔编码器 × 34:1 减速比 = 510 PPR(电机轴)
  • CPR(每转计数数) = 4 × PPR = 2040(4× 倍频后)
  • 转速换算:RPM = Δcount / CPR / Δt × 60

六、CubeMX 配置(TIM3 PWM + TIM4 编码器)

TIM3 配置(PWM 输出)

配置项取值
Clock Source Internal Clock
Channel1 PWM Generation CH1
Prescaler 71(72MHz/72=1MHz)
Counter Period 999(1kHz PWM)
Pulse 0(初始占空比 0)
AutoReload Preload Enable

TIM4 配置(编码器接口)

配置项取值
Combined Channels Encoder Mode
Encoder Mode TI1 and TI2(4× 倍频)
IC1/IC2 Polarity Rising Edge
IC1/IC2 Filter 6(机械编码器抗抖)
Counter Period 65535
NVIC TIM4 global interrupt → Enabled(用于溢出扩展)

GPIO 配置

  • PB12、PB13:Output Push-Pull,初始 Low

七、HAL 库代码

1. 初始化与启动

// main.c 中 /* USER CODE BEGIN 2 */ 区域
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 启动 PWM
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); // 启动编码器
HAL_TIM_Base_Start_IT(&htim4); // 启动溢出中断(用于 32 位扩展)

// 方向控制宏
#define MOTOR_FORWARD {HAL_GPIO_WritePin(GPIOB, GPIO_Pin_12, GPIO_PIN_SET); \\
HAL_GPIO_WritePin(GPIOB, GPIO_Pin_13, GPIO_PIN_RESET);}

#define MOTOR_BACKWARD {HAL_GPIO_WritePin(GPIOB, GPIO_Pin_12, GPIO_PIN_RESET); \\
HAL_GPIO_WritePin(GPIOB, GPIO_Pin_13, GPIO_PIN_SET);}

#define MOTOR_STOP {HAL_GPIO_WritePin(GPIOB, GPIO_Pin_12, GPIO_PIN_RESET); \\
HAL_GPIO_WritePin(GPIOB, GPIO_Pin_13, GPIO_PIN_RESET);}

// 设置速度(0~999 对应 0%~100%)
void Motor_SetSpeed(uint16_t duty) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
}

2. 速度读取(10ms 周期调用)

#define PPR 510
#define CPR (4 * PPR) // 4× 倍频

int32_t g_ext_count = 0; // 32 位扩展计数
uint16_t g_last_cnt = 0;

float Motor_GetSpeed(void) {
uint16_t cnt = __HAL_TIM_GET_COUNTER(&htim4);
int32_t delta = (int32_t)cnt – (int32_t)g_last_cnt;
// 处理 16 位溢出
if (delta > 32768) delta -= 65536;
else if (delta < –32768) delta += 65536;
g_last_cnt = cnt;
// 10ms 周期 → 0.01s;RPM = Δcount / CPR / 0.01 * 60
return (float)delta / CPR / 0.01f * 60.0f;
}

3. 溢出中断处理

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4) {
if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim4))
g_ext_count -= 65536;
else
g_ext_count += 65536;
}
}

4. PID 速度环(10ms 定时中断中调用)

typedef struct {
float Kp, Ki, Kd;
float err, err_last, integral;
float target;
} PID_Handle;

float PID_Calc(PID_Handle *pid, float feedback) {
pid->err = pid->target – feedback;
pid->integral += pid->err * 0.01f;
float derivative = (pid->err – pid->err_last) / 0.01f;
pid->err_last = pid->err;
float out = pid->Kp * pid->err + pid->Ki * pid->integral + pid->Kd * derivative;
if (out > 999) out = 999;
if (out < 0) out = 0;
return out;
}

// 10ms 定时器回调中:
extern PID_Handle motor_pid;
float speed = Motor_GetSpeed();
uint16_t duty = (uint16_t)PID_Calc(&motor_pid, speed);
Motor_SetSpeed(duty);

八、标准库代码(STM32F103)

1. 时钟与 GPIO 初始化

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 | RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

// PB12/PB13 方向控制推挽输出
GPIO_InitTypeDef gpio = {0};
gpio.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio);

// PB8/PB9 编码器输入(浮空/上拉,依编码器类型)
gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 若为 OC 输出则改用 IPU
GPIO_Init(GPIOB, &gpio);

// PA6 (TIM3_CH1) PWM 复用推挽
gpio.GPIO_Pin = GPIO_Pin_6;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);

2. TIM3 PWM 配置

TIM_TimeBaseInitTypeDef tim = {0};
tim.TIM_Prescaler = 71;
tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Period = 999;
tim.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &tim);

TIM_OCInitTypeDef oc = {0};
oc.TIM_OCMode = TIM_OCMode_PWM1;
oc.TIM_OutputState = TIM_OutputState_Enable;
oc.TIM_Pulse = 0;
oc.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &oc);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);

TIM_Cmd(TIM3, ENABLE);

3. TIM4 编码器模式

TIM_TimeBaseInitTypeDef tim4 = {0};
tim4.TIM_Prescaler = 0;
tim4.TIM_CounterMode = TIM_CounterMode_Up;
tim4.TIM_Period = 65535;
tim4.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &tim4);

// 4× 倍频:TI1 和 TI2 双边沿计数
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

// 输入滤波
TIM_ICInitTypeDef ic = {0};
ic.TIM_Channel = TIM_Channel_1;
ic.TIM_ICFilter = 6;
TIM_ICInit(TIM4, &ic);
ic.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM4, &ic);

TIM_Cmd(TIM4, ENABLE);

4. 使用

// 设置占空比
TIM_SetCompare1(TIM3, duty); // duty: 0~999

// 读取编码器
int16_t cnt = TIM_GetCounter(TIM4);
uint8_t dir = (TIM4->CR1 & TIM_CR1_DIR) ? 1 : 0; // 0=正转, 1=反转

// 方向控制
GPIO_SetBits(GPIOB, GPIO_Pin_12);
GPIO_ResetBits(GPIOB, GPIO_Pin_13); // 正转

九、开环 → 闭环:开发流程的四个阶段

  • 阶段一:PWM 开环——固定占空比,验证电机能转、方向正确、L298N 不发热。占空比从 30% 起步,避免启动电流冲击。
  • 阶段二:编码器计数——读取 TIM4->CNT,手动转电机轴,确认计数递增/递减与转向一致。若反向,交换 A/B 接线或翻转极性。
  • 阶段三:速度计算——10ms 周期读取 Δcount,换算 RPM。用串口打印,观察速度是否稳定。
  • 阶段四:PID 速度闭环——设定目标 RPM,PID 输出占空比。先调 Kp(从小到大),再调 Ki 消除稳态误差,Kd 一般可选。

十、避坑清单(实战干货)

  • 电机啸叫:PWM 频率太低(< 5kHz),提高到 10kHz~20kHz
  • 编码器计数乱跳:A/B 线未加滤波,提高 IC1F/IC2F 到 6~10;或 A/B 线太长引入干扰,缩短走线或改用差分信号
  • STM32 反复复位:电机电源与逻辑电源未分开,或共地不良
  • 方向反了:交换 A/B 接线,或翻转 IC1P/IC2P 极性(改代码比改线快)
  • 16 位溢出:高速旋转时 CNT 会溢出,必须在更新中断里做 32 位软件扩展
  • L298N 发热严重:导通压降 1.2~2V,效率较低,大功率场景建议换 TB6612 或 MOSFET 驱动
  • PID 震荡:Kp 过大,逐步减小;或 10ms 控制周期太短,改为 20~50ms 试试

十一、文章收尾:三条可执行结论

  • "双电源 + 共地"是系统的地基——这一步错了,后面所有代码都白搭。
  • L298N 的本质是"方向 GPIO + PWM 使能"——IN1/IN2 控方向,ENA 控速度,逻辑简单但够用。
  • STM32 定时器编码器接口是"零成本反馈"——硬件 4× 倍频 + 自动辨向,软件只需读 CNT 和处理溢出,剩下的交给 PID。


  • 感谢您的阅读!本号长期深耕电子信息、通信与嵌入式领域的技术分享,如果这篇文章对您有所启发,欢迎点赞、收藏并关注,后续将持续输出更多硬核干货,与您一同成长!

    赞(0)
    未经允许不得转载:171主机测评 » 基于 STM32 驱动有刷编码电机:从电源到代码的完整开发流程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址