蓝桥杯电子类单片机组学习一——LED灯,数码管,矩阵键盘和蜂鸣器继电器

目录

文章目录

前言

单片机资源数据包_2023

一、电亮LED灯与数码管

1.74HC138

2.74HC573

二、点亮LED灯和数码管

1.点亮LED灯

2.点亮数码管

​编辑

3.代码实现

三、矩阵键盘的扫描

1.矩阵按键的扫描

2.独立按键的扫描

四、代码实现



前言

本系列文章意在帮助各位正在准备蓝桥杯单片机组的同学,提供一个参考与指南,但是所有指南的前提是,默认你已经有单片机基础,本系列文章会提供本人对蓝桥杯单片机组编程方面的一些源码实现。当然,或许与你写代码的style完全不想同,那咱们也可以彼此相互交流各自的优缺点,或许你可能只是一个小白,就是想来抄一些可供借鉴的代码,这些都不重要,重要的是能给正在准备单片机组的同学提供到一些帮助。

此外代码可能有写的不完善的地方,但是每一个代码都是经过测试可行之后才发出来的,不敢保证十全十美,但是跑起来应该没问题。

此外,比赛时会提供一个单片机资源数据包,里面的内容比较多,这里只放一个下载链接,是2023年省赛是提供的单片机资源数据包(今年才2024,已经是最新的资源数据包了),正文中会直接使用资源数据包内的资料:

单片机资源数据包_2023(点击下载)

关于此资源数据包还得补充两句,他的3-底层驱动代码参考与往些年不同(但其实差异也很小,这里不再赘述),而且还是快比赛时才公布的这个资源数据包。也就是说,在写底层驱动代码时,我都是以2023年的资源数据包为基础写的,如果你使用的不是2023年的资源数据包的话,可能会跟我使用的底层驱动不完全相同,不过影响也不大。

下图为单片机资源数据包_2023内的所有资源,其中最关键的就是3-底层代码驱动以及SCH_硬件原理图V30了,当然共阳数码管段码表也十分常用,不过一般也就新写一个项目时,会把里面的东西CV到main函数里,然后这个文件就用不着了。


一、锁存器及其驱动

为了节省单片机资源,也为了充分利用单片机引脚,使之可以驱动更多外设,蓝桥杯的板子上使用了74HC138与大量的74HC573(锁存器),锁存器的存在使得单片机可以在控制完一个外设之后,在使用相同的引脚去控制其他外设,以充分利用单片机引脚资源:

1.74HC138

下图是原理图上的74HC138

以及Y4 Y5 Y6 Y7所连接的电路:

74HC132与一个反相器(这里使用的是74HC02,与非门)相连是一个常用的电路,简单点儿说,通过赋给P25,P26,P27值,来选择Y0到Y7,就可以输出对应高电平的Y0C到Y7C;而Y4C,Y5C,Y6C,Y7C又与多个74HC573的使能端(LE)相连,HC573是一个D型锁存器芯片,LE是引脚标识为锁存器使能。它用于控制锁存器的工作状态。当LE引脚处于高电平时,输入信号将被锁存;当LE引脚处于低电平时,输入信号不会被锁存,也就是锁存器将不工作。

也就是说,通过赋给P25,P26,P27值,来控制74HC132,进而可以制定某一个锁存器开始工作(当然,也可以所有锁存器都不工作)

那么怎么使用P25,P26,P27来控制74HC132呢?其实很简单,可以吧P27 P26 P25看成一个三位二进制数,那这个三位二进制说的取值就是000到111,转化为十进制就是从0到7,刚好对应74HC138的输出Y0到Y7。

举个例子,当P27 P26 P25=100时,那么Y4=0;在经过与非门,YC4=1;就启动了控制LED灯的74HC573锁存器了。

这里一般只用到5中P27 P26 P25组合,分别是刚才提到的控制lED灯的锁存器的Y4;控制数码管位选锁存器的Y7;控制数码管段选锁存器的Y6;控制ULN2003的Y5;以及什么都不控制,平时让P27 P26 P25经常处于的Y0,当然这个因个人习惯的不同,什么都不控制时让Yx的x等于多少也会有所不同。

这里还有一个问题,如何让P27,P26,P25等于某个指定的数,比如100呢,其实也很简单,你可以直接给P27,P26,P25赋值,也可以通过与或非的形式,这里默认大家都会了,不再赘述。

//逐位赋值
P27=1;
P26=0;
P25=0;

//通过与或非的形式
P2|=0x80;//让P27置1
P2&=0x9F;让P26和P25置为0
P2&=0x1F;//让74HC138指向一个没用的Yx

2.74HC573

74HC573就比较简单了,这里以连接到LED灯上的74HC573为例,

当Y4C,也就是74HC573的LE为高电平时,74HC573左边的数据会存到右边,也就是说,P0的值(或者说左边D1到D8的值)就会赋给Q1到Q8,当Y4C为低电平时。举个栗子:先赋给P0=0xF0,然后令Y4C=1;再然后令Y4C=0,此时保持P0的值不变;此时Q1到Q8就等于0xF0了(确切地说,应该是Q8到Q1),在然后,不管怎么修改P0的值,只要Y4C=0,Q1到Q8的值就不会再变,直到Y4C再次等于1。


