发一个简单的山寨版PID控制例子
本帖最后由 panxiaoyi 于 2012-10-4 00:51 编辑/*****************************************************
CodeWizardAVR V1.25.6 Professional
Chip type : ATmega48
Clock frequency : 8 MHz
Memory model : Small
External SRAM size: 0
Data Stack size : 128
*****************************************************/
#include <mega48.h>
#include <delay.h>
#include <adc.h>
#include <nokia3310.h>
#define ADC 100 //ADC目标读数(相当于目标电压)
#define kp 9 //P系数/请小心全局数据溢出!
#define ki 1 //I系数/取消则I=0
#define kd 3 //D系数/取消则D=0
unsigned int adc_data;
int wc0, wc1,wc2, xz, pwm; //当前,上次,再上次的误差,修正,输出
void main(void)
{
TCCR1A=0b10000001; //OCR1A正比例PWM/相位修正模式
TCCR1B=0b00000001; //时钟1分频
OCR1AL=0; //PWM占空比调整0-255
DDRB.1=1; //输出PWM
lcd_init(); lcd_cls(); //LCD初始化
while (1)
{
delay_ms(98); //简易的间隔检测时间设定
adc_start(5); //启动某通道ADC/参考电压5V/8bit
adc_data=adc_read(); //等待和读结果/电压=ADC_DATA*98/5
wc2=wc1;
wc1=wc0;
wc0=ADC-adc_data; //误差值=目标值-当前值
xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd;//修正值=比例放大+积分放大+微分放大
pwm=pwm+xz; //输出值=旧数值+修正值
if(pwm>255) pwm=255; if(pwm<0) pwm=0; //输出值限幅
OCR1AL=pwm;
lcd_gotoxy(0,1); //LCD显示
lcd_putsf("PWM:",4); lcd_putbyte(OCR1AL);
lcd_gotoxy(0,3);
lcd_putsf("ADC:",4); lcd_putbyte(adc_data);
};
}
如下图:
单片机通过PWM输出供电,驱动3个串联电阻,其中50K是一个电位器,人为不断的旋动电位器,来模拟负载无规律的变化。
目标要求:100uF电容两端的电压不受负载的影响,即ADC读数要求稳定在100左右。
大概的输出电压mV = 5000mV * ADC读数 / 255
调整 P、I、D 这三个系数,还有调整间隔检测时间,即可调整负载电压的稳定度和皱纹度。
本例比较简单,没有多少语句,且都是16位的整数运算,适合8位单片机运行,但是它并没有按照PID的公式来套(说实话我对那些公式也是一知半解),它只是按照PID的思路来工作而已,所以,这个程序只能说是山寨程序,适合要求不高的场所。还好,它的语句短、运行速度特快 Mark一下,谢谢分享,有机会的话试一下 有意思,学习了 这样的资料很好,很亲民!比那些砖家叫兽的论文强多了! 细读了下程序,感觉很实用,改天有空做个样品放到工业场所看下实际运行效果。 确实挺不错的~ 嗯,Mark。
改天试试效果。 简单明了,没有多余的语句···· 学习了,呵呵 楼主你的网站怎么不搞了,现在还有没有串口的STK500 f117_2r 发表于 2012-10-5 19:05 static/image/common/back.gif
楼主你的网站怎么不搞了,现在还有没有串口的STK500
本论坛有大量STK500的制作资料 楼主
电路有实际测试过吗? 学习学习 简单明了,学习一下,多谢楼主。 amrk{:smile:} 已经通过实际电路测试过的,还有LCD辅助显示观测,效果很好。
1:先测试逐步调整(检测电压不够,PWM+1,第二次检测不够,PWM再+1,反之,PWM-1,以此类推)
2:测试比例调整,就是调整P系数,I、D 两个系数设为0
3:测试PID调整
结果,PID最好,其次P调整,逐步调整最差。 这个是用ATmega48实现的?,它内部有ADC模块? mark 学习一下 f117_2r 发表于 2012-10-5 19:05 static/image/common/back.gif
楼主你的网站怎么不搞了,现在还有没有串口的STK500
你好,我转行好多年了,现在玩电子只是月余爱好而已 本帖最后由 panxiaoyi 于 2012-10-6 17:49 编辑
小提示,假设P系数要设为0.85,而又不想使用浮点运算,可以这样 :
1:当前误差*85/100
2:如果 最大当前误差*85 有可能使变量溢出的话,可以:当前误差*17/20
这样做的好处是代码量小,运行速度快,缺点是 精确度、准确度 偏差一点。 maimaige 发表于 2012-10-6 17:20 static/image/common/back.gif
这个是用ATmega48实现的?,它内部有ADC模块?
是M48,有ADC 楼主亲测,哈哈 好例子,下来仔细看看 偶路过友情顶下帖 支持一下 好东西 !收藏 mark 学习 有意思,学习了 学习下谢谢分享! 不错,正需要 mark 很好 公式: xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd; 中的: ((wc0-wc1) - (wc1-wc2))*kd,为什么不是:((wc0-wc1) + (wc1-wc2))*kd,即两次微分求和?(wc0+wc1+wc2)*ki就比较好理解-积分即累加求和. 好文,谢谢分享{:victory:} summarize 发表于 2012-10-10 15:55 static/image/common/back.gif
公式: xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd; 中的: ((wc0-wc1) - (wc1-wc2))*kd,为 ...
搞这个东西,我们不能只看文字,还要动手画图才会有比较好的理解,如下图。
图a的误差是一条直线,说明误差不断的在变大,但是,它每次变大的数值与之前一次比较都是一样的
就是 (wc0-wc1)-(wc1-wc2) = 0 ,也就是说,这个变化不需要PID的D调整。
图b的误差是一条曲线,说明误差一次比一次大,显然,我们调整的力度也需要一次比一次大,最好是超前调整
这时 (wc0-wc1)-(wc1-wc2) 〉0 ,也就是说,这个变化需要PID的D调整,即超前调整 误差变量是“有符号”整数,它同样适合误差全部是负数,或者有负数有正数的混合运算,这个在理解上可能会复杂一点点 panxiaoyi 发表于 2012-10-14 11:11 static/image/common/back.gif
误差变量是“有符号”整数,它同样适合误差全部是负数,或者有负数有正数的混合运算,这个在理解上可能会复 ...
谢谢您的解答! 这个是使用增量式的pid吧,怎么感觉公式有点不对头? 简单有效 xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd;//修正值=比例放大+积分放大+微分放大
积分部分只有连续3次误差之和,如何理解?增量式积分? 好东西{:victory:} 这个可以研究哈,,,,比以前那些例子好很多, mark , 谢谢 记号,什么时候用下 cool
谢谢,参考一下 有空也试试。 有意思,学习了
保存先,以后有用 程序简单,谢谢分享 非常实用哦,我等小白很适合! Delong_z 发表于 2012-11-1 11:23 static/image/common/back.gif
xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd;//修正值=比例放大+积分放大+微分放大
积分部分 ...
只有 wc0 wc1 wc2,是从节省单片机资源来考虑的,当然,有更多次数的误差累积更好,还有使用实数变量也更好,只是,程序会变得复杂,运行会变慢,代码也会变大。
至于什么是增量式,我还真的不懂,我在1楼上是这样说的
“本例比较简单,没有多少语句,且都是16位的整数运算,适合8位单片机运行,但是它并没有按照PID的公式来套(说实话我对那些公式也是一知半解),它只是按照PID的思路来工作而已,所以,这个程序只能说是山寨程序,适合要求不高的场所。还好,它的语句短、运行速度特快”
呵呵,献丑了 呵呵,我把楼主的程序变形了下,做成子程序,对象是控制直流电机电压,计算可控硅导通角,还没调试通,还在调试中,先谢谢楼主分享了
/*-----------------------------------------------------------------
函数名称: void PID(void)
函数功能: 行走电机电压控制
参 数: 入口行走电压
返 回 值: pwm调节
说 明: Iv为给定,adc_data为实际值
int wc0,wc1,wc2, xz, pwm; //当前,上次,再上次的误差,修正,输出
-----------------------------------------------------------------*/
uchar PID_xz(uchar Iv,uchar adc_data)
{
static int wc0,wc1,wc2,xz,pwm; //当前,上次,再上次的误差,修正,输出
uchar kp=1; //P系数/请小心全局数据溢出!9
uchar ki=1; //I系数/取消则I=0 1
uchar kd=0; //D系数/取消则D=0 3
wc2=wc1;
wc1=wc0;
wc0=Iv-adc_data; //误差值=目标值-当前值
xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd;//修正值=比例放大+积分放大+微分放大
pwm=pwm+xz; //输出值=旧数值+修正值
if(pwm>256)pwm=200; if(pwm<0)pwm=0; //输出值限幅
return pwm;
} panxiaoyi 发表于 2012-10-14 10:54 static/image/common/back.gif
搞这个东西,我们不能只看文字,还要动手画图才会有比较好的理解,如下图。
图a的误差是一条直线,说明误 ...
不错,好好学习一下 cdwess 发表于 2012-11-2 23:30 static/image/common/back.gif
呵呵,我把楼主的程序变形了下,做成子程序,对象是控制直流电机电压,计算可控硅导通角,还没调试通,还在 ...
这个更牛呀 正在弄步进电机加速算法 ,不错, 不错,谢了 mark 顶 简单有效 {:smile:}非常不错的东西啊 好东西,对学习PID有帮助 学习了 谢谢{:smile:}{:smile:} 不错,学习了
楼主用的是增量PID吧 ,简单易懂 ,就是不知道精度怎么样。 顶一个{:smile:}{:smile:}{:smile:} 感谢楼主,亲民的PID,我等小菜受教了!!! 楼主能留下扣扣吗?我也正在学习这个遇到很多问题想要请教你,谢谢 zhenglingo 发表于 2012-11-15 20:42 static/image/common/back.gif
楼主能留下扣扣吗?我也正在学习这个遇到很多问题想要请教你,谢谢
呵呵,我从来就不太喜欢QQ聊天的 panxiaoyi 发表于 2012-11-15 20:53 static/image/common/back.gif
呵呵,我从来就不太喜欢QQ聊天的
哪在这问你篇幅太长啊也不方便啊 zhenglingo 发表于 2012-11-15 20:55 static/image/common/back.gif
哪在这问你篇幅太长啊也不方便啊
我就是想请教你一些关于PWM输出PIDAD 等遇到的问题开始时和我宿舍的同学一起去参加了个四轴的比赛 那时候真的没有珍惜好机会所以没学到什么东西不过或多或少学到一些 所以打算模拟下PID 控制PWM输出然后AD采集很楼主的几乎一模一样而在当中我遇到了很多问题希望和楼主多多交流谢谢 panxiaoyi 发表于 2012-11-15 20:53 static/image/common/back.gif
呵呵,我从来就不太喜欢QQ聊天的
楼主还在吗? 楼上,有什么事就论坛说吧,这里有很多热心人的 简单明了! 不错。。。。。。 panxiaoyi 发表于 2012-11-15 21:15 static/image/common/back.gif
楼上,有什么事就论坛说吧,这里有很多热心人的
好 吧哪我将我的问题贴上楼主多多指点啊
在AD模块上我根据数据手册上的信息需要将IO口先设置成高阻状态或者开漏状态(STC12C5A60S2的片子)但是用这个带AD功能的IO去采集模拟PWM输出的IO,结果我先用电压表测量采集值和输出的值相差很大不知道是否是因为万用表采集频率低无法动态显示变化的值
模拟PWM的输出我的程序是
sbit PWM_IO = P0^0 ;
externINT16U TimeCounter ;
void pwm(INT16U PWM)
{
staticINT16U LedPWMCounter = 0 ; // PWM_IO占空比
staticINT16U PWMCounter = 0 ;
if(TimeCounter > 256) //每50MS 调整一下 PWM_IO 的占空比
{
TimeCounter = 0;
LedPWMCounter = PWM ;
if(LedPWMCounter > 257)
{
LedPWMCounter = 257 ;
}
if(LedPWMCounter < 0)
{
LedPWMCounter = 0 ;
}
PWMCounter = LedPWMCounter ; //获取LED 的占空比
}
if( PWMCounter > 0)
{
PWM_IO = 1 ;
PWMCounter--;
}
else
{
PWM_IO = 0 ;
}
}
}
void Time0Isr(void) interrupt 1
{
TH0 = (65536 - 195) / 256 ; //定时器重新赋初值
TL0 = (65536 - 195) % 256 ;
TimeCounter++;
pwm(Pwm) ;
}
zhenglingo 发表于 2012-11-15 21:43 static/image/common/back.gif
好 吧哪我将我的问题贴上楼主多多指点啊
在AD模块上我根据数据手册上的信息需要将IO口先设置成 ...
sbit PWM_IO = P0^0 ;
externINT16U TimeCounter ;
void pwm(INT16U PWM)
{
staticINT16U LedPWMCounter = 0 ; // PWM_IO占空比
staticINT16U PWMCounter = 0 ;
if(TimeCounter > 256) //每50MS 调整一下 PWM_IO 的占空比
{
TimeCounter = 0;
LedPWMCounter = PWM ;
if(LedPWMCounter > 257)
{
LedPWMCounter = 257 ;
}
if(LedPWMCounter < 0)
{
LedPWMCounter = 0 ;
}
PWMCounter = LedPWMCounter ; //获取LedPWMCounter的占空比
}
}
if( PWMCounter > 0)
{
PWM_IO = 1 ;
PWMCounter--;
}
else
{
PWM_IO = 0 ;
}
}
void Time0Isr(void) interrupt 1
{
TH0 = (65536 - 195) / 256 ; //定时器重新赋初值
TL0 = (65536 - 195) % 256 ;
TimeCounter++;
pwm(Pwm) ;
}
PWM是这个问题可能有我测试过不加AD采集能根据输入的占空比来显示出电压 panxiaoyi 发表于 2012-11-15 21:15 static/image/common/back.gif
楼上,有什么事就论坛说吧,这里有很多热心人的
楼主能否帮我解答下呢?谢谢 很好,楼主是高手~~ 这个是正常的pid算法
wc0*kp这个是比例控制的值,后面两个来修正这个值。
(wc0+wc1+wc2)*ki误差越大调整力度越大
((wc0-wc1)-(wc1-wc2))*kd;数据趋势,决定调整值的正负
主要是kp,ki,kd的值不好确定,有个牛逼的自整定算法就好了。
果然强大,哈哈,简单明了,干脆利落 值得收藏的资料 zhenglingo 发表于 2012-11-15 22:10 static/image/common/back.gif
楼主能否帮我解答下呢?谢谢
对不起,我帮不了你,因为我看不懂你的代码,也没有用过这个片子,不过,你的代码好像与PID也扯不上什么关系 panxiaoyi 发表于 2012-11-16 23:02 static/image/common/back.gif
对不起,我帮不了你,因为我看不懂你的代码,也没有用过这个片子,不过,你的代码好像与PID也扯不上什么 ...
这只是PWM部分啊,我用的是PID去控制PWM输出,然后用AD去采集 我用的是51的单片机 目前我的模块就是用电压表测的电压与用串口显示出来的电压区别有点大而且串口上电压是动态变化的 这个也好理解 但是万用表上测的是静态的这种情况是否为万用表采样率太低呢 无法读会变化的电压?还有就是感觉会有这种变化可能是AD采样输出的状态,开始AD输出的状态为高阻或者开漏但是我测了下这两种状况下都还会有电压输出这样是否会影响采集呢? 很好,很精练,值得一学 回复zhenglingo ,PWM输出的是方波,瞬时输出电压要么是VCC电压,要么是GND电压,要输出其它电压,改变PWM的占空比即可,且必须是IO口串联一个电阻,然后电阻的另外一端对地并联一个滤波电容,电阻越大、电容越大,电压越稳定,这样AD模块才能检测。如果不是这个问题,请你上传一个电路图让大家看看 mark{:smile:} panxiaoyi 发表于 2012-11-17 11:14 static/image/common/back.gif
回复zhenglingo ,PWM输出的是方波,瞬时输出电压要么是VCC电压,要么是GND电压,要输出其它电压,改变PWM ...
我的硬件是没问题了都是这样连接的限流电阻1K滤波电容用的是10uF 我的思想是用定时器产生一个可以改变占空比的PWM输出,然后用AD采集输出,目标电压是3V 然后通过PID来时PWM输出稳定在3V,到时可以加进负载,来改变采样电压使PID做出调整 现在我遇到的问题就是用AD采集回来的值在串口上显示与万用表测量的值不相同,而且相差很大,并且串口上的值时变化的(开始时是允许的,但稳定后还是很大变动),而万用表的是不变的所以现在特别纠结我现在不知道是我哪里出错了 我就想请教下楼主的控制思想是否和我的一样? marl............................ 学习一下 发一个简单的山寨版PID控制例子--AVr实现,mark一下 MARK~~~~~~~~~ 真的不错,呵呵呵,,,学习了,,, 学习学习 学习了,楼主思路不错 自己用VB写了一个小的测试程序奉献给大家 谢谢楼主,学习了 这玩意有意思,短小精干 工程师030 发表于 2012-12-14 18:56 static/image/common/back.gif
自己用VB写了一个小的测试程序奉献给大家
有意思,有机会向你学习一下VB 这里是标准的PID直接计算法公式,而不是PID增量法计算公式,所以:
pwm=pwm+xz; //输出值=旧数值+修正值
应该为
pwm=xz; gongkailin 发表于 2012-12-15 21:53 static/image/common/back.gif
这里是标准的PID直接计算法公式,而不是PID增量法计算公式,所以:
pwm=pwm+xz; ...
假设由于负载变化、供电变化、程序调节等原因,使得误差1、误差2、误差3都是0的话,那么
xz=wc0*kp+(wc0+wc1+wc2)*ki+((wc0-wc1)-(wc1-wc2))*kd;//修正值=比例放大+积分放大+微分放大
xz就会等于0
那么pwm也会等于0
所以,在本例中,pwm不能等于xz 本帖最后由 gongkailin 于 2012-12-15 22:59 编辑
我以前做的孵化机控温用的用的这个公式,M128的PWM驱动固态继电器用电热管加热,PWM周期0.5秒。当检测温度与设定温度偏差为0时,PWM输出为零停止加热。设定温度37.5可以准确控制到+37,4至37.4,已经用了近10年了。
在你的例子中,当一瞬间误差1、误差2、误差3都是0的话,那么电容C上的电压正好为设定电压,这一瞬间PWM应该为0没有输出。
但是,本例中是有负载的,pwm一旦为0,那么,输出电压就会马上下降,经测试,电位器旋转到某个位置时,pwm就会快速停留在某个值不变,或者在临近两个值中不断变动 学习学习.......
页:
[1]
2