PID算法

8 篇文章 14 订阅

1.什么自动控制系统是

  在无人直接参与下可使生产过程按预定程序进行的控制系统。

一个控制系统包括控制器、传感器、变送器、执行机构、输入输出接口。 具体过程是:控制器的输出经过输出接口、执行机构,加到被控系统上;控制系统的被控量,经过传感器、变送器,通过输入接口送到控制器。

自动控制系统可分为开环控制系统闭环控制系统

开环控制系统(open-loop control system) :输出对于输入没有影响。在开环控制系统中,系统输出只受输入的控制,控制精度和抑制干扰的特性都比较差。开环控制系统中,基于按时序进行逻辑控制的称为顺序控制系统;由顺序控制装置、检测元件、执行机构和被控工业对象所组成。

闭环控制系统(closed-loop control system): 输出会通过反馈电路返送回来影响输入,形成一个或多个闭环的控制系统。把控制系统输出量的一部分或全部,通过一定方法和装置反送回系统的输入端,然后将反馈信息与原输入信息进行比较,再将比较的结果施加于系统进行控制,避免系统偏离预定目标。闭环控制系统利用的是负反馈。

2.什么是PID控制

偏差比例(Proportion)、积分(Integral)和微分(Differential)通过线性组合构成控制量,用这一控制量对被控对象进行控制,这样的控制器称PID控制器。

当被控对象的结构和参数不能完全掌握,或得不到精确的数学模型时,控制理论的其它技术难以采用时,系统控制器的结构和参数必须依靠经验和现场调试来确定,这时应用PID控制技术最为方便。即当我们不完全了解一个系统和被控对象,或不能通过有效的测量手段来获得系统参数时,最适合用PID控制技术。 PID控制,实际中也有PI和PD控制。PID控制器就是根据系统的误差,利用比例、积分、微分计算出控制量进行控制的。

列子:以电机转速调节为例。调节PWM占空比可以实现电机调速,编码器可以检测当前电机转速。那现在我需要控制电机转速为3圈/s(目标速度),并且是控制在不同负载时电机都运行在这个速度。 以下开始电机处于停止状态此时PWM占空比为0,开始时候我们不知道给多少占空比合适,就给占空比为45%,电机旋转,通过编码器我们得到当前的速度只有2.5圈/s(实际速度)(数值是假设的,可能跟实际有所偏差),此时我们需要加大占空比,给到50%,编码器得到速度才2.8圈/s;没办法,我们还需要再加占空比,改为55%,编码器得到3.1圈/s,给大了,再调,改为54%,这次刚刚好了,编码器速度在3圈/s左右变动,勉强满足要求。 如果现在给电机加了一些负载,本来占空比54%有3圈/s的速度的,现在下降为2.3圈/s了,现在为达到3圈/s速度,又要类似上面的尝试修改过程,改为60%,只有2.5圈/s,改为80%,超了,到了3.2圈/s,改为77%,差一点点,改为78%,效果还不错。 现在,又增加或者减少一点负载,又要运行类似上面的占空比调整过程… … 我们会思考,有没有办法编个程序让STM32实现上面的电机控制过程呢??

解析:不管增加负载还是减小负载,让程序自己调整占空比从而使电机转速控制在目标速度。用一个数学公式。该公式有一个变量(当前速度与目标速度的差值)。而公式的结果是占空比。所以PID算法就是解决这个问题的数学公式。有时我们不仅希望在PID算法程序中实现占空比的自动调节,并且希望在很短时间内快速调节到目标值。一般PID算法要实现:稳、准、快。

1.P(比列运算)

比例控制是一种最简单的控制方式。其控制器的输出输入信号误差成比例关系。当仅有比例控制时系统输出存在稳态误差(Steady-state error),假设输入误差为0,那么输出也为0,显然是不行的。 比例控制考虑当前误差误差值和一个正值的常数Kp(比例系数)相乘。Kp只是在控制器的输出和误差成比例的时候成立。注意在误差是0的时候,控制器的输出也是0。 比例控制的输出如下:

若比例增益大,在相同误差量下,会有较大的输出,但若比例增益太大,会使系统不稳定。 相反的,若比例增益小,若在相同误差量下,其输出较小,因此控制器会较不敏感的。若比例增益太小,当有干扰出现时,其控制信号可能不够大,无法修正干扰的影响。

比列运算是存在稳态误差(在比列控制中,经过一定时间后误差稳定在一定值),稳态误差和比例增益(Kp)成正比,和受控系统本身的增益(Pout)成反比。通过调节KP来达到目标值。

下面是不同KP的曲线:

怎么消除稳态误差?

