打印
[应用方案]

基于单片机的电机转速PID控制

[复制链接]
2384|29
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
0、引言&实验器材
2020的国庆节一个人呆在实验室有点无聊,翻了翻还未填上的坑,发现对PID的理解一直停留在纸面上,不曾在现实中的系统中实际操作过(倒立摆和小四轴这些DIY入门训练都还没完整的做过,实在不甘心),于是从零搭建实验平台,以便深入理解PID算法,最后便有了此篇博客,仅作为记录本人学习之用,如有错误,还望指出,谢谢。

由于使用的串口助手没有曲线绘制功能,故暂无实际效果图(PID参数效果通过时间计算比较)

以下为本次所使用的材料


使用特权

评论回复
沙发
木木guainv|  楼主 | 2021-7-1 15:26 | 只看该作者
本帖最后由 木木guainv 于 2021-7-1 15:28 编辑

1、PID算法
假设已对闭环控制有了最基本的认识(如无,可参考这篇**),参考下图1可以得知,Setpoint为设定值(你所期望系统能达到的值),设定值和反馈值经过计算后得到当前Error(误差),后Error经过PID算法计算得到U,参考下图2,U经过执行机构(如电机)处理、输出,再通过测量元件(如编码器)得到反馈值,再与设定值进行计算,得到下一轮的误差,如此往复,直到设定值与反馈值一致。(位置型)




由上可知PID算法是一种通过对误差值的计算,控制执行机构达到设定值的一种算法。
其中Kp:比例调节、Ki:积分调节、Kd:微分调节,PID算法的公式如下式1所示





需要注意的是,以下所使用的各个计算公式,均是对PID算法进行离散化处理后得到的。


使用特权

评论回复
板凳
木木guainv|  楼主 | 2021-7-1 15:28 | 只看该作者
1.1、Kp:比例调节

比例调节的计算:Kp*Error
由上式可以看出,在比例调节中,输入与输出的信号成比例关系,但如果仅有P调节,系统将可能出现稳态误差。

关于稳态误差:控制系统的输出响应在过渡过程结束后的变化形态称为稳态。稳态误差为期望的稳态输出量与实际的稳态输出量之差。
比例调节作用:是按比例反应系统的偏差,系统一旦出现了偏差,比例调节立即产生调节作用用以减少偏差。比例作用大,可以加快调节,减少误差,但是过大的比例,使系统的稳定性下降,甚至造成系统的不稳定。


使用特权

评论回复
地板
木木guainv|  楼主 | 2021-7-1 15:28 | 只看该作者
1.2、Ki:积分调节

积分调节的计算:Ki*(Error1+Error2+Error3+…+Error k)
简单来说,积分调节的计算过程就将积分系数*误差的累加值
积分调节是为了对稳态误差进行处理,同时提供系统精度,作用原理和效果可参考上方动图。

1.2.1、积分饱和现象:简单来说可理解为Error过大时,积分调节得到的数值过大,所以需要对最大输出值进行限制,防止出现积分饱和的发生。

1.2.2、为方便积分调节原理的理解,可假设系统出现稳态误差时,Error恒定不变,此时Kp与Kd调节均失效,但Ki并不受稳态误差影响,依然累积误差值,从而使PID调节不陷于稳态误差中。

为了消除稳态误差,在控制器中必须引入“积分项”。积分项对误差取决于时间的积分,随着时间的增加,积分项会增大。这样,即便误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减小,直到等于零。因此,比例+积分(PI)控制器,可以使系统在进入稳态后无稳态误差。积分调节作用:是使系统消除稳态误差,提高无差度。因为有误差,积分调节就进行,直至无差,积分调节停止,积分调节输出一常值。积分作用的强弱取决与积分时间常数Ti, Ti越小,积分作用就越强。反之Ti大则积分作用弱,加入积分调节可使系统稳定性下降,动态响应变慢。积分作用常与另两种调节规律结合,组成PI调节器或PID调节器。


使用特权

评论回复
5
木木guainv|  楼主 | 2021-7-1 15:28 | 只看该作者
1.3、Kd:微分调节

