在无人直接参与下可使生产过程按预定程序进行的控制系统。
一个控制系统包括控制器、传感器、变送器、执行机构、输入输出接口。 具体过程是:控制器的输出经过输出接口、执行机构,加到被控系统上;控制系统的被控量,经过传感器、变送器,通过输入接口送到控制器。
自动控制系统可分为开环控制系统和闭环控制系统。
开环控制系统(open-loop control system) :输出对于输入没有影响。在开环控制系统中,系统输出只受输入的控制,控制精度和抑制干扰的特性都比较差。开环控制系统中,基于按时序进行逻辑控制的称为顺序控制系统;由顺序控制装置、检测元件、执行机构和被控工业对象所组成。
闭环控制系统(closed-loop control system): 输出会通过反馈电路返送回来影响输入,形成一个或多个闭环的控制系统。把控制系统输出量的一部分或全部,通过一定方法和装置反送回系统的输入端,然后将反馈信息与原输入信息进行比较,再将比较的结果施加于系统进行控制,避免系统偏离预定目标。闭环控制系统利用的是负反馈。
将偏差的比例(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算法要实现:稳、准、快。
比例控制是一种最简单的控制方式。其控制器的输出与输入信号误差成比例关系。当仅有比例控制时系统输出存在稳态误差(Steady-state error),假设输入误差为0,那么输出也为0,显然是不行的。 比例控制考虑当前误差,误差值和一个正值的常数Kp(比例系数)相乘。Kp只是在控制器的输出和误差成比例的时候成立。注意在误差是0的时候,控制器的输出也是0。 比例控制的输出如下:
若比例增益大,在相同误差量下,会有较大的输出,但若比例增益太大,会使系统不稳定。 相反的,若比例增益小,若在相同误差量下,其输出较小,因此控制器会较不敏感的。若比例增益太小,当有干扰出现时,其控制信号可能不够大,无法修正干扰的影响。
比列运算是存在稳态误差(在比列控制中,经过一定时间后误差稳定在一定值),稳态误差和比例增益(Kp)成正比,和受控系统本身的增益(Pout)成反比。通过调节KP来达到目标值。
下面是不同KP的曲线:
怎么消除稳态误差?
1.加入偏置(在Pout = Kp e(t)后面加一个偏置值。 2.引入积分控制
在积分控制中,控制器的输出与输入误差信号的积分成正比关系。对一个自动控制系统,如果在进入稳态后存在稳态误差,则称这个控制系统是有稳态误差的或简称有差系统(System with Steady-state Error)。为了消除稳态误差,在控制器中必须引入“积分项”。积分项对误差取决于时间的积分,随着时间的增加,积分项会增大。这样,即便误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减小,直到等于零。因此,比例+积分(PI)控制器,可以使系统在进入稳态后无稳态误差。
积分控制考虑过去误差,将过去一段时间误差值和(误差和)乘以一个正值的常数Ki。Ki从过去的平均误差值来找到系统的输出结果和预定值的平均误差。一个简单的比例系统会震荡,会在预定值的附近来回变化,因为系统无法消除多余的纠正。通过加上累计的平均误差值,平均系统误差值就会渐渐减少。所以,最终这个PID回路系统会在设定值稳定下来。积分控制的输出如下:
积分控制会加速系统趋近设定值的过程,并且消除纯比例控制器会出现的稳态误差。 积分增益越大,趋近设定值的速度越快,不过因为积分控制会累计过去所有的误差,可能会使输出出现过冲的情形。
在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。
在控制器中仅引入“比例”项往往是不够的,比例项的作用仅是放大误差的幅值,而目前需要增加的是“微分项”,它能预测误差变化的趋势。在误差没出现之前就抑制住。微分考虑的是未来的误差。这样,具有比例+微分的控制器,就能够提前使抑制误差的控制作用等于零,甚至为负值,从而避免了被控量的严重超调。所以对有较大惯性或滞后的被控对象,比例+微分(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参数的整定到达目标值的快速方法。直流有刷电机的位置(控制转多少圈),速度(每分钟转多少圈或每秒转多少圈)。
有刷电机带有编码器,需要一个定时器做编码器模式。用一个高级定时器产生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来计时间。来反馈当前速度。
让电机维持转多少圈。产生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。调节有不同的方法。