程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

STM32F103直流有刷电机速度闭环控制

在《基于直流有刷电机的速度闭环控制以及matlab仿真》我们介绍了速度闭环控制的实现,其采用的是PID控制算法,本节我们就基于STM32F103来实现直流电机的增量式PID速度闭环控制。

一、软件实现

我们需要将PID控制器应用于STM32F103的速度闭环电机控制,以下是接下来的步骤:

  • 电机速度测量:使用编码器来测量电机的实际转速,需要配置定时器的编码器模式来读取编码器脉冲,从而计算速度;具体参考《STM32F103霍尔编码器测速》;
  • 电机驱动:通常使用PWM驱动电机,需要配置定时器的PWM输出模式,并设置占空比来控制电机速度;具体参考《基于直流有刷电机的开环调速控制以及matlab仿真》;
  • 系统定时:PID计算需要定期执行,因此需要配置一个定时器中断,在中断服务函数中执行PID计算和更新PWM输出;
  • 主程序初始化:初始化所有外设(编码器定时器、电机PWM、中断定时器等)和PID参数;
  • 主循环:通常主循环可以处理一些非实时任务,比如接收用户指令(例如通过串口改变目标速度)等。

1.1 pid.h

control目录下新建pid.h文件,自定义PID结构体;

/******************************************************************************************************/
#ifndef _STM32f10x_PID__H
#define _STM32f10x_PID__H


typedef struct {
    float Kp, Ki, Kd;      // 比例、积分、微分系数
    float Ts;              // 周期
    float prev_error;      // e(k-1)
    float prev_error2;     // e(k-2)
    float out_max;         // 输出的最大值(占空比)
    float out_min;         // 输出的最小值(占空比)
    float output;          // 当前总输出 u(k)
    float target_val;      // 目标值 
} PID;

extern PID g_pid;                    // 全局PID参数
extern void pid_param_init(void);    // 初始化PID参数
extern float pid_compute(float measurement);  // PID计算

#endif 

1.2 pid.c

control目录下新建pid.c文件,

1.2.1 初始化PID参数
/*****************************************************************************/
#include "pid.h"

PID g_pid;     // 全局pid参数

/*****************************************************************************
 *
 *		 Description:   初始化PID参数

********************************************************************************/
void pid_param_init()
{
     g_pid.target_val = 5000.0f;  // 目标值5000 RPM
     g_pid.Ts = 0.01f;            // 10ms控制周期
     g_pid.output = 0.0f;
     g_pid.prev_error = 0.0f;
     g_pid.prev_error2 = 0.0f;
     g_pid.out_max = 100.0f;      // 最大输出(占空比)
     g_pid.out_min = 0.0f;        // 最小输出
     g_pid.Kp = 0.6f;
     g_pid.Ki = 0.4f;
     g_pid.Kd = 0.2f;
}

pid_param_init()函数把结构体g_pid参数初始化,将目标值g_pid.target_val设置为5000.00;将实际值、上一次偏差值和上上次偏差值等初始化为0

1.2.2 PID算法实现

增量式PID算法:

\[Δu(k)=K_p[e(k)-e(k-1)]+K_iT_se(k)+\frac{K_d}{T_s}[e(k)-2e(k-1)+e(k-2)] \]

\[u(k)=u(k-1)+Δu(k) \]

PID算法实现:

/**************************************************************************************************************
 *
 *		 Description: PID控制算法
 *       Parameter  :  measurement:测量值
 *       Return     :  PID输出值
 *
 **************************************************************************************************************/
float pid_compute(float measurement) {
    float error = g_pid.target_val - measurement;

    // 计算控制增量
    float delta_u = g_pid.Kp * (error - g_pid.prev_error)
                  + g_pid.Ki * g_pid.Ts * error
                  + (g_pid.Kd / g_pid.Ts) * (error - 2.0f * g_pid.prev_error + g_pid.prev_error2);

    // 计算新的总输出
    g_pid.output = g_pid.output + delta_u;

    // 输出限幅
    if (g_pid.output > g_pid.out_max) 
        g_pid.output = g_pid.out_max;
    if (g_pid.output < g_pid.out_min) 
        g_pid.output = g_pid.out_min;

    // 更新误差历史
    g_pid.prev_error2 = g_pid.prev_error;
    g_pid.prev_error = error;

    return g_pid.output;
}

