标题: 零基础制作平衡小车【连载】9---增量式PID实战(附源码) [打印本页]

作者: 自动化工程师    时间: 2020-12-2 17:19
标题: 零基础制作平衡小车【连载】9---增量式PID实战(附源码)

粘贴过来的代码都乱了,贴个原文链接吧:

https://blog.csdn.net/D_SEngineer?spm=1011.2124.3001.5113

不定时发送福利,欢迎小伙伴们交流学习。


终于到实战阶段了,前面学习的理论部分着实令人乏味呀。今天来看看增量式PID怎么在STM32中实现控制电机恒速运行的。

硬件连接

我用的战舰开发板和配套的屏幕,电机是带编码器的电机,第一章有关于硬件的介绍,这里就不多少了。
1.使用通用定时器3 通道1 PA6引脚输出PWM
2.编码器输入捕获 用的是 通用定时器2 PA0 PA1引脚
3.电机引脚如下图

电机电源正负极接到TB6612驱动的AO1和AO2(或者BO1和BO2,看你驱动器左侧接的是AIN1、AIN2还是BIN1、BIN2了),编码器电源接5V电源,黄绿信号线接单片机PA0和PA1,如果接反系统会出现异常,可以仿真看下接收到的脉冲值是不是很大(本例中的count),很大说明接反了。将线反接即可。另外就是驱动的PWMA(或者PWMB)接到单片机的PA6引脚。驱动器的STBY接到5V电源上,BIN1和BIN2一个接高一个接低即可。
接线很简单,不会的直接问淘宝卖家。其实看了代码只要知道PWM输出引脚和编码器输入引脚就可以接线了,其他都是固定接线方式。下图是我的硬件图,很乱,小车还没组装好,都是散件,凑合看吧。

