目录
前言
一、为什么能保底省三
二、数显模块的实现
1.数码管显示编辑
1)断码表
2)位选
3)段选
4)扫描
2.菜单
三、按键功能的实现
1.按键扫描
2.菜单切换
四、完整代码演示
五、结语
前言
上一期介绍全家桶时,跟大家提到:比赛时可以做出来按键切换菜单的部分,这样子起码省三就有了。这篇文章就去教大家如何实现省三保底。当然,这些代码只靠背可能不太够,比赛的时候需要根据题目小改一下才行。
一、为什么能保底省三
数显+按键切换菜单完成,基本就可以拿到省三,这个比赛是看你完成度给你“打分”的,数显和按键功能其实已经占了题目不少的一部分了,这里的数显是指:读取不到有效数据,仅能显示一个不会更新的数据。
如果你能灵活使用底层驱动的那几个外设,读取到有效数据,顺便完成一点LED灯,那冲击省二都有可能,如果你又能灵活地对数据进行处理,完成题目对数据处理方面的要求,基本完成赛题上的所有功能,那基本省一就稳了。
当然,这也只是我个人说的哈,但也确实听不少人说“完成数显+按键切换菜单可以拿省三”和“基本完成题目要求的功能就能稳省一”,我也绝不是空穴来风。
我去年参加蓝桥杯时,NE555读取部分没有实现,其他功能基本实现,也顺利进入了国赛。
二、数显模块的实现
这里以第十三届比赛题目为例,介绍一下数显和按键部分的实现。
1.数码管显示
这部分都是十分基础的代码,不过毕竟这篇文章就是“保底省三”的代码,这部分还是有必要详细介绍一下的。
1)断码表
首先,比赛时会给你一个断码表,所以断码自己会使用计算机算即可,没必要记,真需要显示某个符号了,就使用计算机算一算,下面是23年给的断码表
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
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e //F
}
前边那个“code”,抄的时候删不删都可以,没影响。现在不管是个人的电脑,或者赛点的电脑,上边的计算机都有程序员模式,可以切换单位键盘,帮助完成断码
2)位选
在让数码管显示数据之前,我们先需要位选,何为位选呢?其实就是选择你要显示那个数码挂的数据。下图中圈住的,就是用于控制位选的锁存器。我们只需要把需要传输的数据放到P0里,在使能锁存器,并失能锁存器(说人话就是开关一下锁存器,让他更新一下数据就好),就可以把P0的数据存到锁存器里了。P0需要放到数据就是,我们要控制哪一个数码管,高电平有效。一般情况下我们都是一次控制一个数码管,所以P0当中只有一个数据是1,其他都是0.
比如P0=0000 0001就是控制第0个数码管(或者说最左边那个),P0=0000 0010就是控制第一个数码管。P0=0000 1111就是同时控制数码管,给P0赋值之后,开关一次锁存器,就可以选中我们要控制的数码管了,接下来的段选,只影响当前被选中的锁存器。
刚才提到开关锁存器,也就是使能和失能锁存器,锁存器的控制端连在了Y6C,高电平有效,我们只需要给Y6C高电平,在给低电平即可完成开关锁存器。
Y6C连接在一个三线八线译码器上如下图(右边的东西这里不过多介绍)
P27,P26,P25是3线输出,Y0到Y7是7线输出。下边P27,P26,P25简称输入:
如果输入000,则选中Y0;如果输入110,也就是二进制的6,则选择Y6,经过有右边的与非门之后,就可以选中Y6C,打开刚才位选的锁存器。
因此,我们只需要给P2的高三位为110就可以打开位选,给高三位000就可以关闭位选。我们可以定义一个宏定义,来实现位选
#define NIXIE_CHECK() P2|=0xC0;P2&=0xDF;P2&=0x1F;
3)段选
段选与上述位选类似,就是把数码管需要显示的数据放在P0然后再开关一次锁存器即可。也就是下图中左上角的锁存器。P0放的数据,就是我们最开始提到的断码数据。
我们同样使用一个宏定义来完成段选
#define NIXIE_ON() P2|=0xE0;P2&=0xFF;P2&=0x1F;
4)扫描
为了让数码管显示不同的数据,我们一般是一次性控制一个数码管,如果我们想让八个数码管同时显示不同的数据,就需要扫描。扫描通俗点说就是先控制第一个数码管显示数据,趁着第一个还没熄灭就再去控制第二个数码管显示数据,在控制第三个......完成一轮之后再控制第一个,已知循环下去。
由于它对于时间有要求,所以我们需要把它放在定时器完成扫描。使用location记录当前扫描到第几个数码管了,使用Nixie_num数组记录八个数码管各自需要显示的数据,定时器时间为1ms。
中断服务函如下:
void Timer0_Isr(void) interrupt 1
{
P0=0x01<<location;NIXIE_CHECK();
P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
if(++location==8)
location=0;
}
我们只需在main或者菜单显示函数中给Nixie_num数组对应位置赋值,即可让他显示不同的数据
2.菜单
我们定义一个标志位mod,当mod=0时显示第一个菜单,mod=1时显示第二个菜单以此类推。
这里我们手动添加一个断码表,使之可以显示U和“-”,以及对应的带小数点的数字。
我们注意到题目要求数显部分(第二章开头有介绍),第一位数码管显示的都是U,第二位是当前是第几个菜单,所以我们把这连个单独写出来。注意不需要显示的位需要熄灭,避免切换菜单时不这个菜单不显示的位被异常点亮。
这样我们就可以通过改变mod的值,来显示不同的菜单了
void show_menu(void)
{
Nixie_num[0]=21;//显示U
Nixie_num[1]=mod+1;//显示菜单数
if(mod==0)
{
Nixie_num[2]=20;//熄灭
Nixie_num[3]=20;//熄灭
Nixie_num[4]=20;//熄灭
Nixie_num[5]=2;//显示2
Nixie_num[6]=3+10;//显示3.
Nixie_num[7]=5;//显示5
}
else if(mod==1)
{
Nixie_num[2]=20;
Nixie_num[3]=2;
Nixie_num[4]=3;
Nixie_num[5]=22;
Nixie_num[6]=2;
Nixie_num[7]=3;
}
else if(mod==2)
{
Nixie_num[2]=20;
Nixie_num[3]=20;
Nixie_num[4]=20;
Nixie_num[5]=20;
Nixie_num[6]=2;
Nixie_num[7]=3;
}
}
三、按键功能的实现
按键功能要求,以第十三届为例。这里仅完成“切换”按键
1.按键扫描
相信大家单片机入手的第二堂课就是按键(第一堂课应该是点亮LED),这里按键扫描就不过多介绍了,这里放一个我自己用的矩阵按键扫描代码
void get_key(void)
{
unsigned char key_P3=P3;
unsigned char key_P4=P4;
P3=0xFF;
P44=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=7;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=6;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=5;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=4;}
P42=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=11;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=10;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=9;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=8;}
P35=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=15;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=14;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=13;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=12;}
P34=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=19;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=18;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=17;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=16;}
key_value=0;
P3=key_P3;
P4=key_P4;
}
函数末尾把key_value又置为了0,是因为后续所有按键处理都要在按键扫描函数里完成,如果你是刚写完按键扫描,想测试一下的话,可以吧key_value=0给删掉,在main里使用数码管显示key_value的值。
2.菜单切换
简单点说,如果按下了S12,就把菜单从1切换到2或者从2切换到3或者从3切换到1。我们只需要把下面的代码放到上述key_value=0之前即可
if(key_value==12)
{
if(mod==0)
mod=1;
else if(mod==1)
mod=2;
else if(mod==2)
mod=0;
}
可能会有人说,我这样写真的好LOW,不过我感觉,我只是为了应对这个比赛才写这样的代码的,这样写纯属是为了少犯错误,容易修改,仅此而已。你当然也可以把代码写的很华丽,包括阿刚才show_menu函数里的代码,只要能达到预期效果就行。
这里仅仅实现了按键切换菜单,有的按键还有切换参数的功能,需要把数码管显示的数据改为对应的参数,在通过按键对参数进行加减,这里不再介绍。
四、完整代码演示
main.c
#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
#include "ds1302.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
//0.到9.
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
0xFF,//20 熄灭
0xC1,//21 U
0xBF//22 -
};
#define NIXIE_CHECK() P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON() P2|=0xE0;P2&=0xFF;P2&=0x1F;
unsigned char Nixie_num[]={20,20,20,20,20,20,20,20};
void Timer0_Init(void); //1毫秒@12.000MHz
void Delay100ms(void); //@12.000MHz
void get_key(void);
void show_menu(void);
unsigned char location=0;
unsigned char key_value=0;
unsigned char mod=0;
void main()
{
Timer0_Init();
EA=1;
while(1)
{
get_key();
Delay100ms();
}
}
void Timer0_Isr(void) interrupt 1
{
P0=0x01<<location;NIXIE_CHECK();
P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
if(++location==8)
location=0;
}
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x20; //设置定时初始值
TH0 = 0xD1; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
}
void Delay100ms(void) //@12.000MHz
{
unsigned char data i, j, k;
_nop_();
_nop_();
i = 5;
j = 144;
k = 71;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay5ms(void) //@12.000MHz
{
unsigned char data i, j;
i = 59;
j = 90;
do
{
while (--j);
} while (--i);
}
void get_key(void)
{
unsigned char key_P3=P3;
unsigned char key_P4=P4;
P3=0xFF;
P44=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=7;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=6;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=5;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=4;}
P42=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=11;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=10;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=9;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=8;}
P35=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=15;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=14;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=13;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=12;}
P34=0;
if(P30==0){Delay5ms();while(P30==0);Delay5ms();key_value=19;}
else if(P31==0){Delay5ms();while(P31==0);Delay5ms();key_value=18;}
else if(P32==0){Delay5ms();while(P32==0);Delay5ms();key_value=17;}
else if(P33==0){Delay5ms();while(P33==0);Delay5ms();key_value=16;}
//S12
if(key_value==12)
{
if(mod==0)
mod=1;
else if(mod==1)
mod=2;
else if(mod==2)
mod=0;
}
key_value=0;
P3=key_P3;
P4=key_P4;
}
void show_menu(void)
{
Nixie_num[0]=21;//显示U
Nixie_num[1]=mod+1;//显示菜单数
if(mod==0)
{
Nixie_num[2]=20;//熄灭
Nixie_num[3]=20;//熄灭
Nixie_num[4]=20;//熄灭
Nixie_num[5]=2;//显示2
Nixie_num[6]=3+10;//显示3.
Nixie_num[7]=5;//显示5
}
else if(mod==1)
{
Nixie_num[2]=20;
Nixie_num[3]=2;
Nixie_num[4]=3;
Nixie_num[5]=22;
Nixie_num[6]=2;
Nixie_num[7]=3;
}
else if(mod==2)
{
Nixie_num[2]=20;
Nixie_num[3]=20;
Nixie_num[4]=20;
Nixie_num[5]=20;
Nixie_num[6]=2;
Nixie_num[7]=3;
}
}
五、结语
不同人写代码的风格不一样,我只是分享了我写的一套省三代码。如果你自己之前有跟其他人学过,现在不妨把代码整理整理,至少做到连续两次,可以写出一模一样的代码,这样可以减少比赛时犯错误。
最后,希望没有准备充分的同学不要放弃,现在冲一冲那个省二省三也不是问题,提前预祝冲击省一的同学,国赛再见!