历史上的今天
今天是:2024年10月05日(星期六)
2018年10月05日 | STM32上实现增量式PID
2018-10-05 来源:eefocus
目录:
一、什么是增量式PID
二、硬件部分
三、软件部分
---------------------------------------------------------------------------------------------------------------
一、什么是增量式PID
虽然PID不是什么牛逼的东西,但是真心希望以后刚刚接触这块的人能尽快进入状态。特地分享一些自己如何实现的过程。PID控制算法介绍与C程序实现
首先说说增量式PID的公式,这个关系到MCU算法公式的书写,实际上两个公式的写法是同一个公式变换来的,不同的是系数的差异。http://bbs.elecfans.com/jishu_484125_1_1.html
资料上比较多的是:

还有一种的算法是:

这里主要介绍第二种,具体会分析比例、积分、微分三个环节的作用。
---------------------------------------------------------------------------------------------------------------
二、硬件部分
控制系统的控制对象是4个空心杯直流电机,电机带光电编码器,可以反馈转速大小的波形。电机驱动模块是普通的L298N模块。芯片:STM32F103ZET6
---------------------------------------------------------------------------------------------------------------
三、软件部分
PWM输出:
TIM3,可以直接输出4路不同占空比的PWM波
PWM捕获:
STM32除TIM6、TIM7其余的都有捕获功能,本处使用TIM1、TIM2、TIM4、TIM5四个定时器捕获四个反馈信号
PID的采样和处理:
使用了基本定时器TIM6,溢出时间就是我的采样周期,理论上T越小效果会越好,这里我取20ms,依据控制对象吧,如果控制水温什么的采样周期会是几秒几分钟什么的。
上面的PWM输出和捕获关于定时器的设置都有例程,我这里是这样的:
TIM3输出四路PWM,在引脚 C 的 GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9输出
四路捕获分别是TIM4、TIM1、TIM2、TIM5 ,对应引脚是: PB7、PE11、PB3、PA1
高级定时器tim1的初始化略不同,它的中断”名称“和通用定时器不同。具体的内容,请大家看一下我分享的代码就明白了。
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
//使能GPIO外设和AFIO复用功能模块时钟使能
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE); //Timer3全映射 GPIOC-> 6,7,8,9 //用于TIM3的CH2输出的PWM通过该LED显示
//设置该引脚为复用输出功能,输出TIM3 CH1 CH2 CH3 CH4 的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; //初始化GPIO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_ResetBits(GPIOC,GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9);
//默认电机使能端状态:不使能
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc;
//设置用来作为TIMx时钟频率除数的预分频值 这里是72分频,那么时钟频率就是1M
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM3, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR1上的预装载寄存器
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR2上的预装载寄存器
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR3上的预装载寄存器
TIM_OC4Init(TIM3, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR4上的预装载寄存器
TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
}
void TIM4_PWMINPUT_INIT(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //中断配置
TIM_ICInitTypeDef TIM4_ICInitStructure; //TIM4 PWM配置结构体
GPIO_InitTypeDef GPIO_InitStructure; //IO口配置结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //Open TIM4 clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //open gpioB clock
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIO 7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM4_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM4_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM4_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM4_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM4_ICInitStructure.TIM_ICFilter = 0x3; //Filter:过滤
TIM_PWMIConfig(TIM4, &TIM4_ICInitStructure); //PWM输入配置
TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2); //选择有效输入端
TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset); //配置为主从复位模式
TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);//启动定时器的被动触发
TIM_ITConfig(TIM4, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中断配置
TIM_ClearITPendingBit(TIM4, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
TIM_Cmd(TIM4, ENABLE);
}
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
duty_TIM4 = TIM_GetCapture1(TIM4); //采集占空比
if (TIM_GetCapture2(TIM4)>600) period_TIM4 = TIM_GetCapture2(TIM4);//简单的处理
CollectFlag_TIM4 = 0;
}
TIM_ClearITPendingBit(TIM4, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}
void TIM1_PWMINPUT_INIT(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //中断配置
TIM_ICInitTypeDef TIM1_ICInitStructure; //PWM配置结构体
GPIO_InitTypeDef GPIO_InitStructure; //IO口配置结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //Open TIM1 clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //open gpioE clock
GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE); //Timer1完全重映射 TIM1_CH2->PE11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //GPIO 11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn; //TIM1捕获中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM1_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM1_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM1_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM1_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM1_ICInitStructure.TIM_ICFilter = 0x03; //Filter:过滤
TIM_PWMIConfig(TIM1, &TIM1_ICInitStructure); //PWM输入配置
TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2); //选择有效输入端
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset); //配置为主从复位模式
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);//启动定时器的被动触发
// TIM_ITConfig(TIM1, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中断配置
TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE); //通道2 捕获中断打开
//TIM_ClearITPendingBit(TIM1, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
TIM_Cmd(TIM1, ENABLE);
}
void TIM1_CC_IRQHandler(void)
{
{
if (TIM_GetITStatus(TIM1, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
duty_TIM1 = TIM_GetCapture1(TIM1); //采集占空比
if (TIM_GetCapture2(TIM1)>600) period_TIM1 = TIM_GetCapture2(TIM1);
CollectFlag_TIM1 = 0;
}
}
TIM_ClearITPendingBit(TIM1, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}
void TIM2_PWMINPUT_INIT(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //中断配置
TIM_ICInitTypeDef TIM2_ICInitStructure; //TIM2 PWM配置结构体
GPIO_InitTypeDef GPIO_InitStructure; //IO口配置结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Open TIM2 clock
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //open gpioB clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
//使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //关闭JTAG
GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); //Timer2完全重映射 TIM2_CH2->PB3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //GPIO 3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //浮空输入 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICFilter = 0x3; //Filter:过滤
TIM_PWMIConfig(TIM2, &TIM2_ICInitStructure); //PWM输入配置
TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2); //选择有效输入端
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); //配置为主从复位模式
TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable);//启动定时器的被动触发
TIM_ITConfig(TIM2, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中断配置
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
{
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
duty_TIM2 = TIM_GetCapture1(TIM2); //采集占空比
if (TIM_GetCapture2(TIM2)>600) period_TIM2 = TIM_GetCapture2(TIM2);
CollectFlag_TIM2 = 0;
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}
void TIM5_PWMINPUT_INIT(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //中断配置
TIM_ICInitTypeDef TIM5_ICInitStructure; //TIM4 PWM配置结构体
GPIO_InitTypeDef GPIO_InitStructure; //IO口配置结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //Open TIM4 clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //open gpioB clock
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //GPIO 1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //浮空输入 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x3; //Filter:过滤
TIM_PWMIConfig(TIM5, &TIM5_ICInitStructure); //PWM输入配置
TIM_SelectInputTrigger(TIM5, TIM_TS_TI2FP2); //选择有效输入端
TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_Reset); //配置为主从复位模式
TIM_SelectMasterSlaveMode(TIM5, TIM_MasterSlaveMode_Enable);//启动定时器的被动触发
TIM_ITConfig(TIM5, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中断配置
TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
TIM_Cmd(TIM5, ENABLE);
}
void TIM5_IRQHandler(void)
{
{
if (TIM_GetITStatus(TIM5, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
{
duty_TIM5 = TIM_GetCapture1(TIM5); //采集占空比
if (TIM_GetCapture2(TIM5)>600) period_TIM5 = TIM_GetCapture2(TIM5);
CollectFlag_TIM5 = 0;
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}
-------------------------------------------------------
主要讲解PID部分
准备部分:先定义PID结构体:
typedef struct
{
int setpoint;//设定目标
int sum_error;//误差累计
float proportion ;//比例常数
float integral ;//积分常数
float derivative;//微分常数
int last_error;//e[-1]
int prev_error;//e[-2]
}PIDtypedef;
在文件中定义几个关键变量:
float Kp = 0.32 ; //比例常数
float Ti = 0.09 ; //积分时间常数
float Td = 0.0028 ; //微分时间常数
#define T 0.02 //采样周期
#define Ki Kp*(T/Ti) // Kp Ki Kd 三个主要参数
#define Kd Kp*(Td/T)
PID.H里面主要的几个函数:
void PIDperiodinit(u16 arr,u16 psc); //PID 采样定时器设定
void incPIDinit(void); //初始化,参数清零清零
int incPIDcalc(PIDtypedef*PIDx,u16 nextpoint); //PID计算
void PID_setpoint(PIDtypedef*PIDx,u16 setvalue); //设定 PID预期值
void PID_set(float pp,float ii,float dd);//设定PID kp ki kd三个参数
void set_speed(float W1,float W2,float W3,float W4);//设定四个电机的目标转速
PID处理过程:
岔开一下:这里我控制的是电机的转速w,实际上电机的反馈波形的频率f、电机转速w、控制信号PWM的占空比a三者是大致线性的正比的关系,这里强调这个的目的是
因为楼主在前期一直搞不懂我控制的转速怎么和TIM4输出的PWM的占空比联系起来,后来想清楚里面的联系之后通过公式把各个系数算出来了。
正题:控制流程是这样的,首先我设定我需要的车速(对应四个轮子的转速),然后PID就是开始响应了,它先采样电机转速,得到偏差值E,带入PID计算公式,得到调整量也就是最终更改了PWM的占空比,不断调节,直到转速在稳态的一个小范围上下浮动。
上面讲到的“得到调整量”就是增量PID的公式:
int incPIDcalc(PIDtypedef *PIDx,u16 nextpoint)
{
int iError,iincpid;
iError=PIDx->setpoint-nextpoint; //当前误差
iincpid= //增量计算
PIDx->proportion*(iError-PIDx->last_error)
+PIDx->integral*iError
+PIDx->derivative*(iError-2*PIDx->last_error+PIDx->prev_error);
PIDx->prev_error=PIDx->last_error; //存储误差,便于下次计算
PIDx->last_error=iError;
return(iincpid) ;
}
void TIM6_IRQHandler(void) // 采样时间到,中断处理函数
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//更新中断
{
frequency1=1000000/period_TIM4 ; //通过捕获的波形的周期算出频率
frequency2=1000000/period_TIM1 ;
frequency3=1000000/period_TIM2 ;
frequency4=1000000/period_TIM5 ;
PID1.sum_error+=(incPIDcalc(&PID1,frequency1)); //计算增量并累加
pwm1=PID1.sum_error*4.6875 ; //pwm1 代表将要输出PWM的占空比
frequency1=0; //清零
period_TIM4=0;
PID2.sum_error+=(incPIDcalc(&PID2,frequency2)); //计算增量并累加 Y=Y+Y'
pwm2=PID2.sum_error*4.6875 ; //将要输出PWM的占空比
frequency2=0;
period_TIM1=0;
PID3.sum_error+=(incPIDcalc(&PID3,frequency3)); //常规PID控制
pwm3=PID3.sum_error*4.6875 ; //将要输出PWM的占空比
frequency3=0;
period_TIM2=0;
PID4.sum_error+=(incPIDcalc(&PID4,frequency4)); //计算增量并累加
pwm4=PID4.sum_error*4.6875 ; //将要输出PWM的占空比
frequency4=0;
period_TIM5=0;
}
TIM_SetCompare(pwm1,pwm2,pwm3,pwm4); //重新设定PWM值
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除中断标志位
}
上面几个代码是PID实现的关键部分
-------------------------------------------------------
整定过程
办法有不少,这里用的是先KP,再TI,再TD,在微调。其他的办法特别是有个尼古拉斯法我发现不适合我这个控制对象。
先Kp,就是消除积分和微分部分的影响,这里我纠结过到底是让Ti 等于一个很大的值让Ki=Kp*(T/Ti)里面的KI接近零,还是直接定义KI=0,TI=0.
然后发现前者没法找到KP使系统震荡的临界值,第二个办法可以得到预期的效果:即KP大了会产生震荡,小了会让系统稳定下来,当然这个时候是有稳态误差的。
随后把积分部分加进去,KI=Kp*(T/Ti)这个公式用起来,并且不断调节TI 。TI太大系统稳定时间比较长。
然后加上Kd =Kp*(Td/T),对于系统响应比较滞后的情况效果好像好一些,我这里的电机反映挺快的,所以Td值很小。
最后就是几个参数调节一下,让波形好看一点。这里的波形实际反映的是采集回来的转速值,用STM32的DAC功能输出和转速对应的电压,用示波器采集的。
最后的波形是这样的:

-------------------------------------------------------
PID.C:
#include"pid.h"
#include"fuzzy.h"
#include"pwm.h"
#include"dac.h"
PIDtypedef PID1; //PID结构体
PIDtypedef PID2;
PIDtypedef PID3;
PIDtypedef PID4;
extern u8 start_flag;
extern u16 pwm1,pwm2,pwm3,pwm4;
void PIDperiodinit(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); //时钟使能
//定时器TIM6初始化
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级1级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
// TIM_Cmd(TIM6, ENABLE); //放在主程序中使能
}
void TIM6_IRQHandler(void) // 采样时间到,中断处理函数
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//更新中断
{
frequency1=1000000/period_TIM4 ; //通过捕获的波形的周期算出频率
frequency2=1000000/period_TIM1 ;
frequency3=1000000/period_TIM2 ;
frequency4=1000000/period_TIM5 ;
PID1.sum_error+=(incPIDcalc(&PID1,frequency1)); //计算增量并累加
pwm1=PID1.sum_error*4.6875 ; //pwm1 代表将要输出PWM的占空比
frequency1=0; //清零
period_TIM4=0;
PID2.sum_error+=(incPIDcalc(&PID2,frequency2)); //计算增量并累加 Y=Y+Y'
pwm2=PID2.sum_error*4.6875 ; //将要输出PWM的占空比
frequency2=0;
period_TIM1=0;
PID3.sum_error+=(incPIDcalc(&PID3,frequency3)); //常规PID控制
pwm3=PID3.sum_error*4.6875 ; //将要输出PWM的占空比
frequency3=0;
period_TIM2=0;
PID4.sum_error+=(incPIDcalc(&PID4,frequency4)); //计算增量并累加
pwm4=PID4.sum_error*4.6875 ; //将要输出PWM的占空比
frequency4=0;
period_TIM5=0;
}
TIM_SetCompare(pwm1,pwm2,pwm3,pwm4); //重新设定PWM值
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除中断标志位
}
void incPIDinit(void)
{
//PID1参数初始化
PID1.sum_error=0;
PID1.last_error=0;
PID1.prev_error=0;
PID1.proportion=0;
PID1.integral=0;
PID1.derivative=0;
PID1.setpoint=0;
//PID2参数初始化
PID2.sum_error=0;
PID2.last_error=0;
PID2.prev_error=0;
PID2.proportion=0;
PID2.integral=0;
PID2.derivative=0;
PID2.setpoint=0;
//PID3参数初始化
PID3.sum_error=0;
PID3.last_error=0;
PID3.prev_error=0;
PID3.proportion=0;
PID3.integral=0;
PID3.derivative=0;
PID3.setpoint=0;
//PID4参数初始化
PID4.sum_error=0;
PID4.last_error=0;
PID4.prev_error=0;
PID4.proportion=0;
PID4.integral=0;
PID4.derivative=0;
PID4.setpoint=0;
}
void PID_setpoint(PIDtypedef*PIDx,u16 setvalue)
{
PIDx->setpoint=setvalue;
}
int incPIDcalc(PIDtypedef *PIDx,u16 nextpoint)
{
int iError,iincpid;
iError=PIDx->setpoint-nextpoint; //当前误差
iincpid= //增量计算
PIDx->proportion*(iError-PIDx->last_error)
+PIDx->integral*iError
+PIDx->derivative*(iError-2*PIDx->last_error+PIDx->prev_error);
PIDx->prev_error=PIDx->last_error; //存储误差,便于下次计算
PIDx->last_error=iError;
return(iincpid) ;
}
void PID_set(float pp,float ii,float dd)
{
PID1.proportion=pp;
PID1.integral=ii;
PID1.derivative=dd;
PID2.proportion=pp;
PID2.integral=ii;
PID2.derivative=dd;
PID3.proportion=pp;
PID3.integral=ii;
PID3.derivative=dd;
PID4.proportion=pp;
PID4.integral=ii;
PID4.derivative=dd;
}
void set_speed(float W1,float W2,float W3,float W4)
{
float temp;
if(W1>0) //判断W 正负,正数处理
{
motor1_out0=0;
motor1_out1=1;
temp=W1*122.23;
PID_setpoint(&PID1,temp);
}
else if(W1==0) //零值
{
motor1_out0=0;
motor1_out1=0;
PID_setpoint(&PID1,0);
}
else //负数处理
{
motor1_out0=1;
motor1_out1=0;
temp=-W1*122.23;
PID_setpoint(&PID1,temp);
}
if(W2>0)
{
motor2_out0=0;
motor2_out1=1;
temp=W2*122.23;
PID_setpoint(&PID2,temp);
}
else if(W2==0)
{
motor2_out0=0;
motor2_out1=0;
PID_setpoint(&PID2,0);
}
else
{
motor2_out0=1;
motor2_out1=0;
temp=-W2*122.23;
PID_setpoint(&PID2,temp);
}
if(W3>0)
{
motor3_out0=0;
motor3_out1=1;
temp=W3*122.23;
PID_setpoint(&PID3,temp);
}
else if(W3==0)
{
motor3_out0=0;
motor3_out1=0;
PID_setpoint(&PID3,0);
}
else
{
motor3_out0=1;
motor3_out1=0;
temp=-W3*122.23;
PID_setpoint(&PID3,temp);
}
if(W4>0)
{
motor4_out0=0;
motor4_out1=1;
temp=W4*122.23;
PID_setpoint(&PID4,temp);
}
else if(W4==0)
{
motor4_out0=0;
motor4_out1=0;
PID_setpoint(&PID4,0);
}
else
{
motor4_out0=1;
motor4_out1=0;
temp=-W4*122.23;
PID_setpoint(&PID4,temp);
}
}
void stop()
{
PID_setpoint(&PID1,0);
PID1.sum_error=0;
PID1.last_error=0;
PID1.prev_error=0;
PID_setpoint(&PID2,0);
PID2.sum_error=0;
PID2.last_error=0;
PID2.prev_error=0;
PID_setpoint(&PID3,0);
PID3.sum_error=0;
PID3.last_error=0;
PID3.prev_error=0;
PID_setpoint(&PID4,0);
PID4.sum_error=0;
PID4.last_error=0;
PID4.prev_error=0;
}
PID.H
#ifndef __PID_H
#define __PID_H
#include "stm32f10x.h"
#include"usart.h"
#include "math.h"
typedef struct
{
int setpoint;//设定目标
int sum_error;//误差累计
float proportion ;//比例常数
float integral ;//积分常数
float derivative;//微分常数
int last_error;//e[-1]
int prev_error;//e[-2]
}PIDtypedef;
extern PIDtypedef PID1;//定义了四个PID处理单元
extern PIDtypedef PID2;
extern PIDtypedef PID3;
extern PIDtypedef PID4;
void PIDperiodinit(u16 arr,u16 psc); //PID 采样定时器设定
void incPIDinit(void); //初始化,参数清零清零
int incPIDcalc(PIDtypedef*PIDx,u16 nextpoint); //PID计算
void PID_setpoint(PIDtypedef*PIDx,u16 setvalue); //设定 PID预期值
void PID_set(float pp,float ii,float dd);//设定PID kp ki kd三个参数
void set_speed(float W1,float W2,float W3,float W4);//设定四个电机的目标转速
#endif
---------------------------------------------------------------------------------------------------------------
上一篇:STM32F4时钟初始化配置
史海拾趣
|
NVE公司产品指导书!与大家分享!美国NVE品牌采用尖端巨磁阻技术研发的超高速数字隔离器资料,相比传统得光耦器件,美国NVE产品在传送波特率、封装体积、使用寿命上都有了阶越性的提高。 2007年诺贝尔物理学奖颁给了GMR技术 ...… 查看全部问答> |
|
我现在想做一个设备记录现场温度的. 正在选择数据存储芯片. 每条数据的格式大概是:年月日时分秒温度。一天大概需要记录300条数据。要求数据要保存30天以上。请大家帮忙。… 查看全部问答> |
|
坛里有没高手,弄zigbee方面啊~ 请教高手,我在使用TI的z-stack SampleApp例子使用组寻址方式,做的一个无线数据收发测试,当大于130ms左右发送数据包就会产生丢包现象~~… 查看全部问答> |
|
程序实现对DCD申请的一片数组内的数据进行冒泡排序 但是在程序内循环截止向存储保存数据时出现了差错 首地址的数值一直不能被保存住,也就是说单步时能看见它的变化,但是在执行下一行时就不行了,数据又变回来了! ;冒泡排序法实现 N EQU 10 &n ...… 查看全部问答> |
|
請問一下各位高手!! 小弟在調用到這隻程序時 就會一直返回下面的錯誤訊息 E_INVALIDARG (0x80070057L) 再找了下文章之後 發現像是SimSetLockingStatus 這類的 這也是特權API 所以想請教一下~~ 有沒有高手可以 ...… 查看全部问答> |
|
大家好,我想从串口3脚上窃电,请问谁有办法?或者告诉我用什么IC可以,市面上一直有这个东西,就是不知道怎么做到的,请各位大侠帮帮忙呀,我知道电流不大,没多大用,但是我很想知道它是怎么做成的。谢谢啦 !!… 查看全部问答> |