1.加入偏置(在Pout = Kp e(t)后面加一个偏置值。 2.引入积分控制

2.I(积分控制)

在积分控制中,控制器的输出与输入误差信号的积分成正比关系。对一个自动控制系统,如果在进入稳态后存在稳态误差,则称这个控制系统是有稳态误差的或简称有差系统(System with Steady-state Error)。为了消除稳态误差,在控制器中必须引入“积分项”。积分项对误差取决于时间的积分,随着时间的增加,积分项会增大。这样,即便误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减小,直到等于零。因此,比例+积分(PI)控制器,可以使系统在进入稳态后无稳态误差。

积分控制考虑过去误差,将过去一段时间误差值和(误差和)乘以一个正值的常数Ki。Ki从过去的平均误差值来找到系统的输出结果和预定值的平均误差。一个简单的比例系统会震荡,会在预定值的附近来回变化,因为系统无法消除多余的纠正。通过加上累计的平均误差值,平均系统误差值就会渐渐减少。所以,最终这个PID回路系统会在设定值稳定下来。积分控制的输出如下:

积分控制会加速系统趋近设定值的过程,并且消除纯比例控制器会出现的稳态误差。 积分增益越大,趋近设定值的速度越快,不过因为积分控制会累计过去所有的误差,可能会使输出出现过冲的情形。

3.D(微分控制)

在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。

在控制器中仅引入“比例”项往往是不够的,比例项的作用仅是放大误差的幅值,而目前需要增加的是“微分项”,它能预测误差变化的趋势。在误差没出现之前就抑制住。微分考虑的是未来的误差。这样,具有比例+微分的控制器,就能够提前使抑制误差的控制作用等于零,甚至为负值,从而避免了被控量的严重超调。所以对有较大惯性或滞后的被控对象,比例+微分(PD)控制器能改善系统在调节过程中的动态特性。

微分控制考虑将来误差,计算误差的一阶导数,并和一个正值的常数Kd相乘。 这个导数的控制会对系统的改变作出反应。导数的结果越大(说明系统误差变化率大),那么控制系统就对输出结果作出更快速的反应。这个Kd参数也是PID被称为可预测的控制器的原因。 Kd参数对减少控制器短期的改变很有帮助。 微分控制的输出如下:

微分控制可以提升整定时间及系统稳定性。不过因为纯微分器不是因果系统,因此在PID系统实现时,一般会为微分控制加上一个低通滤波以限制高频增益及噪声。 实际上较少用到微分控制,估计PID控制器中只有约20%有用到微分控制。使用PID算法,P是最基本的,可以衍生出来的算法有:P、PI、PD。

总结:P(比列)考虑的是当前误差(存在稳态误差)。I(积分)考虑的是过去误差(对过去误差的积累随着时间消除稳态误差)。D(微分)考虑的是未来误差(对未来误差的预测从而提前去消除)。当有较大惯性组件(环节)或有滞后(delay)组件时才考虑用。

模拟PID框图:

 

r(t)是给定值,y(t)是系统的实际输出值,给定值与实际输出值构成控制偏差e(t)。

上面是模拟PID的公式:

Ki = KP/ Ti       Kd = KP / Td

 比例系数𝐾𝑝越大,控制作用越强,则过渡过程越快,控制过程的静态偏差也就越小; 但是越大,也越容易产生振荡,破坏系统的稳定性。 积分时间𝑇𝑖越大,积分的积累作用越弱,这时系统在过渡时不会产生振荡;增大积分时间会减慢静态误差的消除过程,消除偏差所需的时间也较长,但可以减少超调量,提高系统的稳定性。当𝑇𝑖较小时,则积分的作用较强,这时系统过渡时间中有可能产生振荡,不过消除偏差所需的时间较短。

𝑇𝑑越大时,则它抑制偏差e(t)变化的作用越强;𝑇𝑑越小时,则它反抗偏差e(t)变化的作用越弱。微分环节的作用是阻止偏差的变化。它是根据偏差的变化趋势(变化速度)进行控制。偏差变化的越快,微分控制器的输出就越大,并能在偏差值变大之前进行修正。但微分的作用对输入信号的噪声很敏感,对那些噪声较大的系统一般不用微分,或在微分起作用之前先对输入信号进行滤波。

而如果要在程序中实现不能用模拟的PID公式,需要转换成数字PID(由于计算机控制是一种采样控制,它只能根据采样时刻的偏差计算控制量,而不能像模拟控制那样连续输出控制量,进行连续控制)对模拟PID进行离散化处理转换成数字PID。(离散化相当于ADC的采样处理)

数字PID分为位置PID与增量PID。

1.位置PID

 数字系统中,离散化处理的方法为:以T作为采样周期k作为采样序号则离散采样时间kT对应着连续时间t,用矩形法数值积分近似代替积分用一阶后向差分近似代替微分。

上式中,为了表示的方便,将类似于e(kT)简化成ek等,然后代入模拟PID公式得到数字PID:

但是需要注意的是Ki与Kd跟模拟PID不同。

ki = KP* T/ Ti     Kd = KP * Td / T

如果采样周期足够小(采样频率大),则数字PID公式的近似计算可以获得足够精确的结果,离散控制过程与连续过程十分接近。

数字位置式PID由于全量输出(其结果直接控制被控制量),所以每次输出均与过去状态有关,计算时要对ek进行累加,工作量大;并且,因为计算机输出的uk对应的是执行机构的实际位置,如果计算机出现故障,输出的uk将大幅度变化,会引起执行机构的大幅度变化,有可能因此造成严重的生产事故。引出数字增量式PID。

打个比方来说明数字位置式PID与数字增量式PID。如果我的目标值需要60%的占空比。那么直接用改成60%的占空比。如果是增量式当前没达到目标值的占空比是50%,那么我要达到60%占空比目标值。直接在50%占空比上加10%来达到目标值。

2.增量式PID

增量式PID指数字控制器的输出只是控制量的增量∆uk。当执行机构需要的控制量是增量,而不是位置量的绝对数值时,可以使用增量式PID控制算法进行控制.

增量式PID控制算法可以通过位置式PID公式推导出。由位置式PID公式可以得到控制器的第k-1个采样时刻的输出值为

用K时刻的位置式减去K-1时刻位置式。

∆uk = uk−uk−1 得到如下公式:

由增量式PID公式可以看出,如果计算机控制系统采用恒定的采样周期T,一旦确定A、B、C,只要使用前后三次测量的偏差值(ek,ek-1,ek-2),就可以求出控制量。

而在代码中简化为:   KP*ek + Ki*ek-1 + Kd*ek-2 

而位置式PID控制算法也可以通过增量式控制算法推出递推计算公式:

 总结:以上就是数字PID的介绍以及为什么要引入PID算法,以及PID参数的整定到达目标值的快速方法。直流有刷电机的位置(控制转多少圈),速度(每分钟转多少圈或每秒转多少圈)。

3.学习列子:

1.速度环-----增量式PID

有刷电机带有编码器,需要一个定时器做编码器模式。用一个高级定时器产生PWM并且是互补的。用PWM控制电机需要根据电机驱动板的硬件电路图。

编码器.h

#ifndef __BSP_ENCODER_H__
#define __BSP_ENCODER_H__

#define ENCODER_TIMx                        TIM3
#define ENCODER_TIM_RCC_CLK_ENABLE()        __HAL_RCC_TIM3_CLK_ENABLE()
#define ENCODER_TIM_RCC_CLK_DISABLE()       __HAL_RCC_TIM3_CLK_DISABLE()

#define ENCODER_TIM_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOC_CLK_ENABLE()
#define ENCODER_TIM_CH1_PIN                 GPIO_PIN_6     // PC6 CH1
#define ENCODER_TIM_CH1_GPIO                GPIOC
#define ENCODER_TIM_CH2_PIN                 GPIO_PIN_7     // PC7 CH2
#define ENCODER_TIM_CH2_GPIO                GPIOC

#define TIM_ENCODERMODE_TIx                 TIM_ENCODERMODE_TI12

#define ENCODER_TIM_IRQn                    TIM3_IRQn
#define ENCODER_TIM_IRQHANDLER              TIM3_IRQHandler


// 定义定时器预分频,定时器实际时钟频率为:84MHz/(ENCODER_TIMx_PRESCALER+1)
#define ENCODER_TIM_PRESCALER               0 

// 定义定时器周期,当定时器开始计数到ENCODER_TIMx_PERIOD值是更新定时器并生成对应事件和中断
#define ENCODER_TIM_PERIOD                  0xFFFF

// 使用32bits 的计数器作为编码器计数,F4系列的TIM2,TIM5
 定义定时器周期,当定时器开始计数到ENCODER_TIMx_PERIOD值是更新定时器并生成对应事件和中断
//#define ENCODER_TIM_PERIOD                  0xFFFFFFFF
//#define CNT_MAX                             4294967295

#define USE_16CNT                       // 使用定时器3是16bits 的计数器作为编码器计数,F4系列的TIM3,TIM4
#define ENCODER_TIM_PERIOD                0xFFFF
#define CNT_MAX                           ((int32_t)65536) // 计数值从0~65535

/* 扩展变量 ------------------------------------------------------------------*/
extern TIM_HandleTypeDef htimx_Encoder; // 编码器初始化结构体
extern int32_t OverflowCount ;//定时器溢出次数
/* 函数声明 ------------------------------------------------------------------*/
void ENCODER_TIMx_Init(void);

编码器.c

#include "encoder/bsp_encoder.h"
int32_t OverflowCount = 0;//记录定时器溢出次数

TIM_HandleTypeDef    htimx_Encoder;  // 定时器结初始化构体
TIM_Encoder_InitTypeDef sEncoderConfig; // 编码器结构体

void ENCODER_TIMx_Init(void)
{    
  ENCODER_TIM_RCC_CLK_ENABLE();
  htimx_Encoder.Instance = ENCODER_TIMx;                // 定时器基地址
  htimx_Encoder.Init.Prescaler = ENCODER_TIM_PRESCALER; // 预分频器
  htimx_Encoder.Init.CounterMode = TIM_COUNTERMODE_UP;  // 计数模式
  htimx_Encoder.Init.Period = ENCODER_TIM_PERIOD;       // ARR值
  htimx_Encoder.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; // 时钟分频因子

  sEncoderConfig.EncoderMode        = TIM_ENCODERMODE_TIx;       // 编码器模式   
  sEncoderConfig.IC1Polarity        = TIM_ICPOLARITY_RISING;     // 输入捕获1极性选择
  sEncoderConfig.IC1Selection       = TIM_ICSELECTION_DIRECTTI;  // 输入捕获1选择
  sEncoderConfig.IC1Prescaler       = TIM_ICPSC_DIV1;            // 输入捕获1的预分频器
  sEncoderConfig.IC1Filter          = 13;                        // 输入捕获1滤波器
  
  sEncoderConfig.IC2Polarity        = TIM_ICPOLARITY_RISING;     // 输入捕获2极性选择
  sEncoderConfig.IC2Selection       = TIM_ICSELECTION_DIRECTTI;  // 输入捕获2选择
  sEncoderConfig.IC2Prescaler       = TIM_ICPSC_DIV1;            // 输入捕获2的预分频器
  sEncoderConfig.IC2Filter          = 13;                        // 输入捕获2滤波器
  __HAL_TIM_SET_COUNTER(&htimx_Encoder,0); // 设置计数值为0
  
  HAL_TIM_Encoder_Init(&htimx_Encoder, &sEncoderConfig);

  __HAL_TIM_CLEAR_IT(&htimx_Encoder, TIM_IT_UPDATE);  // 清除更新中断标志位
  __HAL_TIM_URS_ENABLE(&htimx_Encoder);               // 仅允许计数器溢出才产生更新中断
  __HAL_TIM_ENABLE_IT(&htimx_Encoder,TIM_IT_UPDATE);  // 使能更新中断
  
  HAL_NVIC_SetPriority(ENCODER_TIM_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(ENCODER_TIM_IRQn);

  HAL_TIM_Encoder_Start(&htimx_Encoder, TIM_CHANNEL_ALL);
}

/**
  * 函数功能: 基本定时器硬件初始化配置
  * 输入参数: htim_base:基本定时器句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* htim_base)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(htim_base->Instance==ENCODER_TIMx)
  {
    /* 基本定时器外设时钟使能 */
    ENCODER_TIM_GPIO_CLK_ENABLE();

    /* 定时器通道1功能引脚IO初始化 */
    GPIO_InitStruct.Pin = ENCODER_TIM_CH1_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull=GPIO_PULLDOWN;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM4; // 复用推挽
    HAL_GPIO_Init(ENCODER_TIM_CH1_GPIO, &GPIO_InitStruct);
    
    /* 定时器通道2功能引脚IO初始化 */
    GPIO_InitStruct.Pin = ENCODER_TIM_CH2_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM4; // 复用推挽
    HAL_GPIO_Init(ENCODER_TIM_CH2_GPIO, &GPIO_InitStruct);
  }
}

 
/**
  * 函数功能: 定时器更新中断
  * 输入参数: *htim,定时器句柄
  * 返 回 值: 无
  * 说    明: 编码器捕获溢出计数
  */

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

  if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htimx_Encoder))
    OverflowCount--;       //向下计数溢出
  else
    OverflowCount++;       //向上计数溢出
}