微分调节的计算:Kd*(Erorr2-Erorr1)
其中Erorr1为上次的误差,Erorr2为本次的误差。
微分调节可以理解为对被控对象所产生的“阻尼”,当误差变化过快时,微分调节将出现较大“负值”(与误差变化方向相反值),抑制误差继续上升/下降(拉平曲线,参考动图)。

在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。自动控制系统在克服误差的调节过程中可能会出现振荡甚至失稳。其原因是由于存在有较大惯性组件(环节)或有滞后(delay)组件,具有抑制误差的作用,其变化总是落后于误差的变化。解决的办法是使抑制误差的作用的变化“超前”,即在误差接近零时,抑制误差的作用就应该是零。这就是说,在控制器中仅引入 “比例”项往往是不够的,比例项的作用仅是放大误差的幅值,而目前需要增加的是“微分项”,它能预测误差变化的趋势,这样,具有比例+微分的控制器,就能够提前使抑制误差的控制作用等于零,甚至为负值,从而避免了被控量的严重超调。所以对有较大惯性或滞后的被控对象,比例+微分(PD)控制器能改善系统在调节过程中的动态特性。微分调节作用:微分作用反映系统偏差信号的变化率,具有预见性,能预见偏差变化的趋势,因此能产生超前的控制作用,在偏差还没有形成之前,已被微分调节作用消除。因此,可以改善系统的动态性能。在微分时间选择合适情况下,可以减少超调,减少调节时间。微分作用对噪声干扰有放大作用,因此过强的加微分调节,对系统抗干扰不利。此外,微分反应的是变化率,而当输入没有变化时,微分作用输出为零。微分作用不能单独使用,需要与另外两种调节规律相结合,组成PD或PID控制器。


使用特权

评论回复
6
木木guainv|  楼主 | 2021-7-1 15:30 | 只看该作者
2、运行逻辑


使用特权

评论回复
7
木木guainv|  楼主 | 2021-7-1 15:30 | 只看该作者
2.1、计算转速
单片机通过使用输入捕获功能(上升沿触发),计算得到电机周期T,再通过T=1/f计算当前电机转动频率。
(备注:其中输入捕获采样6次以消除初值可能不稳的情况,由于串口中断优先级高于输入捕获优先级,所以人为将输入捕获设置为最高优先级,防止系统bug)
有关输入N76E003捕获的博文见此

初始化

        P00_Input_Mode;//将P00设置为输入模式
        P00 = 1;
        set_ENF0;//打开0通道噪声滤波

        TIMER2_CAP0_Capture_Mode;//采用CAP0组捕获信道
//        TIMER2_CAP1_Capture_Mode;
//        TIMER2_CAP2_Capture_Mode;
       
//        IC3_P00_CAP0_BothEdge_Capture;//设定P00通过CAP0通道,双边沿触发
//        IC3_P00_CAP0_FallingEdge_Capture;//下降沿触发
        IC3_P00_CAP0_RisingEdge_Capture;//上升沿触发,得到的是周期T=1/f
               
        set_ECAP;//使能输入捕获中断(位于拓展中断中)
        set_TR2;  //定时器2计数使能  

        set_T2DIV1;//16分频,通过PWM计算、测试(10ms)
        set_PCAP;
        set_PCAPH;//设定输入捕获为最高中断优先级



计算频率

        while(1)
        {
                if(i>=6)//由于周期会发生变化,该方**造成一定不等延时
                {
                        //周期转换,单位us---->ms
                        time=10000/(temp[5]/100);//转换为频率
                        Send_Data_To_UART0(time);//测试PID参数速度用if
                        i=0;
                   // PID(Ev,time);
                }
        }



输入捕获中断

void Capture_ISR (void) interrupt 12
{
    clr_CAPF0; //清除CAP0 通道中断标志       
        temp[i]=(C0H*256)+C0L;//将高八位低八位合并
        i++;
        clr_TF2;//清除定时器2溢出标志
}         


使用特权

