550 likes | 705 Views
第 7 章 单片机常用算法设计. 7.1 单片机滤波算法的设计 7.2 信号处理的 FFT 变换 7.3 SPWM 正弦逆变算法的设计 7.4 PID 控制算法 7.5 51 单片机 PID 算法程序 7.6 模糊控制算法. 7.1 单片机滤波算法的设计. 电路的滤波分为 模拟滤波 与 数字滤波 。其中数字滤波器具有 精度高、高可靠性和高稳定性 的特点 , 因此被广泛应用。用数字滤波算法克服随机误差主要有如下 优点 : 数字滤波由软件程序实现 , 不需要硬件 , 因此 不存在阻抗匹配的问题 ;
E N D
第7章单片机常用算法设计 7.1 单片机滤波算法的设计 7.2 信号处理的FFT变换 7.3 SPWM正弦逆变算法的设计 7.4 PID控制算法 7.5 51单片机PID算法程序 7.6 模糊控制算法
7.1 单片机滤波算法的设计 • 电路的滤波分为模拟滤波与数字滤波。其中数字滤波器具有精度高、高可靠性和高稳定性的特点,因此被广泛应用。用数字滤波算法克服随机误差主要有如下优点: • 数字滤波由软件程序实现,不需要硬件,因此 不存在阻抗匹配的问题; • 对于多路信号输入通道,可以共用一个软件“滤波器”,降低仪表的设计成本; • 只要改变滤波器程序或元算参数,就能方便的改变滤波特性。
下面我们介绍几种主要的数字滤波法: A.限幅滤波法 • 对于随机干扰, 限幅滤波是一种有效的方法; • 基本方法:比较相邻n 和n - 1时刻的两个采样值y(n)和y(n – 1),根据经验确定两次采样允许的最大偏差。如果两次采样值的差值超过最大偏差范围,认为发生可随机干扰,并认为后一次采样值y(n)为非法值,应予删除,删除y(n)后,可用y(n – 1) 代替y(n);若未超过所允许的最大偏差范围,则认为本次采样值有效。
下面是限幅滤波程序:( A值可根据实际情况调整,value 为有效值,new_value为当前采样值滤波程序返回有效的实际值) #define A 10 char value; char filter() { char new_value; new_value = get_ad(); if ( ( new_value - value > A ) || ( value - new_value > A )) return value; return new_value; }
B.中位值滤波法 • 中位值滤波法能有效克服偶然因素引起的波动或采样不稳定引起的误码等脉冲干扰; • 对温度 液位等缓慢变化的被测参数用此法能收到良好的滤波效果,但是对于流量压力等快速变化的参数一般不宜采用中位值滤波法; • 基本方法:对某一被测参数连续采样n次(一般n 取奇数) ,然后再把采样值按大小排列,取中间值为本次采样值。
下面是中位值滤波程序: #define N 11 char filter() { char value_buf[N], count,i,j,temp; for ( count=0;count<N;count++) { value_buf[count] = get_ad(); delay(); } for (j=0;j<N-1;j++) { for (i=0;i<N-j;i++) { if ( value_buf[i]>value_buf[i+1] ) {temp = value_buf[i]; value_buf[i] = value_buf[i+1]; value_buf[i+1] = temp; } } } return value_buf[(N-1)/2]; }
C.算术平均滤波法 • 算术平均滤波法适用于对一般的具有随机干扰的信号进行滤波。这种信号的特点是信号本身在某一数值范围附近上下波动,如测量流量、 液位; • 基本方法:按输入的N个采样数据 ,寻找这样一个Y ,使得Y与各个采样值之间的偏差的平方和最小。
编写算术平均滤波法程序时严格注意: 一.为了加快数据测量的速度,可采用先测量数据存放在存储器中,测完N 点后,再对N 个数据进行平均值计算; 二.选取适当的数据格式,也就是说采用定点数还是采用浮点数。其程序如下所示: #define N 12 char filter() {int sum = 0,count; for ( count=0;count<N;count++) { sum+=get_ad(); delay();} return (char)(sum/N); }
D.递推平均滤波法 • 基本方法:采用队列作为测量数据存储器, 设队列的长度为N ,每进行一次测量,把测量结果放于队尾,而扔掉原来队首的一个数据,这样在队列中始终就有N 个 “最新” 的数据。当计算平均值时,只要把队列中的N个数据进行算数平均,就可得到新的算数平均值。这样每进行一次测量,就可得到一个新的算术平均值。
程序如下: #define N 12 char value_buf[N],i=0; char filter() { char count; int sum=0; value_buf[i++] = get_ad(); if ( i == N ) i = 0; for ( count=0;count<N;count++) sum = value_buf[count]; return (char)(sum/N); }
E.一阶滞后滤波法 • 优点:对周期性干扰具有良好的抑制作用,适用于波动频率较高的场合; • 缺点:相位滞后,灵敏度低.滞后程度取决于a值大小.不能消除滤波频率高于采样频率的1/2的干扰信号。程序如下: #define a 50 char value; char filter() { char new_value; new_value = get_ad(); return (100-a)*value + a*new_value; }
7.2 信号处理的FFT变换 快速傅里叶变换(Fast Fourier Transfonn,FFT)是为了减少离散傅里叶变换(Discrete Fourier Transform,DFT)计算次数的一种快速有效的算法。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。
FFT变换算法的基本思想:利用WN的周期性和对称性,把一个N项序列(设N=2k,k为正整数),分为两个N/2项的子序列,每个N/2点DFT变换需要(N/2)^2次运算,再用N次运算把两个N/2点的DFT变换组合成一个N点的DFT变换。这样变换以后,总的运算次数就变成N+2(N/2)2=N+N^2/2。其程序片段如下所示:FFT变换算法的基本思想:利用WN的周期性和对称性,把一个N项序列(设N=2k,k为正整数),分为两个N/2项的子序列,每个N/2点DFT变换需要(N/2)^2次运算,再用N次运算把两个N/2点的DFT变换组合成一个N点的DFT变换。这样变换以后,总的运算次数就变成N+2(N/2)2=N+N^2/2。其程序片段如下所示:
#include <stdio.h>#include <stdlib.h>#include <math.h> typedef struct{ double r; double i;}my_complex; //检查a是否为2的整数次方数#define NOT2POW(a) (((a)-1)&(a)||(a)<=0) #define MYPI 3.14159265358979323846//pi my_complex* fft(const my_complex* x, unsigned int len){ unsigned int ex=0,t=len; unsigned int i,j,k; my_complex *y; double tr,ti,rr,ri,yr,yi;
if(NOT2POW(len)) return NULL; //如果失败,返回空指针 for(;!(t&1);t>>=1) ex++; //len应该等于2的ex次方 y=(my_complex*)malloc(len*sizeof(my_complex)); if(!y) return NULL; //变址计算,库里-图基算法 for(i=0;i<len;i++){k=i; j=0; =ex; while((t--)>0){ j<<=1; j|=k&1; k>>=1; } if(j>=i){ y[i]=x[j]; y[j]=x[i]; } } //用变址后的y向量进行计算 for(i=0;i<ex;i++){t=1<<i; for(j=0;j<len;j+=t<<1){ for(k=0;k<t;k++) {ti=-MYPI*k/t; rr=cos(ti); ri=sin(ti); tr=y[j+k+t].r; ti=y[j+k+t].i; yr=rr*tr-ri*ti; yi=rr*ti+ri*tr; tr=y[j+k].r; ti=y[j+k].i;
y[j+k].r=tr+yr; y[j+k].i=ti+yi; y[j+k+t].r=tr-yr; y[j+k+t].i=ti-yi;}}} return y;}//以下为测试int main(){int i,DATA_LEN; my_complex *x,*y; printf("基二FFT测试\n输入生成序列长度:"); scanf("%d",&DATA_LEN); x=(my_complex*)malloc(DATA_LEN*sizeof(my_complex)); for(i=0;i<DATA_LEN;i++){x[i].r=i; x[i].i=i-1; } printf("处理前...\n实部\t\t虚部\n"); for(i=0;i<DATA_LEN;i++) printf("%lf\t%lf\n",x[i].r,x[i].i); y=fft(x,DATA_LEN);if(!y){ printf("序列长度不为2的整数次方!\n"); return 0; }
printf("处理后...\n实部\t\t虚部\n");for(i=0;i<DATA_LEN;i++) printf("%lf\t%lf\n",y[i].r,y[i].i);free(y); free(x); return 0; }
7.3 SPWM正弦逆变算法的设计 • PWM的全称是Pulse Width Modulation(脉冲宽度调制),它是通过改变输出方波的占空比来改变等效的输出电压,广泛地用于电动机调速和阀门控制; • SPWM是在PWM的基础上改变了调制脉冲方式,脉冲宽度时间占空比按正弦规率排列,这样输出波形经过适当的滤波可以做到正弦波输出,它广泛地用于直流交流逆变器等; • SPWM理论基础:冲量相等而形状不同的窄脉冲加在具有惯性的环节上时, 其效果基本相同。
实现SPWM法的几种方案: A.等面积法 • 该方案是用同样数量等幅而不等宽的矩形脉冲序列代替正弦波,然后计算各脉冲的宽度和间隔,并把这些数据存于微机中,通过查表的方式生成PWM信号控制开关器件的通断,以达到预期的目的; • 优点:可准确计算出各开关器件的通断时刻,所得的波形很接近正弦波; • 缺点:计算繁琐,数据占用内存大,不能实时控制。
用面积法实现SPWM正弦波逆变换的程序片段: void CalcSpwmWithArea(float32 a/*调制比*/,Uint16 w_Hz/*调制频率*/,Uint32 z_Hz/*载波频率*/) { //Uint16 tmp_PR; //T1周期值 volatile Uint16 i,n,*p; float32 m,n1,n2; m = z_Hz/w_Hz ; //求出载波比 g_SPWM_Table.SpwmSize =(Uint16)m; //tmp_PR = g_T1_Clk /(2*z_Hz); //计算出其周期值 p=g_SPWM_Table.p_HeadTable; //得到数据表头指针 n=m; m/=2; //除去一半 计算半波 n1=(float32)g_T1_Clk/(8.0*m*w_Hz); // 计算首相 n2=(float32)g_T2_Clk/(8.0*PI*w_Hz)*a; for(i=0;i<n;i++) {*p=n1-n2*(cos(i*PI/m)-cos((i+1)*PI/m)); p++;} }
B.硬件调制法 • 方案原理:把所希望的波形作为调制信号, 把接受调制的信号作为载波,通过对载波的调制得到所期望的PWM波形.用等腰三角波作为载波,当调制信号波为正弦波时,所得到的就是SPWM波形; • 优点:实现方法简单,可以解决等面积法计算繁琐的缺点; • 缺点:模拟电路结构复杂,难以实现精确的控制。
C.自然采样法 • 方案原理:以正弦波为调制波,等腰三角波为载波进行比较,在两个波形的自然交点时刻控制开关器件的通断; • 优点:所得SPWM波形最接近正弦波; • 缺点:脉宽表达式是一个超越方程,计算繁琐,难以实时控制。
D.规则采样法 • 方案原理:用三角波对正弦波进行采样得到阶梯波,再以阶梯波与三角波的交点时刻控制开关器件的通断,从而实现SPWM法;按照三角波在其顶点或底点的位置不同又分为对称规则采样和非对称规则采样; • 优点:计算简单,便于在线实时运算,其中非对称规则采样法因阶数多而更接近正弦; • 缺点:直流电压利用率较低,线性控制范围较小。
用对称规则采样实现SPWM正弦波逆变换的程序片段:用对称规则采样实现SPWM正弦波逆变换的程序片段: void CalcSpwmWithSym(float32 a/*调制比*/,float32 w_Hz/*调制频率*/,float32 z_Hz/*载波频率*/) {Uint16 tmp_PR; //T1周期值 volatile Uint16 i,n,*p; float32 m; m = z_Hz/w_Hz ; //求出载波比 g_SPWM_Table.SpwmSize =(Uint16)m; tmp_PR = g_T1_Clk /(2*z_Hz); //计算出其周期值 p=g_SPWM_Table.p_HeadTable; //得到数据表头指针 for(i=0;i<(Uint16)m;i++) {n=tmp_PR*(0.5-0.5*a*sin((i+0.75)*2*PI/m)); *p=n; p++;} }
用非对称规则采样实现SPWM正弦波逆变换的程序片段:用非对称规则采样实现SPWM正弦波逆变换的程序片段: void CalcSpwmWithImSym(float32 a/*调制比*/,Uint16 w_Hz/*调制频率*/,Uint32 z_Hz/*载波频率*/) {Uint16 tmp_PR; //T1周期值 volatile Uint16 i,n,*p; float32 m; m = z_Hz/w_Hz ; //求出载波比 g_SPWM_Table.SpwmSize =(Uint16)m; tmp_PR = g_T1_Clk /(2*z_Hz); //计算出其周期值 p=g_SPWM_Table.p_HeadTable; //得到数据表头指针 for(i=0;i<(Uint16)m;i++) {n=tmp_PR*(0.5-0.25*a*(sin((i+0.25)*2*PI/m)+sin((i+0.75)*2*PI/m))); *p=n; p++;} }
E.梯形波与三角波比较法 • 方案原理:采用梯形波作为调制信号,三角波为载波,且使两波幅值相等,以两波的交点时刻控制开关器件的通断实现PWM控制; • 优点:可以有效地提高直流电压利用率; • 缺点:输出波形中含有5次,7次等低次谐波。
7.4 PID控制算法 • 在过程控制中,按偏差的比例(P)、积分(I)和微分(D)进行控制的PID控制器(亦称PID调节器)是应用最为广泛的一种自动控制器; • 对于过程控制的典型对象──“一阶滞后+纯滞后”与“二阶滞后+纯滞后”的控制对象,PID控制器是一种最优控制; • PID调节规律是连续系统动态品质校正的一种有效方法,它的参数整定方式简便,结构改变灵活(PI、PD、…)。
一 模拟PID调节器 模拟PID控制系统原理框图
PID调节器各校正环节的作用: • 比例环节:即时成比例地反应控制系统的偏差信号e(t),偏差一旦产生,调节器立即产生控制作用以减小偏差; • 积分环节:主要用于消除静差,提高系统的无差度。积分时间常数TI越大,积分作用越弱,反之则越强; • 微分环节:能反应偏差信号的变化趋势(变化速率),并能在偏差信号的值变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减小调节时间。
PID调节器是一种线性调节器,它将给定值r(t)与实际输出值c(t)的偏差的比例(P)、积分(I)、微分(D)通过线性组合构成控制量,对控制对象进行控制。 • PID调节器的微分方程 【其中 】 • PID调节器的传递函数
二 数字PID控制器 • 模拟PID控制规律的离散化形式
数字PID控制器的差分方程: 式中:,为比例项; ,为积分项; ,为微分项。
常用的控制方式: ①P控制 ② PI控制 ③ PD控制 ④ PID控制
PID算法的两种类型 ① 位置型控制 ② 增量型控制
三 PID算法的程序流程 • 增量型PID算法的程序流程 算式: 式中:
位置型PID算法的程序流程 递推形式:
对控制量的限制 ① 控制算法总是受到一定运算字长的限制; ② 执行机构的实际位置不允许超过上/下极限。
7.5 51单片机PID算法程序 一 位置式PID控制算法 位置式PID控制算法的简化示意图
上图中传递函数为: 在时域的传递函数表达式: 对上式中的微分和积分进行近似:
于是传递函数可以简化为: 其中: u(n)——第k个采样时刻的控制; KP ——比例放大系数; Ki ——积分放大系数; Kd ——微分放大系数;
这种算法有以下缺点: ① 由于全量输出,所以每次输出均与过去状态有关,计算时要对e(k)(k=0,1,…n)进行累加,工作量大; ② 因为计算机输出的u(n)对应的是执行机构的实际位置,如果计算机出现故障,输出u(n)将大幅度变化,会引起执行机构的大幅度变化,有可能因此造成严重的生产事故,这在实际生产中是不允许的。
程序片段如下: #include <reg52.h>#include <string.h> typedef struct PID { double SetPoint; // 设定目标Desired value double Proportion; // 比例常数Proportional Const double Integral; // 积分常数Integral Const double Derivative; // 微分常数Derivative Const double LastError; // Error[-1] double PrevError; // Error[-2] double SumError; // Sums of Errors } PID;
PID计算部分: double PIDCalc( PID *pp, double NextPoint ) { double dError, Error; Error = pp->SetPoint - NextPoint; // 偏差pp->SumError += Error; // 积分dError = Error - pp->LastError; // 当前微分pp->PrevError = pp->LastError; pp->LastError = Error; return (pp->Proportion * Error // 比例项+ pp->Integral * pp->SumError // 积分项+ pp->Derivative * dError ); } // 微分项 void PIDInit (PID *pp) { memset ( pp,0,sizeof(PID)); }
主程序: for (;;) { rIn = sensor (); rOut = PIDCalc ( &sPID,rIn ); actuator ( rOut ); } } double sensor (void) { return 100.0; } void actuator(double rDelta) {} void main(void) { PID sPID; double rOut; double rIn; PIDInit ( &sPID ); sPID.Proportion = 0.5;sPID.Derivative = 0.0; sPID.SetPoint = 100.0;
增量式PID控制算法公式为: 其中:
程序如下: typedef struct PID { int SetPoint; //设定目标 long SumError; //误差累计 double Proportion; //比例常数 double Integral; //积分常数 double Derivative; //微分常数 int LastError; //Error[-1] int PrevError; //Error[-2] } PID; static PID sPID; static PID *sptr = &sPID;
/*PID参数初始化*/ void IncPIDInit(void) { sptr->SumError = 0; sptr->LastError = 0; //Error[-1] sptr->PrevError = 0; //Error[-2] sptr->Proportion = 0; //比例常数 sptr->Integral = 0; //积分常数 sptr->Derivative = 0; //微分常数 sptr->SetPoint = 0; }