PWM输出定时器.h

#ifndef __BDCMOTOR_TIM_H__
#define __BDCMOTOR_TIM_H__
#include "stm32f4xx_hal.h"

#define BDCMOTOR_TIMx                         TIM1
#define BDCMOTOR_TIM_RCC_CLK_ENABLE()         __HAL_RCC_TIM1_CLK_ENABLE()
#define BDCMOTOR_TIM_RCC_CLK_DISABLE()        __HAL_RCC_TIM1_CLK_DISABLE()

#define BDCMOTOR_TIM_CH1_GPIO_CLK_ENABLE()    __HAL_RCC_GPIOA_CLK_ENABLE()     
#define BDCMOTOR_TIM_CH1_PORT                 GPIOA                            
#define BDCMOTOR_TIM_CH1_PIN                  GPIO_PIN_8     // CH1                 
#define BDCMOTOR_TIM_CH1N_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()    
#define BDCMOTOR_TIM_CH1N_PORT                GPIOB                           
#define BDCMOTOR_TIM_CH1N_PIN                 GPIO_PIN_13    // CHN1                  

#define SHUTDOWN_GPIO_CLK_ENABLE()            __HAL_RCC_GPIOH_CLK_ENABLE()    
#define SHUTDOWN_PORT                         GPIOH                           
#define SHUTDOWN_PIN                          GPIO_PIN_6    // SD(驱动板的PWM使能端)      