二、点亮LED灯和数码管

1.点亮LED灯

经过前边的铺垫,我们已经知道了如何启动与关闭LED的锁存器,我们只需在开关LED灯的锁存器之前,给P0赋上我们期望的值,然后在开关一次锁存器即可。代码演示放在本章后边一同演示。

2.点亮数码管

对于数码管也是同理,不过我们对于数码管先要位选,选择我们要控制的数码管,然后再让那个数码管显示出我们期望的数字。

对于数码管,我们往往需要扫描数码管,从而“同时点亮”至多8个数码管,按照我的习惯,我通常是用定时器扫描数码管来实现的,这样可以避免因为在跑其他代码,导致每个数码管电亮的时间长不一致,而造成的“有的数码管暗,有的数码管亮的情况”。当然这跟写出来的代码质量有关,也并不是说:“不用定时器扫描数码管,一定会出现有的数码管暗,有的数码管亮的情况”。

正因为我们要用定时器扫描数码管,所以我们把八个数码管需要显示的数据存在一个uchar数组内,然后通过定时器依次将数组内的数据显示在对应位置上的数码管上。

3.继电器和蜂鸣器

蜂鸣器和继电器的开关可以看成电亮或关闭某个特定的LED灯,

其中74HC573锁存器的作用与控制LED灯的锁存器作用一致,ULN2003芯片只起到增大管脚的驱动能力的作用。

当BUZZ=0时,BUZZ打开:

当RELAY=0时继电器打开:

3.代码实现

#include <stc15.h>
#include <intrins.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
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可

unsigned char LED_Num=0xFF;//用来记录当前LED的状态
unsigned char ULN=0x00;//用来记录当前ULN的值,ULN主要控制蜂鸣器和继电器,也会用来控制电机

//LED有关的三个宏函数的思路都是,先更新LED_Num的值,然后将P0=LED_Num,再然后开关一次LED灯的锁
//存器,以更新数据
//点亮第x的LED灯,将LED_Num中第x位置为0(因为是共阳极LED灯)关闭同理,需将第x位置为1
#define LED_ON(x)		LED_Num&=~(0x01<<x);	P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF(x)	    LED_Num|=0x0x<<x;		P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF_ALL()	LED_Num=0xFF;			P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;

//关于数码管的两个函数,因为需要从数组内读取待显示的数据,还要进行位选,所以这两个函数只起到开
//关锁存器的功能,没有更新数据(会在其他地方实现数据更新和获取)
#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;

//对于蜂鸣器的继电器,与LED类似,不过蜂鸣器是直接为特定的某一位置位。
//其思路可以理解为通过宏函数打开或熄灭某一个LED灯
//对BUZZER_ON就是把ULN第7位置为1,对于RELAY_ON就是吧第5位置为1
#define BUZZER_ON()		ULN|=0x40;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define BUZZER_OFF()	ULN&=0xB0;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;

#define RELAY_ON()		ULN|=0x10;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define RELAY_OFF()		ULN&=0xE0;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;

void Timer0_Init(void);		//1毫秒@11.0592MHz
void Delay100ms(void);	//@11.0592MHz
unsigned char location=0;//用于记录当前到哪一位数码管要显示数据了。是一个中间变量
unsigned char Nixie_num[]={1,2,3,4,5,6,7,8};//用来存储数码管待显示的数据

void main()
{
	LED_OFF_ALL();//熄灭所以LED灯
	RELAY_OFF();//关闭继电器
	BUZZER_OFF();//关闭蜂鸣器
	Timer0_Init();//定时器初始化
	EA=1;//开总中断
	Delay100ms();
	while(1)
	{

		Delay100ms();
	}
	
}
void Timer0_Isr(void) interrupt 1
{
	P0=0x01<<location;NIXIE_CHECK();//选择要显示的某一位
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();//从数组中读取数据并显示
	
	if(++location==8)//位数+1,从而实现扫描
		location=0;
}

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xCD;				//设置定时初始值
	TH0 = 0xD4;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
}
void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Delay5ms(void)	//@11.0592MHz
{
	unsigned char data i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}


三、矩阵键盘的扫描

1.矩阵按键的扫描

矩阵键盘想必大家都不陌生,其扫描就是先让某一列置为0(低电平),在逐个检查每一行的引脚电压有没有为0。当按下低1列第1行的按键时,如果让第一列置为0,在逐一检查每一行,会发现第一行第一列导通,第一列为0使得第一行也为0,从而扫描到按键的位置。

我们用一个void函数get_key()实现按键扫描全局变量key_value来记录键值,以后在实战时,所有对于按键功能的实现,我都会写到get_key()函数里,并且在处理完按键之后,将key_value重新置为0,以免重复处理,当然现在不需要,key_value的值一直等于你按下的上一个按键的值,这样也便于在main函数里测试。