1.3 TIM6_IRQHandler中断处理函数

开启定时中断,这里使用的定时器6,定时周期为10ms,在定时器溢出中断中执行PID运算;修改time.c文件:

#include "encoder.h"
#include "pid.h"

/*******************************************************************************
 * Function Name  : TIM6_IRQHandler
 * Description    : This function handles TIM6 global interrupt request.
 * Input          : None
 * Output         : None
 * Return         : None
 *******************************************************************************/
void TIM6_IRQHandler(void)
{
	//**********************自定义用户任务****************************//
	int res_pwm = 0;    /*PWM值(PID输出)*/

	// 1. 获取编码器速度
	g_motor_speed = get_encoder_speed(10);

    // 2. 进行PID运算,得到PWM输出值
    res_pwm = pid_compute(g_motor_speed);

    // 3. 更新PWM输出
    motor_set_duty(res_pwm);

	//*****************************************************************//
	TIM6->SR &= ~(1 << 0); // 清中断标志
}

其中motor_set_duty定义在motor.c文件中;

/**************************************************************************************************
*
*    Function  :  设置电机PWM占空比
*    Parameter :  duty:PWM占空比
*
**************************************************************************************************/
void  motor_set_duty(int duty)
{
    g_motor_pwm = duty;
	if(g_motor_pwm < 0)
	{
		g_motor_pwm = 0;
	}
	if(g_motor_pwm >100)
	{
		g_motor_pwm = 100;
	}
	motor_update_speed();
}

1.4 主函数

#include "common.h"
#include "stdio.h"
#include "pid.h"

int main()
{
    int duty;
    int speed;
    STM32_Clock_Init(9); // 系统时钟初始化

    // 串口初始化
    STM32_NVIC_Init(2, USART1_IRQn, 0, 1); // 串口中断优先级初始化,其中包括中断使能
    usart_init(USART_1, 115200);           // 串口1初始化,波特率115200 映射到PA9 PA10

    // 按键KEY初始化
    gpio_init(PC5, GPI_UP, HIGH);             // PC5接按键KEY0
    gpio_init(PA15, GPI_UP, HIGH);            // PA15接按键KEY1
    Ex_NVIC_Congig(PC5, FALLING);             // 按键KEY0按下触发 高电平->低电平
    Ex_NVIC_Congig(PA15, FALLING);            // 按键KEY1按下触发 高电平->低电平
    STM32_NVIC_Init(2, EXTI9_5_IRQn, 2, 2);   // EXTI线[9:5]中断优先级初始化,其中包括中断使能
    STM32_NVIC_Init(2, EXTI15_10_IRQn, 2, 2); // EXTI线[15:10]中断优先级初始化,其中包括中断使能

    motor_init(); // 电机初始化,使用的定时器2,PA0/PA1

    encoder_init(); // 编码器初始化,使用的定时器4,PB6/PB7
	
	  pid_param_init();  // 初始化PID参数

    STM32_NVIC_Init(2, TIM6_IRQn, 0, 0); // TIM6溢出中端使能
    TIM_Init_MS(TIMER6, 10);           // TIM6计数到10ms发生中断,在TIM6_IRQHandler中获取转速,并保存到全局变量g_motor_speed
    while (1)
    {
        duty = get_motor_duty();
        printf("duty: %d\n", duty);
        delay_ms(1000);
        printf("Speed: %d RPM\n", g_motor_speed); // 打印转速
			  printf("PID Output: %f\n", g_pid.output); // PID输出
    }
}

二、 测试

编译程序并下载测试,可以通过串口查看当前输出的占空比以及对应的转速;

三、源码下载

源码下载路径:stm32f103

参考文章

[1] 基于直流有刷电机的开环调速控制以及matlab仿真

[2] STM32F103霍尔编码器测速

posted @ 2025-12-01 21:57  大奥特曼打小怪兽  阅读(2)  评论(0)    收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步