#define ENABLE_MOTOR()                        HAL_GPIO_WritePin(SHUTDOWN_PORT,SHUTDOWN_PIN,GPIO_PIN_RESET)
#define SHUTDOWN_MOTOR()                      HAL_GPIO_WritePin(SHUTDOWN_PORT,SHUTDOWN_PIN,GPIO_PIN_SET)

#define BDCMOTOR_TIM_CC_IRQx                  TIM1_CC_IRQn
#define BDCMOTOR_TIM_CC_IRQxHandler           TIM1_CC_IRQHandler

// 定义定时器预分频,定时器实际时钟频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)
#define BDCMOTOR_TIM_PRESCALER               1    // 实际时钟频率为:84MHz

// 定义定时器周期,PWM频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)/(BDCMOTOR_TIM_PERIOD+1)
#define BDCMOTOR_TIM_PERIOD                  4199  // PWM频率为84MHz/(4199+1)=20KHz

#define BDCMOTOR_DUTY_ZERO                   (0)       // 0%占空比
#define BDCMOTOR_DUTY_FULL                   (BDCMOTOR_TIM_PERIOD-100) // 100%占空比 但大不到驱动板有自举电路的存在

#define BDDCMOTOR_DIR_CW()                    {HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);\                                                HAL_TIMEx_PWMN_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);}

#define BDDCMOTOR_DIR_CCW()                   {HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);\                                              HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);}

#define MOTOR_DIR_CW                            1  // 电机方向: 顺时针
#define MOTOR_DIR_CCW                         (-1) // 电机方向: 逆时针

// 定义高级定时器重复计数寄存器值
// 实际PWM频率为:168MHz/(BDCMOTOR_TIMx_PRESCALER+1)/(BDCMOTOR_TIM_PERIOD+1)/(BDCMOTOR_TIM_REPETITIONCOUNTER+1)

#define BDCMOTOR_TIM_REPETITIONCOUNTER       0

/* 扩展变量 ------------------------------------------------------------------*/
extern TIM_HandleTypeDef htimx_BDCMOTOR;
extern __IO int32_t PWM_Duty;

/* 函数声明 ------------------------------------------------------------------*/

void BDCMOTOR_TIMx_Init(void);
void SetMotorDir(int16_t Dir);
void SetMotorSpeed(int16_t Duty);
#endif	/* __BDCMOTOR_TIM_H__ */

PWM输出定时器.c


#include "DCMotor/bsp_BDCMotor.h"

TIM_HandleTypeDef htimx_BDCMOTOR;
__IO int32_t PWM_Duty=BDCMOTOR_DUTY_ZERO; // 占空比:PWM_Duty/BDCMOTOR_TIM_PERIOD*100%

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
  /* BDCMOTOR相关GPIO初始化配置 */
  if(htim == &htimx_BDCMOTOR)
  {
    GPIO_InitTypeDef GPIO_InitStruct;
    /* 引脚端口时钟使能 */
    __HAL_RCC_GPIOE_CLK_ENABLE();
    BDCMOTOR_TIM_CH1_GPIO_CLK_ENABLE();
    BDCMOTOR_TIM_CH1N_GPIO_CLK_ENABLE();
    SHUTDOWN_GPIO_CLK_ENABLE();

    /* BDCMOTOR输出脉冲控制引脚IO初始化 */
    GPIO_InitStruct.Pin = BDCMOTOR_TIM_CH1_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
    HAL_GPIO_Init(BDCMOTOR_TIM_CH1_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = BDCMOTOR_TIM_CH1N_PIN;
    HAL_GPIO_Init(BDCMOTOR_TIM_CH1N_PORT, &GPIO_InitStruct);

    __HAL_RCC_GPIOE_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = SHUTDOWN_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = 0;
    HAL_GPIO_Init(SHUTDOWN_PORT, &GPIO_InitStruct);

    /* 使能电机控制引脚 */
    ENABLE_MOTOR();

  }
}

