【STM32】使用HAL库进行电机速度环PID控制,代码+调参

主控:STM32F103C8T6

1. 电机测速

在进行速度控制之前,我们首先需要进行速度采样,这里参见这篇博文

2. 电机驱动

​ 这里不细说电机驱动模块的选型和使用,而是说一个常见的误区。我们驱动电机要使用两路PWM,一般是一路给PWM信号,一路是纯低电平。但这其实是不好的,正确的做法是一路给PWM,另一路给纯高电平。此时PWM的占空比越低,电机的速度越快。

​ 如果大家使用的是类似于A4950或者DRV8870这样的电机驱动芯片,它们的数据手册中都会有这样的描述

​ 这是DRV8870的,明确说明了PWM加高电平是最佳控制方式。

image-20230120225734578

这是A4950的,用曲线图的方式说明了PWM加高电平时电流会更加稳定。image-20230120230343030

​ 此外,如果使用PWM加高电平的控制方式,在设置速度为0时,两路信号都会输出纯高电平,此时会触发芯片的刹车模式,使得电机快速停止转动,避免我们制作的小车出现刹不住车的情况。

3. 速度环实现

​ PID的原理就不赘述了,我们直接看代码。

​ 现在我们已经在定时器中断中完成了电机的速度采样,得到了电机的速度,接下来我们需要进行PID计算,并输出相应占空比的PWM给电机。

​ 但是在此之前,我们需要编写PID的计算函数和进行相关初始化,下面是代码

PID.h部分

#ifndef _PID_H_
#define _PID_H_

#include "stm32f1xx.h"
#include "encoder.h"
#include <stdio.h>
#include "control.h"

//PID三个参数的值
#define KP_speed 2
#define KI_speed 0
#define KD_speed 0

typedef struct _PID//PID参数结构体
{
    float kp,ki,kd;
    float err,lastErr;
    float integral,maxIntegral; //积分值
    float output,maxOutput;
}PID;

void PID_Init(void);
float Speed_PID_Realize(PID* pid,float target,float feedback);//一次PID计算

PID.c部分

#include "pid.h"
PID pid_speed;

/**********************************
 * 功能:PID结构体参数初始化
 * 输入:无
 * 返回:无
 * *******************************/
void PID_Init(void)//PID参数初始化
{
    pid_speed.err = 0;
    pid_speed.integral = 0;
    pid_speed.maxIntegral = 1000; 
    pid_speed.maxOutput = __HAL_TIM_GetAutoreload(&PWM_TIM);
    pid_speed.lastErr = 0;
    pid_speed.output = 0;
    pid_speed.kp = KP_speed;
    pid_speed.ki = KI_speed;
    pid_speed.kd = KD_speed;
}

/****************************************
 * 作用:速度环PID计算
 * 参数:PID参数结构体地址;目标值;反馈值
 * 返回值:无
 * ****************************************/
