第十五届蓝桥杯单片机组4T模拟赛一
第十五届4T模拟赛一可以说是这几届以来的4T模拟赛题目最难、最有含金量的一届了,难度可以和十三届之前的省赛媲美了,题目中挖的坑很多,加上4T评分的连坐机制,得奖还是不容易的。自测建议用时:2个半小时(从头敲)。
三个难点:
- 矩阵键盘模拟数值输入
- 输入数值时记录时间
- EEPROM写入特定格式的数据
附件:第十五届4T模拟赛一
一、题目分析
首先是第一个难点,矩阵键盘模拟数值输入,而且输入一个数值,先前输入的数值向左移动一位,这个就很烧脑了。
记录页面埋着一个最大的坑。如果你看完题目后和我一样认为记录的时间是从页面切换到记录页面的时间的话,就掉进出题人设计好的陷阱了,它显示的是输入四位数据的起始时间,也就是用户进入输入页面后第一次按下键盘时的时间。
写入EEPROM的数据是有要求的,简单理解就是写入EEPROM的数据是十六进制的,要提前进行进制转换。
分析完题目后,就要开始考虑怎么逐步实现程序了,肯定是要从简单的入手
时间显示页面->记录页面(因为记录页面是和按键相关的,所以要先写按键转换输入数值,再写数码管怎么显示输入数值的过程)->记录页面->Led->EEPROM
二、数码管模块
1.时间页面
老样子,定义SegMode控制页面流转,页面1很简单,代码如下:
/*变量声明区*/
idata u8 SegPos;
idata u8 SegMode;//页面显示模式
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 ucRtc[3] = {0x23,0x59,0x50};//时间存放数组
void SegProc()
{
unsigned char i;
switch(SegMode)
{
case 0:
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;
}
}
2.输入页面
输入页面和按键息息相关,先实现按键转换数值。
S6、S10、S14、S18、S9、S13、S17、S8、S12、S16
对应数字0~9。
S6、S10、S14、S18
对应0~3,不难看出S6、S10、S14、S18
是公差为4的等差数列,,转换后的数值刚好是公差为1的等差数列。所以可以将每个按键的值减去6再除以4得到对应的数值。
(6-6)/4 = 0
(10-6)/4 = 1
(14-6)/4 = 2
(18-6)/4 = 3
同样的,S9、S13、S17
也可以通过以下操作得到对应的数值:
第一步
(9-5)/4=1
(13-5)/4=2
(17-5)/4=3
第二步
1+3 = 4
2+3 = 5
3+3 = 6
S8、S12、S16
:
第一步
(8-8)/4=0
(12-8)/4=1
(16-8)/4=2
第二步
0+7=7
1+7=8
2+7=9
pdata u8 Input[4];//数据存放数组
idata u8 InputIndex;//数组的索引
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyDown = ~KeyVal & KeyOld;
KeyOld = KeyVal;
switch(KeyDown)
{
case 6:case 10:case 14:case 18:
Input[InputIndex++] = (KeyDown - 6) / 4;
break;
case 9:case 13:case 17:
Input[InputIndex++] = (KeyDown - 5) / 4 + 3;
break;
case 8:case 12:case 16:
Input[InputIndex++] = (KeyDown - 8) / 4 + 7;
break;
}
}
由于S6、S10、S14、S18、S9、S13、S17、S8、S12、S16
只在输入页面有效,并且四位数据输入完成后,再按以上按键对显示页面没有影响,因此要加前置条件。
前置条件不能加在
switch
内的原因:switch
内只能存在case
和break
,不能在switch
内出现if-else
语句。
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyDown = ~KeyVal & KeyOld;
KeyOld = KeyVal;
if(SegMode == 1)
{
if(InputIndex < 4)
{
switch(KeyDown)
{
case 6:case 10:case 14:case 18:
Input[InputIndex++] = (KeyDown - 6) / 4;
break;
case 9:case 13:case 17:
Input[InputIndex++] = (KeyDown - 5) / 4 + 3;
break;
case 8:case 12:case 16:
Input[InputIndex++] = (KeyDown - 8) / 4 + 7;
break;
}
}
数码管显示:
由于数码管的显示是和输入数据的个数息息相关的,所以首先考虑使用for
循环并且循环条件和数据个数有关,然后慢慢想可以想到下面的这个for
循环。
void SegProc()
{
unsigned char i;
switch(SegMode)
{
case 1:
SegBuf[0] = 12;
for(i = 0; i < InputIndex; i++)
SegBuf[7-i] = Input[InputIndex-i-1];
break;
}
}
3.记录页面
当第一次输入数值时,记录当时的时间。
第一次输入数值,也就是数据存放数组的索引值为1
pdata u8 ucRtc_record[2];//记录时间
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyDown = ~KeyVal & KeyOld;
KeyOld = KeyVal;
if(SegMode == 1)
{
if(InputIndex < 4)
{
switch(KeyDown)
{
//...
}
if(InputIndex == 1)//索引值为1时
GetucRtc(ucRtc_record);//记录时间
}
}
}
void SegProc()
{
unsigned char i;
switch(SegMode)
{
case 2:
SegBuf[0] = 13;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = ucRtc_record[0] / 16;
SegBuf[4] = ucRtc_record[0] % 16;
SegBuf[5] = 11;
SegBuf[6] = ucRtc_record[1] / 16;
SegBuf[7] = ucRtc_record[1] % 16;
break;
}
}
4.数码管完整代码
void SegProc()
{
unsigned char i;
switch(SegMode)
{
case 0:
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;
case 1:
SegBuf[0] = 12;
for(i = 0; i < InputIndex; i++)
{
SegBuf[7-i] = Input[InputIndex-i-1];
}
break;
case 2:
SegBuf[0] = 13;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = ucRtc_record[0] / 16;
SegBuf[4] = ucRtc_record[0] % 16;
SegBuf[5] = 11;
SegBuf[6] = ucRtc_record[1] / 16;
SegBuf[7] = ucRtc_record[1] % 16;
break;
}
}
三、按键模块
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyDown = ~KeyVal & KeyOld;
KeyOld = KeyVal;
if(SegMode == 1)
{
if(InputIndex < 4)
{
switch(KeyDown)
{
case 6:case 10:case 14:case 18:
Input[InputIndex++] = (KeyDown - 6) / 4;
break;
case 9:case 13:case 17:
Input[InputIndex++] = (KeyDown - 5) / 4 + 3;
break;
case 8:case 12:case 16:
Input[InputIndex++] = (KeyDown - 8) / 4 + 7;
break;
}
if(InputIndex == 1)
GetucRtc(ucRtc_record);
}
//清空输入的数据
if(KeyDown == 5)
{
InputIndex = 0;
Input[0] = Input[1] = Input[2] = Input[3] = 0;
SegBuf[4] = SegBuf[5] = SegBuf[6] = SegBuf[7] = 10;
}
}
if(KeyDown == 4)
{
SegMode++;
//第一次进入输入页面,清空所有数据
if(SegMode == 1)
{
InputIndex = 0;
Input[0] = Input[1] = Input[2] = Input[3] = 0;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 10;
SegBuf[7] = 10;
}
else if(SegMode == 3)
SegMode = 0;
}
}
四、EEPROM模块
也就是说,进入页面二的时候,将记录的时间数据、数据写入EEPROM的内部地址0~4,并且以十六进制的格式写入,所以写入EEPROM前要先进行进制转换。
/*变量声明*/
pdata u8 EEPROM[4];
idata u16 InputDat;//输入的数值转换成十进制
idata u16 InputOld = 9999;//防止第一次输入时标志位使能,第一次默认失效
idata bit LedFlag;
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyDown = ~KeyVal & KeyOld;
KeyOld = KeyVal;
if(SegMode == 1)
{
//...
}
if(KeyDown == 4)
{
SegMode++;
if(SegMode == 1)
{
//...
}
//EEPROM
else if(SegMode == 2)
{
InputDat = Input[0] * 1000 + Input[1] * 100 + Input[2] * 10 + Input[3];
EEPROM[0] = (ucRtc_record[0] / 16) * 10 + ucRtc_record[0] % 16;
EEPROM[1] = (ucRtc_record[1] / 16) * 10 + ucRtc_record[1] % 16;
EEPROM[2] = InputDat >> 8;
EEPROM[3] = InputDat & 0x00ff;
eeprom_write(EEPROM,0,4);
LedFlag = (InputDat > InputOld);//LED4使能标志位
InputOld = InputDat;
}
else if(SegMode == 3)
SegMode = 0;
}
}
这边解释一下EEPROM[2] = InputDat >> 8;
EEPROM[3] = InputDat & 0x00ff;
,由于InputDat
是将存放输入数据的数组转换成十进制,十进制转换成十六进制时,只需要将十进制转换成二进制,每四位二进制对应一位十六进制即可。
例如:InputDat的值为1025
1025的二进制:0000 0100 0000 0001
将0000 0100 0000 0001的高八位提取出来:
0000 0100 0000 0001 >> 8 = 0000 0100
将0000 0100 0000 0001的低八位提取出来:
0000 0100 0000 0001 & 0000 0000 1111 1111
= 0000 0001
也就是0000 0100 0000 0001 & 0x00ff = 0000 0001
五、Led模块
void LedProc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == SegMode);
ucLed[3] = LedFlag;
LedDisp(ucLed);
}
六、完整代码
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "ds1302.h"
#include "iic.h"
/* 变量声明区 */
unsigned char Key_Slow; //按键减速变量 10ms
unsigned char Key_Val, Key_Down, Key_Up, Key_Old; //按键检测四件套
unsigned int Seg_Slow; //数码管减速变量 500ms
unsigned char Seg_Buf[] = {10,10,10,10,10,10,10,10,10,10}; //数码管缓存数组
unsigned char Seg_Pos; //数码管缓存数组专用索引
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0}; //数码管小数点使能数组
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0}; //LED显示数据存放数组
unsigned char ucRtc[3] = {0x23,0x59,0x55}; //存放时间
unsigned char Seg_Mode; //页面显示
unsigned char Input[3]; //存放记录的时、分
unsigned char Input_Dat[4]; //存放输入的四位数据
unsigned char Input_Dat_Index; //存放输入的四位数据索引
unsigned int Dat_New; //输入的四位数据转换十进制
unsigned int Dat_Old = 9999; //输入四位数据上次的值
unsigned char EEPROM_Dat[4]; //EEPROM的数据
bit LED4; //Led使能位
/* 按键处理函数 */
void Key_Proc()
{
unsigned char i;
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;
if(Seg_Mode == 1)//处于输入页面
{
if(Key_Down == 5)
{
for(i = 0; i < 4; i++)
Input_Dat[i] = 10;
Input_Dat_Index = 0;
}
if(Input_Dat_Index == 1)
DS1302_Read(Input);
if(Input_Dat_Index < 4)
{
switch(Key_Down)
{
case 6:case 10:case 14:case 18:
Input_Dat[Input_Dat_Index] = (Key_Down - 6) / 4;
Input_Dat_Index++;
break;
case 9:case 13:case 17:
Input_Dat[Input_Dat_Index] = (Key_Down / 4) + 2;
Input_Dat_Index++;
break;
case 8:case 12:case 16:
Input_Dat[Input_Dat_Index] = (Key_Down / 4) + 5;
Input_Dat_Index++;
break;
}
}
}
if(Key_Down == 4)
{
if(++Seg_Mode == 3)
Seg_Mode = 0;
if(Seg_Mode == 2)
{
Dat_New = Input_Dat[0] * 1000 + Input_Dat[1] * 100 + Input_Dat[2] * 10 + Input_Dat[3];
EEPROM_Dat[0] = (Input[0] / 16) * 10 + Input[0] % 16;
EEPROM_Dat[1] = (Input[1] / 16) * 10 + Input[1] % 16;
EEPROM_Dat[2] = Dat_New >> 8;
EEPROM_Dat[3] = Dat_New & 0x00ff;
EEPROM_Write(EEPROM_Dat,0,4);
LED4 = (Dat_New > Dat_Old);
Dat_Old = Dat_New;
}
for(i = 0; i < 4; i++)
Input_Dat[i] = 10;
Input_Dat_Index = 0;
}
}
/* 信息处理函数 */
void Seg_Proc()
{
unsigned char i;
if(Seg_Slow) return;
Seg_Slow = 1; //数码管减速
DS1302_Read(ucRtc);
switch(Seg_Mode)
{
case 0:
Seg_Buf[2] = Seg_Buf[5] = 11;
for(i = 0; i < 3; i++)
{
Seg_Buf[3*i] = ucRtc[i] / 16;
Seg_Buf[3*i+1] = ucRtc[i] % 16;
}
break;
case 1:
Seg_Buf[0] = 12;
for(i = 1; i < 4; i++)
Seg_Buf[i] = 10;
if(Input_Dat_Index == 0)
for(i = 4; i < 8; i++)
Seg_Buf[i] = 10;
else
for(i = 0; i < Input_Dat_Index; i++)
{
Seg_Buf[7 - i] = Input_Dat[Input_Dat_Index - i - 1];
}
break;
case 2:
Seg_Buf[0] = 13;
Seg_Buf[3] = Input[0] / 16;
Seg_Buf[4] = Input[0] % 16;
Seg_Buf[5] = 11;
Seg_Buf[6] = Input[1] / 16;
Seg_Buf[7] = Input[1] % 16;
break;
}
}
/* 其他显示函数 */
void Led_Proc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == Seg_Mode);
ucLed[3] = LED4;
}
/* 定时器0初始化函数 */
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
}
/* 定时器0中断服务函数 */
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(Seg_Pos,ucLed[Seg_Pos]);
}
void main()
{
DS1302_Write(ucRtc);
Init();
Timer0_Init();
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}