/**
  * 函数功能: BDCMOTOR定时器初始化
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void BDCMOTOR_TIMx_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig;             // 定时器时钟
  TIM_OC_InitTypeDef sConfigOC;

  /* 基本定时器外设时钟使能 */
  BDCMOTOR_TIM_RCC_CLK_ENABLE();

  /* 定时器基本环境配置 */
  htimx_BDCMOTOR.Instance = BDCMOTOR_TIMx;                               // 定时器编号
  htimx_BDCMOTOR.Init.Prescaler = BDCMOTOR_TIM_PRESCALER;                // 定时器预分频器
  htimx_BDCMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;                  // 计数方向:向上计数
  htimx_BDCMOTOR.Init.Period = BDCMOTOR_TIM_PERIOD;                      // 定时器周期
  htimx_BDCMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;              // 时钟分频
  htimx_BDCMOTOR.Init.RepetitionCounter = BDCMOTOR_TIM_REPETITIONCOUNTER;// 重复计数器
  /* 初始化定时器比较输出环境 */
  HAL_TIM_PWM_Init(&htimx_BDCMOTOR);

  /* 定时器时钟源配置 */
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;       // 使用内部时钟源
  HAL_TIM_ConfigClockSource(&htimx_BDCMOTOR, &sClockSourceConfig);



/* 定时器比较输出配置 */
  sConfigOC.OCMode = TIM_OCMODE_PWM1;                 // 比较输出模式:PWM1模式
  sConfigOC.Pulse =  PWM_Duty;                        // 初始占空比
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;          // 输出极性
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;        // 互补通道输出极性
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;          // 快速模式
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;      // 空闲电平
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;    // 互补通道空闲电平
  HAL_TIM_PWM_ConfigChannel(&htimx_BDCMOTOR, &sConfigOC, TIM_CHANNEL_1);

	/* 启动定时器 */
  HAL_TIM_Base_Start(&htimx_BDCMOTOR);
}



/**
  * 函数功能: 设置电机速度
  * 输入函数: Duty,输出脉冲占空比
  * 返 回 值: 无
  * 说    明: 无
  */
void SetMotorSpeed(int16_t Duty)
{
  __HAL_TIM_SET_COMPARE(&htimx_BDCMOTOR,TIM_CHANNEL_1,Duty); // 设置捕获/比较寄存器的值从而改变占空比
}

/**
  * 函数功能: 设置电机转动方向
  * 输入函数: Dir,电机转动方向
  * 返 回 值: 无
  * 说    明: 无
  */
void SetMotorDir(int16_t Dir)
{
  if(Dir == MOTOR_DIR_CW)
  {
    HAL_TIM_PWM_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);
    HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);  // 停止输出
  }
  else
  {
    HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);
    HAL_TIMEx_PWMN_Start(&htimx_BDCMOTOR,TIM_CHANNEL_1);  
  }
}

main.c

#include "stm32f4xx_hal.h"
#include "key/bsp_key.h"
#include "encoder/bsp_encoder.h"
#include "usart/bsp_usartx.h"
#include "DCMotor/bsp_BDCMotor.h"
#include "led/bsp_led.h"
/* 私有类型定义 --------------------------------------------------------------*/
typedef struct
{
  __IO int32_t  SetPoint;    //设定目标 Desired Value
  __IO float    Proportion;  //比例常数 Proportional Const
  __IO float    Integral;    //积分常数 Integral Const
  __IO float    Derivative;  //微分常数 Derivative Const
  __IO int      LastError;   //Error[-1] // 上次误差
  __IO int      PrevError;   //Error[-2] // 上上次误差
}PID_TypeDef;

/* 四舍五入 */
//将浮点数x四舍五入为int32_t
#define ROUND_TO_INT32(x)   ((int32_t)(x)+0.5f)>=(x)? ((int32_t)(x)):((uint32_t)(x)+1) 

/*************************************/
// 定义PID相关宏
// 这三个参数设定对电机运行影响非常大
// PID参数跟采样时间息息相关
/*************************************/
#define  SPD_P_DATA      5.5f        // P参数
#define  SPD_I_DATA      1.56f       // I参数
#define  SPD_D_DATA      0.0f        // D参数
#define  TARGET_SPEED    50.0f      // 目标速度    表示一分钟转50圈 

#define ENCODER         11   // 编码器线数
#define SPEEDRATIO      30   // 电机减速比
// 电机旋转一圈对应的编码器脉冲数(因为是减速型电机实际转30圈,而减速后才转一圈)减速比 1:30
#define PPR             (SPEEDRATIO*ENCODER*4) // Pulse/r 每圈可捕获的脉冲数

#define MAX_SPEED       380  // 空载满速380r/m




__IO uint8_t  Start_flag = 0;              // PID 开始标志

int32_t  Motor_Dir = MOTOR_DIR_CW;        // 电机方向

__IO int32_t LastSpd_Pulse= 0;  // 编码器捕获值 Pulse 


extern __IO uint32_t uwTick;