float Speed_PID_Realize(PID* pid,float target,float feedback)//一次PID计算
{
    pid->err = target - feedback;
    if(pid->err < 0.3 && pid->err > -0.3) pid->err = 0;//pid死区
    pid->integral += pid->err;
    
    if(pid->ki * pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral / pid->ki;//积分限幅
    else if(pid->ki * pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral / pid->ki;

    if(target == 0) pid->integral = 0; // 刹车时清空i


    pid->output = (pid->kp * pid->err) + (pid->ki * pid->integral) + (pid->kd * (pid->err - pid->lastErr));//全量式PID

    //输出限幅
    if(target >= 0)//正转时
    {
        if(pid->output < 0) pid->output = 0;
        else if(pid->output > pid->maxOutput) pid->output = pid->maxOutput;
    }
    else if(target < 0)//反转时
    {
        if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
        else if(pid->output > 0) pid->output = 0;
    }

    pid->lastErr = pid->err;
    if(target == 0) pid->output = 0; // 刹车时直接输出0
    return pid->output;
}

​ 这里的速度环代码在一般的PID上加了点东西。首先是PID死区,即err值很小时认为err=0,让速度发生很小的抖动时,PID输出不会变化,避免大幅度震荡的产生;其次是当目标值为0的时候让积分部分和输出同时等于0,使刹车更加迅速。

​ PID_Init()函数需要放在main.c的循环之前,Speed_PID_Realize()函数需要放在定时器中断的电机测速部分后面。

​ 所以现在的定时器中断函数如下

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器回调函数,用于计算速度和PID计算
{	
    if(htim->Instance==GAP_TIM.Instance)//间隔定时器中断,是时候计算速度了
    {
        /**********************************电机测速************************************/
        motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上计数(正转),返回值为0,否则返回值为1
        motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        
        if(motor1.lastCount - motor1.totalCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum++;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        else if(motor1.totalCount - motor1.lastCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum--;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        
        motor1.speed = (float)(motor1.totalCount - motor1.lastCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 3000;//算得每秒多少转,除以4是因为4倍频
        motor1.speed = Speed_Low_Filter(motor1.speed,speed_Record);
        motor1.lastCount = motor1.totalCount; //记录这一次的计数值
        
        /***************************PID速度环**********************************/
        motor_Out = Speed_PID_Realize(&pid_speed,Target_Speed,motor1.speed);
        //Target_Speed是目标速度,自行定义就好
        if(motor_Out >= 0)
    	{
        __HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_FORWARD, 1000);
        __HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_BACKWARD, 1000-motor_Out);
    	}
    	else
    	{
        __HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_BACKWARD, 1000);
        __HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_FORWARD, 1000+motor_Out);
    	}
        /**********************************************************************/
    }
}

如果觉得定时器中断函数看起来很乱,可以将测速和PID分别封装成函数,定时器中断负责调用函数即可。

现在我们就已经能实现电机的速度环控制了,剩下的就是进行PID调参了。

4. 速度环调参

这里需要说明一下,我给电机测速加上了平均滤波,PID参数在滤波和不滤波的情况下会有比较大的区别

电机速度环比较好调,我的调参经验是这样:

  1. 先让I=D=0,使P从很小值开始增加,直到电机的速度达到目标速度的一半左右。
  2. 一点点增大I,使得电机的速度能够很快达到目标值,哪怕有点震荡、超调也没事。
  3. 增大D,使超调和震荡逐步减小,还剩有一点点超调就行了。保留一点点超调是为了使达到稳定所需的时间比较短。

为了更好地进行PID调参,我们最好使用能画曲线的串口上位机,这里推荐VOFA+,使用VOFA+进行PID调参可以看这里

速度环调得好的效果应该是这样的:

image-20230123111659976

上图中,绿线是电机的实际速度,红线是电机的目标速度。我们主要检查以下几点:

  1. 从静止到正最大转速
  2. 从正最大转速到反最大转速
  3. 从正最大转速或反最大转速到静止

如果电机到正反最大转速所需时间较短,有一点点超调,没有震荡;到静止时所需时间很短,且没有震荡,那么就说明速度环调好了。

下图展示了速度环调好后电机速度稳定和停下来所需的时间

调得好的速度环2

电机速度稳定需要60ms左右,而停下来只需要20ms左右

当然不同电机不同环境下情况会不一样,我这里只是一个参考。

<think>嗯,用户想用STM32HAL通过A4988驱动步进电机。首先,我需要回忆一下A4988的工作原理和STM32的配置方法。根据引用[1],A4988需要步进脉冲信号(STEP)和方向信号(DIR),还有MS引脚设置微步模式。所以用户需要配置GPIO来控制DIR,可能还需要设置MS1-MS3的引脚电平来选择微步细分。 然后,HAL方面,引用[2]提到使用HAL_TIM_PWM_Start来启动定时器的PWM输出,修改频率可以控制转速。同时,引用[3]中的回调函数使用OC(输出比较)模式来生成脉冲,每次比较值到达时翻转电平,这样就能产生连续的脉冲信号。这里可能需要比较PWM和OC两种方式的优缺点,比如PWM更容易调节频率,而OC更灵活? 接下来,硬件连接部分,A4988的VMOT需要接电机电源,VDD接逻辑电压,比如3.3V。STEP和DIR连接到STM32的GPIO,同时MS1-MS3也要接对应的GPIO来选择微步细分模式,比如全步、1/8步等。还要注意ENA引脚是否使能,可能需要拉低才能让驱动器工作。 软件配置方面,使用定时器的PWM模式或者输出比较模式来生成STEP脉冲。比如,配置TIM的通道为PWM生成,占空比可以设为50%,然后通过改变ARR的值来调整频率,进而控制电机速度。或者使用输出比较,每次在回调函数中更新比较值,这样每个脉冲的时间间隔由比较值的增量决定,即Toggle_Pulse的值,这会影响脉冲的频率。 另外,方向控制是通过DIR引脚的高低电平来决定的,所以用户需要另一个GPIO来控制DIR,比如置高正转,置低反转。同时,初始化时要设置MS1-MS3的引脚状态,根据需要的微步模式设置对应的高低电平组合,比如全步进模式可能需要MS1=0, MS2=0, MS3=0。 可能需要注意的问题包括:电流调节,通过A4988的电位器调节参考电压,避免电机过热;还有电机的加速减速曲线,避免突然启停导致失步;以及HAL中定时器配置是否正确,比如时钟源、预分频器、计数模式等。 最后,测试部分,用户需要逐步验证每个功能模块是否正常,比如先测试DIR信号是否能改变方向,再测试STEP脉冲是否正常产生,然后调整频率观察电机转速变化,最后结合微步模式进行精细控制。</think>### 使用STM32 HAL驱动A4988控制步进电机的方法 #### 1. **硬件连接** - **A4988与STM32**: - `STEP`引脚:连接STM32的定时器PWM/OC输出引脚(如TIM1_CH1),用于发送步进脉冲。 - `DIR`引脚:连接STM32的GPIO引脚(如PA0),控制电机方向。 - `MS1/MS2/MS3`引脚:根据微步模式需求连接GPIO(如PA1-PA3),设置电平组合(见引用[^1])。 - `ENA`引脚:可接GPIO控制使能(默认悬空或拉低使能)。 - **电源与电机**: - `VMOT`接电机电源(8-35V),`GND`与STM32共地。 - `VDD`接STM32的3.3V逻辑电源。 #### 2. **软件配置** ##### 2.1 **定时器配置(生成STEP脉冲)** - **方法一:PWM模式**(适合固定频率调速): 1. 配置定时器为PWM模式(如TIM1_CH1)。 2. 设置预分频器(`PSC`)和自动重装载值(`ARR`),决定脉冲频率: $$ \text{频率} = \frac{\text{TIM时钟}}{(\text{PSC}+1) \times (\text{ARR}+1)} $$ 3. 启动PWM输出: ```c HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动PWM[^2] ``` - **方法二:输出比较模式**(灵活控制脉冲间隔): 1. 配置定时器为输出比较模式(OC),设置回调函数: ```c void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) { __IO uint16_t count = __HAL_TIM_GET_COUNTER(htim); __HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, count + Toggle_Pulse); // 更新比较值[^3] } ``` 2. 启动定时器: ```c HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1); // 启动中断模式 ``` ##### 2.2 **方向与微步控制** - 通过GPIO设置`DIR`电平(高/低)控制转向: ```c HAL_GPIO_WritePin(GPIOA, DIR_Pin, GPIO_PIN_SET); // 正转 ``` - 配置`MS1/MS2/MS3`电平组合(例如1/8步进模式:MS1=1, MS2=1, MS3=0)。 #### 3. **关键代码示例** ```c // 初始化定时器与GPIO MX_TIM1_Init(); // 配置TIM1为PWM/OC模式 MX_GPIO_Init(); // 配置DIR、MS1-MS3引脚 // 启动PWM生成脉冲 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 设置方向 HAL_GPIO_WritePin(GPIOA, DIR_Pin, GPIO_PIN_SET); // 调节速度(修改ARR值) __HAL_TIM_SET_AUTORELOAD(&htim1, new_arr_value); ``` #### 4. **调试与优化** - **电流调节**:通过A4988电位器调整参考电压,匹配电机额定电流。 - **加减速控制**:逐步改变脉冲频率(如梯形加速曲线),避免失步。 - **保护机制**:增加过温、堵转检测逻辑。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值