代码实现的功能代码思路部分重要源码讲解/*使用通用定时器3  通道1  PA6引脚输出PWM------------ PWM信号 周期和占空比的计算 --------------- ARR :自动重装载寄存器的值 CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1) PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M 占空比P=CCR/(ARR+1) 时钟分频71,系统总线时钟72M,可以算出定时器时钟为72/(3+1)=18M在这里我们需要设定PWM频率为20Khz=0.05ms,因此自动重装载的值ARR=900.因为定时器时钟为18M=0.0556us,因此只需要累加900次,就等于0.05ms=20Khz了。因此我们配置如下TIM_TimeBaseInitTypeStructure.TIM_Prescaler = 3;                   //定时器时钟分频TIM_TimeBaseInitTypeStructure.TIM_Period = (900 - 1);                //自动重装载的值 */void PWM_Init(void){    //基本定时器初始化部分        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    TIM_OCInitTypeDef TIM_OCInitStructure;    //初始化定时器    TIM_DeInit(TIM3);        //GPIO初始化部分    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA,&GPIO_InitStructure);        //使能定时器时钟    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //死区时间时钟源,该例程只进行PWM输出,没有用到死区,因此不设定    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //向上计数模式    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    //自动重装载的值设定为1000    TIM_TimeBaseInitStructure.TIM_Period = (900 - 1);    //定时器周期分频数    TIM_TimeBaseInitStructure.TIM_Prescaler = 3;    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;        //初始化定时器3    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);            //PWM初始化部分    //pwm1模式    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    //初始化电平    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    //使能输出比较    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //初始化PWM占空比为0    TIM_OCInitStructure.TIM_Pulse = 0;    //初始化pwm    TIM_OC1Init(TIM3,&TIM_OCInitStructure);        //使能TIM3在CCR2上的预装载寄存器    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);     //使能定时器3    TIM_Cmd(TIM3,ENABLE);    }//输入捕获  通用定时器2  PA0  PA1void Advance_TIM_Init(void){    //GPIO初始化        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;    TIM_ICInitTypeDef  TIM_ICInitStructure;    NVIC_InitTypeDef NVIC_InitStructure;                             // 设置中断来源    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                         // 设置抢占优先级                      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;                   // 设置子优先级    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    NVIC_Init(&NVIC_InitStructure);        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(GPIOA,&GPIO_InitStructure);            //基本定时器初始化部分    //使能定时器时钟    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//设置缺省值    // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断        TIM_TimeBaseStructure.TIM_Period = 60000;                // 驱动CNT计数器的时钟 = Fck_int/(psc+1)        TIM_TimeBaseStructure.TIM_Prescaler = 0;                // 时钟分频因子 ,配置死区时间时需要用到        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;                        // 计数器计数模式,设置为向上计数        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;                        // 重复计数器的值,没用到不用管        TIM_TimeBaseStructure.TIM_RepetitionCounter=0;                // 初始化定时器        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);        //编码器模式    TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);            TIM_ICStructInit(&TIM_ICInitStructure);    TIM_ICInitStructure.TIM_ICFilter = 10;                //10经验值,不必纠结,起滤波作用    TIM_ICInit(TIM2, &TIM_ICInitStructure);            //使能中断    TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);                // 清除中断标志位        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);        // 使能高级控制定时器,计数器开始计数    TIM_Cmd(TIM2, ENABLE);} if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == 0)  {     SetPoint=160;           //设定目标     z=1; } if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0) {     SetPoint=120;           //设定目标     z=2; }  if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == 0) {     SetPoint=20;           //设定目标     z=3; }  if(z != j) {     LCD_Clear(WHITE);       //delay_ms(120); } j = z ;  LCD_Fill(0,SetPoint,lcddev.width,SetPoint+2,BLUE);        //目标值设定,一条直线          //如果FLAG=1时,说明200ms到了,将采样的count值打印在屏幕上,         //k是x轴坐标,200MS+1,conunt是y轴坐标,代表当前时刻的采样的脉冲值 if(flag == 1)    {     k++;     LCD_Fill(k,count,k+2,count+2,BLUE);     flag = 0; }/********************增量式PID控制设计************************************/int IncPIDCalc(int PresentPoint) {    int iError , iIncpid;       //iIncpid增量值   iError当前误差    iError = SetPoint-PresentPoint;                    //增量计算    iIncpid =(Proportion * iError)                  //E[k]项              -(Integral * LastError)     //E[k-1]项              +(Derivative * PrevError);  //E[k-2]项    PrevError = LastError;    LastError=iError;    return (iIncpid);}

是不是很简单,就这么几行代码,但是我却写了好几篇帖子来学习它的理论部分。过来之后就能看清庐山真面目了。

根据前面推导的公式,这里只是将这些公式用C写出来。首先定义一个当前误差iError和一个计算出来的增量值iIncpid。当前误差iError=目标值SetPoint-传感器采集的当前值PresentPoint,之后进行增量式PID运算,得出增量值iIncpid。

将式子和程序对比下,是不是一样的?再看看还是不一样。上面式子中没有减号,代码中却有;上面式子中明明微分项是𝐄𝐤−𝟐𝐄(𝐤−𝟏)+𝐄(𝐤−𝟐),即便将Kp*Td/T看成Kd也还是和程序不一样呀。程序中确直接乘上一个上上次误差PrevError,你说一样吗?你心中有没有这样的疑问?有的话也不奇怪,我朋友看了也有(斜眼笑)。

解惑

看完上面的推导,应该能解决你心中的疑惑了吧。

源码

昨天开通了一个公众号,以后代码就放到公众号了,后续我会将之前的代码也都放到公众号上,欢迎小伙伴们交流学习哈。关注公众号回复
“增量式PID”
获取源码。

调试过程

本来想写在这个帖子上的,不过一看时间有点晚了,明天再开一贴吧,明天还有工作呢,不熬夜,早睡早起。晚安,小伙伴们。







欢迎光临 (http://www.51hei.com/bbs/) Powered by Discuz! X3.1