聚沙成塔:4T网单片机按键键值实时显示练习指南

题目:

今日疑惑:键盘扫描消抖时,使用10ms的软件延时,数码管显示则利用1ms触发一次的定时中断。由于数码管需要显示按键值,那么软件延时和定时中断是否会产生冲突,从而显示错误呢?

答案:在51单片机中,使用软件延时10ms进行按键消抖和使用定时器中断1ms中断一次不会直接冲突。软件延时10ms会阻塞主程序的执行,如果在这段时间内定时器中断被触发,中断服务程序仍然会执行,但主程序的延时循环不会受到影响。主要是因为软件延时利用的是CPU,只影响CPU的进程,而定时器中断则是利用的额外的定时器硬件。

今天的练习一波三折,提交了4次才满分……全是易错点,总之收获满满,毕竟在考试前踩的坑越多,在考试的时候就越顺利喽。

代码:

1、STC15F2K60S2基础配置、宏定义、类型重定义

#include <STC15F2K60S2.H>

#define Y0C P2=P2&0X1F|0X00
#define Y4C P2=P2&0X1F|0X80
#define Y5C P2=P2&0X1F|0XA0
#define Y6C P2=P2&0X1F|0XC0
#define Y7C P2=P2&0X1F|0XE0

typedef unsigned char u8;	//取值范围0~255,2^8=256,1111 1111=255,即0xff=255
typedef unsigned int u16; //取值范围0~65535,2^16=65536,1111 1111=65535,即0xff=65535

2、定义变量和数组

//段选表
code unsigned char Seg_Table[] =  
{ 
0xc0,  //0
0xf9,  //1
0xa4,  //2
0xb0,  
0x99,  
0x92,  
0x82,  
0xf8,  
0x80,  
0x90,  //9
//符号
0xff,	 //空
//字母
0xc8,  //N 1100 1000
0x88,  //A
0x83,  //b
0xc6,  //C
0xa1,  //d
0x86,  //E
0x8e   //F
};
//位选表
code u8 com[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

//段选索引
u8 seg_index[]={11,10,10,10,10,10,10,0};

 3、按键消抖延迟10ms函数

void Delay10ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 117;
	j = 184;
	do
	{
		while (--j);
	} while (--i);
}

4、设备初始化函数

void init()
{
	Y4C;P0=0XFF;Y0C;
	Y5C;P0=0X00;Y0C;
	Y4C;P0=0XFF;Y0C;
	Y4C;P0=0XFF;Y0C;
}

5、数码管显示函数

void seg_display()
{
	static u8 num=0;
	//消隐(段选、位选、段选)
	Y7C;P0=0XFF;Y0C;
	Y6C;P0=com[num];Y0C;
	Y7C;P0=Seg_Table[seg_index[num]];Y0C;
	if(++num==8)
		num=0;
}

6、数码管索引值函数

void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7)
{
	//d0对应最左边的数码管
	seg_index[0]=d0;
	seg_index[1]=d1;	
	seg_index[2]=d2;	
	seg_index[3]=d3;	
	
	seg_index[4]=d4;	
	seg_index[5]=d5;	
	seg_index[6]=d6;	
	seg_index[7]=d7;	
}

7、获取键值函数

//获取键值函数
u8 get_key()
{
	//静态变量,用于存放键盘值,记得必须赋值初始化0
	//!!!使用静态变量代表第一次调用该函数默认key_value=0,后续调用不再初始化为0。
	static u8 key_value=0;
	
	//→ 第一列
	P34=1;P35=1;P42=1;P44=0;
	if(P33==0) {
		Delay10ms();
		if(P33==0)key_value=1;
		while(!P33);
	}
	if(P32==0) {
		Delay10ms();
		if(P32==0)key_value=2;
		while(!P32);
	}
	if(P31==0) {
		Delay10ms();
		if(P31==0)key_value=3;
		while(!P31);
	}
	if(P30==0) {
		Delay10ms();
		if(P30==0)key_value=4;
		while(!P30);
	}
	
	//→ 第二列
	P34=1;P35=1;P42=0;P44=1;
	if(P33==0) {
		Delay10ms();
		if(P33==0)key_value=5;
		while(!P33);
	}
	if(P32==0) {
		Delay10ms();
		if(P32==0)key_value=6;
		while(!P32);
	}
	if(P31==0) {
		Delay10ms();
		if(P31==0)key_value=7;
		while(!P31);
	}
	if(P30==0) {
		Delay10ms();
		if(P30==0)key_value=8;
		while(!P30);
	}
		
	//→ 第三列
	P34=1;P35=0;P42=1;P44=1;
	if(P33==0) {
		Delay10ms();
		if(P33==0)key_value=9;
		while(!P33);
	}
	if(P32==0) {
		Delay10ms();
		if(P32==0)key_value=10;
		while(!P32);
	}
	if(P31==0) {
		Delay10ms();
		if(P31==0)key_value=11;
		while(!P31);
	}
	if(P30==0) {
		Delay10ms();
		if(P30==0)key_value=12;
		while(!P30);
	}
		
	//→ 第四列
	P34=0;P35=1;P42=1;P44=1;	
	if(P33==0) {
		Delay10ms();
		if(P33==0)key_value=13;
		while(!P33);
	}
	if(P32==0) {
		Delay10ms();
		if(P32==0)key_value=14;
		while(!P32);
	}
	if(P31==0) {
		Delay10ms();
		if(P31==0)key_value=15;
		while(!P31);
	}
	if(P30==0) {
		Delay10ms();
		if(P30==0)key_value=16;
		while(!P30);
	}
	
	return key_value;
}

