?前言:
本次Timer0模塊改裝了一下,注意!!!今天只是簡單的實現一下,明天用次功能顯示遙控密碼鎖
演示視頻:
在審核
源代碼:
如上圖將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 "Delay.h"
#include "LCD1602.h"
#include "IR.h"unsigned char Mode=-1;//數字
unsigned char Key1,Key2,Key3,Key4,Key;//
unsigned char Num=0;//數字
unsigned char Command;//獲取紅外線命令void main(){LCD_Init();//初始化LCDLCD_ShowString(1,1,"Hello!");//初始化顯示LCDLCD_ShowNum(2,1,0,4);//初始密碼數字IR_Init();//初始化紅外線模塊while(1){if(IR_GetDataFlag()){//接收Command=IR_GetCommand();if(Command==IR_0){Num=0;}if(Command==IR_1){Num=1;}if(Command==IR_2){Num=2;}if(Command==IR_3){Num=3;}if(Command==IR_4){Num=4;}if(Command==IR_5){Num=5;}if(Command==IR_6){Num=6;}if(Command==IR_7){Num=7;}if(Command==IR_8){Num=8;}if(Command==IR_9){Num=9;}LCD_ShowNum(2,16,Num,1);//顯示數字// if(Command==IR_POWER){
// Mode=-Mode;
// if(Mode==1){
//
// LCD_ShowString(1,1," ");
// LCD_ShowString(1,1,"ON");//初始化顯示LCD
// while(1){
//
// Key1=Num;
// LCD_ShowNum(2,4,Key1,1);//顯示數字
// if(Command==IR_RPT){Key1=0;}
// LCD_ShowNum(2,4,Key1,1);//顯示數字
// if(Key1){
// Key2=Num;
// LCD_ShowNum(2,3,Key2,1);//顯示數字
// if(Command==IR_RPT){Key2=0;}
// LCD_ShowNum(2,3,Key2,1);//顯示數字
// }
// if(Key2){
// Key3=Num;
// LCD_ShowNum(2,2,Key3,1);//顯示數字
// if(Command==IR_RPT){Key3=0;}
// LCD_ShowNum(2,2,Key3,1);//顯示數字
// }
// if(Key3){
// Key4=Num;
// LCD_ShowNum(2,1,Key4,1);//顯示數字
// if(Command==IR_RPT){Key4=0;}
// LCD_ShowNum(2,1,Key4,1);//顯示數字
// }
// Key=Key1+Key2*10+Key3*100+Key4*1000;
// if(Command==IR_USD){
// if(Key==0301){
// LCD_ShowString(1,12,"Sure");//初始化顯示LCD
// }else{
// LCD_ShowString(1,12,"Eorr");//初始化顯示LCD
// }
// break;
// }
// }
// }else{
// LCD_ShowString(1,1," ");
// LCD_ShowString(1,1,"OFF");//初始化顯示LCD
// LCD_ShowNum(2,1,0,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
?Delay.c
void Delay(unsigned int xms)
{unsigned char i, j;while(xms--){i = 2;j = 239;do{while (--j);} while (--i);}
}
?Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__void Delay(unsigned int xms);#endif
Int0.c?
#include <STC89C5xRC.H>void Int0_Init(){IT0=1;IE0=0;EX0=1;EA=1;PX0=1;
}外部中斷函數模版
//void Int0_Routine() interrupt 0{
// Num++;
//}
Int0.h
#ifndef __Int0_H__
#define __Int0_H__void Int0_Init();#endif
?Timer0.c
#include <REGX52.H>/*** @brief 定時器0初始化* @param 無* @retval 無*/
void Timer0_Init(void)
{TMOD &= 0xF0; //設置定時器模式TMOD |= 0x01; //設置定時器模式TL0 = 0; //設置定時初值TH0 = 0; //設置定時初值TF0 = 0; //清除TF0標志TR0 = 0; //定時器0不計時
}/*** @brief 定時器0設置計數器值* @param Value,要設置的計數器值,范圍:0~65535* @retval 無*/
void Timer0_SetCounter(unsigned int Value)
{TH0=Value/256;TL0=Value%256;
}/*** @brief 定時器0獲取計數器值* @param 無* @retval 計數器值,范圍:0~65535*/
unsigned int Timer0_GetCounter(void)
{return (TH0<<8)|TL0;
}/*** @brief 定時器0啟動停止控制* @param Flag 啟動停止標志,1為啟動,0為停止* @retval 無*/
void Timer0_Run(unsigned char Flag)
{TR0=Flag;
}
??Timer0.h
#ifndef _Timer0_h_
#define _Timer0_h_void Timer0_Init();
void Timer0_SetCounter(unsigned int Value);
unsigned int Timer0_GetCounter();
void Timer0_Run(unsigned char Flag);#endif
??IR.c
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Int0.h"unsigned int IR_Time;//外部中斷計時
unsigned char IR_State;//紅外遙控現階段狀態unsigned char IR_Data[4];//數據(共32位,分皮來儲存和表示)
unsigned char IR_pData;//分批數據0-31,用來IR_Data[IR_pData]表示數據unsigned char IR_DataFlag;//數據幀信號
unsigned char IR_RepeatFlag;//連發幀信號
unsigned char IR_Address;//地址
unsigned char IR_Command;//命令/*** @brief 紅外遙控初始化* @param 無* @retval 無*/
void IR_Init(void)
{Timer0_Init();Int0_Init();
}/*** @brief 紅外遙控獲取收到數據幀標志位* @param 無* @retval 是否收到數據幀,1為收到,0為未收到*/
unsigned char IR_GetDataFlag(void)
{if(IR_DataFlag){IR_DataFlag=0;return 1;}return 0;
}/*** @brief 紅外遙控獲取收到連發幀標志位* @param 無* @retval 是否收到連發幀,1為收到,0為未收到*/
unsigned char IR_GetRepeatFlag(void)
{if(IR_RepeatFlag){IR_RepeatFlag=0;return 1;}return 0;
}/*** @brief 紅外遙控獲取收到的地址數據* @param 無* @retval 收到的地址數據*/
unsigned char IR_GetAddress(void)
{return IR_Address;
}/*** @brief 紅外遙控獲取收到的命令數據* @param 無* @retval 收到的命令數據*/
unsigned char IR_GetCommand(void)
{return IR_Command;
}//外部中斷0中斷函數,下降沿觸發執行
void Int0_Routine(void) interrupt 0
{if(IR_State==0) //狀態0,空閑狀態{Timer0_SetCounter(0); //定時計數器清0Timer0_Run(1); //定時器啟動IR_State=1; //置狀態為1}else if(IR_State==1) //狀態1,等待Start信號或Repeat信號{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為13.5ms,則接收到了Start信號(判定值在12MHz晶振下為13500,在11.0592MHz晶振下為12442)if(IR_Time>12442-500 && IR_Time<12442+500){IR_State=2; //置狀態為2}//如果計時為11.25ms,則接收到了Repeat信號(判定值在12MHz晶振下為11250,在11.0592MHz晶振下為10368)else if(IR_Time>10368-500 && IR_Time<10368+500){IR_RepeatFlag=1; //置收到連發幀標志位為1Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}else //接收出錯{IR_State=1; //置狀態為1}}else if(IR_State==2) //狀態2,接收數據{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為1120us,則接收到了數據0(判定值在12MHz晶振下為1120,在11.0592MHz晶振下為1032)if(IR_Time>1032-500 && IR_Time<1032+500){IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //數據對應位清0IR_pData++; //數據位置指針自增}//如果計時為2250us,則接收到了數據1(判定值在12MHz晶振下為2250,在11.0592MHz晶振下為2074)else if(IR_Time>2074-500 && IR_Time<2074+500){IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //數據對應位置1IR_pData++; //數據位置指針自增}else //接收出錯{IR_pData=0; //數據位置指針清0IR_State=1; //置狀態為1}if(IR_pData>=32) //如果接收到了32位數據{IR_pData=0; //數據位置指針清0if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //數據驗證{IR_Address=IR_Data[0]; //轉存數據IR_Command=IR_Data[2];IR_DataFlag=1; //置收到連發幀標志位為1}Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}}
}
?IR.h
#ifndef __IR_H__
#define __IR_H__//紅外遙控對應命令
#define IR_POWER 0x45
#define IR_MODE 0x46
#define IR_MUTE 0x47
#define IR_START_STOP 0x44
#define IR_PREVIOUS 0x40
#define IR_NEXT 0x43
#define IR_EQ 0x07
#define IR_VOL_MINUS 0x15
#define IR_VOL_ADD 0x09
#define IR_0 0x16
#define IR_RPT 0x19
#define IR_USD 0x0D
#define IR_1 0x0C
#define IR_2 0x18
#define IR_3 0x5E
#define IR_4 0x08
#define IR_5 0x1C
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4Avoid IR_Init();
unsigned char IR_GetDataFlag();
unsigned char IR_GetRepeatFlag();
unsigned char IR_GetAddress();
unsigned char IR_GetCommand();#endif
?代碼解析與教程:
?Timer0模塊
- 包含源代碼與頭文件,需要知道怎么實現,會用
- 51單片機的定時器和計數器十分重要,要理解怎么用,要知道原理是什么,要結合原理圖來分析怎么做,先看代碼
#include <STC89C5xRC.H>//void Timer0_Init() //{ // TF0=0;TR0=1;//TCON,寄存器 // //TMOD=0x01;//0000 0001,寄存器 // TMOD=TMOD&0xF0;//把TMOD的低四位清零,高四位不變,方便使用兩個定時器 // TMOD=TMOD&0x01;//把TMOD的最低位置1,高四位不變,方便使用兩個定時器 // TH0=64535/256;//高電位,寄存器,1毫秒 // TL0=64535%256;//低電位,寄存器,1毫秒 // ET0=1;EA=1;PT0=0;//打開中斷開關 // //} //定時器0初始化函數 void Timer0_Init() //1毫秒@11.0592MHz { // AUXR &= 0x7F; //定時器時鐘12T模式TMOD &= 0xF0; //設置定時器模式TMOD |= 0x01; //設置定時器模式TL0 = 0x66; //設置定時初值TH0 = 0xFC; //設置定時初值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時ET0=1;//允許中斷EA=1;//允許總中斷PT0=0;//低優先級 }中斷程序函數 //中斷函數模版 //void Timer0_Routine() interrupt 1 //{ // static unsigned int T0Count; // TL0 = 0x66; //設置定時初值 // TH0 = 0xFC; //設置定時初值 // T0Count++; // if(T0Count>=1000){ // T0Count=0; // //下面是代碼區 // } //}
- 最上面注釋掉的代碼是要求理解的;中間的代碼是STC-ISP軟件生成的;最下面的代碼是中斷函數模版,拿到main.c中可直接使用,但是也要了解原理:
定時器/計數器、中斷教程(重點!!!!)
- 首先,要理解原理,會認原理圖:
(本篇均是我自己理解的,只是幫助大家理解,若像深學深究,請去自行找資源,如有不對,希望大家指出)
先看官方解釋:
- 紅色部分是定時器,作用是自己設定一個最大值,讓計數器達到時,完成什么什么,比如執行中斷
- 黃色部分是計數器,作用是自己設定,讓其變化,比如+1,毫秒,微妙,完成時繼續怎么怎
- 藍色部分是中斷器,中斷當前執行,執行自己設定的東西,中斷這部分可以嵌套,像if函數一樣,低優先級讓高優先級
他們三者的關系非常微妙,僅僅相連,相輔相成:
- 定時器就是設定一個時間,計數器開始計時,到點了中斷器開始執行;舉個例子:你訂一個10.00的鬧鐘(定時器)提醒你起床,時間一點一點的過去(計數器),10.00的時候鬧鐘提醒你(定時器),隨后你開始起床(中斷器);那么他們是怎么實現的呢
?定時器/計數器(模式一)
- 先看原理圖:
????
- 相關寄存器:
????
- 官方解釋
????
????
- 模式一官方解釋
????
- 下面開始來解釋我的理解(再看原理圖):
????
- 序號1是非門:反向輸出,例如:GATE是1,輸出0,是0,輸入1。
- 序號2是或門:符號為梯形(或弧形缺口),邏輯上滿足 “有 1 出 1,全 0 出 0”。
- 序號3是與門:符號為矩形缺口,邏輯上滿足 “全 1 出 1,有 0 出 0”。
????
1,2,3序號均是TMOD里的,請看原理圖:????
- 代碼TMOD=0x01,是16進制,轉化為二進制為0000 0001,前(高)四位對應著定時器1;后(低)四位對應著定時器0;本代碼使用的是定時器0, 0001分別對應GATE,C/T,M1,M0,由上面的官方解釋可得,M1=0,M0=1時,就是使用并啟動本寄存器。
但是這樣定義有弊端,也就是定時器1和定時器0不能一起使用,因次,使用下面的代碼:TMOD &=0xF0,也就是TMOD = TMOD & 0xF0(1111 0000)。看不懂沒關系,舉個例子:1010 0101 &? 1111 0000 = 1010 0000,也就是有0出0;
1010 0101 |? 1111 0000 = 1111?0101,也就是有1出1;因此使用代碼:就可以定義定時器0;
TMOD &= 0xF0; //設置定時器模式
TMOD |= 0x01; //設置定時器模式
- 因此,當GATE=0時,通過序號1,輸出1;然后通過序號2,輸出1;因此,只需要將TR0設定成1,輸出就是1,就可以啟動寄存器了。
- 序號4是高電位和低電位寄存器:用來設置定時初值,如圖:
????
- 先解釋上面的代碼,可幫助理解,下面的代碼可以不理解(后續可生成):TH0和TL0,最大位就是65535,為了分開儲存,用TH0和TL0分別儲存,例如:現在有一個數123,但是一個盒子只能裝進2位數,因此分成123/100=1和123%100=23儲存,同理身為16進制,就要用256來做除數;代碼中設定為64535的目的是因為1000毫秒就是1秒,65535-64535=1000,用來表示1秒,計時器每次加1秒。
- 序號7.8(圖中忘記標了(TR0))是TCON(定時器控制)寄存器:TF0=0時,可以理解成初始化;TR0=1時,表示允許計時;反之兩個就是反義理解
- 序號5.6是TMOD(定時器模式)寄存器:用來控制定時器和計數器的模式,本篇只講用的多的模式一
?中斷器
- 先看原理圖:
????
這里我們定義好定時器0后,TF0=1,ET0=1,打開中斷,隨后PT0=0,接入低級優先:理解上面的東西后,再看中斷函數:
????
運用靜態局部變量T0Count,來表示定時區間,達到1000毫秒(1秒)后重新執行該函數
????
P20行代碼是代碼區,也就是放你想每1秒就重復執行的代碼。
?Dealy模塊
- 包含源代碼與頭文件,不需要知道怎么實現的會用即可,后續使用,直接將頭文件和源代碼拿過來用即可;
?
xms是定義的毫秒,1000毫秒就是1秒;模版生成的是1毫秒的,因此xms等于1000
Int0和Timer0外部中斷模塊?
- 先來了解一下圖:
之前講過定時器中斷就是第二個,今天來講一下第一個0,外部中斷,對應引腳是P32;
先初始化:
上邊這條INT0全通就可以初始化成功:#include <STC89C5xRC.H>void Int0_Init(){IT0=1;IE0=0;EX0=1;EA=1;PX0=1; }外部中斷函數模版 //void Int0_Routine() interrupt 0{ // Num++; //}
下面配合Timer0來實現外部中斷:
?void Timer0_Init(void) {TMOD &= 0xF0; //設置定時器模式TMOD |= 0x01; //設置定時器模式TL0 = 0; //設置定時初值TH0 = 0; //設置定時初值TF0 = 0; //清除TF0標志TR0 = 0; //定時器0不計時 }
將初值TL0,TH0設定為0,TR0定時器不計時設置為0;
設置定時器0計數器值:
?/*** @brief 定時器0設置計數器值* @param Value,要設置的計數器值,范圍:0~65535* @retval 無*/ void Timer0_SetCounter(unsigned int Value) {TH0=Value/256;TL0=Value%256; }
獲取定時器0計數器值:
?/*** @brief 定時器0獲取計數器值* @param 無* @retval 計數器值,范圍:0~65535*/ unsigned int Timer0_GetCounter(void) {return (TH0<<8)|TL0; }
控制定時器0的啟動和關閉:
?/*** @brief 定時器0啟動停止控制* @param Flag 啟動停止標志,1為啟動,0為停止* @retval 無*/ void Timer0_Run(unsigned char Flag) {TR0=Flag; }
現在就完成了外部中斷的配置,在外部函數中書寫代碼即可;
?LCD1602模塊
??
- LCD1602相關重要知識:
- LCD1602有兩上下兩行顯示屏,每行各有16個小顯示屏,如上圖中的LCD_ShowString(1,3,"Hello"),第一個參數是第一行還是第二行,第2個參數是對應第幾行的第幾個小顯示屏,最后一個是輸出的東西,同理,到LCD_ShowNum(1,9,123,3)里,前三個和前面一樣,最后一個參數是顯示的位數,不夠就在前面補0,例如輸入1,參數為4,顯示就是0001,輸入23,參數為3,顯示就是023
- 先看原理圖
?
?
?
VO是調節顯示亮度的,讓你的顯示屏顯示更清楚。
RS,WR,EN(E)是引腳定義,看單片機核心,對應著P25,P26,P27:(下述所有代碼WR都寫成了RW,不過不影響,引腳對就行)
再看D0-D7,也就是數據,看單片機核心可知,對應著P00-P07,也就是P0,因此宏定義LCD_DataPort=P0;
//引腳配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0
?
LCD1602,屏幕是16*2的,每一個小格格就是CGRAM+CGROM(字模庫)組成,這個東西就是顯示數據的,這些數據是DDPAN(數據顯示區)來存儲的,然后映射到屏幕上,一一對應;但是DDRAM有40*2個小格格,因此,LCD1602,其實可以滾動顯示;然后DDRAM是由控制器來決定的,AC就是小格格的地址;
DDRAM部分
?
看第8個DDRAM部分:前兩個是00
?
上圖就是DDRAM的地址,也就是小格格的,如果是第一行顯示,A6前面那個等于0就行了,A6前面等于1就是第二行顯示,剩下的A6-A0就是小格格的地址,比如第一個,0000 0000,因此簡寫成00H,第二行第一個,0100?0000,0x40,簡寫40H,以此類推;
CGRAM+CGROM部分
?
這部分可以理解成二維數組,比如你想顯示A,在圖中找到A,A的列是0100,行是0001,把列放到行的前面組成二進制就是他的地址,0100 0001,就是0x41;
?
來看編碼表,A確實是0x41;
- 掌握上述知識,就可以寫顯示數據了,這里只講寫數據/指令:
?
寫指令/數據部分
可以看到,寫入一個數據要將RS變化(默認為1),R/W變化(默認為1),EN(E)也要變化,因此有:
/*** @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(); }
注意:EN(E)操作的時候需要時間,數據讀取需要時間,因此延時1ms;RS在寫數據和指令的時候不一樣,前者1,后者0;
屏幕顯示部分
?
由上控制指令,完成操作,就可以初始化屏幕:
/*** @brief LCD1602初始化函數* @param 無* @retval 無*/ void LCD_Init() {LCD_WriteCommand(0x38);//八位數據接口,兩行顯示,5*7點陣LCD_WriteCommand(0x0c);//顯示開,光標關,閃爍關LCD_WriteCommand(0x06);//數據讀寫操作后,光標自動加一,畫面不動LCD_WriteCommand(0x01);//光標復位,清屏 }
然后是顯示字符的操作,先用指令確定AC地址:
/*** @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 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); }
確定AC地址,然后用函數直接寫數據;
字符串:
/*** @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]);} }
這部分就是C語言程序部分,看看代碼很好理解
數字:
/*** @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');} }
這部分也是C語言代碼部分,將數字各位分開,例如:
?
這樣操作。
其他部分:
/*** @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');} }
和數字部分差不多,也是C語言程序,認真看代碼即可;
?IR紅外遙控模塊
- 先來認識一下
初始化和定義變量:
使用前先初始化外部中斷模塊,和定義變量:
/*** @brief 紅外遙控初始化* @param 無* @retval 無*/ void IR_Init(void) {Timer0_Init();Int0_Init(); }
unsigned int IR_Time;//外部中斷計時 unsigned char IR_State;//紅外遙控現階段狀態unsigned char IR_Data[4];//數據(共32位,分皮來儲存和表示) unsigned char IR_pData;//分批數據0-31,用來IR_Data[IR_pData]表示數據unsigned char IR_DataFlag;//數據幀信號 unsigned char IR_RepeatFlag;//連發幀信號 unsigned char IR_Address;//地址 unsigned char IR_Command;//命令
檢查紅外線標志位(是否接收成功):
/*** @brief 紅外遙控獲取收到數據幀標志位* @param 無* @retval 是否收到數據幀,1為收到,0為未收到*/ unsigned char IR_GetDataFlag(void) {if(IR_DataFlag){IR_DataFlag=0;return 1;}return 0; }/*** @brief 紅外遙控獲取收到連發幀標志位* @param 無* @retval 是否收到連發幀,1為收到,0為未收到*/ unsigned char IR_GetRepeatFlag(void) {if(IR_RepeatFlag){IR_RepeatFlag=0;return 1;}return 0; }
獲取紅外線現在的地址和命令:
/*** @brief 紅外遙控獲取收到的地址數據* @param 無* @retval 收到的地址數據*/ unsigned char IR_GetAddress(void) {return IR_Address; }/*** @brief 紅外遙控獲取收到的命令數據* @param 無* @retval 收到的命令數據*/ unsigned char IR_GetCommand(void) {return IR_Command; }
配合外部中斷(重點!!!):
//外部中斷0中斷函數,下降沿觸發執行 void Int0_Routine(void) interrupt 0 {if(IR_State==0) //狀態0,空閑狀態{Timer0_SetCounter(0); //定時計數器清0Timer0_Run(1); //定時器啟動IR_State=1; //置狀態為1}else if(IR_State==1) //狀態1,等待Start信號或Repeat信號{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為13.5ms,則接收到了Start信號(判定值在12MHz晶振下為13500,在11.0592MHz晶振下為12442)if(IR_Time>12442-500 && IR_Time<12442+500){IR_State=2; //置狀態為2}//如果計時為11.25ms,則接收到了Repeat信號(判定值在12MHz晶振下為11250,在11.0592MHz晶振下為10368)else if(IR_Time>10368-500 && IR_Time<10368+500){IR_RepeatFlag=1; //置收到連發幀標志位為1Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}else //接收出錯{IR_State=1; //置狀態為1}}else if(IR_State==2) //狀態2,接收數據{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為1120us,則接收到了數據0(判定值在12MHz晶振下為1120,在11.0592MHz晶振下為1032)if(IR_Time>1032-500 && IR_Time<1032+500){IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //數據對應位清0IR_pData++; //數據位置指針自增}//如果計時為2250us,則接收到了數據1(判定值在12MHz晶振下為2250,在11.0592MHz晶振下為2074)else if(IR_Time>2074-500 && IR_Time<2074+500){IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //數據對應位置1IR_pData++; //數據位置指針自增}else //接收出錯{IR_pData=0; //數據位置指針清0IR_State=1; //置狀態為1}if(IR_pData>=32) //如果接收到了32位數據{IR_pData=0; //數據位置指針清0if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //數據驗證{IR_Address=IR_Data[0]; //轉存數據IR_Command=IR_Data[2];IR_DataFlag=1; //置收到連發幀標志位為1}Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}} }
紅外線遙控有三個狀態:
其中State=0是空閑狀態;State=1是等待Start信號或Repeat信號;State=2是接收數據;
空閑狀態的時候,要把定時器0清空并打開(IR_State=0):
if(IR_State==0) //狀態0,空閑狀態{Timer0_SetCounter(0); //定時計數器清0Timer0_Run(1); //定時器啟動IR_State=1; //置狀態為1}
State=1是等待Start信號或Repeat信號,要判斷當前定時器0計數器的值在哪個區間來判斷接收Data數據區間,如圖:
如果定時器0計數器在9+4.5=13.5ms,13500us的時候,就是Start(接收數據)狀態,于是準備接收Data,然后將State=2,(置為狀態2);在9+2.25=11.25ms,11250us的時候,就是Repeat(連續接收數據)狀態,這時Data已經被接收了,只需要重新從State=0開始即可重復接收;如果接收失敗,將State=1,放棄這次數據重新接收即可:
else if(IR_State==1) //狀態1,等待Start信號或Repeat信號{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為13.5ms,則接收到了Start信號(判定值在12MHz晶振下為13500,在11.0592MHz晶振下為12442)if(IR_Time>12442-500 && IR_Time<12442+500){IR_State=2; //置狀態為2}//如果計時為11.25ms,則接收到了Repeat信號(判定值在12MHz晶振下為11250,在11.0592MHz晶振下為10368)else if(IR_Time>10368-500 && IR_Time<10368+500){IR_RepeatFlag=1; //置收到連發幀標志位為1Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}else //接收出錯{IR_State=1; //置狀態為1}}
?State=2是接收數據;如果狀態1通過,進入狀態2(接收數據);先獲取上次的中斷定時器0計數器值,判斷接收的是高電平還是低電平;
如果定時器0計數器在560=560us=1120us的時候,就是接收低電平,然后將數據(IR_Data[])對應位置為0,然后數組數據(IR_pData)增加;在560+1690us=2250us,就是接收高電平,然后將數據(IR_Data[])對應位置為0,然后數組數據(IR_pData)增加;如果接收失敗,將數組指針(IR_pData)和State=1,放棄這次數據重新接收即可:
else if(IR_State==2) //狀態2,接收數據{IR_Time=Timer0_GetCounter(); //獲取上一次中斷到此次中斷的時間Timer0_SetCounter(0); //定時計數器清0//如果計時為1120us,則接收到了數據0(判定值在12MHz晶振下為1120,在11.0592MHz晶振下為1032)if(IR_Time>1032-500 && IR_Time<1032+500){IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //數據對應位清0IR_pData++; //數據位置指針自增}//如果計時為2250us,則接收到了數據1(判定值在12MHz晶振下為2250,在11.0592MHz晶振下為2074)else if(IR_Time>2074-500 && IR_Time<2074+500){IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //數據對應位置1IR_pData++; //數據位置指針自增}else //接收出錯{IR_pData=0; //數據位置指針清0IR_State=1; //置狀態為1}
然后判斷數據是否達到32位:
如果達到就進行數據驗證,驗證方法是看數據第一位和第二位的補碼是否相等,看數據第三位和第四位的補碼是否相等;驗證通過的話,轉存數據:
Address對應IR_Data[0];Command對應IR_Data[2],存儲之后,IR_DataFlag=1;?置收到數據幀標志位為1
if(IR_pData>=32) //如果接收到了32位數據{IR_pData=0; //數據位置指針清0if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //數據驗證{IR_Address=IR_Data[0]; //轉存數據IR_Command=IR_Data[2];IR_DataFlag=1; //置收到連發幀標志位為1}Timer0_Run(0); //定時器停止IR_State=0; //置狀態為0}
然后停止定時器0,置紅外線狀態為空閑狀態0;
?注:該代碼是本人自己所寫,可能不夠好,不夠簡便,歡迎大家指出我的不足之處。如果遇見看不懂的地方,可以在評論區打出來,進行討論,或者聯系我。上述內容全是我自己理解的,如果你有別的想法,或者認為我的理解不對,歡迎指出!!!如果可以,可以點一個免費的贊支持一下嗎?謝謝各位彥祖亦菲!!!!!