共用體除非必要,否則我們不推薦使用,枚舉的用法比較簡單,在本書 19 章的項(xiàng)目實(shí)踐中有很好的示例,這節(jié)課我們先來練習(xí)一下結(jié)構(gòu)體的使用。下邊這個程序的功能是一個帶日期的電子鐘,相當(dāng)于一個簡易萬年歷了,并且加入了按鍵調(diào)時功能。學(xué)有余力的同學(xué)看到這里,不妨先不看我們提供的代碼,自己寫寫試試。如果能夠獨(dú)立寫一個按鍵可調(diào)的萬年歷程序,單片機(jī)可以說基本入門了。如果自己還不能夠獨(dú)立完成這個程序,那么還是老規(guī)矩,先抄并且理解,而后自己獨(dú)立默寫出來,并且要邊默寫邊理解。
本例直接忽略了星期這項(xiàng)內(nèi)容,通過上、下、左、右、回車、ESC 這 6 個按鍵可以調(diào)整時間。這也是一個具有綜合練習(xí)性質(zhì)的實(shí)例,雖然在功能實(shí)現(xiàn)上沒有多少難度,但要進(jìn)行的操作卻比較多而且煩瑣,同學(xué)們可以從中體會到把繁雜的功能實(shí)現(xiàn)分解為一步步函數(shù)操作的必要性以及方便靈活性。簡單說一下這個程序的幾個要點(diǎn),方便大家閱讀理解程序。
把 DS1302 的底層操作封裝為一個 DS1302.c 文件,對上層應(yīng)用提供基本的實(shí)時時間的操作接口,這個文件也是我們的又一個功能模塊了,我們的積累也越來越多了。
定義一個結(jié)構(gòu)體類型 sTime 用來封裝日期時間的各個元素,又用該結(jié)構(gòu)體定義了一個時間緩沖區(qū)變量 bufTime 來暫存從 DS1302 讀出的時間和設(shè)置時間時的設(shè)定值。需要注意的是在其它文件中要使用這個結(jié)構(gòu)體變量時,必須首先再聲明一次 sTime 類型;
定義一個變量 setIndex 來控制當(dāng)前是否處于設(shè)置時間的狀態(tài),以及設(shè)置時間的哪一位,該值為 0 就表示正常運(yùn)行,1~12 分別代表可以修改日期時間的 12 個位;
由于這節(jié)課的程序功能要進(jìn)行時間調(diào)整,用到了 1602 液晶的光標(biāo)功能,添加了設(shè)置光標(biāo)的函數(shù),我們要改變哪一位的數(shù)字,就在 1602 對應(yīng)位置上進(jìn)行光標(biāo)閃爍,所以 Lcd1602.c在之前文件的基礎(chǔ)上添加了兩個控制光標(biāo)的函數(shù);
時間的顯示、增減、設(shè)置移位等上層功能函數(shù)都放在 main.c 中來實(shí)現(xiàn),當(dāng)按鍵需要這些函數(shù)時則在按鍵文件中做外部聲明,這樣做是為了避免一組功能函數(shù)分散在不同的文件內(nèi)而使程序顯得凌亂。
/***************************DS1302.c 文件程序源代碼*****************************/
#include 《reg52.h》
sbit DS1302_CE = P1^7;
sbit DS1302_CK = P3^5;
sbit DS1302_IO = P3^4;
struct sTime { //日期時間結(jié)構(gòu)體定義
unsigned int year; //年
unsigned char mon; //月
unsigned char day; //日
unsigned char hour; //時
unsigned char min; //分
unsigned char sec; //秒
unsigned char week; //星期
};
/* 發(fā)送一個字節(jié)到 DS1302 通信總線上 */
void DS1302ByteWrite(unsigned char dat){
unsigned char mask;
for (mask=0x01; mask!=0; mask《《=1){ //低位在前,逐位移出
if ((mask&dat) != 0){ //首先輸出該位數(shù)據(jù)
DS1302_IO = 1;
}else{
DS1302_IO = 0;
}
DS1302_CK = 1; //然后拉高時鐘
DS1302_CK = 0; //再拉低時鐘,完成一個位的操作
}
DS1302_IO = 1; //最后確保釋放 IO 引腳
}
/* 由 DS1302 通信總線上讀取一個字節(jié) */
unsigned char DS1302ByteRead(){
unsigned char mask;
unsigned char dat = 0;
for (mask=0x01; mask!=0; mask《《=1){ //低位在前,逐位讀取
if (DS1302_IO != 0){ //首先讀取此時的 IO 引腳,并設(shè)置 dat 中的對應(yīng)位
dat |= mask;
}
DS1302_CK = 1; //然后拉高時鐘
DS1302_CK = 0; //再拉低時鐘,完成一個位的操作
}
return dat; //最后返回讀到的字節(jié)數(shù)據(jù)
}
/* 用單次寫操作向某一寄存器寫入一個字節(jié),reg-寄存器地址,dat-待寫入字節(jié) */
void DS1302SingleWrite(unsigned char reg, unsigned char dat){
DS1302_CE = 1; //使能片選信號
DS1302ByteWrite((reg《《1)|0x80); //發(fā)送寫寄存器指令
DS1302ByteWrite(dat); //寫入字節(jié)數(shù)據(jù)
DS1302_CE = 0; //除能片選信號
}
/* 用單次讀操作從某一寄存器讀取一個字節(jié),reg-寄存器地址,返回值-讀到的字節(jié) */
unsigned char DS1302SingleRead(unsigned char reg){
unsigned char dat;
DS1302_CE = 1; //使能片選信號
DS1302ByteWrite((reg《《1)|0x81); //發(fā)送讀寄存器指令
dat = DS1302ByteRead(); //讀取字節(jié)數(shù)據(jù)
DS1302_CE = 0; //除能片選信號
return dat;
}
/* 用突發(fā)模式連續(xù)寫入 8 個寄存器數(shù)據(jù),dat-待寫入數(shù)據(jù)指針 */
void DS1302BurstWrite(unsigned char *dat){
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBE); //發(fā)送突發(fā)寫寄存器指令
for (i=0; i《8; i++){ //連續(xù)寫入 8 字節(jié)數(shù)據(jù)
DS1302ByteWrite(dat[i]);
}
DS1302_CE = 0;
}
/* 用突發(fā)模式連續(xù)讀取 8 個寄存器的數(shù)據(jù),dat-讀取數(shù)據(jù)的接收指針 */
void DS1302BurstRead(unsigned char *dat){
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBF); //發(fā)送突發(fā)讀寄存器指令
for (i=0; i《8; i++){ //連續(xù)讀取 8 個字節(jié)
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
}
/* 獲取實(shí)時時間,即讀取 DS1302 當(dāng)前時間并轉(zhuǎn)換為時間結(jié)構(gòu)體格式 */
void GetRealTime(struct sTime *time){
unsigned char buf[8];
DS1302BurstRead(buf);
time-》year = buf[6] + 0x2000;
time-》mon = buf[4];
time-》day = buf[3];
time-》hour = buf[2];
time-》min = buf[1];
time-》sec = buf[0];
time-》week = buf[5];
}
/* 設(shè)定實(shí)時時間,時間結(jié)構(gòu)體格式的設(shè)定時間轉(zhuǎn)換為數(shù)組并寫入 DS1302 */
void SetRealTime(struct sTime *time){
unsigned char buf[8];
buf[7] = 0;
buf[6] = time-》year;
buf[5] = time-》week;
buf[4] = time-》mon;
buf[3] = time-》day;
buf[2] = time-》hour;
buf[1] = time-》min;
buf[0] = time-》sec;
DS1302BurstWrite(buf);
}
/* DS1302 初始化,如發(fā)生掉電則重新設(shè)置初始時間 */
void InitDS1302(){
unsigned char dat;
struct sTime code InitTime[] = { //2013 年 10 月 8 日 12:30:00 星期二
0x2013,0x10,0x08, 0x12,0x30,0x00, 0x02
};
DS1302_CE = 0; //初始化 DS1302 通信引腳
DS1302_CK = 0;
dat = DS1302SingleRead(0); //讀取秒寄存器
if ((dat & 0x80) != 0){ //由秒寄存器最高位 CH 的值判斷 DS1302 是否已停止
DS1302SingleWrite(7, 0x00); //撤銷寫保護(hù)以允許寫入數(shù)據(jù)
SetRealTime(&InitTime); //設(shè)置 DS1302 為默認(rèn)的初始時間
}
}
DS1302.c 最終向外提供出與具體時鐘芯片寄存器位置無關(guān)的、由時間結(jié)構(gòu)類型 sTime 作為接口的實(shí)時時間的讀取和設(shè)置函數(shù),如此處理體現(xiàn)了我們前面提到過的層次化編程的思想。應(yīng)用層可以不關(guān)心底層實(shí)現(xiàn)細(xì)節(jié),底層實(shí)現(xiàn)的改變也不會對應(yīng)用層造成影響,比如說日后你可能需要換一款時鐘芯片,而它與 DS1302 的操作和時間寄存器順序是不同的,那么你需要做的也僅是針對這款新的時鐘芯片設(shè)計出底層操作函數(shù),最終提供出同樣的以 sTime 為接口的操作函數(shù)即可,應(yīng)用層無需做任何的改動。
/***************************Lcd1602.c 文件程序源代碼*****************************/
#include 《reg52.h》
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
/* 等待液晶準(zhǔn)備好 */
void LcdWaitReady(){
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do {
LCD1602_E = 1;
sta = LCD1602_DB; //讀取狀態(tài)字
LCD1602_E = 0;
} while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重復(fù)檢測直到其等于 0 為止
}
/* 向 LCD1602 液晶寫入一字節(jié)命令,cmd-待寫入命令值 */
void LcdWriteCmd(unsigned char cmd){
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫入一字節(jié)數(shù)據(jù),dat-待寫入數(shù)據(jù)值 */
void LcdWriteDat(unsigned char dat){
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 設(shè)置顯示 RAM 起始地址,亦即光標(biāo)位置,(x,y)-對應(yīng)屏幕上的字符坐標(biāo) */
void LcdSetCursor(unsigned char x, unsigned char y){
unsigned char addr;
if (y == 0){ //由輸入的屏幕坐標(biāo)計算顯示 RAM 的地址
addr = 0x00 + x; //第一行字符地址從 0x00 起始
}else{
addr = 0x40 + x; //第二行字符地址從 0x40 起始
}
LcdWriteCmd(addr | 0x80); //設(shè)置 RAM 地址
}
/* 在液晶上顯示字符串,(x,y)-對應(yīng)屏幕上的起始坐標(biāo),str-字符串指針 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
LcdSetCursor(x, y); //設(shè)置起始地址
while (*str != ‘\0’){ //連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
LcdWriteDat(*str++);
}
}
/* 打開光標(biāo)的閃爍效果 */
void LcdOpenCursor(){
LcdWriteCmd(0x0F);
}
/* 關(guān)閉光標(biāo)顯示 */
void LcdCloseCursor(){
LcdWriteCmd(0x0C);
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
LcdWriteCmd(0x38); //16*2 顯示,5*7 點(diǎn)陣,8 位數(shù)據(jù)接口
LcdWriteCmd(0x0C); //顯示器開,光標(biāo)關(guān)閉
LcdWriteCmd(0x06); //文字不動,地址自動+1
LcdWriteCmd(0x01); //清屏
}
為了本例的具體需求,在之前文件的基礎(chǔ)上添加兩個控制光標(biāo)效果打開和關(guān)閉的函數(shù),雖然函數(shù)都很簡單,但為了保持程序整體上良好的模塊化和層次化,還是應(yīng)該在液晶驅(qū)動文件內(nèi)以函數(shù)的形式提供,而不是由應(yīng)用層代碼直接來調(diào)用具體的液晶寫命令操作。
/***************************keyboard.c 文件程序源代碼****************************/
(此處省略,可參考之前章節(jié)的代碼)
/*****************************main.c 文件程序源代碼******************************/
#include 《reg52.h》
struct sTime { //日期時間結(jié)構(gòu)體定義
unsigned int year;
unsigned char mon;
unsigned char day;
unsigned char hour;
unsigned char min;
unsigned char sec;
unsigned char week;
};
bit flag200ms = 1; //200ms 定時標(biāo)志
struct sTime bufTime; //日期時間緩沖區(qū)
unsigned char setIndex = 0; //時間設(shè)置索引
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)
void ConfigTimer0(unsigned int ms);
void RefreshTimeShow();
extern void InitDS1302();
extern void GetRealTime(struct sTime *time);
extern void SetRealTime(struct sTime *time);
extern void KeyScan();
extern void KeyDriver();
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void LcdSetCursor(unsigned char x, unsigned char y);
extern void LcdOpenCursor();
extern void LcdCloseCursor();
void main(){
unsigned char psec=0xAA; //秒備份,初值 AA 確保首次讀取時間后會刷新顯示
EA = 1; //開總中斷
ConfigTimer0(1); //T0 定時 1ms
InitDS1302(); //初始化實(shí)時時鐘
InitLcd1602(); //初始化液晶
//初始化屏幕上固定不變的內(nèi)容
LcdShowStr(3, 0, “20 - - ”);
LcdShowStr(4, 1, “ : : ”);
while (1){
KeyDriver(); //調(diào)用按鍵驅(qū)動
if (flag200ms && (setIndex == 0)){ //每隔 200ms 且未處于設(shè)置狀態(tài)時,
flag200ms = 0;
GetRealTime(&bufTime); //獲取當(dāng)前時間
if (psec != bufTime.sec){ //檢測到時間有變化時刷新顯示
RefreshTimeShow();
psec = bufTime.sec; //用當(dāng)前值更新上次秒數(shù)
}
}
}
}
/* 將一個 BCD 碼字節(jié)顯示到屏幕上,(x,y)-屏幕起始坐標(biāo),bcd-待顯示 BCD 碼 */
void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd){
unsigned char str[4];
str[0] = (bcd 》》 4) + ‘0’;
str[1] = (bcd&0x0F) + ‘0’;
str[2] = ‘\0’;
LcdShowStr(x, y, str);
}
/* 刷新日期時間的顯示 */
void RefreshTimeShow(){
ShowBcdByte(5, 0, bufTime.year);
ShowBcdByte(8, 0, bufTime.mon);
ShowBcdByte(11, 0, bufTime.day);
ShowBcdByte(4, 1, bufTime.hour);
ShowBcdByte(7, 1, bufTime.min);
ShowBcdByte(10, 1, bufTime.sec);
}
/* 刷新當(dāng)前設(shè)置位的光標(biāo)指示 */
void RefreshSetShow(){
switch (setIndex){
case 1: LcdSetCursor(5, 0); break;
case 2: LcdSetCursor(6, 0); break;
case 3: LcdSetCursor(8, 0); break;
case 4: LcdSetCursor(9, 0); break;
case 5: LcdSetCursor(11, 0); break;
case 6: LcdSetCursor(12, 0); break;
case 7: LcdSetCursor(4, 1); break;
case 8: LcdSetCursor(5, 1); break;
case 9: LcdSetCursor(7, 1); break;
case 10: LcdSetCursor(8, 1); break;
case 11: LcdSetCursor(10, 1); break;
case 12: LcdSetCursor(11, 1); break;
default: break;
}
}
/* 遞增一個 BCD 碼的高位 */
unsigned char IncBcdHigh(unsigned char bcd){
if ((bcd&0xF0) 《 0x90){
bcd += 0x10;
}else{
bcd &= 0x0F;
}
return bcd;
}
/* 遞增一個 BCD 碼的低位 */
unsigned char IncBcdLow(unsigned char bcd){
if ((bcd&0x0F) 《 0x09){
bcd += 0x01;
}else{
bcd &= 0xF0;
}
return bcd;
}
/* 遞減一個 BCD 碼的高位 */
unsigned char DecBcdHigh(unsigned char bcd){
if ((bcd&0xF0) 》 0x00){
bcd -= 0x10;
}else{
bcd |= 0x90;
}
return bcd;
}
/* 遞減一個 BCD 碼的低位 */
unsigned char DecBcdLow(unsigned char bcd){
if ((bcd&0x0F) 》 0x00){
bcd -= 0x01;
}else{
bcd |= 0x09;
}
return bcd;
}
/* 遞增時間當(dāng)前設(shè)置位的值 */
void IncSetTime(){
switch (setIndex){
case 1: bufTime.year = IncBcdHigh(bufTime.year); break;
case 2: bufTime.year = IncBcdLow(bufTime.year); break;
case 3: bufTime.mon = IncBcdHigh(bufTime.mon); break;
case 4: bufTime.mon = IncBcdLow(bufTime.mon); break;
case 5: bufTime.day = IncBcdHigh(bufTime.day); break;
case 6: bufTime.day = IncBcdLow(bufTime.day); break;
case 7: bufTime.hour = IncBcdHigh(bufTime.hour); break;
case 8: bufTime.hour = IncBcdLow(bufTime.hour); break;
case 9: bufTime.min = IncBcdHigh(bufTime.min); break;
case 10: bufTime.min = IncBcdLow(bufTime.min); break;
case 11: bufTime.sec = IncBcdHigh(bufTime.sec); break;
case 12: bufTime.sec = IncBcdLow(bufTime.sec); break;
default: break;
}
RefreshTimeShow();
RefreshSetShow();
}
/* 遞減時間當(dāng)前設(shè)置位的值 */
void DecSetTime(){
switch (setIndex){
case 1: bufTime.year = DecBcdHigh(bufTime.year); break;
case 2: bufTime.year = DecBcdLow(bufTime.year); break;
case 3: bufTime.mon = DecBcdHigh(bufTime.mon); break;
case 4: bufTime.mon = DecBcdLow(bufTime.mon); break;
case 5: bufTime.day = DecBcdHigh(bufTime.day); break;
case 6: bufTime.day = DecBcdLow(bufTime.day); break;
case 7: bufTime.hour = DecBcdHigh(bufTime.hour); break;
case 8: bufTime.hour = DecBcdLow(bufTime.hour); break;
case 9: bufTime.min = DecBcdHigh(bufTime.min); break;
case 10: bufTime.min = DecBcdLow(bufTime.min); break;
case 11: bufTime.sec = DecBcdHigh(bufTime.sec); break;
case 12: bufTime.sec = DecBcdLow(bufTime.sec);
default: break;
}
RefreshTimeShow();
RefreshSetShow();
}
/* 右移時間設(shè)置位 */
void RightShiftTimeSet(){
if (setIndex != 0){
if (setIndex 《 12){
setIndex++;
}else{
setIndex = 1;
}
RefreshSetShow();
}
}
/* 左移時間設(shè)置位 */
void LeftShiftTimeSet(){
if (setIndex != 0){
if (setIndex 》 1){
setIndex--;
}else{
setIndex = 12;
}
RefreshSetShow();
}
}
/* 進(jìn)入時間設(shè)置狀態(tài) */
void EnterTimeSet(){
setIndex = 2; //把設(shè)置索引設(shè)置為 2,即可進(jìn)入設(shè)置狀態(tài)
LeftShiftTimeSet(); //再利用現(xiàn)成的左移操作移到位置 1 并完成顯示刷新
LcdOpenCursor(); //打開光標(biāo)閃爍效果
}
/* 退出時間設(shè)置狀態(tài),save-是否保存當(dāng)前設(shè)置的時間值 */
void ExitTimeSet(bit save){
setIndex = 0; //把設(shè)置索引設(shè)置為 0,即可退出設(shè)置狀態(tài)
if (save){ //需保存時即把當(dāng)前設(shè)置時間寫入 DS1302
SetRealTime(&bufTime);
}
LcdCloseCursor(); //關(guān)閉光標(biāo)顯示
}
/* 按鍵動作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)的操作,keycode-按鍵鍵碼 */
void KeyAction(unsigned char keycode){
if ((keycode》=‘0’) && (keycode《=‘9’)){ //本例中不響應(yīng)字符鍵
}else if (keycode == 0x26){ //向上鍵,遞增當(dāng)前設(shè)置位的值
IncSetTime();
}else if (keycode == 0x28){ //向下鍵,遞減當(dāng)前設(shè)置位的值
DecSetTime();
}else if (keycode == 0x25){ //向左鍵,向左切換設(shè)置位
LeftShiftTimeSet();
}else if (keycode == 0x27){ //向右鍵,向右切換設(shè)置位
RightShiftTimeSet();
}else if (keycode == 0x0D){ //回車鍵,進(jìn)入設(shè)置模式/啟用當(dāng)前設(shè)置值
if (setIndex == 0){ //不處于設(shè)置狀態(tài)時,進(jìn)入設(shè)置狀態(tài)
EnterTimeSet();
}else{ //已處于設(shè)置狀態(tài)時,保存時間并退出設(shè)置狀態(tài)
ExitTimeSet(1);
}
}else if (keycode == 0x1B){ //Esc 鍵,取消當(dāng)前設(shè)置
ExitTimeSet(0);
}
}
/* 配置并啟動 T0,ms-T0 定時時間 */
void ConfigTimer0(unsigned int ms){
unsigned long tmp; //臨時變量
tmp = 11059200 / 12; //定時器計數(shù)頻率
tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
tmp = 65536 - tmp; //計算定時器重載值
tmp = tmp + 28; //補(bǔ)償中斷響應(yīng)延時造成的誤差
T0RH = (unsigned char)(tmp》》8); //定時器重載值拆分為高低字節(jié)
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 為模式 1
TH0 = T0RH; //加載 T0 重載值
TL0 = T0RL;
ET0 = 1; //使能 T0 中斷
TR0 = 1; //啟動 T0
}
/* T0 中斷服務(wù)函數(shù),執(zhí)行按鍵掃描和 200ms 定時 */
void InterruptTimer0() interrupt 1{
static unsigned char tmr200ms = 0;
TH0 = T0RH; //重新加載重載值
TL0 = T0RL;
KeyScan(); //按鍵掃描
tmr200ms++;
if (tmr200ms 》= 200){ //定時 200ms
tmr200ms = 0;
flag200ms = 1;
}
}
-
模塊
+關(guān)注
關(guān)注
7文章
2633瀏覽量
47240 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4265瀏覽量
62262 -
源代碼
+關(guān)注
關(guān)注
96文章
2943瀏覽量
66583
發(fā)布評論請先 登錄
相關(guān)推薦
評論