1.题目
一、基本要求
使用大赛组委会统一提供的四梯/国信长天单片机竞赛实训平台,完成本试题的程序设计与调试。程序编写、调试完成后,选手需通过考试系统提交以准考证号命名的hex文件。不符合以上文件提交要求的作品将被评为零分或者被酌情扣分。
硬件设置:
将IAP15F2K61S2单片机内部振荡器频率设定为12MHz。
键盘工作模式跳线J5配置为KBD矩阵按键模式。
扩展方式跳线J13配置为IO模式。
请注意:选手需严格按照以上要求配置竞赛板,编写和调试程序,不符合以上配置要求的作品将被评为零分或者被酌情扣分。
二、硬件框图
图1 系统硬件框图
三、功能描述
3.1 基本功能描述
1)通过读取DS1302 RTC芯片,获取时间数据;
2)通过EEPROM实现数据记录功能;
3)通过LED指示灯完成试题要求的状态指示功能;
4)通过数码管、按键完成试题要求的数据显示和界面切换功能。
3.2 显示功能
1)时间界面
时间界面如图2所示,显示内容包括时、分、秒数据和间隔符“-” 时、分、秒数据固定占 2 位显示宽度,不足 2 位补 0。
图2 时间界面
2)输入界面
输入界面如图3所示,由标识符(C)和一个4位数据组成,4位数据通过4位数码管显示,每输入一位数据,数码管显示向左移动一位,直到完成4位数据的输入。
图3 输入界面
每次重新进入输入界面时,默认显示4位数据的4位数码管处于熄灭状态。
3)记录界面
记录界面如图4所示,由标识符(E)和输入4位数据的起始时和分数据以及时、分数据的间隔符(-)。
图4 记录界面
4)显示要求
按照题目要求的界面格式和切换方式进行设计。
数码管显示无重影、闪烁、过暗、亮度不均匀等严重影响显示效果的缺陷。
3.3 按键功能
1)功能说明
① S4:定义为“切换”,按下S4按键,切换“时间界面”、“输入界面”和“记录界面”。
图5 界面切换顺序
② S5:定义为“清除”,在输入界面下,按下S5,清除当前输入的数据,显示输入数据的4位数码管全部熄灭。
③ S6、S10、S14、S18、S9、S13、S17、S8、S12、S16分别对应数值“0-9”,在“输入界面”下,按下对应按键,实现一位对应数据的输入,显示格式及要求如图3所。若当前4位数据输入完成,按键继续输入不影响当前已输入完成的4位数据。
2)按键要求
按键应做好消抖处理,避免出现一次按键动作导致功能多次触发。
按键动作不影响数码管显示等其他功能。
按键 S5、S6、S10、S14、S18、S9、S13、S17、S8、S12、S16仅在“输入界面”有效。
3.4 记录功能
每次进入记录界面时,将4位数据和输入数据的起始时间保存到E2PROM,存储位置要求如下:
输入数据起始时间(时):E2PROM内部地址0;
输入数据起始时间(分):E2PROM内部地址1;
输入数据(高字节):E2PROM内部地址2;
输入数据(低字节):E2PROM内部地址3;
注意:
E2PROM写入操作发生在切换到记录界面时,其它时间不写入。
输入数据以HEX编码写入E2PROM,例如数据数据为1234,E2PROM内部地址2中应保存04H, 内部地址3中保存D2H。
3.5 LED指示灯功能
1、界面指示灯
1)时间界面下,指示灯 L1 点亮,否则指示灯 L1 熄灭。
2)输入界面下,指示灯 L2 点亮,否则指示灯 L2 熄灭。
3)记录界面下,指示灯 L3 点亮,否则指示灯 L3 熄灭。
2、记录指示灯
若本次记录的4位数据较上一次记录的4位数据大,则指示灯L4点亮,否则指示灯熄灭。
3.6 初始状态
请严格按照以下要求设计作品的上电初始状态。
1)处于时间界面
2)指示灯L1点亮,其余指示灯熄灭。
2.代码
2.1 main.c
main.c
/**************头文件声明区*******************/
#include <STC15F2K60S2.H>
#include <ds1302.h>
#include <iic.h>
#include <Init.h>
#include <Key.h>
#include <Seg.h>
#include <Led.h>
/***************变量定义区*********************/
/****键盘专用******/
unsigned char Key_Slow_Down;//10
unsigned char Key_Val,Key_Down,Key_Up,Key_Old;
/****数码管专用****/
unsigned int Seg_Slow_Down;//500
unsigned char Seg_Pos;
unsigned char Seg_Buf[8]={10,10,10,10,10,10,10,10};
unsigned char Seg_Point[8]={0,0,0,0,0,0,0,0};
/****Led专用******/
unsigned char ucLed[8]={0,0,0,0,0,0,0,0};
/****界面专用*****/
unsigned char Seg_Disp_Mode;//界面标志位:0-时间界面;1-输入界面;2-记录界面
//时间界面
unsigned char ucRtc[3]={0x24,0x09,0x59};
//输入界面
bit Input_Flag;//判断是否输入数据 1-输入数据 0-没有输入数据
unsigned char Input[4]={10,10,10,10};//输入数组,默认为10(熄灭状态)
unsigned char Input_Index;//输出数组标志位 0-3
unsigned int Value_Old;//记录上一次数码管输入的值
unsigned int Value;//输入的值
//记录界面
unsigned char ucRtc_Data[3]={0x00,0x00,0x00};
unsigned char Hour,Minute;//记录时-分
unsigned char high,low;//记录Value
/****************函数处理区********************/
//清除数码管数据并将数码管(4-7)熄灭
void Clear_Data()
{
unsigned char i;
for(i=4;i<8;i++)
{
Seg_Buf[i]=10;
Seg_Point[i]=0;
Input[i-4]=10;
}
Input_Index=0;//恢复Input_Index的值
}
//记录按下的时间
void Remeber_Time(void)
{
unsigned char i;
for(i=0;i<3;i++)
ucRtc_Data[i]=ucRtc[i];
}
//按键处理函数
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down=1;
//消抖
Key_Val=Key_Read();
Key_Down=Key_Val&(Key_Old^Key_Val);
Key_Up=~Key_Val&(Key_Old^Key_Val);
Key_Old=Key_Val;
if(Key_Down>=1&&Key_Down<=10&&Input_Index<=3)//输入值在0-9且数组在0-3
{
if(Seg_Disp_Mode==1)
{
Input[Input_Index]=Key_Down-1;
//记录按下第一个数子的时间
if(Input_Index==0) Remeber_Time();
Input_Index++;
//数码管显示
Input_Flag=1;
}
}
switch(Key_Down)
{
//S4
case 11:
if(++Seg_Disp_Mode==3)
Seg_Disp_Mode=0;
//令输入界面数码管清零
if(Seg_Disp_Mode==0)
{
Value_Old=Value;//记录下上一次的值
Input_Flag=0;
Input_Index=0;//令数据重新等于0
}
//记录输入界面输入的数据
else if(Seg_Disp_Mode==2)
{
Value=Input[0]*1000+Input[1]*100+Input[2]*10+Input[3];
high=(unsigned char)(Value>>8);
low=(unsigned char)(Value&0x00ff);
//16进制要变成10进制
Hour=ucRtc_Data[0]/16*10+ucRtc_Data[0]%16;
Minute=ucRtc_Data[1]/16*10+ucRtc_Data[1]%16;
Write_EEPROM(&Hour,0,1);
Write_EEPROM(&Minute,1,1);
Write_EEPROM(&high,2,1);
Write_EEPROM(&low,3,1);
}
break;
//S5
case 12:
if(Seg_Disp_Mode==1)
{
Clear_Data();
Value=0;//此时输入数据为0
}
break;
}
}
//数码管清理函数
void Clear_Seg()
{
unsigned char i;
for(i=0;i<8;i++)
{
Seg_Buf[i]=10;
Seg_Point[i]=0;
}
}
//数码管处理函数
void Seg_Proc()
{
if(Seg_Slow_Down) return;
Seg_Slow_Down=1;
Read_Rtc(ucRtc);//实时获取时间
switch(Seg_Disp_Mode)
{
/****时间界面******/
case 0:
Clear_Seg();
Seg_Buf[0]=ucRtc[0]/16;
Seg_Buf[1]=ucRtc[0]%16;
Seg_Buf[2]=11;//-
Seg_Buf[3]=ucRtc[1]/16;
Seg_Buf[4]=ucRtc[1]%16;
Seg_Buf[5]=11;//-
Seg_Buf[6]=ucRtc[2]/16;
Seg_Buf[7]=ucRtc[2]%16;
break;
/****输入界面******/
case 1:
Clear_Seg();
Seg_Buf[0]=12;//C
if(Input_Flag==1)
{
if(Input_Index-1==0)
Seg_Buf[7]=Input[0];
else if(Input_Index-1==1)
{
Seg_Buf[6]=Input[0];
Seg_Buf[7]=Input[1];
}
else if(Input_Index-1==2)
{
Seg_Buf[5]=Input[0];
Seg_Buf[6]=Input[1];
Seg_Buf[7]=Input[2];
}
else
{
Seg_Buf[4]=Input[0];
Seg_Buf[5]=Input[1];
Seg_Buf[6]=Input[2];
Seg_Buf[7]=Input[3];
}
}
break;
/****记录界面******/
case 2:
Clear_Seg();
Seg_Buf[0]=13;//E
Seg_Buf[3]=ucRtc_Data[0]/16;
Seg_Buf[4]=ucRtc_Data[0]%16;
Seg_Buf[5]=11;//-
Seg_Buf[6]=ucRtc_Data[1]/16;
Seg_Buf[7]=ucRtc_Data[1]%16;
break;
}
}
//其他处理函数
void Led_Proc()
{
unsigned char i;
for(i=0;i<3;i++)
ucLed[i]=(i==Seg_Disp_Mode);
ucLed[3]=(Value>Value_Old)?1:0;
}
//定时器0初始化
void Timer0Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
}
void Timer0Sever()interrupt 1
{
if(++Key_Slow_Down==10) Key_Slow_Down=0;
if(++Seg_Slow_Down==500) Seg_Slow_Down=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]);
}
/****************main()函数********************/
void main()
{
Timer0Init();
System_Init();
Set_Rtc(ucRtc);
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
2.2 底层代码
2.2.1 DS1302
ds1302.c
/* # DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <ds1302.h>
#include <STC15F2K60S2.H>
#include <intrins.h>
sbit SDA=P2^3;
sbit SCK=P1^7;
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);
}
void Set_Rtc(unsigned char *ucRtc)
{
unsigned char i;
Write_Ds1302_Byte(0x8e,0);//关闭写保护
for(i=0;i<3;i++)
Write_Ds1302_Byte(0x84-2*i,ucRtc[i]);
Write_Ds1302_Byte(0x8e,1);//打开写保护
}
void Read_Rtc(unsigned char *ucRtc)
{
unsigned char i;
for(i=0;i<3;i++)
ucRtc[i]=Read_Ds1302_Byte(0x85-2*i);
}
2.2.2 EEPROM
iic.c
/* # I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <iic.h>
#include <intrins.h>
sbit scl=P2^0;
sbit sda=P2^1;
#define DELAY_TIME 5
//
static void I2C_Delay(unsigned char n)
{
do
{
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}
while(n--);
}
//
void I2CStart(void)
{
sda = 1;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 0;
I2C_Delay(DELAY_TIME);
scl = 0;
}
//
void I2CStop(void)
{
sda = 0;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 1;
I2C_Delay(DELAY_TIME);
}
//
void I2CSendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++){
scl = 0;
I2C_Delay(DELAY_TIME);
if(byt & 0x80){
sda = 1;
}
else{
sda = 0;
}
I2C_Delay(DELAY_TIME);
scl = 1;
byt <<= 1;
I2C_Delay(DELAY_TIME);
}
scl = 0;
}
//
unsigned char I2CReceiveByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++){
scl = 1;
I2C_Delay(DELAY_TIME);
da <<= 1;
if(sda)
da |= 0x01;
scl = 0;
I2C_Delay(DELAY_TIME);
}
return da;
}
//
unsigned char I2CWaitAck(void)
{
unsigned char ackbit;
scl = 1;
I2C_Delay(DELAY_TIME);
ackbit = sda;
scl = 0;
I2C_Delay(DELAY_TIME);
return ackbit;
}
//
void I2CSendAck(unsigned char ackbit)
{
scl = 0;
sda = ackbit;
I2C_Delay(DELAY_TIME);
scl = 1;
I2C_Delay(DELAY_TIME);
scl = 0;
sda = 1;
I2C_Delay(DELAY_TIME);
}
void Read_EEPROM(unsigned char* EEPROM_string,unsigned char addr,unsigned char num)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xA1);
I2CWaitAck();
while(num--)
{
*EEPROM_string++=I2CReceiveByte();
if(num) I2CSendAck(0);
else I2CSendAck(1);
}
I2CStop();
}
void Write_EEPROM(unsigned char* EEPROM_string,unsigned char addr,unsigned num)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
while(num--)
{
I2CSendByte(*EEPROM_string++);
I2CWaitAck();
I2C_Delay(200);
}
I2CStop();
}
2.3 其他(Key.c)
关于数码管,Led灯代码可以参考我第十四届的代码,下面是我按键的代码
Key.c
#include <STC15F2K60S2.H>
#include <Key.h>
unsigned char Key_Read(void)
{
unsigned char temp=0;
P44=0;P42=1;P35=1;P34=1;
if(P33==0) temp=11;
if(P32==0) temp=12;
if(P31==0) temp=1;
P44=1;P42=0;P35=1;P34=1;
if(P33==0) temp=8;
if(P32==0) temp=5;
if(P31==0) temp=2;
P44=1;P42=1;P35=0;P34=1;
if(P33==0) temp=9;
if(P32==0) temp=6;
if(P31==0) temp=3;
P44=1;P42=1;P35=1;P34=0;
if(P33==0) temp=10;
if(P32==0) temp=7;
if(P31==0) temp=4;
return temp;
}
3.后话
代码仅供参考,欢迎大家指出不足,一起交流