STM32实战:基于STM32F103的编码器电机测速与闭环控制

文章目录

一、项目前言

在嵌入式开发、智能车、机器人等项目中,编码器电机 是最常用的执行部件,而精准测速+闭环控制 是保证电机转速稳定的核心。本教程针对零基础小白,基于最经典的STM32F103C8T6最小系统板,从零开始实现:

  1. 增量式编码器电机转速测量(单位:转/分钟 rpm)
  2. 基于PID算法的电机转速闭环控制
  3. 代码可直接复制编译,硬件接线清晰,步骤详细可落地

硬件准备

  1. 核心板:STM32F103C8T6最小系统板
  2. 执行器:带编码器的直流减速电机(13线/11线编码器通用)
  3. 驱动模块:L298N/DRV8833电机驱动模块
  4. 电源:12V直流电源(给电机供电)、5V电源(给STM32供电)
  5. 辅助:杜邦线若干、USB转TTL模块(下载程序)

软件准备

  1. Keil5 MDK-ARM(STM32F1固件库)
  2. ST-LINK/USB-TTL下载器
  3. 串口调试助手

二、硬件接线(核心步骤,零基础必看)

本项目使用定时器编码器模式 读取编码器信号,通用定时器PWM 控制电机转速,定时器中断做测速与PID计算。

1. 编码器接线(AB相输出)

编码器一般有5根线:VCC、GND、A相、B相、Z相(Z相可悬空)

编码器引脚 STM32引脚 功能说明
VCC 3.3V 编码器供电
GND GND 共地
A相 PA0 定时器2通道1
B相 PA1 定时器2通道2

2. 电机驱动模块接线

以L298N为例,IN1/IN2控制正反转,ENA控制转速

L298N引脚 STM32引脚 功能说明
12V 12V电源正极 电机供电
GND STM32 GND + 电源GND 共地(必须接!)
IN1 PB0 电机方向控制1
IN2 PB1 电机方向控制2
ENA PA6 定时器3通道1 PWM输出

3. 共地说明

STM32、电机驱动、电源的GND必须连接在一起,否则信号紊乱,无法正常工作!


三、工程创建与基础配置

本教程基于STM32标准库开发,新建工程后,添加标准库文件,然后按照以下步骤创建代码文件。

工程文件结构

我们需要创建5个核心文件:

  1. encoder.c / encoder.h:编码器读取、测速函数
  2. motor.c / motor.h:电机驱动、PWM输出函数
  3. pid.c / pid.h:PID闭环控制算法
  4. tim.c / tim.h:定时器初始化(编码器、PWM、定时中断)
  5. main.c:主函数、逻辑调度

四、定时器初始化代码(tim.c / tim.h)

文件名:tim.h

c 复制代码
#ifndef __TIM_H
#define __TIM_H

#include "stm32f10x.h"

// 定时器初始化函数声明
void TIM2_Encoder_Init(void);   // 定时器2 编码器模式初始化
void TIM3_PWM_Init(void);       // 定时器3 PWM输出初始化
void TIM4_Init(u16 arr,u16 psc);// 定时器4 中断初始化(10ms中断一次)
int Read_Encoder(void);         // 读取编码器计数值

#endif

文件名:tim.c

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

// 定时器2 编码器接口模式初始化
void TIM2_Encoder_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef  TIM_ICInitStructure;

    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // PA0 PA1 浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 定时器基础配置
    TIM_TimeBaseStructure.TIM_Period = 65535;           // 自动重装载值
    TIM_TimeBaseStructure.TIM_Prescaler = 0;            // 不分频
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    // 编码器模式配置:AB相都计数,四倍频
    TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

    TIM_ICStructInit(&TIM_ICInitStructure);
    TIM_ICInit(TIM2, &TIM_ICInitStructure);

    // 清空计数器
    TIM_SetCounter(TIM2, 0);
    // 使能定时器2
    TIM_Cmd(TIM2, ENABLE);
}

// 读取编码器数值
int Read_Encoder(void)
{
    int temp = 0;
    temp = (short)TIM_GetCounter(TIM2);
    TIM_SetCounter(TIM2, 0);  // 读取后清零
    return temp;
}

// 定时器3 PWM初始化 PA6
void TIM3_PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;

    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // PA6 复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 定时器基础配置:72M/720=100Khz, 自动重装载1000,PWM周期10ms
    TIM_TimeBaseStructure.TIM_Period = 999;
    TIM_TimeBaseStructure.TIM_Prescaler = 719;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // PWM模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);

    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM3, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
}

