基于STC12C5A60S2的内嵌A/D和内嵌PWM的PID参数整定

非 Y 不嫁゛ 提交于 2019-12-10 15:55:42

控制对象传递函数:Gp=20/(s(0.1s+1)(0.2s+1)

第一个实验主要是出现等幅震荡的波形(纯比例控制):

#include  <STC12C5A60S2.H>
#include  <absacc.h>
typedef unsigned char u8;       //无符号字符型变量新表示方法定义
typedef unsigned int u16;       //无符号整型变量新表示方法定义
//#define AD0   XBYTE [0xF0FF]    //给定量外部AD通道0的端口地址
//#define AD1   XBYTE [0xF1FF]    //反馈量外部AD通道1的端口地址
//#define DA    XBYTE [0xEFFF]    //外部DA转换数据输入端口地址
#define D_port XBYTE [0x7FFF]   //数码管段码锁存器端口地址
#define B_port XBYTE [0xBFFF]   //数码管位码锁存器端口地址
#define A0_r -9.27                   //实验五的4通道数据
#define Am_r 10.24                   //实验五的4通道数据
#define N0_r 0x20               //实验五的4通道数据
#define Nm_r 0xe0               //实验五的4通道数据
#define A0_m -9.32                   //实验五的5通道数据
#define Am_m 9.91                   //实验五的5通道数据
#define N0_m 0x20               //实验五的5通道数据
#define Nm_m 0xe0               //实验五的5通道数据
#define Any -8.5                    //实验三数据
#define Apy 7.86                    //实验三数据
#define Nny 0x20                //你的实验板的实验三数据
#define Npy 0xe0                //你的实验板的实验三数据
sbit    SW8 = P1^7;             //ON为PID控制,OFF为不做控制,误差标度反变换后直接送DA
bit     new_cycle_flag=0;       //有新采样数据的位变量定义并初始化(1表示有新采样数据)
u8      r_kT,r_kT_L,m_kT,m_kT_L;//内嵌10位AD4通道结果高8位、低2位及显示计算用中间变量、内嵌10位AD5通道结果高8位、低2位及显示计算用中间变量定义定义
u8      status;                 //存放内嵌ADC状态信息变量定义(1是转换结束)
u16     temp;                   //平均滤波计算用中间变量定义
char    Ax;                     //显示用中间变量定义
char    buf[8];                 //显示缓冲区字符型数组定义
float   Ax_r,a1_r,b1_r;         //给定电压及其标度变换的斜率和截距等浮点型变量定义
float   Ax_m,a1_m,b1_m;         //反馈电压及其标度变换的斜率和截距等浮点型变量定义
float   e_kT,e_kT_T;            //当前电压误差、上次电压误差浮点型变量定义(伏特)
float   p_P_kT,p_I_kT,p_D_kT,p_I_kT_T,p_kT;//当前各分量,上次积分分量及总控制量浮点变量定义
float   Kp,Ki,Kd;               //PID参数等浮点型变量定义
float   a2,n0;                  //标度反变换的斜率和截距等浮点型变量定义
u8      M=0,No_ch=0,n;            //扫描显示位计数和AD通道变量定义并初始化
u8 code Stab[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//共阴段码表
u8 code Btab[8]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};           //共阴位码表
float code Kptab[8]={0.6,0.61,0.62,0.63,0.64,0.67,0.70,0.75};//0.61时会出现衰减,0.62时正好等幅振荡
//float code Kptab[8]={0.60,0.61,0.63,0.65,0.66,0.67,0.68,0.69};//换数做,应介于0.61-0.63之间
//float code Kptab[8]={0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8};//换数做,应在0.6-0.7之间
/********标度变换初始化********/
void Scale_init(void)
{ Ax_r=Nm_r,Ax_m=Am_r;
  Ax_r-=N0_r,Ax_m-=A0_r;
  a1_r=Ax_m/Ax_r;               //式(3-6-1)
  b1_r=-a1_r;
  b1_r*=N0_r;
  b1_r+=A0_r;                   //式(3-6-2)
  Ax_r=Nm_m,Ax_m=Am_m;
  Ax_r-=N0_m,Ax_m-=A0_m;
  a1_m=Ax_m/Ax_r;               //式(3-6-1)
  b1_m=-a1_m;
  b1_m*=N0_m;
  b1_m+=A0_m;                   //式(3-6-2)
  Ax_r=Npy,Ax_m=Apy;
  Ax_r-=Nny,Ax_m-=Any;
  a2=Ax_r/Ax_m;                 //式(3-6-9)
  n0=-a2;
  n0*=Any;
  n0+=Nny;                      //式(3-6-10)
}
/*********内嵌ADC初始化*********/ //10位AD高8位在ADC_RES,10位AD低2位在ADC_RESL
void AD_init(void)              //内嵌ADC初始化程序
{ ADC_CONTR|=0x80;              //给内嵌ADC通电
  for(temp=0;temp<1000;temp++); //适当延时
  P1ASF=0x10;                   //选择P1.4引脚作为模拟量输入
  ADC_CONTR=0xc4;               //继续通电 180个时钟 标志清0 不启动 通道4
  for(temp=0;temp<1000;temp++); //适当延时
}
/*************PWM初始化*************/
void  PWM_init(void)               //硬件二阶RC低通Tao=1ms>>0.0463ms=0.5*(1/10.8KHz)
{ CMOD=0x8a;                       //选Fosc/4,fPWM=Fosc/4/256=10.8KHz,禁止中断
  CCON=0x00;                       //00000000B控制,关闭PCA计数,CF\CCF1\CCF0均清0
  CL=0x00;                         //PCA计数器低8位清0
  CH=0x00;                         //PCA计数器高8位清0
  CCAPM0=0x42;                     //8位PWM,允许比较器功能,P1.3做PWMO输出,禁止中断
  PCA_PWM0=0x00;                   //最低2位EPC0H和EPC0L均设置为0
  CCAP0H=0x80;                     //后续PWM为50%的占空比
  CCAP0L=0x80;                     //当前PWM为50%的占空比
  CR=1;                            //允许PCA计数器工作
}
/***********T0T1初始化***********/
void  T0T1_init(void)           //T0、T1初始化程序(11.0592MHz)
{ TMOD=0x21;                    //T0方式1定时、T1方式2定时(8位自动重装)
  SCON=0x40;                    //8位UART波特率可变(2^SMOD*T1溢出率/32)
  PCON=0x80;                    //第8位SMOD=1,波特率加倍
  TH0=0xf3;                     //T0频率=(11059200/12)/(65536-0xf366)=285.679Hz
  TL0=0x66;                     //T0方式1定时周期=1/285.679=0.0035s=3.5ms
  TH1=0xfd;                     //T1溢出率=(11059200/12)/(256-253)=307200Hz
  TL1=0xfd;                     //波特率=2^SMOD*307200/32=19200bps
  EA=1;                         //允许中断
  ES=0;                         //禁止串口中断
  ET0=1;                        //允许T0中断
  TR0=1;                        //启动T0工作
  TR1=1;                        //启动T1工作
}
/*********数字PID控制计算********/
void  PID(void)                 //采用与教材中的式(8-15)相同的PID算法
{ p_P_kT=Kp*e_kT;               //控制量的比例部分计算
  p_I_kT=p_I_kT_T;              //控制量的积分部分计算
  p_I_kT+=Ki*e_kT;              //累加当前积分部分
  p_D_kT=Kd*(e_kT-e_kT_T);      //控制量的微分部分计算
  p_kT=p_P_kT+p_I_kT+p_D_kT;    //教材中的式(8-15)相同
}
/***********DA输出函数***********/
void  DAC(void)                 //
{ p_kT+=n0;                     //标度反变换的加法
  if(p_kT>254.5) CCAP0H = 255 ;     //DA输出的修正值超大处理
  else if(p_kT<0.5) CCAP0H = 0 ;    //DA输出的修正值为负处理
  else CCAP0H = (u8)(p_kT+0.5) ;    //DA输出的修正值适合DA输出的处理,含四舍五入
}
/*******内嵌4通道AD输入*******/
void inputch4(void)
{
    for(temp=0,n=0;n<4;n++)       //采样周期到,进行4次AD转换
    { ADC_CONTR|=0x08;          //启动内嵌AD转换,转换时间16.276微秒
      status=0;                 //设状态为0
      while(status==0)status=ADC_CONTR&0x10;//如果状态还为0,转换还没结束
      ADC_CONTR&=0xe7;          //状态为1,将ADC_FLAG位清0
      r_kT=ADC_RES;             //读取内嵌10位AD结果的高8位
      r_kT_L=ADC_RESL&0x03;     //读取内嵌10位AD结果的低2位
      temp+=((u16)r_kT<<2);     //累加内嵌10位AD结果的高8位
      temp+=(u16)r_kT_L;        //累加内嵌10位AD结果的低2位
    }
    P1ASF=0x20;                   //选择P1.5引脚作为模拟量输入
      ADC_CONTR=0xc5;               //继续通电 180个时钟 标志清0 不启动 通道5
      No_ch=1;                      //改成5通道输入的模拟量的A/D转换的标识
}                                    //4次内嵌AD转换总时间>65.1微秒                          
/*******内嵌5通道AD输入*******/
void inputch5(void)
{
    for(temp=0,n=0;n<4;n++)       //采样周期到,进行4次AD转换
    { ADC_CONTR|=0x08;          //启动内嵌AD转换,转换时间16.276微秒
      status=0;                 //设状态为0
      while(status==0)status=ADC_CONTR&0x10;//如果状态还为0,转换还没结束
      ADC_CONTR&=0xe7;          //状态为1,将ADC_FLAG位清0
      m_kT=ADC_RES;             //读取内嵌10位AD结果的高8位
      m_kT_L=ADC_RESL&0x03;     //读取内嵌10位AD结果的低2位
      temp+=((u16)m_kT<<2);     //累加内嵌10位AD结果的高8位
      temp+=(u16)m_kT_L;        //累加内嵌10位AD结果的低2位            
    }
    P1ASF=0x10;               //重新选择P1.4引脚作为模拟量输入
      ADC_CONTR=0xc4;           //继续通电 180个时钟 标志清0 不启动 通道4
      new_cycle_flag=1;         //置2个通道均有采样数据的标志,4次内嵌AD转换总时间>65.1微秒
}        
/*****位置控制的显示数据更新*****/
void disp_g(void)               //给定量显示缓冲区的显示信息更新
{ Ax=(char)(Ax_r*10);           //加快运算速度,给定量放大10倍取整数处理
  buf[7]=0x00;                  //给定符号位预更新为正
  if(Ax<0)                      //如果给定量为负
  { Ax=-Ax;                     //则取绝对值
    buf[7]=0x40;                //给定量符号位修改为负号段码
  }
  buf[4]=Stab[Ax%10];           //给定电压小数位显示信息送显示缓冲第4位
  buf[5]=Stab[Ax/10%10]|0x80;   //给定电压个位显示信息送显示缓冲第5位
  buf[6]=Stab[Ax/100];          //给定电压十位显示信息送显示缓冲第6位
}
void  disp_f(void)              //反馈量显示缓冲区的显示信息更新
{ Ax=(char)(Ax_m*10);           //加快运算速度,反馈量放大10倍取整数处理
  buf[3]=0x00;                  //反馈符号位预更新为正
  if(Ax<0)                      //如果反馈量是负
  { Ax=-Ax;                     //则取绝对值
    buf[3]=0x40;                //反馈量符号位修改为负号段码
  }
  buf[0]=Stab[Ax%10];           //反馈电压小数位显示信息送显示缓冲第0位
  buf[1]=Stab[Ax/10%10]|0x80;   //反馈电压个位显示信息送显示缓冲第1位
  buf[2]=Stab[Ax/100];          //反馈电压十位显示信息送显示缓冲第2位
}
/********延时函数****************/
void delay()
{
    int i,j;
    for(i=0;i<70;i++)
        for(j=0;j<100;j++);
}
/*************主程序*************/
void  main(void)                
{

  BUS_SPEED=0x37;             
  Kp=Kptab[P1&0x07];            //利用网络标号SW3~SW1设置PID参数Kp
  //Kp=1.2726;          //控制度1.05时,Kp=0.63*Ks=1.2726
  Ki=0.03666;           //控制度1.05时,Ti=0.49*Ts=0.16317,Ki=Kp*T/Ti=0.03665637
  Kd=12.62311;          //控制度1.05时,Td=0.14*Ts=0.04662,Kd=Kp*Td/T=12.6231089
  AD_init();
  PWM_init();
  Scale_init();                 //标度变换初始化
  e_kT_T=0.0,p_I_kT_T=0.0;      //上次误差和上次控制量积分部分必须初始化为0.0
  T0T1_init();                  //T0、T1初始化程序(11.0592MHz)
  //IT0=1;                        //外部中断引脚INT0以下降沿方式触发中断请求
  //EX0=1;                        //允许外部INT0申请中断
  do
  { if(No_ch==0)                //如果标志为0,启动内嵌4通道ad
        {
            inputch4();
            r_kT=(u8)(temp>>4);       //temp是4次10位AD值的总和,8位AD结果N=temp/16
            if((temp&0x0008==1)&&(r_kT<0xff))r_kT+=1;//考虑四舍五入        
        }
    if(No_ch==1)                //如果标志为1,启动内嵌5通道ad
        {
            inputch5();
            m_kT=(u8)(temp>>4);       //temp是4次10位AD值的总和,8位AD结果N=temp/16
            if((temp&0x0008==1)&&(m_kT<0xff))m_kT+=1;//考虑四舍五入
        }
    if(new_cycle_flag==1)       //有新采样数据
    { Ax_r=a1_r*(float)r_kT+b1_r;//给定量线性标度变换算出当前采样值对应的给定模拟电压
      Ax_m=a1_m*(float)m_kT+b1_m;//反馈量线性标度变换算出当前采样值对应的反馈模拟电压
      e_kT=Ax_r-Ax_m;           //计算当前误差电压,单位为伏特
      e_kT*=a2;                 //标度反变换的乘法修正,把电压误差(V)放大得到DA数字误差
      if(SW8==0) PID();         //如果SW8实际设置在ON状态,做PID控制
      else p_kT=Kp*e_kT;        //否则SW8置在OFF状态,仅做P控制
      DAC();                    //做标度反变换的加法修正,即先+n0后控制量8位D/A输出
      TI=0;                     //清除发送结束状态标志
      ES=1;                     //允许串行发送结束中断
      SBUF =r_kT;               //发送报文第1字节,即给定量的A/D值
      e_kT_T=e_kT,p_I_kT_T=p_I_kT;//迭代移位,为下一次控制做准备
      disp_g();                 //给定部分显示缓冲区中段码信息的更新
      disp_f();                 //反馈部分显示缓冲区中段码信息的更新
      new_cycle_flag=0;         //新采样周期标志清0
      delay();                  //延时函数    
    }
  }while(1);                    //固定循环
}
/*********T0中断服务程序*********/
void t0(void) interrupt 1 using 1//3.5ms中断1次,每次均要进行显示处理
{ TH0=0xfb;                     //T0时间常数高字节重装
  TL0=0x80;                     //T0时间常数低字节重装
  if(M==8)                      //10ms到了吗?因为1.25ms*8=10ms
  { 
    No_ch=0;                    //中断标志为0
    M=0;                        //M清0        
  }
  B_port=0xff;                  //关闭显示
  D_port=buf[M];                //对应数码管的段码值送给段码端口U2
  B_port=Btab[M];               //对应数码管的位码值送给位码端口U3
  M++;                          //修改到下一次要显示的数码管
}
/********INT0中断服务程序********/
/*void int0(void) interrupt 0 using 2//每个采样周期中断2次
{ if(No_ch==0)                  //是IN0通道输入的模拟量的A/D转换的标识
  { //r_kT=AD0;                   //读取IN0通道给定量的A/D结果
    inputch4();
    r_kT=(u8)(temp>>4);       //temp是4次10位AD值的总和,8位AD结果N=temp/16
      if((temp&0x0008==1)&&(r_kT<0xff))r_kT+=1;//考虑四舍五入    
    //No_ch=1;                    //改成IN1通道输入的模拟量的A/D转换的标识
    //AD1=0x00;                   //启动AD0809的IN1通道——反馈量A/D
  }
  else                          //否则就是IN1通道输入的模拟量的A/D转换标识
  { 
      inputch4();
    r_kT=(u8)(temp>>4);       //temp是4次10位AD值的总和,8位AD结果N=temp/16
    if((temp&0x0008==1)&&(r_kT<0xff))r_kT+=1;//考虑四舍五入    //m_kT=AD1;                   //读取IN1通道反馈量A/D结果
    //new_cycle_flag=1;           //置2个通道均有采样数据的标志
  }
} */
/********TXD中断服务程序*********/
void  txd(void) interrupt 4 using 3//每个采样周期需发2个字节,第2字节靠串行发送中断完成
{ TI=0;                         //清除发送结束状态标志
  ES=0;                         //发送到报文最后1个字节,禁止发送结束中断
  SBUF=m_kT;                    //发送报文第2字节,即反馈量的A/D值
}

该实验只采用了内嵌的A/D和PWM,所以其他的外部中断的程序都可以删除。特殊说明,程序的实验五和实验三具体指的是:基于内嵌10位数模转换,基于内嵌PWM波输出后低通滤波的等效8位数模转换。

实验的波形:

 

注意:该实验利用上位机时设置截距斜率用的是实验5的4通道和5通道的

  需要用到的软件:上位机和STC-ISP-15(烧录程序)和Keil

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!