什么?你把最近幾屆省賽真題做完已經無題可做了,那不妨來看看老古董第八屆省賽的題目吧!
附件:第八屆藍橋杯單片機省賽
一、數碼管
1.頁面流轉
以上的頁面流轉功能可以用下圖總結:
這邊給出一種實現方法:
- 定義
unsigned char
型變量SetMode
來控制三個主要頁面(SetMode=0
:時間顯示頁面、SetMode=1
:時鐘設置頁面、SetMode=2
:鬧鐘設置頁面) - 然后定義
bit
型變量SegMode
在時間顯示頁面(SetMode=0
)單獨控制溫度顯示頁面(SegMode=0
:時間顯示頁面、SegMode=1
:溫度顯示頁面)
2.變量初定義
關于時鐘設置和鬧鐘設置是在設置期間數據就生效還是退出到時間顯示頁面才生效這個問題,題目并沒有明確指出,本篇文章默認時鐘設置和鬧鐘設置在退出到時間顯示頁面才生效。
typedef unsigned char u8;
typedef unsigned int u16;
/* 頁面參數 */
idata u8 SetMode; //0-時間頁面 1-時間設置 2-鬧鐘設置
idata bit SegMode;//0-時間顯示 1-溫度顯示
/* 時鐘、鬧鐘參數 */
idata u8 Index;//0-無 1-時 2-分 3-秒
pdata u8 Rtc[3] = {0x23,0x59,0x50};//時鐘顯示數據
pdata u8 RtcSet[3];//時鐘設置時修改的數據
pdata u8 Alarm[3] = {0x00,0x00,0x00};//鬧鐘存放時間
pdata u8 AlarmSet[3];//鬧鐘設置時修改的數據
/* 溫度 */
idata u8 tem;
/* 老朋友 */
idata u8 SegPos;
pdata u8 SegBuf[8] = {10,10,10,10,10,10,10,10};
pdata u8 SegPoint[8] = {0,0,0,0,0,0,0,0};
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
3.時間顯示頁面
ds1302.h
#ifndef __ds1302_H__
#define __ds1302_H__void SetRtc(unsigned char *Rtc);
void GetRtc(unsigned char *Rtc);#endif
ds1302.c
#include <STC15F2K60S2.H>
#include <intrins.h>sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;void Write_Ds1302(unsigned char temp)
{unsigned char i;for (i=0;i<8;i++) { SCK = 0;SDA = temp&0x01;temp>>=1; SCK=1;}
} //
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{RST=0; _nop_();SCK=0; _nop_();RST=1; _nop_(); Write_Ds1302(address); Write_Ds1302(dat); RST=0;
}//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{unsigned char i,temp=0x00;RST=0; _nop_();SCK=0; _nop_();RST=1; _nop_();Write_Ds1302(address);for (i=0;i<8;i++) { SCK=0;temp>>=1; if(SDA)temp|=0x80; SCK=1;} RST=0; _nop_();SCK=0; _nop_();SCK=1; _nop_();SDA=0; _nop_();SDA=1; _nop_();return (temp);
}code unsigned char DS1302[4] = {0x84,0x82,0x80,0x8E};void SetRtc(unsigned char *Rtc)
{unsigned char i;Write_Ds1302_Byte(DS1302[3],0x00);for(i = 0; i < 3; i++)Write_Ds1302_Byte(DS1302[i],Rtc[i]);Write_Ds1302_Byte(DS1302[3],0x80);
}
void GetRtc(unsigned char *Rtc)
{unsigned char i;for(i = 0; i < 3; i++)Rtc[i] = Read_Ds1302_Byte(DS1302[i]+1);
}
main.c
void SegProc()
{unsigned char i;GetRtc(Rtc);//讀取當前時間switch(SetMode){case 0:if(!SegMode){SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = Rtc[i] / 16;SegBuf[3*i+1] = Rtc[i] % 16;}}break;}
}void main()
{SystemInit();Timer0_Init();SetRtc(Rtc);//上電時設置時間while(1){//...}
}
4.溫度顯示頁面
ds18b20.h
#ifndef __ds18b20_H__
#define __ds18b20_H__float TemRead();#endif
ds18b20.c
#include <STC15F2K60S2.H>sbit DQ = P1^4;void Delay_OneWire(unsigned int t)
{unsigned char i;while(t--){for(i=0;i<12;i++);}
}//
void Write_DS18B20(unsigned char dat)
{unsigned char i;for(i=0;i<8;i++){DQ = 0;DQ = dat&0x01;Delay_OneWire(5);DQ = 1;dat >>= 1;}Delay_OneWire(5);
}//
unsigned char Read_DS18B20(void)
{unsigned char i;unsigned char dat;for(i=0;i<8;i++){DQ = 0;dat >>= 1;DQ = 1;if(DQ){dat |= 0x80;} Delay_OneWire(5);}return dat;
}//
bit init_ds18b20(void)
{bit initflag = 0;DQ = 1;Delay_OneWire(12);DQ = 0;Delay_OneWire(80);DQ = 1;Delay_OneWire(10); initflag = DQ; Delay_OneWire(5);return initflag;
}float TemRead()
{unsigned char tem_low, tem_hig;init_ds18b20();Write_DS18B20(0xcc);Write_DS18B20(0x44);init_ds18b20();Write_DS18B20(0xcc);Write_DS18B20(0xbe);tem_low = Read_DS18B20();tem_hig = Read_DS18B20();return ((tem_hig << 8) | tem_low) / 16.0;
}
main.c
void SegProc()
{tem = TemRead();switch(SetMode){case 0:if(!SegMode){//...}else//溫度顯示頁面{SegBuf[0] = 10;SegBuf[1] = 10;SegBuf[2] = 10;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = tem / 10;SegBuf[6] = tem % 10;SegBuf[7] = 12;//C}break;}
}
5.時鐘設置頁面
只顯示時鐘設置的數據:
for(i = 0; i < 3; i++)
{ SegBuf[3*i] = RtcSet[i] / 16;SegBuf[3*i+1] = RtcSet[i] % 16;
}
根據按鍵S7去選擇待調整的時、分、秒,當前選擇的顯示單元以 1 秒為間隔亮滅。
可以通過Index
來選擇時、分、秒,選中后,定義計時變量idata u16 Time_1s;
來計時1s讓idata bit SegFlash;
取反達到數碼管閃爍的效果(和指示燈閃爍一樣)。
idata u16 Time_1s;
idata bit SegFlash;void SegProc()
{//...switch(SetMode){case 0://...break;case 1://時鐘設置頁面SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = (Index == i+1 && SegFlash) ? 10 : RtcSet[i] / 16;SegBuf[3*i+1] = (Index == i+1 && SegFlash) ? 10 : RtcSet[i] % 16;}break;}
}void Timer0_Isr(void) interrupt 1
{if(++SegPos == 8) SegPos = 0;SegDisp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);if(++Time_1s == 1000){Time_1s = 0;SegFlash = !SegFlash;}
}
6.鬧鐘設置頁面
鬧鐘設置頁面不用考慮數碼管閃爍
void SegProc()
{u8 i;switch(SetMode){case 2:SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = AlarmSet[i] / 16;SegBuf[3*i+1] = AlarmSet[i] % 16;}break;}
}
7.數碼管完整代碼
typedef unsigned char u8;
typedef unsigned int u16;
/* 頁面參數 */
idata u8 SetMode; //0-時間頁面 1-時間設置 2-鬧鐘設置
idata bit SegMode;//0-時間顯示 1-溫度顯示
/* 時鐘、鬧鐘參數 */
idata u8 Index;//0-無 1-時 2-分 3-秒
pdata u8 Rtc[3] = {0x23,0x59,0x50};//時鐘顯示數據
pdata u8 RtcSet[3];//時鐘設置時修改的數據
pdata u8 Alarm[3] = {0x00,0x00,0x00};//鬧鐘存放時間
pdata u8 AlarmSet[3];//鬧鐘設置時修改的數據
/* 溫度 */
idata u8 tem; void SegProc()
{unsigned char i;tem = TemRead();GetRtc(Rtc);switch(SetMode){case 0:if(!SegMode){SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = Rtc[i] / 16;SegBuf[3*i+1] = Rtc[i] % 16;}}else{SegBuf[0] = 10;SegBuf[1] = 10;SegBuf[2] = 10;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = tem / 10;SegBuf[6] = tem % 10;SegBuf[7] = 12;}break;case 1:SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = (Index == i+1 && SegFlash) ? 10 : RtcSet[i] / 16;SegBuf[3*i+1] = (Index == i+1 && SegFlash) ? 10 : RtcSet[i] % 16;}break;case 2:SegBuf[2] = SegBuf[5] = 11;for(i = 0; i < 3; i++){ SegBuf[3*i] = AlarmSet[i] / 16;SegBuf[3*i+1] = AlarmSet[i] % 16;}break;}
}void Timer0_Init(void) //1毫秒@12.000MHz
{AUXR &= 0x7F; //定時器時鐘12T模式TMOD &= 0xF0; //設置定時器模式TL0 = 0x18; //設置定時初始值TH0 = 0xFC; //設置定時初始值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時ET0 = 1; //使能定時器0中斷EA = 1;
}void Timer0_Isr(void) interrupt 1
{if(++SegPos == 8) SegPos = 0;SegDisp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);if(++Time_1s == 1000){Time_1s = 0;SegFlash = !SegFlash;}
}
二、按鍵
1.S4、S5
在設置時鐘和鬧鐘的時候,由于要考慮參數的邊界性,所以在加和減參數的時候要考慮最大值和最小值,防止數據越界,小時的范圍是00 ~ 23
,分鐘的范圍是00 ~ 59
,秒鐘的范圍是00 ~ 59
,這邊需要注意的是,unsigned char
的數據范圍是0~255
,假設參數減到0,再按下S4后,下一次的值是255。
void KeyProc()
{KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;switch(KeyDown){case 5:if(SetMode == 1)//時間設置{RtcSet[Index-1]++;if(RtcSet[0] == 24)RtcSet[0] = 23;if(RtcSet[1] == 60)RtcSet[1] = 59;if(RtcSet[2] == 60)RtcSet[2] = 59;}else if(SetMode == 2)//鬧鐘設置{AlarmSet[Index-1]++;if(AlarmSet[0] == 24)AlarmSet[0] = 23;if(AlarmSet[1] == 60)AlarmSet[1] = 59;if(AlarmSet[2] == 60)AlarmSet[2] = 59;}break;case 4:if(SetMode == 1){RtcSet[Index-1]--;if(RtcSet[Index-1] == 255)RtcSet[Index-1] = 0;}else if(SetMode == 2){AlarmSet[Index-1]--;if(AlarmSet[Index-1] == 255)AlarmSet[Index-1] = 0;}break;}
}
1.S6、S7
對于S6、S7。只需要在進入小時的設置時將原先的時鐘/鬧鐘數據傳給AlarmSet/AlarmSet
,完成設置返回時間顯示頁面時再將AlarmSet/AlarmSet
的值傳回時鐘/鬧鐘。
switch(KeyDown){case 6:Index++;if(Index == 1){SetMode = 2;memcpy(AlarmSet,Alarm,3);}if(Index == 4){SetMode = 0;memcpy(Alarm,AlarmSet,3);Index = 0;}break;case 7:Index++;if(Index == 1){SetMode = 1;memcpy(RtcSet,Rtc,3);}if(Index == 4){SetMode = 0;memcpy(Rtc,RtcSet,3);Index = 0;SetRtc(Rtc);}break;
3.完整代碼
void KeyProc()
{KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;//長按S4進入溫度顯示頁面,松手返回時間顯示頁面if(!SetMode){if(KeyOld == 4)SegMode = 1;if(KeyUp == 4)SegMode = 0;}switch(KeyDown){case 6:Index++;if(Index == 1){SetMode = 2;memcpy(AlarmSet,Alarm,3);}if(Index == 4){SetMode = 0;memcpy(Alarm,AlarmSet,3);Index = 0;}break;case 7:Index++;if(Index == 1){SetMode = 1;memcpy(RtcSet,Rtc,3);}if(Index == 4){SetMode = 0;memcpy(Rtc,RtcSet,3);Index = 0;SetRtc(Rtc);}break;case 5:if(SetMode == 1){RtcSet[Index-1]++;if(RtcSet[0] == 24)RtcSet[0] = 23;if(RtcSet[1] == 60)RtcSet[1] = 59;if(RtcSet[2] == 60)RtcSet[2] = 59;}else if(SetMode == 2){AlarmSet[Index-1]++;if(AlarmSet[0] == 24)AlarmSet[0] = 23;if(AlarmSet[1] == 60)AlarmSet[1] = 59;if(AlarmSet[2] == 60)AlarmSet[2] = 59;}break;case 4:if(SetMode == 1){RtcSet[Index-1]--;if(RtcSet[Index-1] == 255)RtcSet[Index-1] = 0;}else if(SetMode == 2){AlarmSet[Index-1]--;if(AlarmSet[Index-1] == 255)AlarmSet[Index-1] = 0;}break;}
}
三、指示燈
先實現鬧鐘響的時候L1以200ms為間隔閃爍,持續5s。
idata u8 Time_200ms;//閃爍間隔
idata u16 Time_5s;//定時5s
idata bit AlarmFlag;//鬧鐘響
idata bit LedFlash;//指示燈閃爍void LedProc()
{//鬧鐘響了if(Alarm[0] == Rtc[0] && Alarm[1] == Rtc[1] && Alarm[2] == Rtc[2])AlarmFlag = 1;ucLed[0] = LedFlash;LedDisp(ucLed);
}void Timer0_Isr(void) interrupt 1
{systick++;if(++SegPos == 8) SegPos = 0;SegDisp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);if(AlarmFlag)//鬧鐘響{Time_5s++;//開始計時5sif(Time_5s == 5000)//計時到5s關閉鬧鐘{Time_5s = 0;AlarmFlag = 0;//關閉鬧鐘}else//還未計時5秒,L1閃爍{if(++Time_200ms == 200){Time_200ms = 0;LedFlash = !LedFlash;}}}elseLedFlash = 0;
}
接下來寫在任意按鍵按下關閉鬧鐘
void KeyProc()
{//...if(AlarmFlag){if(KeyDown)//按鍵值不為0AlarmFlag = 0;//關閉鬧鐘//防止這次的按鍵干擾到其他功能按鍵,關閉鬧鐘直接退出不執行下面語句return;}
}