// 定时器4 中断初始化 10ms中断一次
void TIM4_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

    // 中断配置
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
    TIM_Cmd(TIM4,ENABLE);
}

五、编码器测速代码(encoder.c / encoder.h)

文件名:encoder.h

c 复制代码
#ifndef __ENCODER_H
#define __ENCODER_H

#include "stm32f10x.h"

// 外部变量声明
extern int motor_speed;     // 电机实时转速 rpm
extern int encoder_count;   // 编码器计数值

// 测速函数声明
void Get_Motor_Speed(void);

#endif

文件名:encoder.c

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

// 全局变量定义
int motor_speed = 0;    // 实时转速
int encoder_count = 0;  // 编码器计数值

// 电机测速函数(10ms调用一次)
// 编码器线数:13线,减速比:30,四倍频
void Get_Motor_Speed(void)
{
    // 读取编码器差值
    encoder_count = Read_Encoder();

    // 转速计算公式:rpm = 计数值 * 60 / (13*4*30*0.01)
    // 13:编码器线数  4:四倍频  30:减速比  0.01:10ms采样时间
    motor_speed = encoder_count * 60 / (13 * 4 * 30 * 0.01);

    // 负数取绝对值(方向不影响转速大小)
    if(motor_speed < 0) motor_speed = -motor_speed;
}

六、电机驱动代码(motor.c / motor.h)

文件名:motor.h

c 复制代码
#ifndef __MOTOR_H
#define __MOTOR_H

#include "stm32f10x.h"

// 电机控制函数声明
void Motor_GPIO_Init(void);       // 电机方向引脚初始化
void Set_Motor_Speed(int speed); // 设置电机转速(0-1000)
void Motor_Forward(void);        // 电机正转
void Motor_Backward(void);       // 电机反转
void Motor_Stop(void);           // 电机制动

#endif

文件名:motor.c

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

// 电机方向控制引脚初始化 PB0 PB1
void Motor_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 初始状态停止
    Motor_Stop();
}

// 电机正转
void Motor_Forward(void)
{
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}

// 电机反转
void Motor_Backward(void)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
    GPIO_SetBits(GPIOB, GPIO_Pin_1);
}

// 电机制动
void Motor_Stop(void)
{
    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
    GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}

// 设置电机PWM转速 0-1000
void Set_Motor_Speed(int speed)
{
    if(speed < 0) speed = 0;
    if(speed > 1000) speed = 1000;
    TIM_SetCompare1(TIM3, speed);
}

七、PID闭环控制代码(pid.c / pid.h)

文件名:pid.h

c 复制代码
#ifndef __PID_H
#define __PID_H

#include "stm32f10x.h"

// PID结构体
typedef struct
{
    float target;     // 目标转速
    float actual;     // 实际转速
    float err;        // 当前误差
    float last_err;   // 上一次误差
    float kp;         // 比例系数
    float ki;         // 积分系数
    float kd;         // 微分系数
    float pwm;        // 输出PWM值
    float integral;   // 积分累加值
} PID_TypeDef;

// 外部变量声明
extern PID_TypeDef motor_pid;

// PID函数声明
void PID_Init(void);                // PID参数初始化
float PID_Calc(PID_TypeDef *pid, float target, float actual);  // PID计算

#endif

文件名:pid.c

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

// 定义PID结构体变量
PID_TypeDef motor_pid;

// PID初始化
void PID_Init(void)
{
    motor_pid.target = 0;      // 初始目标转速0
    motor_pid.actual = 0;      // 实际转速
    motor_pid.err = 0;         // 当前误差
    motor_pid.last_err = 0;    // 上一次误差
    motor_pid.kp = 10.0;       // 比例系数(可调节)
    motor_pid.ki = 0.5;        // 积分系数(可调节)
    motor_pid.kd = 0.2;        // 微分系数(可调节)
    motor_pid.pwm = 0;         // 输出PWM
    motor_pid.integral = 0;    // 积分值
}

// PID增量式算法计算
float PID_Calc(PID_TypeDef *pid, float target, float actual)
{
    pid->target = target;
    pid->actual = actual;

    // 计算误差
    pid->err = pid->target - pid->actual;

    // 累加积分(限幅防止积分饱和)
    pid->integral += pid->err;
    if(pid->integral > 1000) pid->integral = 1000;
    if(pid->integral < -1000) pid->integral = -1000;

    // PID公式计算
    pid->pwm = pid->kp * pid->err
             + pid->ki * pid->integral
             + pid->kd * (pid->err - pid->last_err);

    // 保存上一次误差
    pid->last_err = pid->err;

    // PWM限幅 0-1000
    if(pid->pwm > 1000) pid->pwm = 1000;
    if(pid->pwm < 0) pid->pwm = 0;

    return pid->pwm;
}