/* PID结构体 */
PID_TypeDef  sPID;                 // 定义PID参数结构体
PID_TypeDef  *ptr =  &sPID;


void PID_ParamInit(void);

int32_t SpdPIDCalc(float NextPoint);

void StopMotor(void);

void StartMotor(void);

/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  __HAL_RCC_PWR_CLK_ENABLE();                                     // 使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  // 设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        // 打开HSE
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    // 打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            // PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 // 8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               // 336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     // 2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 // USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用

 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                 // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 串口初始化 */
  MX_USARTx_Init();
  /* LED初始化 */
  LED_GPIO_Init();
  /* 按键初始化 */
  KEY_GPIO_Init();
  /* 编码器初始化及使能编码器模式 */
  ENCODER_TIMx_Init();
	/* 高级控制定时器初始化并配置PWM输出功能 */
  BDCMOTOR_TIMx_Init();
  /* 设定占空比 */
  PWM_Duty = 0;
  SetMotorSpeed(PWM_Duty);  // 0% 占空比 电机一开始是停止的
  /* PID 参数初始化 */
  PID_ParamInit();
  /* 无限循环 */
  while (1)
  {
    /* 启动按钮 */
    if(KEY1_StateRead()==KEY_DOWN)
    {
      StartMotor();
    }
    if(KEY2_StateRead()==KEY_DOWN)
    {
      StopMotor();
    }
    if(KEY3_StateRead()==KEY_DOWN)//加速
    {
      sPID.SetPoint += 2;       // 改变目标值
      if(sPID.SetPoint >= MAX_SPEED) // 限制速度范围 r/m
        sPID.SetPoint = MAX_SPEED;
    }
    if(KEY4_StateRead()==KEY_DOWN)//减速
    {
      sPID.SetPoint -= 2;
      if(sPID.SetPoint <=-MAX_SPEED)
        sPID.SetPoint = -MAX_SPEED;
    }
  }
}

/**
  * 函数功能:启动电机转动
  * 输入参数:无
  * 返 回 值:无
  * 说    明:无
  */
void StartMotor(void)
{
  if(sPID.SetPoint > 0)
  {
    Motor_Dir = MOTOR_DIR_CCW;
    BDDCMOTOR_DIR_CCW();
  }
  else
  {
    Motor_Dir = MOTOR_DIR_CW;
    BDDCMOTOR_DIR_CW();
  }
  Start_flag = 1;
  ENABLE_MOTOR(); // 使能电机
}
/**
  * 函数功能:停止电机转动
  * 输入参数:无
  * 返 回 值:无
  * 说    明:无
  */
void StopMotor(void)
{
  SHUTDOWN_MOTOR();
  HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);         // 停止输出
  /* 复位数据 */ 
  Start_flag = 0;
  PWM_Duty = 0;
  sPID.PrevError  = 0;
  sPID.LastError = 0;
  SetMotorSpeed(PWM_Duty);
}
/**
  * 函数功能: 系统滴答定时器中断回调函数
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 每发生一次滴答定时器中断进入该回调函数一次
  */
void HAL_SYSTICK_Callback(void)
{
  __IO int32_t Spd_Pulse = 0;       // 编码器捕获值 Pulse
  
  __IO int32_t Spd_PPS   = 0;       // 速度值 Pulse/Sample
  __IO float Spd_RPM     = 0;       // 速度值 r/m
  __IO int32_t FB_Speed  = 0;       // 用于反馈速度值到上位机 

  /* 速度环周期100ms */
  if(uwTick % 100 == 0)
  {
    Spd_Pulse = (OverflowCount*CNT_MAX) + (int32_t)__HAL_TIM_GET_COUNTER(&htimx_Encoder); // 100ms内编码器的计数值
    Spd_PPS = Spd_Pulse - LastSpd_Pulse; // 编码器增量 脉冲数
    LastSpd_Pulse = Spd_Pulse ; // 保存上一次编码器的计数值

    /* 11线编码器,30减速比,一圈脉冲信号是11*30*4 */
    /* 放大10倍计算r/s,放大60则是计算r/m*/
    Spd_RPM = ((((float)Spd_PPS/(float)PPR)*10.0f)*(float)60); // 100ms内转多少圈   PPR:一圈所需的脉冲数    *10 化成秒  *60 每分钟旋转多少圈

    /* 计算PID结果 */
    if(Start_flag == 1)
    {
      // PWM_Duty += SpdPIDCalc(Spd_RPM);  // Spd_RPM:实际的转速

      PWM_Duty = PWM_Duty + SpdPIDCalc(Spd_RPM);  // 上次占空比 + 增量的占空比 (因为是增量式PID)
      
      /* 判断当前运动方向 */
      if(PWM_Duty < 0)
      {
        Motor_Dir = MOTOR_DIR_CW;
        BDDCMOTOR_DIR_CW();
        /* 限制占空比 */
        if(PWM_Duty < -BDCMOTOR_DUTY_FULL)
          PWM_Duty = -BDCMOTOR_DUTY_FULL;
        /* 直接修改占空比 */
        SetMotorSpeed(-PWM_Duty);
      }
      else
      {
        Motor_Dir = MOTOR_DIR_CCW;
        BDDCMOTOR_DIR_CCW();
        if(PWM_Duty > BDCMOTOR_DUTY_FULL)
          PWM_Duty = BDCMOTOR_DUTY_FULL;
        SetMotorSpeed(PWM_Duty);
      }
    }

    printf("Spd:%d Pulse -- Spd: %.2f r/m \n",Spd_PPS,Spd_RPM);   //编码器增量 脉冲数   增量的占空比(一分钟转多少圈)
  }
}
 
