PID算法
时间:2022-10-27 05:30:00
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%,效果不错。 如果现在给电机增加一些负载,原本占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。调节有不同的方法。