找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2225|回复: 0
收起左侧

零基础制作平衡小车【连载】9---增量式PID实战(附源码)

[复制链接]
ID:223481 发表于 2020-12-2 17:19 | 显示全部楼层 |阅读模式

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

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输出引脚和编码器输入引脚就可以接线了,其他都是固定接线方式。下图是我的硬件图,很乱,小车还没组装好,都是散件,凑合看吧。

代码实现的功能
  • 按键设定目标转速
  • stm32通过pwm控制电机维持目标转速运转
  • 并且具备显示功能,把重要参数显示在屏幕上
代码思路
  • 要先把PWM调通,实现PWM控制电机旋转
  • 在实现PWM控制电机旋转之后,加上按键调节占空比
  • 将编码器检测加进去,能实现检测到电机反馈的脉冲数
  • 将编码器检测的脉冲数显示出来,或者串口打印出来,总之要实时知道这个值,方便后面调试PID参数
  • 之后再将PID加进去
  • 整理代码
部分重要源码讲解
  • PWM
    PWM的生成之前章节已经讲解过了,在这里就不复述了,直接上代码。在这里需要注意一点就是之前PWM设置的是1Khz,这里我们设置成20Khz。大家可以看看注释,理解一下怎么设置成不同的频率。
/*使用通用定时器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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 编码器输入
    不懂的可以仔细看看代码注释,注释也看不懂的,可以翻翻我之前的帖子,代码如下:
//输入捕获  通用定时器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);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 按键和屏幕显示
    按键很简单,我注释的也很详细,这是放在主函数中循环检测的,这个也不用多说了吧,直接代码:
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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 增量式PID
    重点来了,学了这么多理论,你会发现,其实代码就一点点,两三行就完事了。先看代码:
/********************增量式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);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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

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

解惑

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

源码

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

调试过程

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


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表