/******************** PID 控制设计 ***************************/
/**
  * 函数功能: PID参数初始化
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void PID_ParamInit()
{
    sPID.LastError = 0;               // Error[-1]
    sPID.PrevError = 0;               // Error[-2]
    sPID.Proportion = SPD_P_DATA; // 比例常数 Proportional Const
    sPID.Integral = SPD_I_DATA;   // 积分常数  Integral Const
    sPID.Derivative = SPD_D_DATA; // 微分常数 Derivative Const
    sPID.SetPoint = TARGET_SPEED;     // 设定目标Desired Value
}
/**
  * 函数名称:速度闭环PID控制设计
  * 输入参数:当前控制量
  * 返 回 值:目标控制量
  * 说    明:无
  */
int32_t SpdPIDCalc(float NextPoint)
{
  float iError,iIncpid;
  iError = (float)sPID.SetPoint - NextPoint; //偏差  = 目标值-当前值

  /* 给恒定的占空比的时候,0.5r/m 的跳动范围是正常的 */
  if((iError<0.5f )&& (iError>-0.5f))   // 如果偏差很小就不改变(很合适)
    iError = 0.0f;

  /*简化的公式 = KP*ek + Ki*ek-1 + Kd*ek-2 */

  iIncpid=(sPID.Proportion * iError)                //E[k]项 (当前误差)
              -(sPID.Integral * sPID.LastError)     //E[k-1]项
              +(sPID.Derivative * sPID.PrevError);  //E[k-2]项

  sPID.PrevError = sPID.LastError;  //把上次误差赋给上上次误差 
  sPID.LastError = iError;          // 当前误差赋给上次误差
  return(ROUND_TO_INT32(iIncpid));  //返回增量值

}

速度环用滴答定时器100ms来计时间。来反馈当前速度。

2.位置环-----位置式PID

让电机维持转多少圈。产生PWM定时器与定时器编码器上个列子都一样。只是main.c不同。

#include "stm32f4xx_hal.h"
#include "key/bsp_key.h"
#include "encoder/bsp_encoder.h"
#include "usart/bsp_usartx.h"
#include "DCMotor/bsp_BDCMotor.h"
#include "led/bsp_led.h"
/* 私有类型定义 --------------------------------------------------------------*/
typedef struct
{
  __IO int32_t  SetPoint;    //设定目标 Desired Value
  __IO float    SumError;    //误差累计
  __IO float    Proportion;  //比例常数 Proportional Const
  __IO float    Integral;    //积分常数 Integral Const
  __IO float    Derivative;  //微分常数 Derivative Const
  __IO int      LastError;   //Error[-1]
}PID_TypeDef;


/*************************************/
// 定义PID相关宏
// 这三个参数设定对电机运行影响非常大
// PID参数跟采样时间息息相关
/*************************************/
#define  SPD_P_DATA      1.025f       // P参数
#define  SPD_I_DATA      0.215f       // I参数
#define  SPD_D_DATA      0.1f         // D参数
#define  TARGET_LOC      6600        // 目标速度 脉冲数:PPR (一圈的脉冲数)  

#define ENCODER         11   // 编码器线数
#define SPEEDRATIO      30   // 电机减速比
#define PPR             (SPEEDRATIO*ENCODER*4) // Pulse/r 每圈可捕获的脉冲数
#define MAX_SPEED       380  // 空载满速380r/m

#define FB_USE_GRAPHIC          // 使用图像曲线作为反馈
/* 私有变量 ------------------------------------------------------------------*/
__IO uint8_t  Start_flag = 0;       // PID 开始标志
int32_t  Motor_Dir = MOTOR_DIR_CW;  // 电机方向
__IO int32_t Loc_Pulse;             // 编码器捕获值 Pulse

/* 扩展变量 ------------------------------------------------------------------ */
extern __IO uint32_t uwTick;

/* PID结构体 */
PID_TypeDef  sPID;               // PID参数结构体
PID_TypeDef  *ptr =  &sPID;
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
void PID_ParamInit(void) ;
int32_t LocPIDCalc(int32_t NextPoint);
void StopMotor(void);
void StartMotor(void);
/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 系统时钟配置
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  __HAL_RCC_PWR_CLK_ENABLE();                                     // 使能PWR时钟

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  // 设置调压器输出电压级别1

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;      // 外部晶振,8MHz
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;                        // 打开HSE
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                    // 打开PLL
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;            // PLL时钟源选择HSE
  RCC_OscInitStruct.PLL.PLLM = 8;                                 // 8分频MHz
  RCC_OscInitStruct.PLL.PLLN = 336;                               // 336倍频
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;                     // 2分频,得到168MHz主时钟
  RCC_OscInitStruct.PLL.PLLQ = 7;                                 // USB/SDIO/随机数产生器等的主PLL分频系数
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;       // 系统时钟:168MHz
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;              // AHB时钟: 168MHz
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;               // APB1时钟:42MHz
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;               // APB2时钟:84MHz
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

  HAL_RCC_EnableCSS();                                            // 使能CSS功能,优先使用外部晶振,内部时钟源为备用

 	// HAL_RCC_GetHCLKFreq()/1000    1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);                 // 配置并启动系统滴答定时器
  /* 系统滴答定时器时钟源 */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* 系统滴答定时器中断优先级配置 */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{
  /* 复位所有外设,初始化Flash接口和系统滴答定时器 */
  HAL_Init();
  /* 配置系统时钟 */
  SystemClock_Config();
  /* 串口初始化 */
  MX_USARTx_Init();
  /* LED初始化 */
  LED_GPIO_Init();
  /* 按键初始化 */
  KEY_GPIO_Init();
  /* 编码器初始化及使能编码器模式 */
  ENCODER_TIMx_Init();
	/* 高级控制定时器初始化并配置PWM输出功能 */
  BDCMOTOR_TIMx_Init();
  /* 设定占空比 */
  PWM_Duty = 0;
  SetMotorSpeed(PWM_Duty);  // 0% 占空比
  /* PID 参数初始化 */
  PID_ParamInit();
  /* 无限循环 */
  while (1)
  {
    /* 启动按钮 */
    if(KEY1_StateRead()==KEY_DOWN)
    {
      StartMotor();
    }
    if(KEY2_StateRead()==KEY_DOWN)
    {
      StopMotor();
    }
    if(KEY3_StateRead()==KEY_DOWN)//加速
    {
      sPID.SetPoint += PPR; // +1 r  加一圈 +1320
    }
    if(KEY4_StateRead()==KEY_DOWN)//减速
    {
      sPID.SetPoint -= PPR; // -1 r  减一圈 -1320
    }
  }
}