评论回复
8
木木guainv|  楼主 | 2021-7-1 15:31 | 只看该作者
2.2、数据收发
单片机在收到PC端下发的设定值后,将其放入PID函数中进行计算,后变为PWM占空比的脉宽,通过电机驱动器,调节电机转速。

(备注:为了保证串口中断不被更高优先级的输入捕获打断,所以串口接收中断时会关闭输入捕获,接收完成后再打开输入捕获中断使能。)

初始化

//----------串口1、串口0配置----------------       
        InitialUART0_Timer1(9600);
        //在不配置SM1,SM2寄存器的情况下,默认工作在模式0(半双工)         
        IE = 0x90;//允许总中断中断,串口0中断



串口接收中断

/****************串口0中断服务程序***************************/
void Uart0_test() interrupt 4
{
        if(RI)//串口0接收中断标志(有数据时,硬件置1)
        {
                clr_ECAP;//关闭输入捕获中断
                        RI=0;//软件置0
                        TI=0;//同时打开,需要对TI也置低
               
                        UART0_RX_BUF[UART0_RX_STA]=SBUF-48;//从SBUF缓存中读取接收到的数据
                        UART0_RX_STA++ ;                //计算接收长度
                        //进行数据校验,通过RX_BUF&RX_STA
                        if(UART0_RX_STA==3)
                        {
                                //收到期望值
                                Ev=UART0_RX_BUF[0]*100+UART0_RX_BUF[1]*10+UART0_RX_BUF[2];
                                UART0_RX_STA=0;//清空接收计数器
                                uFlag=1;
                        }
        }
        if(uFlag)//接收完成进入
        {
//                start=1;//测试PID参数速度用
//                PID(Ev,time);
                uFlag=0;//清除接收完成标志位
                set_ECAP;//开启输入捕获中断
        }
}


使用特权

评论回复
9
木木guainv|  楼主 | 2021-7-1 15:32 | 只看该作者
2.3、PID计算&变频
当串口中断接收到新的设定值后,Err=设定值-当前电机频率,后通过PID算法进行计算,得到的值为PWM高电平的时间(脉宽),再放入PWM中,通过电机驱动器控制电机变频,再通过测量与电机轴同轴转动的霍尔编码器所产生的脉冲信号(输入捕获),计算当前频率,再更新Err,同时将前一个Err进行累积和赋值给Err1,再放入PID计算,如此往复。

本程序使用的是位置式PID。




使用特权

评论回复
10
木木guainv|  楼主 | 2021-7-1 15:32 | 只看该作者
初始化PWM

//-----------------------------产生1KHz占空比50%的PWM---------------------------------------------               
        P03_PushPull_Mode;
        PWM5_P03_OUTPUT_ENABLE;//使能PWM5,通过P03引脚输出
        clr_PWMTYP;//边沿对齐模式
        clr_PWMMOD0;//设置为独立输出模式
        clr_PWMMOD1;
        PWM_CLOCK_DIV_8;//8分频模式16MHz/8
        PWMPH = 0x07;//1999
        PWMPL = 0xcf;
       
    set_SFRPAGE;//PWM4 and PWM5 duty seting is in SFP page 1
    PWM5H = 0x03;//999         
    PWM5L = 0xe7;
    clr_SFRPAGE;                                            
    set_LOAD;//载入周期和占空比
    set_PWMRUN;//开始输出PWM


使用特权

评论回复
11
木木guainv|  楼主 | 2021-7-1 15:32 | 只看该作者
PID函数&重装PWM值

#define Kp 3.0
#define Ki 0.9
#define Kd 4.1

int Err=0,Err1=0;//PID过程中使用的算法
uint Ev=100;//初始期望值
int ErrAdd;//存放误差累积
uint PWM_Value;//需要改变的占空比值