8、显示键值函数

void key_display()
{
	// 调用 key() 函数获取按键值
	u8 key_value = get_key();
	//键盘值为一位数
	if(key_value<10)
		set_index(11,10,10,10,10,10,10,key_value);
	//键盘值为两位数
	else
		set_index(11,10,10,10,10,10,key_value/10,key_value%10);
}

9、中断服务函数,1ms刷新一个数码管

void tm1_isr() interrupt 3 
{
	seg_display();//中断服务,负责刷新数码管
}

10、定时器 1的一毫秒定时初始化函数

void Timer1Init()		//1毫秒@12.000MHz
{
	AUXR |= 0x40;		//定时器时钟1T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x20;		//设置定时初值
	TH1 = 0xD1;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	ET1 = 1;                        //使能定时器0中断
  EA = 1;
}

11、主函数

main()
{
	init();
	Timer1Init();
	while(1)
	{
		//调用键盘显示函数
		key_display();
	}
}

学习总结:

1、定时器的选择

PCA定时器用于超声波

模式 用途 注意事项
定时器0 16位自动重载 Ne555测频率 修改代码作计数器
定时器1 16位自动重载 模式0 主程序控制,中断服务 添加代码开中断
定时器2 16位自动重载 模式0 串口通信
PCA定时器 超声波

本实验只需用到定时器1,来刷新数码管显示即可。

拓展:看到大佬说如果用到了定时器2,则在键盘扫描函数中,需要在开头关闭T2R(定时器2),是为了避免串口和按键冲突,最后结尾记得再次打开。

AUXR &= 0xEF;  表示关闭          AUXR |= 0x10;  表示打开

2、键盘基础知识

矩阵键盘的顺序:左 -> 右,下 -> 上,依次是S4—S19。

引脚分配如图所示:

行row(30 31 32 33)列colum(34 35 42 44)

3、键盘扫描函数编写要仔细

  • 这块代码,冗长繁复,复制粘贴后,一定要逐个仔细认真不留遗漏的修改数字,我今天已经在这上面吃了好多次亏了。以下就是错误示例:
  •     if(P33==0) {
            Delay10ms();
            if(P30==0)key_value=9;
            while(!P30);
        }

  • if 语句判断相等条件的时候,一定要注意是 “ == ” ,不是 “ = ” !!!
  • 静态变量的定义:定义一个变量时,要保持如下的良好思维习惯!!!!
  • 4、充分利用函数返回值

    如果定义了两个函数,函数A和函数B,思考一下两个函数之间有没有关联性,如果函数B需要用到函数A中得到的某个值,那么可以让函数A返回该值,然后在B中调用A,定义一个变量存储A的返回值。

    例如,今天我在按键显示的时候,要存储按键值,一开始想到的是定义一个全局变量,这样子键盘扫描函数可以用来确定按键的值,然后键盘显示函数可以再次利用此变量控制数码管的显示。这样的问题在于在编程过程中,应优先考虑使用局部变量、函数参数传递、返回值等方式处理数据,只有在确实必要的情况下,才使用全局变量。并且,我还要在主函数调用两个函数键盘扫描函数+键盘显示函数,才能实现键盘显示。

    但后来受到启发,发现可以直接让键盘扫描函数返回键盘值,变成一个通过键盘扫描获取键值的函数,然后在键盘显示函数中调用该函数获得返回值,这样,就像是把两个函数串起来了,那么我不仅省了一个全部变量变为局部变量,而且还在主函数中只需调用键盘显示函数即可。

    5、板子上的跳帽

    在烧录之前,一定要养成检查各个跳帽是否正确的习惯!!!

    我就是改好代码后,烧录板子发现还是错误,半天才发现原来是按键跳帽接错了,BTN和KBD一定要事先确认无误!!!!

    作者:7

    物联沃分享整理
    物联沃-IOTWORD物联网 » 聚沙成塔:4T网单片机按键键值实时显示练习指南

    发表回复