/**
  * 函数功能:启动电机转动
  * 输入参数:无
  * 返 回 值:无
  * 说    明:无
  */
void StartMotor(void)
{
  if(sPID.SetPoint > 0)
  {
    Motor_Dir = MOTOR_DIR_CCW;
    BDDCMOTOR_DIR_CCW();
  }
  else
  {
    Motor_Dir = MOTOR_DIR_CW;
    BDDCMOTOR_DIR_CW();
  }
  Start_flag = 1;
  ENABLE_MOTOR();
}
/**
  * 函数功能:停止电机转动
  * 输入参数:无
  * 返 回 值:无
  * 说    明:无
  */
void StopMotor(void)
{
  SHUTDOWN_MOTOR();
  HAL_TIM_PWM_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);
  HAL_TIMEx_PWMN_Stop(&htimx_BDCMOTOR,TIM_CHANNEL_1);         // 停止输出
  /* 复位数据 */ 
  Start_flag = 0;
  PWM_Duty = 0;
  sPID.SumError  = 0;
  sPID.LastError = 0;
  SetMotorSpeed(PWM_Duty);
}
/**
  * 函数功能: 系统滴答定时器中断回调函数
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 每发生一次滴答定时器中断进入该回调函数一次
  */
void HAL_SYSTICK_Callback(void)
{
  int32_t tmpPWM_Duty = 0;

  /* 速度环周期100ms */
  if(uwTick % 100 == 0)
  {
    Loc_Pulse = (OverflowCount*CNT_MAX) + (int32_t)__HAL_TIM_GET_COUNTER(&htimx_Encoder);

    /* 计算PID结果 */
    if(Start_flag == 1)
    {
      PWM_Duty = LocPIDCalc(Loc_Pulse);  // 位置式PID

      if(PWM_Duty >= BDCMOTOR_DUTY_FULL/2)  // 2分之1
        PWM_Duty = BDCMOTOR_DUTY_FULL/2;
      if(PWM_Duty <= -BDCMOTOR_DUTY_FULL/2)
        PWM_Duty = -BDCMOTOR_DUTY_FULL/2;

      /* 判断当前运动方向 */
      if(PWM_Duty < 0)
      {
        Motor_Dir = MOTOR_DIR_CW;
        BDDCMOTOR_DIR_CW();
        tmpPWM_Duty = -PWM_Duty;
      }
      else
      {
        Motor_Dir = MOTOR_DIR_CCW;
        BDDCMOTOR_DIR_CCW();
        tmpPWM_Duty = PWM_Duty;
      }
      /* 输出PWM */
      SetMotorSpeed( tmpPWM_Duty );
    }

    printf("Target:%d / Loc: %d (Pulse) = %.2f (r) \n",sPID.SetPoint,
             Loc_Pulse, (float)Loc_Pulse/(float)PPR); // 目标值  反馈值 控制旋转多少圈
  }
}

/******************** PID 控制设计 ***************************/
/**
  * 函数功能: PID参数初始化
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void PID_ParamInit()
{
    sPID.LastError = 0;               // Error[-1]
    sPID.SumError = 0;               // 累积误差
    sPID.Proportion = SPD_P_DATA; // 比例常数 Proportional Const
    sPID.Integral = SPD_I_DATA;   // 积分常数  Integral Const
    sPID.Derivative = SPD_D_DATA; // 微分常数 Derivative Const
    sPID.SetPoint = TARGET_LOC;     // 设定目标Desired Value
}
/**
  * 函数名称:位置闭环PID控制设计
  * 输入参数:当前控制量
  * 返 回 值:目标控制量
  * 说    明:无
  */
int32_t LocPIDCalc(int32_t NextPoint)
{
  int32_t iError,dError;
  iError = sPID.SetPoint - NextPoint; //偏差

  if( (iError<50) && (iError>-50) ) // 偏差50个脉冲就忽略
    iError = 0;

  /* 限定积分区域 */
  if((iError<400 )&& (iError>-400))
  {
    
    sPID.SumError += iError; //积分
    /* 设定积分上限 */
    if(sPID.SumError >= (TARGET_LOC*10))
       sPID.SumError  = (TARGET_LOC*10);
    if(sPID.SumError <= -(TARGET_LOC*10))
      sPID.SumError = -(TARGET_LOC*10);
  }

  dError = iError - sPID.LastError; //微分
  sPID.LastError = iError;
  return (int32_t)( (sPID.Proportion * (float)iError) //比例项
                    + (sPID.Integral * (float)sPID.SumError) //积分项
                    + (sPID.Derivative * (float)dError) ); //微分项
}


总结:位置式PID与增量式PID都是根据公式来写的。怎么快速稳定达到目标值。需要调节KP,Ki,Kd。调节有不同的方法。

  • 5
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值