//        SHz为设定值,EHz为当前值(time
void PID(uint SHz,uint EHz)
{
        Err1=Err;//获得上次的值
        Err=SHz-EHz;//计算得到当前差值
        if(Err>1||Err<-1)//由于实际系统中存在各类干扰,所以设定阈值为±1,提高系统稳定性
        {
                ErrAdd+=Err;//累积误差值
                //PWM_Value为高电平持续脉宽,初值为1000
                PWM_Value=1000+((Kp*Err)+(Ki*ErrAdd)+(Kd*(Err-Err1)));//PID计算
                //防止积分饱和,由于PWM_Value为无符号整数型,为负值时会大于2000。
                if(PWM_Value>2000)PWM_Value=1999;
                //--------PWM重装初值---------------
                clr_LOAD;//关闭载入之前的值
                set_SFRPAGE;//PWM4 and PWM5 duty seting is in SFP page 1
                PWM_Value-=1;
                PWM5H= PWM_Value/256;//得到高八位
                PWM5L= PWM_Value%256;//得到第八位
                clr_SFRPAGE;   
                set_LOAD;//设定完成,开始载入
        }
}


使用特权

评论回复
12
木木guainv|  楼主 | 2021-7-1 15:33 | 只看该作者
3、程序代码&电路


使用特权

评论回复
13
木木guainv|  楼主 | 2021-7-1 15:34 | 只看该作者
#include "N76E003.h"
#include "Common.h"
#include "Delay.h"
#include "SFR_Macro.h"
#include "Function_define.h"


#define uint unsigned int
#define uchar unsigned char

#define Kp 3.0
#define Ki 0.9
#define Kd 4.1


//#define TH0_INIT 67

uint time;//频率
uint temp[10];//存放输入捕获值
int i=0;

uint PWM_Value;//需要改变的占空比值

uchar UART0_RX_BUF[3];//串口0,串口1数据接收缓冲区
uchar UART0_RX_STA=0;//串口0,串口1接收计数器
uchar uFlag=0;//串口中断指令接收完成标志

int Err=0,Err1=0;//PID过程中使用的算法
uint Ev=100;//期望值
int ErrAdd;//存放误差累积


char start=0;
/*
        SHz为设定值,EHz为当前值(time
*/
void PID(uint SHz,uint EHz)
{
                Err1=Err;//获得上次的值
                Err=SHz-EHz;//计算得到当前差值
        if(Err>1||Err<-1)
//        if(SHz!=EHz)
        {

                ErrAdd+=Err;
                PWM_Value=1000+((Kp*Err)+(Ki*ErrAdd)+(Kd*(Err-Err1)));//PID计算
                if(PWM_Value>2000)PWM_Value=1999;
                //--------PWM重装初值---------------
                clr_LOAD;//关闭载入之前的值
                set_SFRPAGE;//PWM4 and PWM5 duty seting is in SFP page 1
                PWM_Value-=1;
                PWM5H= PWM_Value/256;//得到高八位
                PWM5L= PWM_Value%256;//得到第八位
                clr_SFRPAGE;   
                set_LOAD;//设定完成,开始载入
        }
}


/*
通过输入捕获得到的周期,计算当前频率,采样6次发送中间值
由于串口中断优先级高于输入捕获,所以人为将输入捕获设置为最高优先级,防止系统bug
串口中断中会关闭输入捕获,接收完成后再打开输入捕获
PWM需要在运行中变频。
PID函数是对参数进行设定,使用位置式(直接赋值)
电压10V
*/