void Delay5ms(void)	//@11.0592MHz
{
	unsigned char data i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

//Delay5ms()以及while(xxx);是为了按键消抖,避免按下一次按键重复处理
void get_key()
{
	
	unsigned char key_P3=P3;//记录当前P3的值,扫描完按键之后再将P3的值复位
	unsigned char key_P4=P4;//记录当前P4的值,扫描完按键之后再将P4的值复位
	
    //扫描第一列
	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;}
	
	P3=key_P3;
	P4=key_P4;

}

2.独立按键的扫描

有了刚才矩阵按键的扫描,这里再讲独立按键显得有些多余——只需将扫描第二三四列的代码删去即可。当然在进入考场到发题目这30分钟内,没必要去赌它是考独立按键还是矩阵按键,我一般直接写矩阵按键的代码,真考独立按键就考呗,矩阵按键的代码完全可以扫描独立按键。


四、代码实现

这里做个总结,主要实现以下功能,也是对刚才的代码进行测试:

1.上电打开LED1(从左到右第二个LED灯)

2.数码管前两位显示当前按下按键的值

3.上电关闭蜂鸣器和继电器

4.当按下S7时,蜂鸣器和继电器打开,松开S7时,蜂鸣器和继电器关闭

#include <stc15.h>
#include <intrins.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
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可

unsigned char LED_Num=0xFF;//用来记录当前LED的状态
unsigned char ULN=0x00;//用来记录当前ULN的值,ULN主要控制蜂鸣器和继电器,也会用来控制电机

//LED有关的三个宏函数的思路都是,先更新LED_Num的值,然后将P0=LED_Num,再然后开关一次LED灯的锁
//存器,以更新数据
//点亮第x的LED灯,将LED_Num中第x位置为0(因为是共阳极LED灯)关闭同理,需将第x位置为1
#define LED_ON(x)		LED_Num&=~(0x01<<x);	P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF(x)	    LED_Num|=0x01<<x;		P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF_ALL()	LED_Num=0xFF;			P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;

//关于数码管的两个函数,因为需要从数组内读取待显示的数据,还要进行位选,
//所以这两个函数只起到开关锁存器的功能,没有更新数据(会在其他地方实现数据更新和获取)
#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;

//对于蜂鸣器的继电器,与LED类似,不过蜂鸣器是直接为特定的某一位置位。
//其思路可以理解为通过宏函数打开或熄灭某一个LED灯
//对BUZZER_ON就是把ULN第7位置为1,对于RELAY_ON就是吧第5位置为1
#define BUZZER_ON()		ULN|=0x40;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define BUZZER_OFF()	ULN&=0xB0;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;

#define RELAY_ON()		ULN|=0x10;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define RELAY_OFF()		ULN&=0xE0;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;

void Timer0_Init(void);		//1毫秒@11.0592MHz
void Delay100ms(void);	//@11.0592MHz
void Delay5ms(void);	//@11.0592MHz
void get_key();

unsigned char location=0;//用于记录当前到哪一位数码管要显示数据了。是一个中间变量
unsigned char Nixie_num[]={10,10,10,10,10,10,10,10};//用来存储数码管待显示的数据
unsigned char key_value=0;
void main()
{
	LED_OFF_ALL();//熄灭所以LED灯
	RELAY_OFF();//关闭继电器
	BUZZER_OFF();//关闭蜂鸣器
	Timer0_Init();//定时器初始化
	EA=1;//开总中断
	Delay100ms();
	while(1)
	{
		get_key();//读取按键的值
		Nixie_num[0]=key_value/10;//显示十位
		Nixie_num[1]=key_value%10;//显示个位
		Delay100ms();
	}
	
}
void Timer0_Isr(void) interrupt 1
{
	P0=0x01<<location;NIXIE_CHECK();//选择要显示的某一位
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();//从数组中读取数据并显示
	
	if(++location==8)//位数+1,从而实现扫描
		location=0;//位数=8,重新清零(location取值是0到7八个数)
}

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xCD;				//设置定时初始值
	TH0 = 0xD4;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
}
void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Delay5ms(void)	//@11.0592MHz
{
	unsigned char data i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

//Delay5ms()以及while(xxx);是为了按键消抖,避免按下一次按键重复处理
void get_key()
{
	unsigned char key_P3=P3;//记录当前P3的值,扫描完按键之后再将P3的值复位
	unsigned char key_P4=P4;//记录当前P4的值,扫描完按键之后再将P4的值复位
	
    //扫描第一列
	P44=0;
	//第一个if这样写只是为了方便看启动蜂鸣器和继电器,也只在测试时会这样写了。
	//一般所有对于按键的处理都在get_key()内实现。
	if(P30==0)
	{
			Delay5ms();//消抖
			BUZZER_ON();//测试蜂鸣器
			RELAY_ON();//测试继电器
			while(P30==0);
			BUZZER_OFF();//关闭蜂鸣器
			RELAY_OFF();//关闭继电器
			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;}
	
	P3=key_P3;
	P4=key_P4;

}

后续会带大家逐步深入的分享蓝桥杯比赛的代码

作者:旺仔nai糖

物联沃分享整理
物联沃-IOTWORD物联网 » 蓝桥杯电子类单片机组学习一——LED灯,数码管,矩阵键盘和蜂鸣器继电器

发表回复