18b20時序要求比較嚴格。
所遇問題:測出溫度值不準確,不停的從255于實際溫度值跳變。
解決方法:在讀取溫度值時,關掉中斷。不過燈有一些閃。
該程序所測溫度的精度不能達到0.5,需要稍微改動。
不知道1602穩定了沒,明天再試一次。
反應好像還不夠靈敏。
#include "reg52.h"
#include<intrins.h>
#define uchar unsigned char
//溫度傳感器與單片機接口為 P0.1
sbit DQ=P1^5;
//
sbit E=P0^7;
sbit RW=P0^6;
sbit RS=P0^5;
sbit LED=P3^5;
//sbit lcd_flag=P2^7; //定義是lcd否忙標志
sbit P1_7 = P3 ^ 7; // 控制LED11
sbit P1_6 = P3 ^ 6; // 控制LED10
sbit P1_2 = P3 ^ 4;
//
uchar timer=0x00; //字節變量timer用于計時,每計數器記滿100次,就采樣一次溫度
uchar chars[]={"GYRGB.Liuzhou^_^"}; // 液晶顯示第一行
uchar chars2[]={"Welcome!"}; //液晶顯示第二行
uchar scale_0; //燈光灰度
///////////////////////////////
//各個功能子函數
///////////////////////////////
void delay(void); //18b20溫度轉換后所需的延時
void delay1(void); //動態數碼管顯示所需的較短的延時
unsigned char Read18B20(void); //讀18b20子函數
void Write18B20(uchar ch); //寫18b20子函數
void Delay15(uchar n); //讀寫所需的最基本單位時間的延時
void Init18B20(void); //初始化18b20函數
/////////////////////////////
//新增的液晶顯示程序
/////////////////////////////
void WriteLCDcom(uchar ch);
void WriteLCDdat(uchar ch);
void main(void)
{
uchar i,tl,th; //變量i用于循環計數,tl和th用于獲得二進制溫度值
uchar temp; //變量temp用于存放有效的溫度值
uchar bith,bitt,biti,bitz; //將要顯示在數碼管上的百位、十位、個位和十分位的碼值
unsigned int n;
RCAP2H =0xFE; // 賦T2的預置值,溢出1次是1/2000秒鐘
RCAP2L =0x0C;
ET2=1; // 打開定時器2中斷
EA=1; //總中斷允許
TR2=1; // 啟動定時器2
WriteLCDcom(0x01); //清屏
WriteLCDcom(0x38);
WriteLCDcom(0x0c);
WriteLCDcom(0x06);
WriteLCDcom(0x80);
for(i=0;i<16;i++)
WriteLCDdat(chars);
WriteLCDcom(0xc0);
for(i=0;i<8;i++)
WriteLCDdat(chars2);
WriteLCDcom(0xce);
WriteLCDdat(0xdf);
WriteLCDdat(0x43);
////////////////////////////////
//12M晶振,16位計數器
//溢出時間 65536us約等于65.5ms
//溢出100次采樣溫度一次,采樣周期6.5秒
////////////////////////////////
while(1)
{
LED=1;
Init18B20(); //復位18b20,每次復位18b20都是默認的12位轉換精度
Write18B20(0xcc);//向18b20寫入跳過激光rom操作
_nop_(); //稍等片刻
Write18B20(0x44);//命令18b20開始溫度的測量以及模數轉換
delay(); //18b20轉換時間較長應該等待稍長時間
Init18B20(); //每一次對18b20的讀寫都要先復位
TR2=0; //18b20對時序要求比較嚴格,因此在讀取溫度時要關中斷,否則測的溫度值會出錯
Write18B20(0xcc);//照例跳過ROM的操作
_nop_(); //等
Write18B20(0xbe); //讀18b20的溫度數據,可以連續讀5個字節
_nop_(); //等
tl=Read18B20(); //讀第一個字節,里面是12位有效數字的低八位
th=Read18B20(); //讀第二個字節,是擴展的符號位和有效值的高四位
TR2=1;
Init18B20();
temp=(th<<4)+(tl>>4);
//實際上,temp=(th<<4)+(tl>>4)這個式子得到的是只包含了數整值的溫度值
//th向左移4位,拋棄了擴展的符號位;tl向右移4位,拋棄了4位小數位
bith=temp/100; //得到百位數字
bitt=(temp%100)/10; //得到十位數字
biti=temp%10; //得到個位數字,由于要顯示小數點,所以要減去一個0x80
bitz=0; //只是作為好看的位數,實際我們在計算temp的時候已經將小數位舍去了
/*****************************************/
// 液晶的顯示
/*****************************************/
WriteLCDcom(0xc8);
WriteLCDdat(bith+0x30);
WriteLCDdat(bitt+0x30);
WriteLCDdat(biti+0x30);
WriteLCDdat(0x2e);
WriteLCDdat(bitz+0x30);
LED=1;
for(n=0;n<50000;n++); // 每過一會兒就自動加一個檔次的亮度
if(temp==26) scale_0=0;
else if(temp==27) scale_0=1;
else if(temp==28) scale_0=2;
else if(temp==29) scale_0=3;
else if(temp==30) scale_0=4;
else if(temp==31) scale_0=5;
else if(temp==32) scale_0=6;
else if(temp==33) scale_0=7;
else if(temp==34) scale_0=8;
else scale_0=9;
}
}
/********************************************************************************************
* 函數名稱:Timer2_Server()
* 功 能:定時器2溢出中斷服務程序。1/2000 秒中斷1次。
* 入口參數:無
* 出口參數:無
*********************************************************************************************/
void Timer2_Server(void) interrupt 5
{
static uchar tt; // tt用來保存當前時間在1秒中的比例位置
TF2=0;
tt++;
if(tt==10) // 每1/200秒整開始輸出低電平
{
tt=0;
if(scale_0!=0) // 加入該句的目的是避免滅燈時發生閃爍
{
P1_7=0;
P1_6=0;
P1_2=0;
}
}
if(scale_0==tt) // 按照當前占空比切換輸出高電平
{
P1_7=1;
P1_6=1;
P1_2=1;
}
}
void delay(void) //長延時,18b20在執行溫度轉換操作的時候需要耗費較長時間
{ //在這段時間里18b20需要測溫,做模數轉換,并將轉換的二進制數值存儲到自帶的臨時寄存區里去
uchar i,j;
for(i=0;i<200;i++) //具體需要多長時間芯片手冊上有介紹,這個要繼續深入了解 /*************************??******/
for(j=0;j<100;j++)
;
}
unsigned char Read18B20(void) //最基本的讀18b20的函數,并向主函數返回讀到的那個字節
{
unsigned char ch; //相當于串行緩存器
unsigned char q ; //循環計數器
for(q=0;q<8;q++) //循環8次,讀出串行的8位數據,先讀到的是數據的最低位
//因此要從ch的最高位存起,然后依次將ch向右移,就像火車進站那樣
{ // 7 6 5 4 3 2 1 0 (ch)
// MSB-6-5-4-3-2-1-LSB ----> (數據)
ch=ch>>1; //先移位再賦值,出現賦值8次但是只移位7次的效果
DQ=0; //單線總線的要求,要讀器件,就要產生一個上升沿,然后釋放總線,現在要回到低電平
_nop_(); // 稍微停頓,讓器件探測到電平已經變低了
DQ=1; //拉高總線產生上升沿,同時,在某種意義上,對單片機的端口寫1,也就是讓端口處于待讀的狀態,一舉兩得
_nop_();_nop_(); //4個空操作函數,等待,給18b20響充分的時間響應,具體需要多久的時間要看器件手冊
_nop_();_nop_(); // 讀響應是多久,需要繼續深入了解 /********************??*****/
//而且這個-nop-()函數到底會延時多長時間,要深入了解 /****************??***/
if(DQ==1) //開始讀端口,如果為1,則將ch最高位寫為1
{ ch=ch|0x80;} //ch|0x80就是 XXXX XXXX | 1000 0000 每一位相或的結果就是得到 1XXX XXXX,火車開始進站了
else //如果讀到的數是0,那么就把ch的最高位置為0
{ ch=ch&0x7f;} // ch & 0x7f 就是 XXXX XXXX & 0111 1111 ,結果自然就是 0XXX XXXX,數字最低位就進站好了
Delay15(3); //延時少少,延時多長要繼續深入了解 /****************??*****/
DQ=1; //讀完之后再次置端口為1,好為下一次讀做準備,其實很關鍵的一步
}
return (ch); //將讀到的數據返回給主函數,這就我們想要的結果了
}
void Write18B20(uchar ch) //寫18b20的函數
{
uchar i; //一個循環計數變量
for(i=0;i<8;i++) //循環8次,每次一位二進制
{
DQ=0; //從讀和寫函數的比較可以得知,產生讀/寫的條件都是要先產生低電平,只是低電平的維持時間長短不一
Delay15(1); //寫操作需要的低電平持續時間比讀操作要長的多
DQ=ch &0x01; //向總線上寫ch的最低位,跟讀的操作類似,只是這時“車站”是18b20,而“列車”是ch
Delay15(3); // 7 6 5 4 3 2 1 0 (18b20)
// MSB-6-5-4-3-2-1-LSB ----> (ch)
// ch & 0x01 就是 xxxx xxxx & 0000 0001,結果是0000 000x
//為什么可以用一個位變量 DQ= 0000 000x,這個問題需要繼續深入了解 /*************??****/
DQ=1; //寫完一位后,將總線抬高,為下一次拉低做準備
ch=ch >>1; //將ch第二低位推到最低位,等待發射出去
_nop_(); //等待一段時間
}
}
void Init18B20(void) //初始化18b20
{
DQ=0; //初始化操作同樣是由低電平開始,但是這個初始化低電平要持續得最久
Delay15(30);//至少延時480us,到底是多少,要找到-nop-()函數源碼,反匯編之后才知道/*******************??****/
DQ=1; //抬高總線,一舉兩得,可以準備接受18b20的存在低電平
Delay15(10);//至少延時100us
}
void Delay15(uchar n) //貌似滿精確的延時程序
{ //具體要在 intrins.h頭文件中找到它,那么首先就要找到intrins.h /****************??*****/
do
{
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_();
n--;
}while(n);
}
/*
bit LCDbusy()
{
bit flag;
RS=0; //寄存器為LOW
RW=1; //是否讀寫為high
E=1; //使能端為high
if(lcd_flag==1) flag=1;
E=0;
return flag;
}
*/
void WriteLCDcom(uchar ch)
{
// while(LCDbusy());
RS=0;
RW=0;
E=0;
P2=ch;
delay();
E=1;
E=0;
}
void WriteLCDdat(uchar ch)
{
// while(LCDbusy());
RS=1;
RW=0;
E=0;
P2=ch;
delay();
E=1;
E=0;
}
不明白為什么加了檢測1602是否忙的程序進去,為什么P2口就沒有數據了呢?液晶就不能顯示了呢?
