?演示視頻:
LCD顯示溫度
?源代碼
如上圖將9個文放在Keli5 中即可,然后燒錄在單片機中就行了
燒錄軟件用的是STC-ISP,不知道怎么安裝的可以去看江科大的視頻:
【51單片機入門教程-2020版 程序全程純手打 從零開始入門】https://www.bilibili.com/video/BV1Mb411e7re?p=2&vd_source=ada7b122ae16cc583b4add52ad89fd5e
源代碼:
頭文件要記得宏定義和重定義,避免重復調用:
#ifndef _Timer0_h_//名字根據文件名定義即可
#define _Timer0_h_//聲明函數……#endif
?main.c
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"float T;//浮點數溫度//主函數
void main(){DS18B20_ConvertT();//上電先轉換一次溫度,防止第一次讀數據錯誤Delay(1000);//延時1秒啟動LCD_Init();//LCD1602初始化LCD_ShowString(1,1,"Temperature:");//LCD初始化顯示while(1){//無限循環DS18B20_ConvertT();//轉換一次溫度T=DS18B20_ReadT();//讀取溫度if(T<0){//如果溫度小于0LCD_ShowChar(2,1,'-');//顯示負號T=-T;//將溫度變為正數}else{//如果溫度大于等于0LCD_ShowChar(2,1,'+');//顯示正號}LCD_ShowNum(2,2,T,3);//顯示溫度整數部分LCD_ShowChar(2,5,'.');//顯示小數點LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//顯示溫度小數部分}
}
LCD1602.c
#include <STC89C5xRC.H>//引腳配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0//函數定義:
/*** @brief LCD1602延時函數,12MHz調用可延時1ms* @param 無* @retval 無*/
void LCD_Delay() //@11.0592MHz
{unsigned char i, j;i = 11;j = 190;do{while (--j);} while (--i);
}/*** @brief LCD1602寫命令* @param Command 要寫入的命令* @retval 無*/
void LCD_WriteCommand(unsigned char Command)
{LCD_RS=0;LCD_RW=0;LCD_DataPort=Command;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @brief LCD1602寫數據* @param Data 要寫入的數據* @retval 無*/
void LCD_WriteData(unsigned char Data)
{LCD_RS=1;LCD_RW=0;LCD_DataPort=Data;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @brief LCD1602設置光標位置* @param Line 行位置,范圍:1~2* @param Column 列位置,范圍:1~16* @retval 無*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{if(Line==1){LCD_WriteCommand(0x80|(Column-1));}else if(Line==2){LCD_WriteCommand(0x80|(Column-1+0x40));}
}/*** @brief LCD1602初始化函數* @param 無* @retval 無*/
void LCD_Init()
{LCD_WriteCommand(0x38);//八位數據接口,兩行顯示,5*7點陣LCD_WriteCommand(0x0c);//顯示開,光標關,閃爍關LCD_WriteCommand(0x06);//數據讀寫操作后,光標自動加一,畫面不動LCD_WriteCommand(0x01);//光標復位,清屏
}/*** @brief 在LCD1602指定位置上顯示一個字符* @param Line 行位置,范圍:1~2* @param Column 列位置,范圍:1~16* @param Char 要顯示的字符* @retval 無*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{LCD_SetCursor(Line,Column);LCD_WriteData(Char);
}/*** @brief 在LCD1602指定位置開始顯示所給字符串* @param Line 起始行位置,范圍:1~2* @param Column 起始列位置,范圍:1~16* @param String 要顯示的字符串* @retval 無*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=0;String[i]!='\0';i++){LCD_WriteData(String[i]);}
}/*** @brief 返回值=X的Y次方*/
int LCD_Pow(int X,int Y)
{unsigned char i;int Result=1;for(i=0;i<Y;i++){Result*=X;}return Result;
}/*** @brief 在LCD1602指定位置開始顯示所給數字* @param Line 起始行位置,范圍:1~2* @param Column 起始列位置,范圍:1~16* @param Number 要顯示的數字,范圍:0~65535* @param Length 要顯示數字的長度,范圍:1~5* @retval 無*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief 在LCD1602指定位置開始以有符號十進制顯示所給數字* @param Line 起始行位置,范圍:1~2* @param Column 起始列位置,范圍:1~16* @param Number 要顯示的數字,范圍:-32768~32767* @param Length 要顯示數字的長度,范圍:1~5* @retval 無*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{unsigned char i;unsigned int Number1;LCD_SetCursor(Line,Column);if(Number>=0){LCD_WriteData('+');Number1=Number;}else{LCD_WriteData('-');Number1=-Number;}for(i=Length;i>0;i--){LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief 在LCD1602指定位置開始以十六進制顯示所給數字* @param Line 起始行位置,范圍:1~2* @param Column 起始列位置,范圍:1~16* @param Number 要顯示的數字,范圍:0~0xFFFF* @param Length 要顯示數字的長度,范圍:1~4* @retval 無*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i,SingleNumber;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){SingleNumber=Number/LCD_Pow(16,i-1)%16;if(SingleNumber<10){LCD_WriteData(SingleNumber+'0');}else{LCD_WriteData(SingleNumber-10+'A');}}
}/*** @brief 在LCD1602指定位置開始以二進制顯示所給數字* @param Line 起始行位置,范圍:1~2* @param Column 起始列位置,范圍:1~16* @param Number 要顯示的數字,范圍:0~1111 1111 1111 1111* @param Length 要顯示數字的長度,范圍:1~16* @retval 無*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');}
}
LCD1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__//用戶調用函數:
void LCD_Init();//初始化
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);//顯示單個字符
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);//顯示字符串
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//顯示數字
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);//顯示帶符號數字
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//顯示十進制數字
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//顯示二進制數字#endif
DS18B20.c
//DS18B20.c#include <STC89C5xRC.H>
#include "OneWire.h"//DS18B20指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE/*** @brief DS18B20開始溫度變換* @param 無* @retval 無*/
void DS18B20_ConvertT(){OneWire_Init();//單總線初始化OneWire_SendByte(DS18B20_SKIP_ROM);//單總線發送1個字節(跳過ROM指令)OneWire_SendByte(DS18B20_CONVERT_T);//單總線發送1個字節(轉換溫度指令)
}/*** @brief DS18B20讀取溫度* @param 無* @retval 溫度數值*/
float DS18B20_ReadT(){unsigned char TLSB,TMSB;//溫度格式int Temp;//臨時變量float T;//浮點數溫度OneWire_Init();//單總線初始化OneWire_SendByte(DS18B20_SKIP_ROM);//單總線發送1個字節(跳過ROM指令)OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//單總線發送1個字節(轉換溫度指令)TLSB=OneWire_ReceiveByte();//低8位TMSB=OneWire_ReceiveByte();//高8位Temp=(TMSB<<8)|TLSB;//合并溫度16位T=Temp/16.0;//正確轉換溫度(浮點數)return T;//返回溫度值
}
DS18B20.h
//DS18B20.h#ifndef __DS18B20_H__
#define __DS18B20_H__float DS18B20_ReadT();
void DS18B20_ConvertT();#endif
OneWire.c
//OneWire.c#include <STC89C5xRC.H>//引腳定義
sbit OneWire_DQ=P3^7;/*** @brief 單總線初始化* @param 無* @retval 從機響應位,0為響應,1為未響應*/
unsigned char OneWire_Init(){unsigned char i;unsigned char AckBit;OneWire_DQ=1;OneWire_DQ=0;i = 247;while (--i);//Delay 500usOneWire_DQ=1;i = 32;while (--i);//Delay 70usAckBit=OneWire_DQ;i = 247;while (--i);//Delay 500usreturn AckBit;
}/*** @brief 單總線發送一位* @param Bit 要發送的位* @retval 無*/
void OneWire_SendBit(unsigned char Bit){unsigned char i;OneWire_DQ=0;i = 4;while (--i);//Delay 10usOneWire_DQ=Bit;i = 24;while (--i);//Delay 50usOneWire_DQ=1;
}/*** @brief 單總線接收一位* @param 無* @retval 讀取的位*/
unsigned char OneWire_ReceiveBit(){unsigned char i;unsigned char Bit;OneWire_DQ=0;i = 2;while (--i);//Delay 5usOneWire_DQ=1;i = 2;while (--i);//Delay 5usBit=OneWire_DQ;i = 24;while (--i);//Delay 50usreturn Bit;
}/*** @brief 單總線發送一個字節* @param Byte 要發送的字節* @retval 無*/
void OneWire_SendByte(unsigned char Byte){unsigned char i;for(i=0;i<8;i++){OneWire_SendBit(Byte&(0x01<<i));}
}/*** @brief 單總線接收一個字節* @param 無* @retval 接收的一個字節*/
unsigned char OneWire_ReceiveByte(){unsigned char i;unsigned char Byte=0x00;for(i=0;i<8;i++){if(OneWire_ReceiveBit()){(Byte|=0x01<<i);}}return Byte;
}
OneWire.h
//OneWire.h#ifndef __OneWire_H__
#define __OneWire_H__unsigned char OneWire_Init();
unsigned char OneWire_ReceiveByte();
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveBit();
void OneWire_SendBit(unsigned char Bit);#endif
Delay.c?
//Delay.c#include <STC89C5xRC.H>
#include <INTRINS.H>//延時函數
void Delay(unsigned int xms) //@11.0592MHz
{unsigned char i, j;while(xms){i = 2;j = 199;do{while (--j);} while (--i);xms--;}
}
?Delay.h
//Delay.h#ifndef __Delay_H__
#define __Delay_H__//延時函數頭文件
void Delay(unsigned int xms);
#endif
?代碼解析與教程:
?Dealy模塊
- 包含源代碼與頭文件,不需要知道怎么實現的會用即可,后續使用,直接將頭文件和源代碼拿過來用即可;
xms是定義的毫秒,1000毫秒就是1秒;模版生成的是1毫秒的,因此xms等于1000
?LCD1602模塊
- 包含源代碼與頭文件,不需要知道怎么實現的會用即可,后續使用,直接將頭文件和源代碼拿過來用即可;使用格式:(參考江科大的視頻素材)
?
- LCD1602相關重要知識:
- LCD1602有兩上下兩行顯示屏,每行各有16個小顯示屏,如上圖中的LCD_ShowString(1,3,"Hello"),第一個參數是第一行還是第二行,第2個參數是對應第幾行的第幾個小顯示屏,最后一個是輸出的東西,同理,到LCD_ShowNum(1,9,123,3)里,前三個和前面一樣,最后一個參數是顯示的位數,不夠就在前面補0,例如輸入1,參數為4,顯示就是0001,輸入23,參數為3,顯示就是023
?
- 上圖是LCD1602的開發原理模塊圖,由圖可知,P10-P17是控制顯示屏的重點;像矩陣那樣,S1,由P17和P13控制,同理,其他按鍵一樣,由于開發板限制,我們一般使用列遍歷來控制按鍵,例如:S1,S5,S9,S13他們四個共用P13,所以將其為1列,將P13設定為1,P17設定為0,S1就會亮,其他按鍵同理。
單總線模塊
- 包含源代碼與頭文件,需要知道怎么實現的,會用,后續使用,直接將頭文件和源代碼拿過來用即可;因為這部分比較難,輕度認識理解就行,越深越難
- 在此之前,我們要認識一下單總線:
- 單總線和上節課將的I2C總線有相似之處:
(51單片機)LCD顯示數據存儲(DS1302時鐘模塊教學)(LCD1602教程)(獨立按鍵教程)(延時函數教程)(I2C總線認識)(AT24C02認識)-CSDN博客
- 了解之后,di總線可以理解成一個傳輸協議,或者傳輸方式,規則;作用是將一個機器(主機)的數據傳輸到另一個機器(從機);來看DS18B20原理圖
由于可以看到I/O口是P37,因此單總線引腳就是P37
//引腳定義 sbit OneWire_DQ=P3^7;
- 通過這個東西,將主機(單片機)數據傳遞到從機(DS18B20);下面來看傳輸是怎么實現的
- 單總線傳輸時有時序結構,時序結構通過一條DQ線來控制,因此叫單總線
- 因為單總線只有一條線,需要通過不同的延時來實現數據傳輸,因此需要用STC-ISP的軟件延時功能,生成延時:
- 時序結構有5個小模塊,看圖:
這是初始化部分,中間那條線就是DQ通訊線,高電平是1(高一點的部分),低電平是0(低一點的部分);圖中可以看到DQ從1變0,然后480us后,釋放總線變1,然后15-60us后再變0,60-240us后變1;因此有:
/*** @brief 單總線初始化* @param 無* @retval 從機響應位,0為響應,1為未響應*/ unsigned char OneWire_Init(){unsigned char i;unsigned char AckBit;OneWire_DQ=1;OneWire_DQ=0;i = 247;while (--i);//Delay 500usOneWire_DQ=1;i = 32;while (--i);//Delay 70usAckBit=OneWire_DQ;i = 247;while (--i);//Delay 500usreturn AckBit; }
接下來是發送數據,發送一個bit,DQ先1后0,在1-15us內,讓DS18B20數據感應,然后總時間大于60us釋放總線變1,即可發送一個bit:
/*** @brief 單總線發送一位* @param Bit 要發送的位* @retval 無*/ void OneWire_SendBit(unsigned char Bit){unsigned char i;OneWire_DQ=0;i = 4;while (--i);//Delay 10usOneWire_DQ=Bit;i = 24;while (--i);//Delay 50usOneWire_DQ=1; }
發送bit是參數Bit,i是延時用的,重點來了:注意看圖中的時間先后;
?
接下來是接收bit,接收一個bit,DQ先1后0,在1-15us內變1,要在接近15us的時候讓DS18B20數據感應,然后總時間大于60us,即可接收一個bit:
/*** @brief 單總線接收一位* @param 無* @retval 讀取的位*/ unsigned char OneWire_ReceiveBit(){unsigned char i;unsigned char Bit;OneWire_DQ=0;i = 2;while (--i);//Delay 5usOneWire_DQ=1;i = 2;while (--i);//Delay 5usBit=OneWire_DQ;i = 24;while (--i);//Delay 50usreturn Bit; }
接收bit是無參數,返回值是bit,i是延時用的,重點來了:注意看圖中的先后順序,而且要在15us附近數據感應;
?![]()
接下來是發送一個字節和接收一個字節。
?/*** @brief 單總線發送一個字節* @param Byte 要發送的字節* @retval 無*/ void OneWire_SendByte(unsigned char Byte){unsigned char i;for(i=0;i<8;i++){OneWire_SendBit(Byte&(0x01<<i));} }
/*** @brief 單總線接收一個字節* @param 無* @retval 接收的一個字節*/ unsigned char OneWire_ReceiveByte(){unsigned char i;unsigned char Byte=0x00;for(i=0;i<8;i++){if(OneWire_ReceiveBit()){(Byte|=0x01<<i);}}return Byte; }
低位在前,因此左移,&(有0即0)的操作是獲取當前位,|(有1即1)的操作當OneWire_ReceiveBit不為0時,將當前位賦值給Byte
DS18B20模塊
- 包含源代碼與頭文件,需要知道怎么實現的,會用,后續使用,直接將頭文件和源代碼拿過來用即可;因為這部分比較難,輕度認識理解就行,越深越難
- DS18B20是溫度傳感器,可以理解上述中所提到的從機,主機就是單片機
DS18B20有字節地址操作,作用是進行一定的操作,ROM和功能操作:
- 了解之后,來看數據幀,也就是數據是怎么在單總線中傳輸的:
首先可以看到溫度變換的操作,先初始化,然后發送跳過ROM的字節地址進行跳過ROM,然后發送溫度變換的字節地址,進行溫度轉換
/*** @brief DS18B20開始溫度變換* @param 無* @retval 無*/ void DS18B20_ConvertT(){OneWire_Init();//單總線初始化OneWire_SendByte(DS18B20_SKIP_ROM);//單總線發送1個字節(跳過ROM指令)OneWire_SendByte(DS18B20_CONVERT_T);//單總線發送1個字節(轉換溫度指令) }
首先可以看到溫度讀取的操作,先初始化,然后發送跳過ROM的字節地址進行跳過ROM,然后發送溫度讀取的字節地址,進行讀暫存器和連續的讀操作
/*** @brief DS18B20讀取溫度* @param 無* @retval 溫度數值*/ float DS18B20_ReadT(){unsigned char TLSB,TMSB;//溫度格式int Temp;//臨時變量float T;//浮點數溫度OneWire_Init();//單總線初始化OneWire_SendByte(DS18B20_SKIP_ROM);//單總線發送1個字節(跳過ROM指令)OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//單總線發送1個字節(轉換溫度指令)TLSB=OneWire_ReceiveByte();//低8位TMSB=OneWire_ReceiveByte();//高8位Temp=(TMSB<<8)|TLSB;//合并溫度16位T=Temp/16.0;//正確轉換溫度(浮點數)return T;//返回溫度值 }
從上圖可以看出,TLSB和TMSB溫度格式,是8位的,MS的前5為都是符號位。將它倆合到一塊就是溫度。
?main模塊
- 注釋寫的很清楚,這里不做解釋了
#include <STC89C5xRC.H> #include "LCD1602.h" #include "DS18B20.h" #include "Delay.h"float T;//浮點數溫度//主函數 void main(){DS18B20_ConvertT();//上電先轉換一次溫度,防止第一次讀數據錯誤Delay(1000);//延時1秒啟動LCD_Init();//LCD1602初始化LCD_ShowString(1,1,"Temperature:");//LCD初始化顯示while(1){//無限循環DS18B20_ConvertT();//轉換一次溫度T=DS18B20_ReadT();//讀取溫度if(T<0){//如果溫度小于0LCD_ShowChar(2,1,'-');//顯示負號T=-T;//將溫度變為正數}else{//如果溫度大于等于0LCD_ShowChar(2,1,'+');//顯示正號}LCD_ShowNum(2,2,T,3);//顯示溫度整數部分LCD_ShowChar(2,5,'.');//顯示小數點LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//顯示溫度小數部分} }
?注:該代碼是本人自己所寫,可能不夠好,不夠簡便,歡迎大家指出我的不足之處。如果遇見看不懂的地方,可以在評論區打出來,進行討論,或者聯系我。上述內容全是我自己理解的,如果你有別的想法,或者認為我的理解不對,歡迎指出!!!如果可以,可以點一個免費的贊支持一下嗎?謝謝各位彥祖亦菲!!!!!