前言
- 本篇文章主要是对第十五届省赛的详细讲解,代码实现在下一讲。
- 蓝桥杯单片机组自从第十二届开始难度有显著的提升,不再像以前一样一次比赛只涉及几次模块,题目量也很少。
- 最近几届的省赛、国赛更侧重考察参赛选手对单片机模块的掌握以及对题目的阅读能力和解决问题的逻辑思维能力也有了更高的要求。
- 第十五届省赛考察的模块有:基本三件套(按键数码管指示灯)+进阶三件套(PCF8591的DA模块、DS1302时钟模块和NE555脉冲)
- 后篇已经更新完毕,阅读完本篇点此传送门即可传送
传送门:第十五届蓝桥杯完整版(超级详细)
附件:蓝桥杯单片机组第十五届省赛
完整阅读一遍题目
关注性能要求
按键和LED指示灯按照正常的延迟即可,重点是对数码管的响应时间要求隐藏在了数码管的第五小点,而且这个要求还是非常重要的,数码管响应时间≤100ms将是致命的扣分点。
题目要求
阅读完题目后,我们可以得到下面一个流程图
知道了大体的流程图,我们就可以从头开始一步步入手了。
频率
通过单片机P34引脚测量NE555测量的频率在前篇文章已经详细地讲过了,如果还不清楚NE555地原理或者如何实现可以点此传送门:
直接定义unsigned int型全局变量freq接受测量得到地频率,然后马上让freq加上校准值得到校准后的频率再返回到数码管函数中即可,定时器代码如下所示:
typedef unsigned char u8;
typedef unsigned int u16;
typedef signed int s16;
idata u16 Time_1s, freq;
idata s16 calibration; //范围:-900~900
void Timer0_Init(void) //0毫秒@12.000MHz
{
TMOD &= 0xf0;
TMOD |= 0x05; //设置定时器模式
TL0 = 0; //设置定时初始值
TH0 = 0; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
void Timer1_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器1中断
EA = 1;
}
void Timer1_Isr(void) interrupt 3
{
if(++SegPos == 8)
SegPos = 0;
Seg_Disp(SegPos,SegBuf[SegPos],SegPoint[SegPos]);
if(++Time_1s == 1000)
{
Time_1s = 0;
freq = (TH0 << 8) | TL0;
TH0 = TL0 = 0;
freq += calibration;//让freq保留经过校准的频率值
DateError = (freq > 32000);
}
}
其中DateError = (freq > 32000);
是因为在板子上单单显示频率,可以看到NE555生成的频率最大只有31000多,然后unsigned int型数据是不显示负数的,也就是加上校准值后,如果为负数,就会产生下溢,此时从unsigned int型变量的上限开始减,也就是说freq减去一个大于freq的数,最终结果一定是大于32000的(前提条件:这个数的范围为-900~900)。
数码管
页面流转
定义unsigned char型变量SegMode来控制四个页面
在按键模块中实现页面地流转
代码如下:
idata u8 KeyVal, KeyDown, KeyUp, KeyOld;
idata u8 SegMode;
void Key_Proc()
{
KeyVal = Key_Disp();
KeyDown = KeyVal & ~KeyOld;
KeyUp= ~KeyVal & KeyOld;
KeyOld = KeyVal;
switch(KeyDown)
{
case 4:
SegMode++;
if(SegMode == 4)
{
SegMode = 0;
}
break;
}
页面一显示
本题数码管涉及高位熄灭(高位为0时熄灭)
实现数码管高位熄灭的方法:while语句实现
//退出循环条件:每一位数码管段码都不为0或该位段码为0的数码管下一位段码不为0
while(!SegBuf[i])//从要执行高位熄灭的第一位开始,如果碰到第一位为0则开始循环语句
{
SegBuf[i] = 10;//熄灭
i++;//判断下一个数码管
if(i == 7)//防止程序卡死,让最后一位正常显示
break;
}
void Seg_Proc()
{
u8 i;
switch(SegMode)
{
case 0:
SegBuf[0] = 12;
SegBuf[1] = 10;
SegBuf[2] = 10;
if(!DateError)//如果频率数据正常
{
SegBuf[3] = freq / 10000 % 10;
SegBuf[4] = freq / 1000 % 10;
SegBuf[5] = freq / 100 % 10;
SegBuf[6] = freq / 10 % 10;
SegBuf[7] = freq % 10;
i = 3;
while(!SegBuf[i])
{
SegBuf[i] = 10;
i++;
if(i == 7)
break;
}
}
else//如果频率数据异常
{
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 13;
SegBuf[7] = 13;
}
break;
}
}
页面二显示
页面二的考点是如何分别显示正数、负数
这个点在16届4T模拟一中提过了。
也就是正数、负数分别考虑,正数正常显示,负数的话,定义一个正数的值为负数的绝对值,然后对这个新定义的正数正常显示即可。
代码如下:
case 1:
SegBuf[0] = 14;
SegBuf[2] = 10;
SegBuf[3] = 10;
if(!SetMode)//上限参数界面
{
SegBuf[1] = 1;
SegBuf[4] = freq_set / 1000 % 10;
SegBuf[5] = freq_set / 100 % 10;
SegBuf[6] = freq_set / 10 % 10;
SegBuf[7] = freq_set % 10;
}
else//校准值界面
{
SegBuf[1] = 2;
if(calibration > 0)
{
SegBuf[4] = 10;
SegBuf[5] = calibration / 100 % 10;
SegBuf[6] = calibration / 10 % 10;
SegBuf[7] = calibration % 10;
}
else if(calibration == 0)
{
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 10;
SegBuf[7] = 0;
}
else
{
unsigned int positive = -calibration;
SegBuf[4] = 11;
SegBuf[5] = positive / 100 % 10;
SegBuf[6] = positive / 10 % 10;
SegBuf[7] = positive % 10;
}
}
break;
页面三显示
页面三考点只有使用DS1302时钟芯片,如果你还不知道怎么使用DS1302芯片,可以点击下方传送门。
case 2:
SegBuf[2] = SegBuf[5] = 11;
for(i = 0; i < 3; i++)
{
SegBuf[3*i] = ucRtc[i] / 16;
SegBuf[3*i+1] = ucRtc[i] % 16;
}
break;
页面四显示
页面四显示的是时间回显和最大频率回显,所以我们在读取时间的缓存函数中要加入判断语句:定义一个接受频率最大值的变量a,然后每次采集得到的频率b与该变量依次进行对比,如果b>a,则更新a的值为b,并记录当前时间即可。
由于文章篇幅较长,按键、LED、DA部分以及完整代码在下一讲给出,敬请期待。