八、主函数逻辑(main.c)

文件名:main.c

c 复制代码
#include "stm32f10x.h"
#include "tim.h"
#include "motor.h"
#include "encoder.h"
#include "pid.h"

// 延时函数
void Delay_ms(u16 ms)
{
    u16 i,j;
    for(i=ms; i>0; i--)
        for(j=110; j>0; j--);
}

int main(void)
{
    // 模块初始化
    TIM2_Encoder_Init();   // 编码器初始化
    TIM3_PWM_Init();       // PWM初始化
    Motor_GPIO_Init();     // 电机GPIO初始化
    PID_Init();            // PID初始化
    TIM4_Init(999,719);    // 10ms定时中断

    // 设置电机正转
    Motor_Forward();

    while(1)
    {
        // 主循环可以添加串口打印、逻辑判断等
        // 闭环控制在定时器中断中执行
        Delay_ms(200);
    }
}

// 定时器4中断服务函数 10ms执行一次
void TIM4_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
    {
        // 1. 获取电机实时转速
        Get_Motor_Speed();

        // 2. PID计算 目标转速:100rpm(可修改)
        int pwm_out = PID_Calc(&motor_pid, 100, motor_speed);

        // 3. 输出PWM控制电机
        Set_Motor_Speed(pwm_out);

        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    }
}

九、系统执行流程图(Mermaid)

系统上电
硬件初始化
编码器定时器初始化
PWM定时器初始化
电机GPIO初始化
PID参数初始化
定时中断初始化
电机设置正转
返回主循环
10ms定时器中断触发
读取编码器计数值
计算电机实时转速
PID算法计算输出PWM
PWM限幅处理
输出PWM控制电机转速
清除中断标志位


十、零基础编译与下载步骤

  1. 打开Keil5,新建工程,选择STM32F103C8芯片
  2. 添加STM32标准库核心文件
  3. 新建上述所有.c.h文件,复制代码
  4. 点击Build编译工程,确保无报错
  5. 连接ST-LINK,点击Download下载程序到开发板
  6. 给电机驱动模块接上12V电源,电机自动开始闭环调速

十一、参数调节说明

  1. 编码器参数修改
    如果你的电机减速比、编码器线数不同,修改encoder.c中的转速计算公式即可。
  2. PID参数调节
    • 先调节kp:从小到大,直到电机转速出现小幅震荡
    • 再调节ki:消除静差,让转速稳定在目标值
    • 最后调节kd:抑制超调,让转速响应更平滑
  3. 目标转速修改
    main.c的中断函数中,修改PID_Calc(&motor_pid, 100, motor_speed)中的100,即可修改目标转速(单位rpm)。

十二、常见问题排查

  1. 电机不转
    • 检查电源是否正常,GND是否共地
    • 检查方向引脚、PWM引脚接线是否正确
  2. 转速读取为0
    • 编码器接线错误,A/B相接反
    • 编码器模式初始化失败
  3. 转速不稳定
    • PID参数不合适,重新调节
    • 电机供电不足,更换大功率电源
相关推荐
djarmy1 小时前
哪些海外国家最可能落地矿鸿/OpenHarmony矿山方案?1. 资源型发展中国家(最优先)
嵌入式硬件·开源
东京老树根2 小时前
Arduino - 入门03 - Arduino编程基础 Arduino常用函数-digitalRead
单片机·机器人
guygg882 小时前
适用于 STM32 系列单片机的 USB DFU 上位机程序
stm32·单片机·mongodb
东京老树根2 小时前
Arduino - 入门01 - Arduino,SimulIDE 简介
单片机·机器人
kaikaile19953 小时前
基于 STM32 的双闭环控制直流无刷电机(BLDC)方案
stm32·单片机·嵌入式硬件
Heartache boy3 小时前
野火STM32_HAL库版课程笔记-DWT应用与DHT11温湿度传感器
笔记·stm32·单片机·嵌入式硬件
无人装备硬件开发爱好者11 小时前
STM32G474 + 1.32 寸 OLED(128×96)俄罗斯方块游戏实现指南
stm32·嵌入式硬件·游戏
三佛科技-1341638421211 小时前
SM2850P无电感离线稳压器 5V输出 典型应用电路分析(管脚、关键设计要点)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
潜创微科技12 小时前
IT6636+USB 协同芯片 3 进 1 出 HDMI2.1 KVM 切换器一体化方案
嵌入式硬件·音视频