#include?<reg51.h>
#include?<intrins.h>?
#define?uint?unsigned?int?
#define?uchar?unsigned?
char?sbit?sw=P2^0;???????//定義升溫?
sbit?jw=P2^1;???????//定義降溫?
sbit?beep?=?P2^2;???//定義揚聲器?
sbit?rs=P2^5;????//lcd的控制?
sbit?rw=P2^6;?
sbit?e=P2^7;?
sbit?DQ=P1^0;??
bit?flag=1;??
uchar??T=0,temp1=0,temp2=0,flag_dis=0;?
uchar?table[]={"Welcom?to?watch?"};?
uchar?table1[]={'0','1','2','3','4','5','6','7','8','9'};?
uchar?table2[]={"T=?"};
/******************************************************?
*?函數:1602?模塊?
*******************************************************/?
void?delay(uint?z)???//延時???
{?uchar?x,y;??
for(x=20;x>1;x--)???
for(y=z;y>1;y--);?
}?
void?lcd_writecom(uchar?com)???//寫地址函數?
{??rs=0;??
rw=0;??
P0=com;??
delay(10);?????
e=1;??
delay(10);??
e=0;?
}?
void?lcd_writedate(uchar?dat)???//寫數據函數?
{???
rs=1;
rw=0;??
P0=dat;??
delay(1);?????
e=1;??
delay(1);??
e=0;?
}?
void?init_1602(void)????//1602初始話?
{?delay(10);?
??
lcd_writecom(0x38);???
delay(5);?
??
lcd_writecom(0x38);???
delay(5);?
??
lcd_writecom(0x38);???
delay(5);?
??
lcd_writecom(0x01);????
delay(5);?
??
lcd_writecom(0x08);????
delay(5);?
??
lcd_writecom(0x06);?????
delay(5);
lcd_writecom(0x0C);?
}
/******************************************************????????????????
*?函數:18B20?模塊*?
*******************************************************/
bit?init_DS18B20(void)????
//18b20初始化程序?
{??bit?flag;???
uchar?time;???????????
DQ=1;????
_nop_();?
????
DQ=0;??????????????????//再將數據線從高拉低,要求保持480~960us?
for(time=0;time<200;time++)??//略微延時約600微秒//以向DS18B20發出?//一持續480~960us的低電平復位脈沖??
?DQ=1;???????????????????//釋放數據線(將數據線拉高)?
for(time=0;time<20;time++);?//延時約60us(釋放總線后需等待15~60us讓DS18B20輸出存在脈沖)?
flag=DQ;??????????????????//讓單片機檢測是否輸出了存在脈沖(DQ=0表示存在)???????
for(time=0;time<200;time++);??//延時足夠長時間,等待存在脈沖輸出完畢
return?(flag);???????//返回檢測成功標志?
}??
void??WriteOneChar?(?uchar?dat)???//18B20?寫?數據?
{?uchar?i,time;?
for?(i=0;?i<8;?i++)???
{?DQ=1;????
_nop_();??
?
DQ=0;??????????//將數據線從高拉低時即啟動寫時序??
for(time=0;time<1;time++);????????
DQ=dat&0x01;???//利用與運算取出要寫的某位二進制數據,并將其送到數據線上等待DS18B20采樣?
for(time=0;time<10;time++);//延時約30us,DS18B20在拉低后的約15~60us期間從數據線上采樣?
DQ=1;??????????//釋放數據線???????
for(time=0;time<1;time++)?????//延時3us,兩個寫時序間至少需要1us的恢復期?
dat>>=1;???????//將dat中的各二進制位數據右移1位???}?}?
?
uchar?ReadOneChar(void)???//18B20?讀數據
?
{??
uchar?time,i,date=0;???//儲存讀出的一個字節數據?
for?(i=0;i<8;i++)???
{?DQ=1;????
_nop_();??
DQ?=?0;??????//單片機從DS18B20讀書據時,將數據線從高拉低即啟動讀時序?
for(time=0;time<1;time++);????//等待一個機器周期??????
DQ?=?1;????//將數據線"人為"拉高,為單片機檢測DS18B20的輸出電平作準備?
for(time=0;time<2;time++);??//延時約6us,使主機在15us內采樣?
date>>=1;?????????
if(DQ==1)?
???
date|=0x80;??//如果讀到的數據是1,則將1存入dat???
else?
??????
date|=0x00;?????????????//如果讀到的數據是0,則將0存入dat????????
for(time=0;time<15;time++);??//延時3us,兩個讀時序之間必須有大于1us的恢復期??
??}??????????????????????
??
return(date);??????//返回讀出的十六進制數據?}??
void?tempchange_get(void)??//啟動18B20溫度轉換+?讀取數據
{???
??uchar?a,b[3],p,time,tempL,tempH;???
init_DS18B20();?????//18B20?復位???
WriteOneChar(0xcc);???//跳過讀ROM?指令???
WriteOneChar(0x44);??//寫溫度轉換指令????
delay(10);?
????
init_DS18B20();?????????//準備讀數據的初始化??????
for(time=0;time<2;time++);??
????
WriteOneChar(0xcc);???//跳過ROM?????
WriteOneChar(0xbe);???//讀暫存器?????
tempL?=?ReadOneChar();???//讀低8?位?????
tempH?=?ReadOneChar();????//讀高8?位????
a=tempH&0x80;?
if(a)?????????????????//為一則是負溫度,補碼修正,為零則是正溫度,不處理?
??
{?if(tempL==0){tempH--;tempL=255;}?????
tempL-=1;??
tempL=~tempL;??
tempH=~tempH;?????
flag_dis=1;???
}
else?????
flag_dis=0;????//對取出的數據處理?
??
temp1=tempL&0x0F;??????//取第一字節低四位???
b[0]=temp1&0x08;???
b[1]=temp1&0x04;???
b[2]=temp1&0x02;???
b[3]=temp1&0x01;????
??
b[0]>>=3;???
b[1]>>=2;???
b[2]>>=1;?
??
p=b[0]*5000+b[1]*2500+b[2]*1250+b[3]*625;???//小數位化整數???
temp2=p/1000;????????//第一個小數位???
??
tempL&=0xF0;?????????//取第一字節高四位???
tempL>>=4;?
??
temp1=tempH&0x0F;??//取第二字節低四位???
temp1<<=4;?
??
temp1=temp1&0x7F;???????//只取7位??
temp1=temp1|tempL;???//合并為整數位?數據?
}
/******************************************************?
*?函數:對18B20?獲取的溫度進行處理*?
*******************************************************/
void?deal(uint?t)?
{if(flag_dis==1)t=0;?
if((t<10)||(t>40))beep=0;?
else?beep=1;?
if(t<20)sw=0;
else?
sw=1;??
if(t>30)
jw=0;
else?
jw=1;??}????????
/******************************************************????????????
函數功能:顯示模塊*?
*******************************************************/?
void?display(uchar?temp1,uchar?temp2?)?
{??uchar?i;?
???
lcd_writecom(0x80);
delay(3);?
for(i=0;i<3;i++)?
??
{lcd_writedate(table2[i]);??????
delay(3);}?
if(flag_dis)lcd_writedate('-');?????//結果為負數顯示??
lcd_writedate(table1[temp1/100]);??//?百位為0不顯示????
delay(3);?
?
lcd_writedate(table1[temp1%100/10]);??????
delay(3);?
?
lcd_writedate(table1[temp1%100%10]);?????
delay(3);?
?
lcd_writedate('.');??//小數點?????
delay(3);?
?
lcd_writedate(table1[temp2]);??
lcd_writecom(0x88);???
lcd_writedate('0');?}?
void?error_ds(flag)?
{?uchar?i;?
if(flag==1)?
?
{lcd_writecom(0x80);???//等待的顯示
delay(3);??
for(i=0;i<16;i++)?
?
{?lcd_writedate(table[i]);?????
delay(3);????}??}?}?
/******************************************************???????????????
函數名稱:main(void);?*??
*******************************************************/?
void?main(void)?{???
????
init_1602();????//LCD?初始化?????
error_ds(flag);?
while(init_DS18B20());??//18b20初始化???
lcd_writecom(0x01);???//清屏?
while(1)???????
{?????
???
tempchange_get();??//溫度轉換
deal(temp1);????????//溫度處理????
display(temp1,temp2);???//????
delay(5);?????????????//掃描周期???}?}?
#include <at89x51.h>//用AT89C51時就用這個頭文件
#include <absacc.h>
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <DS18B20.h>
#include "LCD1602.h" ////液晶顯示頭文件
//sbit DQ = P3^4; //定義DQ引腳為P3.4
unsigned char t[2],*pt; //用來存放溫度值,測溫程序就是通過這個數組與主函數通信的
unsigned char TempBuffer1[9]={0x2b,0x31,0x32,0x32,0x2e,0x30,0x30,0x43,'\0'};
//顯示實時溫度,上電時顯示+125.00C
unsigned char TempBuffer0[17]={0x54,0x48,0x3a,0x2b,0x31,0x32,0x35,0x20,
0x54,0x4c,0x3a,0x2b,0x31,0x32,0x34,0x43,'\0'};
//顯示溫度上下限,上電時顯示TH:+125 TL:+124C
unsigned char code dotcode[4]={0,25,50,75};
/***因顯示分辨率為0.25,但小數運算比較麻煩,故采用查表的方法*******
再將表值分離出十位和個位后送到十分位和百分位********************/
void covert0( unsigned char TH, unsigned char TL) //將溫度上下限轉換為LCD顯示的數據
{
if(TH>0x7F) //判斷正負,如果為負溫,將其轉化為其絕對值
{
TempBuffer0[3]=0x2d; //0x2d為"-"的ASCII碼
TH=~TH;
TH++;
}
else TempBuffer0[3]=0x2b; //0x2B為"+"的ASCII碼
if(TL>0x7f)
{
TempBuffer0[11]=0x2d; //0x2d為"-"的ASCII碼
TL=~TL+1;
}
else TempBuffer0[11]=0x2b; //0x2B為"+"的ASCII碼
TempBuffer0[4]=TH/100+0x30; //分離出TH的百十個位
if( TempBuffer0[4]==0x30) TempBuffer0[4]=0xfe; //百位數消隱
TempBuffer0[5]=(TH%100)/10+0x30; //分離出十位
TempBuffer0[6]=(TH%100)%10+0x30; //分離出個位
TempBuffer0[12]=TL/100+0x30; //分離出TL的百十個位
if( TempBuffer0[12]==0x30) TempBuffer0[12]=0xfe; //百位數消隱
TempBuffer0[13]=(TL%100)/10+0x30; //分離出十位
TempBuffer0[14]=(TL%100)%10+0x30; //分離出個位
}
void covert1(void) //將溫度轉換為LCD顯示的數據
{
unsigned char x=0x00,y=0x00;
t[0]=*pt;
pt++;
t[1]=*pt;
if(t[1]>0x07) //判斷正負溫度
{
TempBuffer1[0]=0x2d; //0x2d為"-"的ASCII碼
t[1]=~t[1]; /*下面幾句把負數的補碼*/
t[0]=~t[0]; /* 換算成絕對值*********/
x=t[0]+1; /***********************/
t[0]=x; /***********************/
if(x>255) /**********************/
t[1]++; /*********************/
}
else TempBuffer1[0]=0x2b; //0xfe為變"+"的ASCII碼
t[1]<<=4; //將高字節左移4位
t[1]=t[1]&0x70; //取出高字節的3個有效數字位
x=t[0]; //將t[0]暫存到X,因為取小數部分還要用到它
x>>=4; //右移4位
x=x&0x0f; //和前面兩句就是取出t[0]的高四位
t[1]=t[1]|x; //將高低字節的有效值的整數部分拼成一個字節
TempBuffer1[1]=t[1]/100+0x30; //+0x30 為變 0~9 ASCII碼
if( TempBuffer1[1]==0x30) TempBuffer1[1]=0xfe; //百位數消隱
TempBuffer1[2]=(t[1]%100)/10+0x30; //分離出十位
TempBuffer1[3]=(t[1]%100)%10+0x30; //分離出個位
t[0]=t[0]&0x0c; //取有效的兩位小數
t[0]>>=2; //左移兩位,以便查表
x=t[0];
y=dotcode[x]; //查表換算成實際的小數
TempBuffer1[5]=y/10+0x30; //分離出十分位
TempBuffer1[6]=y%10+0x30; //分離出百分位
}
void delay(unsigned char i)
{
while(i--);
}
main()
{
unsigned char TH=110,TL=-20; //下一步擴展時可能通過這兩個變量,調節上下限
//測溫函數返回這個數組的頭地址
while(1)
{
pt=ReadTemperature(TH,TL,0x3f); //上限溫度-22,下限-24,分辨率10位,也就是0.25C
//讀取溫度,溫度值存放在一個兩個字節的數組中,
delay(100);
covert1();
covert0(TH,TL);
LCD_Initial(); //第一個參數列號,第二個為行號,為0表示第一行
//為1表示第二行,第三個參數為顯示數據的首地址
LCD_Print(0,0,TempBuffer0);
LCD_Print(0,1,TempBuffer1);
}
}
//第一步:定義PID變量結構體,代碼如下:
struct _pid{
float SetSpeed; //定義設定值
float ActualSpeed; //定義實際值
float err; //定義偏差值
float err_last; //定義上一個偏差值
float Kp,Ki,Kd; //定義比例、積分、微分系數
float voltage; //定義電壓值(控制執行器的變量)
float integral; //定義積分值
}
//pid控制算法中所需要用到的參數在一個結構體中統一定義,方便后面的使用。
//第二部:初始化變量,代碼如下:
void PID_init(){
printf("PID_init begin \n");
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.2;
pid.Ki=0.015;
pid.Kd=0.2;
printf("PID_init end \n");
}
//統一初始化變量,尤其是Kp,Ki,Kd三個參數,調試過程當中,對于要求的控制效果,可以通過調節這三個量直接進行調節。
//第三步:編寫控制算法,代碼如下:
float PID_realize(float speed){
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.integral+=pid.err;
pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
//注意:這里用了最基本的算法實現形式,沒有考慮死區問題,沒有設定上下限,只是對公式的一種直接的實現,后面的介紹當中還會//逐漸的對此改進。
//到此為止,PID的基本實現部分就初步完成了。下面是測試代碼:
int main(){
printf("System begin \n");
PID_init();
int count=0;
while(count<1000)
{
float speed=PID_realize(200.0);
printf("%f\n",speed);
count++;
}
return 0;
}
//pid.h
#ifndef __PID__
#define __PID__
/*PID = Uk + KP*[E(k)-E(k-1)]+KI*E(k)+KD*[E(k)-2E(k-1)+E(k-2)];(增量型PID算式)
函數入口: RK(設定值),CK(實際值),KP,KI,KD
函數出口: U(K)*/
typedef struct PIDValue
{
int8 KP;
int8 KI;
int8 KD;
int8 F;
int8 BITMOV;
int EK[3];
int UK;
int RK;
int CK;
int UK_REAL;
}pid_str;
//PIDValueStr PID;
void pid_exe(pid_str *PID) ;
#endif
//pid.c
/*PID = PID->UK_REAL + PID->KP*[E(k)-E(k-1)]+PID->KI*E(k)+PID->KD*[E(k)-2E(k-1)+E(k-2)];(增量型PID算式)
函數入口: PID->RK(設定值),PID->CK(實際值),PID->KP,PID->KI,PID->KD
函數出口: U(K)*/
#include"defines.h"
#include"pid.h"
#define MAXOUT 0xff
//#define MAXGAP 100
void pid_exe(pid_str*PID)
{
PID->EK[2]=PID->EK[1];
PID->EK[1]=PID->EK[0];
PID->EK[0]=PID->RK-PID->CK;
PID->UK_REAL=PID->UK_REAL
+PID->KP*(PID->EK[0]-PID->EK[1])//微分一次后積分即原數
+(float)PID->KI*PID->EK[0]/PID->F//直接積分
+(float)PID->KD*(PID->EK[0]-2*PID->EK[1]+PID->EK[2])*PID->F;//二階微分后積分即一階微分
if((PID->UK_REAL>>PID->BITMOV)>=MAXOUT)
{
PID->UK=MAXOUT;
}else if(PID->UK_REAL>>PID->BITMOV<=-MAXOUT)
{
PID->UK=-MAXOUT;
}else
{
PID->UK=PID->UK_REAL>>PID->BITMOV;
}
}