第八届蓝桥杯单片机省赛
什么?你把最近几届省赛真题做完已经无题可做了,那不妨来看看老古董第八届省赛的题目吧!
附件:第八届蓝桥杯单片机省赛
一、数码管
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++;//开始计时5s
if(Time_5s == 5000)//计时到5s关闭闹钟
{
Time_5s = 0;
AlarmFlag = 0;//关闭闹钟
}
else//还未计时5秒,L1闪烁
{
if(++Time_200ms == 200)
{
Time_200ms = 0;
LedFlash = !LedFlash;
}
}
}
else
LedFlash = 0;
}
接下来写在任意按键按下关闭闹钟
void KeyProc()
{
//...
if(AlarmFlag)
{
if(KeyDown)//按键值不为0
AlarmFlag = 0;//关闭闹钟
//防止这次的按键干扰到其他功能按键,关闭闹钟直接退出不执行下面语句
return;
}
}