void main (void)
{
        Set_All_GPIO_Quasi_Mode;//所有IO设置为双向模式
        P03_PushPull_Mode;
        P00_Input_Mode;//将P00设置为输入模式
        P00 = 1;
        set_ENF0;//打开0通道噪声滤波
       
        TIMER2_CAP0_Capture_Mode;//采用CAP0组捕获信道
//        TIMER2_CAP1_Capture_Mode;
//        TIMER2_CAP2_Capture_Mode;
       
//        IC3_P00_CAP0_BothEdge_Capture;//设定P00通过CAP0通道,双边沿触发
//        IC3_P00_CAP0_FallingEdge_Capture;//下降沿触发
        IC3_P00_CAP0_RisingEdge_Capture;//上升沿触发,得到的是周期T=1/f
               
        set_ECAP;//使能输入捕获中断(位于拓展中断中)
        set_TR2;  //定时器2计数使能  

        set_T2DIV1;//16分频,通过PWM计算、测试(10ms)
        set_PCAP;
        set_PCAPH;//设定输入捕获为最高中断优先级
//                set_EA;
//-----------------------------产生1KHz占空比50%的PWM---------------------------------------------               
        PWM5_P03_OUTPUT_ENABLE;//使能PWM5,通过P03引脚输出
        clr_PWMTYP;//边沿对齐模式
        clr_PWMMOD0;//设置为独立输出模式
        clr_PWMMOD1;
        PWM_CLOCK_DIV_8;//16分频模式
        PWMPH = 0x07;//00
        PWMPL = 0xcf;//0f
       
    set_SFRPAGE;//PWM4 and PWM5 duty seting is in SFP page 1
    PWM5H = 0x03;//00               
    PWM5L = 0xe7;//08
    clr_SFRPAGE;                                            
    set_LOAD;//载入周期和占空比
    set_PWMRUN;//开始输出PWM
//----------串口1、串口0配置----------------       
        InitialUART0_Timer1(9600);
        //在不配置SM1,SM2寄存器的情况下,默认工作在模式0(半双工)         
        IE = 0x90;//允许总中断中断,串口0中断
//---------输入你自己的代码-------------------
        while(1)
        {
                if(i>=6)//由于周期会发生变化,该方**造成一定不等延时
                {
                        //周期转换,单位us---->ms
                        time=10000/(temp[5]/100);//转换为频率
                        if(start)Send_Data_To_UART0(time);//测试PID参数速度用if
                        i=0;
                    PID(Ev,time);
                }
        }
}

/****************串口0中断服务程序*****************/
void Uart0_test() interrupt 4
{
        if(RI)//串口0接收中断标志(有数据时,硬件置1)
        {
                clr_ECAP;//关闭输入捕获中断
                        RI=0;//软件置0
                        TI=0;//同时打开,需要对TI也置低
               
                        UART0_RX_BUF[UART0_RX_STA]=SBUF-48;//从SBUF缓存中读取接收到的数据
                        UART0_RX_STA++ ;                //计算接收长度
                        //进行数据校验,通过RX_BUF&RX_STA
                        if(UART0_RX_STA==3)
                        {
                                //收到期望值
                                Ev=UART0_RX_BUF[0]*100+UART0_RX_BUF[1]*10+UART0_RX_BUF[2];
                                UART0_RX_STA=0;//清空接收计数器
                                uFlag=1;
                        }
        }
       
        if(uFlag)//接收完成进入
        {
                start=1;//测试PID参数速度用
//                PID(Ev,time);
                uFlag=0;//清除接收完成标志位
                set_ECAP;//开启输入捕获中断
        }
}

/****************输入捕获中断服务程序*****************/
void Capture_ISR (void) interrupt 12
{
    clr_CAPF0; //清除CAP0 通道中断标志       
        temp[i]=(C0H*256)+C0L;
        i++;
        clr_TF2;//清除定时器2溢出标志
}


使用特权

评论回复
14
木木guainv|  楼主 | 2021-7-1 15:34 | 只看该作者
4、调参思路






使用特权

评论回复
15
chenjun89| | 2021-7-3 09:02 | 只看该作者
学习了,谢谢楼主分享。

使用特权

评论回复
16
两只袜子| | 2021-7-3 15:42 | 只看该作者
很棒的,学习学习

使用特权

评论回复
17
nomomy| | 2021-7-3 21:28 | 只看该作者
谢谢你共享的资料!!                                 

使用特权

评论回复
18
yeates333| | 2021-7-3 21:28 | 只看该作者
以后多交流交流                  

使用特权

评论回复
19
houjiakai| | 2021-7-3 21:28 | 只看该作者
相当全的资料,很适合初学者                                 

使用特权

评论回复
20
juliestephen| | 2021-7-3 21:28 | 只看该作者
这些资料太全了!!!                 

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

134

主题

3870

帖子

5

粉丝