本赛题为4T平台推出的第十六届蓝桥杯模拟赛一,题目虽然看起来简单,也是有可圈可点的地方的,比如数码管显示界面涉及了负数的显示,测量温度时不仅仅读取DS18B20传感器的温度,还要加上校准值等等,正在备赛蓝桥杯的选手可以靠本套题练练手。
附件:第十六届蓝桥杯4T模拟赛一
完整地阅读一遍题目
拿到试题时,第一件事并不是直接开始敲模板(也不是不行),最好还是先完整地阅读一遍题目地要求,当然阅读题目要有目的的去阅读,比如说有没有哪些考到的模块的底层有更改,例如第十三届国赛中直接把超声波的底层改了,要是第一时间去把模板敲完还要做更改;再比如说性能要求,有一届省赛直接规定数码管显示时间<=100ms,很多人平时敲多了200~500ms的数码管模块连题目都没看白白被扣分,无缘奖项。
阅读完题目,可以得到以下信息:
- 按键响应为200ms内,所以可以继续使用模板,模板的按键时间是100ms的符合题意。
- 指示灯响应在100ms内也是符合模板的。
- 数码管需要的标识符是C、E、H、-和熄灭,所以我们在引用官方的数码管共阳极段码表后要自行添加没有的部分。
- 按键只用了S4、S5、S8、S9,所以Key函数只写这四个按键即可,节省时间。
- 采集的温度等于DS18B20测得的实际温度还要加上校准值,所以在写数码管的同时也要记得定义校准值,防止写到后面忘记了。
- 校准值的取值范围为-99~99,要怎么去实现数码管显示负数呢,这也是本题最难的一个点。
开始敲
在备赛蓝桥杯的中篇、后篇、末篇已经讲解了怎么实现这些模板,这边直接一笔带过。
Init.h
#ifndef __Init_H__
#define __Init_H__
void SystemInit();
#endif
Init.c
#include <STC15F2K60S2.H>
void SystemInit()
{
P0 = 0xff;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
P0 = 0x00;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
}
Led.h
#ifndef __Led_H__
#define __Led_H__
void Led_Disp(unsigned char *ucLed);
#endif
Led.c
#include <STC15F2K60S2.H>
void Led_Disp(unsigned char *ucLed)
{
unsigned char i, temp = 0x00;
static unsigned char temp_old;
for(i = 0; i < 8; i++)
temp |= (ucLed[i] << i);
if(temp != temp_old)
{
P0 = ~temp;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
temp_old = temp;
}
}
Seg.h
#ifndef __Seg_H__
#define __Seg_H__
void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point);
#endif
Seg.c
#include <STC15F2K60S2.H>
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xff, //空
0xbf, //-
0xc6, //C
0x86, //E
0x89 //H
};
void Seg_Disp(unsigned char wela, unsigned char dula, unsigned char point)
{
P0 = 0xff;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
P0 = (0x01 << wela);
P2 = P2 & 0x1f | 0xc0;
P2 &= 0x1f;
P0 = Seg_Table[dula];
if(point)
P0 &= 0x7f;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
}
ds18b20.h
#ifndef __ds18b20_H__
#define __ds18b20_H__
float Tem_Read();
#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 Tem_Read()
{
idata unsigned char TL, TH;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
TL = Read_DS18B20();
TH = Read_DS18B20();
return ((TH << 8) | TL) / 16.0;
}
main函数初定义
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "ds18b20.h"
/* 变量声明区 */
idata unsigned char Key_Slow; //按键减速变量 10ms
idata unsigned char Key_Val, Key_Down, Key_Up, Key_Old; //按键检测四件套
idata unsigned int Seg_Slow; //数码管减速变量 500ms
idata unsigned char Seg_Pos;//数码管缓存数组专用索引
idata unsigned int Tem_10x;//温度测量最终值(实际值+校准值)
idata signed char calibration;//校准值,初值为0
idata unsigned char SegMode;//页面流转模式
pdata unsigned char Seg_Buf[] = {10,10,10,10,10,10,10,10,10,10};//数码管缓存数组
pdata unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点使能数组
pdata unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//LED显示数据存放数组
/* 按键处理函数 */
void Key_Proc()
{
if(Key_Slow) return;
Key_Slow = 1; //按键减速
Key_Val = Key();
Key_Down = Key_Val & ~Key_Old;
Key_Up = ~Key_Val & Key_Old;
Key_Old = Key_Val;
}
/* 信息处理函数 */
void Seg_Proc()
{
if(Seg_Slow) return;
Seg_Slow = 1; //数码管减速
}
/* 其他显示函数 */
void Led_Proc()
{
}
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;
}
/* 定时器1中断服务函数 */
void Timer0_Server() interrupt 1
{
if(++Key_Slow == 10) Key_Slow = 0; //按键延迟
if(++Seg_Slow == 100) Seg_Slow = 0; //数码管延迟
if(++Seg_Pos == 8) Seg_Pos = 0; //数码管显示
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
LED_Disp(ucLed);
}
void main()
{
Init();
Timer0_Init();
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
数码管
页面流转
由题目得,按键S4控制三个页面的流转,所以我们可以定义unsigned char型变量SegMode,其值为0时温度界面,为1时校准值界面,为2时参数界面,再按下回到温度界面,页面流转的实现在按键模块里完成,代码如下:
void Key_Proc()
{
if(Key_Slow) return;
Key_Slow = 1; //按键减速
Key_Val = Key();
Key_Down = Key_Val & ~Key_Old;
Key_Up = ~Key_Val & Key_Old;
Key_Old = Key_Val;
switch(KeyDown)
{
case 4:
SegMode ++;
if(SegMode == 3)
SegMode = 0;
break;
}
}
温度界面
- 温度读取
/* 信息处理函数 */
void Seg_Proc()
{
if(Seg_Slow) return;
Seg_Slow = 1; //数码管减速
//Tem = Tem_Read() + calibration;//如果定义Tem为float型变量用本行
Tem_10x = (Tem_Read + calibration) * 10;//直接定义unsigned int型变量Tem_10x接收10倍的温度
switch(SegMode)
{
case 0:
//写法一:定义unsigned int型变量Tem_10x
SegBuf[0] = 12;//C
SegBuf[1] = 10;//空
SegBuf[2] = 10;//空
SegBuf[3] = 10;//空
SegBuf[4] = 10;//空
SegBuf[5] = Tem_10x / 100;//温度扩大十倍后的百位
SegBuf[6] = Tem_10x / 10 % 10; //温度扩大十倍后的十位
SegPoint[6] = 1;//小数点使能
SegBuf[7] = Tem_10x % 10;//温度扩大十倍后的个位
//写法二:定义浮点型变量Tem
/*
SegBuf[0] = 12;//C
SegBuf[1] = 10;//空
SegBuf[2] = 10;//空
SegBuf[3] = 10;//空
SegBuf[4] = 10;//空
SegBuf[5] = Tem / 10;//温度的十位
SegBuf[6] = Tem % 10; //温度的个位
SegPoint[6] = 1;//小数点使能
SegBuf[7] = (Tem * 10) % 10;//温度的小数点后一位
*/
break;
}
}
- 校准值页面
由于校准值的取值范围为-99~99,在备赛系列前篇已经介绍了各种常用变量的取值范围,unsigne char型取值范围为0~255,所以这里应该定义校准值为signed char型(-128 ~ 127)。
所以在页面1的数码管模块中,我们要先讨论校准值的正负型,可以用三目运算符去写,有点麻烦,最简单最不用动脑子的写法就是屎山代码if-else。
先介绍一下三目运算符:
格式:(判断主体) ? 判断主体为真时实现 : 判断主体为假时实现;
举个例子:a ? b = 1 : b = 0;//a是否为真?为真的话b=1,为假的话b=0。
case 1:
SegBuf[0] = 13;
SegPoint[6] = 0;//让页面0的数码管使能失效
if(calibration >= 0)//如果校准值为正数
{
SegBuf[5] = 10;//熄灭
//校准值的十位是否为0,为0的话说明校准值为个位,所以百位和十位数码管熄灭
SegBuf[6] = (calibration / 10) ? calibration / 10 : 10;
SegBuf[7] = calibration % 10;
}
else//校准值为负数时
{
unsigned char positive = -calibration;//定义无符号字符型变量接收校准值的相反值,它的相反值一定是正数
/*
由于负数需要同时判断符号显示在哪个数码管和高位熄灭问题,所以我们先考虑负数的十位是否为0,如果十位为0,说明
原先控制十位的数码管要显示负号并且百位数码管熄灭,如果不为0,百位数码管显示负号。
*/
SegBuf[6] = (positive / 10) ? positive / 10 : 11;
SegBuf[5] = (SegBuf[6] == 11) ? 10 : 11;
SegBuf[7] = positive % 10;
}
break;
参数页面
实现了校准值页面,参数直接复制粘贴就可以了。
idata signed char TemSet = 26;
case 2:
SegBuf[0] = 14;
if(TemSet >= 0)
{
SegBuf[5] = 10;
SegBuf[6] = (TemSet / 10) ? TemSet / 10 : 10;
SegBuf[7] = TemSet % 10;
}
else
{
unsigned char positive = -TemSet;
SegBuf[6] = (positive / 10) ? positive / 10 : 11;
SegBuf[5] = (SegBuf[6] == 11) ? 10 : 11;
SegBuf[7] = positive % 10;
}
break;
数码管完整代码
void Seg_Proc()
{
if(Seg_Slow) return;
Seg_Slow = 1; //数码管减速
switch(SegMode)
{
case 0:
SegBuf[0] = 12;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = Tem_10x / 100;
SegBuf[6] = Tem_10x / 10 % 10;
SegPoint[6] = 1;
SegBuf[7] = Tem_10x % 10;
break;
case 1:
SegBuf[0] = 13;
SegPoint[6] = 0;
if(calibration >= 0)
{
SegBuf[5] = 10;
SegBuf[6] = (calibration / 10) ? calibration / 10 : 10;
SegBuf[7] = calibration % 10;
}
else
{
unsigned char positive = -calibration;
SegBuf[6] = (positive / 10) ? positive / 10 : 11;
SegBuf[5] = (SegBuf[6] == 11) ? 10 : 11;
SegBuf[7] = positive % 10;
}
break;
case 2:
SegBuf[0] = 14;
if(TemSet >= 0)
{
SegBuf[5] = 10;
SegBuf[6] = (TemSet / 10) ? TemSet / 10 : 10;
SegBuf[7] = TemSet % 10;
}
else
{
unsigned char positive = -TemSet;
SegBuf[6] = (positive / 10) ? positive / 10 : 11;
SegBuf[5] = (SegBuf[6] == 11) ? 10 : 11;
SegBuf[7] = positive % 10;
}
break;
}
}
到这边可能有同学会问,case 1和case 2中定义了一个相同的变量positive,他们不会冲突吗?答案是当然不会冲突的,这和变量的生存域有关系,在case里面定义的局部变量生存期只有在本个case语句内有效。
按键
数码管模块都坚持过来了,按键模块肯定没问题了,这边直接给出按键模块代码:
void Key_Proc()
{
if(Key_Slow) return;
Key_Slow = 1; //按键减速
KeyVal = Key();
KeyDown = KeyVal & ~KeyOld;
KeyUp = ~KeyVal & KeyOld;
KeyOld = KeyVal;
switch(KeyDown)
{
case 4:
SegMode ++;
if(SegMode == 3)
SegMode = 0;
break;
case 5:
Ctr ^= 1;
break;
case 8:
if(SegMode == 1)
{
if(--calibration == -100)
calibration = -99;
}
else if(SegMode == 2)
{
if(--TemSet == -100)
TemSet = -99;
}
break;
case 9:
if(SegMode == 1)
{
if(++calibration == 100)
calibration = 99;
}
else if(SegMode == 2)
{
if(++TemSet == 100)
TemSet = 99;
}
break;
}
}
Led
Led里面的实现也是特别简单的,点亮L1、L2、L3可以直接用if-else语句,这边给大家介绍一个更简单的方法,没错他就是传说中的互斥点亮。
void Led_Proc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == SegMode);
/*
当i=0时,ucLed[0] = (0 == SegMode);
当且仅当SegMode = 0时, 0 = SegMode为真,ucLed[0] = 1即L1点亮
反之为假,L1熄灭,L1、L2、L3的亮灭互不干涉,也就是互斥点亮。
*/
//L4、L5、L8的点亮逻辑直接给出
ucLed[3] = !Ctr;
ucLed[4] = Ctr;
if(!Ctr)
{
if(Tem_10x > TemSet * 10)
ucLed[7] = 1;
else
ucLed[7] = 0;
}
else
{
if(Tem_10x < TemSet * 10)
ucLed[7] = 1;
else
ucLed[7] = 0;
}
}
本代码在4T平台已经通过,如有错误或者不足,请移步评论区提出,我会及时回复。
好了,本道题目到这边结束了,如果觉得阅读完本篇文章有收获的,可以点个小红心支持一下哦,后面会陆续推出各届模拟赛、省赛、国赛的代码讲解,敬请期待。