Bootstrap

tda7294参数引脚功能_项目三 按键功能实现

(一)项目任务

  • 任务1:按键控制LED
    • 子任务1.1 按键电路认识
    • 子任务1.2 C语言知识点
    • 子任务1.3 单个按键控制一个LED
    • 子任务1.4 多个按键控制LED灯

(二)相关知识点

2.1、按键原理认识

根据原理图可知,配套的开发板上有4个独立按键,如图1所示,在正常情况下,按键默认引脚为高电平。以K1为例,当按下按键的时候,K1引脚检测到低电平,说明此时按键被按下,这就是按键的基本原理。

a1091cde7c2e85b48bb4ef3050ec8bca.png
(a)按键原理图

5c5acb1b3d90d2e8f99d59e2258a10db.png
(b)硬件实物图

图1 按键原理实物图

理想情况下按键按下去引脚为低电平,松手后为高电平,但在实际按键过程中,存在抖动现象,如图2所示,在按键按下的瞬间出现毛刺,毛刺瞬间电压达到高电平,因此在按下按键的过程中可能会出现多次触发高电平和低电平的情况,同理在按键弹起的过程也会出现类似情况。

常用的去抖动方法有两种,一种是硬件去抖,另一种是软件去抖,在本例中采用软件去抖的方式进行消抖,通过延时的方式跳过抖动区,在学完定时器项目后会采用更实用的消抖方法。

3c442b1a8c010ef7ef517e071cd4fec0.png
图2 按键理想波形和实际波形

2.2、MOS管基础

MOS管又叫场效应管,跟三极管类似。三极管用小电流控制大电流,MOS管用小电压控制电流大小。MOS又分N沟道增强型和P沟道增强型,如图3所示,不管N型还是P型都有三个极:栅极(G)、源极(S)和漏极(D)。

栅极是控制极,通过是否在栅极加电压来控制源极和漏极是否导通,对于N型MOS管来说,在栅极加上电压则源极和漏极导通,去掉电压则截止;对于P型MOS管来说,在栅极加上电压则源极和漏极截止,去掉电压则导通。

ace600a5c24e0386888305755e711528.png

4cfb45aec3a8c1e6b1201c26efabd59b.png
图3 MOS场效应管

2.3、IO口模式介绍

STC89C52系列单片机所有I/O口均3种工作类型:准双向口/弱上拉(标8051输出模式)、仅为输入(高阻)或开漏输出功能。

STC89C52系列单片机的P1/P2/P3上复位后为准双向口/弱上拉(传统8051的I/O口)模式,P0口上电复位后是开漏输出。P0口作为总线扩展用时,不用加上拉电阻,作为I/O口用时,需加10K-4.7K上拉电阻。

2.3.1、准双向口输出配置

准双向口输出类型可用作输出和输入功能而不需重新配置。单片机中P1、P2、P3均为准双向口输出类型,内部结构如图4所示。方框内的电路都是指单片机内部部分。这个地方大家要注意一下,当读取外部按键信号的时候,单片机必须先给该引脚写“1”,也就是高电平,这样才能正确读取到外部按键信号。这是因为当内部输出高电平,经过一个反向器变成低电平,N型MOS管不会导通,那么单片机IO口从内部来看,由于上拉电阻R的存在,所以是一个高电平。当外部没有按键按下将电平拉低的话,VCC也是+5V,它们之间虽然有电阻,但是没有压差,就不会有电流,线上所有的位置都是高电平,这个时候就可以正常读取到按键的状态了。

当内部输出是个低电平,经过一个反相器变成高电平,MOS管导通,那么单片机的内部IO口就是个低电平,所以不管按键是否按下,单片机的IO口上输入到单片机内部的状态都是低电平,就无法正常读取到按键的状态了。

178adc8890dd3983ed43195b5cdbe17a.png

图4 准双向口/弱上拉内部示意图

2.3.2、开漏输出配置

开漏输出和准双向IO的唯一区别就是开漏输出把内部的上拉电阻去掉了。开漏输出如果要输出高电平时,MOS管关断,IO电平要靠外部的上拉电阻才能拉成高电平,如果没有外部上拉电阻IO电平就是一个不确定态。标准51单片机的P0口默认就是开漏输出,如果要用的时候外部需要加上拉电阻,如图5所示,从原理图中也可以看到,P0口加了一个排阻就是此原因。

9a55c2f1e71f69932b9749c9634febff.png
图5 开漏输出内部示意图

单片机IO还有一种状态叫高阻态。通常用来做输入引脚的时候,可以将IO口设置成高阻态,高阻态引脚本身如果悬空,用万用表测量的时候可能是高可能是低,它的状态完全取决于外部输入信号的电平,高阻态引脚对GND的等效电阻很大(理论上相当于无穷大,但实际上总是有限值而非无穷大),所以称之为高阻。这就是单片机的IO口的这些状态,在51单片机的学习过程中,主要应用的是准双向IO口。

2.4、上、下拉电阻

2.3小节很多次提到上拉电阻、下拉电阻,具体什么是上、下拉电阻,上下拉电阻都有何作用?

上拉电阻就是将不确定的信号通过一个电阻拉到高电平,同时此电阻也起到一个限流作用,下拉就是下拉到低电平。

比如IO设置为开漏输出高电平或者是高阻态时,默认的电平就是不确定,外部经一个电阻接到VCC,也就是上拉电阻,那么相应的引脚就是高电平,如图4中的电阻R。

上拉电阻应用很多,都可以起到什么作用呢?主要先了解最常用的以下几点。

1、OC门要输出高电平,必须外部加上拉电阻才能正常使用,其实OC门就相当于单片机IO的开漏输出。

2、加大普通IO口的驱动能力。标准51单片机的内部IO口的上拉电阻,一般都是在几十K欧,比如STC89C52内部是20K的上拉电阻,所以最大输出电流是250uA,因此外部加个上拉电阻,可以形成和内部上拉电阻的并联结构,增大高电平时电流的输出能力。

3、单片机中未使用的引脚,比如总线引脚,引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序造成什么影响,但通常会增加单片机的功耗,加上一个对VCC的上拉电阻或者一个对GND的下拉电阻后,可以有效的抵抗电磁干扰。

下拉电阻是指经一个电阻接到GND,那么相应的引脚就是一个低电平。为什么按键引脚在接有上拉电阻的情况下,如果另一端接地还是低电平呢?这个原理跟水流其实很类似,内部和外部,只要有一边是低电位,那么电流就会顺流而下,由于只有上拉电阻,下边没有电阻分压,直接到GND上了,所以不管另外一边是高还是低,电平肯定就是低电平了。从上面的分析就可以得出一个结论,这种具有上拉的准双向IO口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是1,如果内部输出0,则无论外部信号是1还是0,这个引脚读进来的都是0。

那么在进行电路设计的时候,又该如何选择合适的上下拉电阻的阻值?

1、从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小;

2、从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。

综合考虑各种情况,常用的上下拉电阻值大多选取在1K到10K之间,具体到底多大通常要根据实际需求来选,通常情况下在标准范围内就可以,不一定是一个固定的值。

2.5、C语言知识点—形参和实参

在项目二中的3.2实例中,通过delay()函数达到延时的目的,delay()函数在进行函数调用的时候,不需要任何参数传递,所以函数定义和调用时括号内空,但是更多的时候需要在主调函数和被调用函数之间传递参数。在调用一个有参数的函数时,函数名后边括号中的参数叫做实际参数,简称实参。而被调用的函数在进行定义时,括号里的参数叫做形式参数,简称形参。

以本小节的3.1为例,子函数“void delay(unsigned int cnt)”中的“cnt”称为形式参数,简称“形参”,形参中一定要有参数的类型,如“unsigned int”。在主函数main()中的“delay(5);”中的“5”为实参,因为在本例中需要延时5ms,需写5即可,如延时1000ms,即写“delay(1000);”即可,数值传递过程如图6所示,当执行到delay(5)时,数值5传递给子函数delay中的变量cnt,那么for循环中的cnt变成5,整个delay函数执行183*5次空循环达到延时5ms的目的。

e2d1868ab215bcc8f0ec3e56d64d5ef8.png

图6 函数值传递过程

演示程序虽然很简单,但是函数调用的全部内容都囊括在内。主调函数main和被调用函数delay之间的数据通过形参和实参发生了传递关系,而函数运算完后把值传递给了变量cnt,函数只要不是void类型,就都会有返回值,返回值类型就是函数的类型。关于形参和实参,还有以下几点需要注意。

1、函数定义中指定的形参,在未发生函数调用时不占内存,只有函数调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。

2、实参可以是常量,也可以是简单或者复杂的表达式,但是要求他们必须有确定的值,在调用发生时将实参的值传递给形参。

3、形参必须要指定数据类型,和定义变量一样,因为它本来就是局部变量。

4、实参和形参的数据类型应该相同或者赋值兼容。和变量赋值一样,当形参和实参出现不同类型时,则按照不同类型数值的赋值规则进行转换。

5、主调函数在调用函数之前,应对被调函数做原型声明。

6、实参向形参的数据传递是单向传递,不能由形参再回传给实参。也就是说,实参值传递给形参后,调用结束,形参单元被释放,而实参单元仍保留并且维持原值。

2.6、if语句

if 语句有两个关键字:if和else,两个关键字翻译一下就是:“如果”和“否则”。if语句一共有三种格式,我们分别来看。

1、if语句的默认形式:

if (条件表达式)

{

语句 1;

}

其执行过程:if(即如果)条件表达式的值为“真”,则执行语句1;如果条件表达式的值为“假”,则不执行语句1。

C语言一个分号表示一条语句的结束,因此如果 if 后边只有一条执行语句的时候,可以省略大括号,但是如果有多条执行语句的话,必须加上大括号,初学者建议加上大括号。

在例程中有以下程序。

if(KEY == 0)

{

语句;

}

当KEY的值等于0的时候,括号里的值才是“真”,那么就执行“语句”,当KEY的值不等于0的时候,括号里的值才是“假”,那么就不执行“语句”。

2、 if...else 语句

有些情况下,除了要在括号里条件满足时执行相应的语句外,在不满足该条件的时候,也要执行一些另外的语句,这时候就用到了 if...else 语句,它的基本语法形式是:

if (条件表达式)

{

语句1;

}

else

{

语句2;

}

3、 if....else if 语句

if...esle 语句是一个二选一的语句,或者执行 if 分支后的语句,或者执行 else 分支后的语句。还有一种多选一的用法就是 if...else if 语句。他的基本语法格式是:

if (条件表达式 1) {语句 1;}

else if (条件表达式 2) {语句 2;}

else if (条件表达式 3) {语句 3;}

... ...

else {语句 n;}

他的执行过程是:依次判断条件表达式的值,当出现某个值为“真”时,则执行相对应的语句,然后跳出整个if的语句块,执行“语句n”后面的程序;如果所有的表达式都为“假”,则执行else分支的“语句 n”后,再执行“语句n”后边的程序。if 语句在C语言编程中使用频率很高,用法也不复杂,所以必须要熟练掌握。

2.7、数组概念

数组是具有相同数据类型的有序数据的组合,一般来讲,数组定义后满足以下三个条件。

1、具有相同的数据类型;

2、具有相同的名字;

3、在存储器中是被连续存放的。

数组的声明格式如下:

数据类型 数组名 [数组长度];

1、数组的数据类型声明的是该数组的每个元素的类型,即一个数组中的元素具有相同的数据类型。

2、数组名的声明要符合 C 语言固定的标识符的声明要求,只能由字母、数字、下划线这三种符号组成,且第一个字符只能是字母或者下划线。

3、方括号中的数组长度是一个常量或常量表达式,并且必须是正整数。

例如“char day[6]={1,7,2,3,4,5};”表示字符1,7,2,3,4,5组成了一个数组,类型为字符型,数组名为day,“6”表示数组中的组员个数为6,注意day[0]的值为1,day[1]的值为7,day[2]的值为2,day[3]的值为3,day[4]的值为4,day[5]的值为5,数组的下标从0开始到5结束,如图7所示。

73cbe6c52e8a37090694fa4d8d1f66e1.png
图7 字符存放结构图

当定义一个数组为“int day[6]={1,7,2,3};”时,6表示元素的个数,但大括号里的元素个数只有四个,此时默认的元素还有两个零在数字3后面,即{1,7,2,3,0,0}。

当定义一个数组为“int day[]={1,7,2,3};”时,中括号里面没有元素,此时元素个数由大括号里的元素个数决定,即为4。

数组初始化总结:

1、初值列表里的数据之间要用逗号隔开;

2、初值列表里的初值的数量必须等于或小于数组长度,当小于数组长度时,数组的后边没有赋初值的元素由系统自动赋值为 0。

3、若给数组的所有元素都赋初值,那么可以省略数组的长度。

4、系统为数组分配连续的存储单元的时候,数组元素的相对次序由下标来决定。

(三)设计实施

3.1、单个按键控制LED

#include <reg52.h>
sbit KEY = P3^4;
sbit LED = P0^7;
void delay(unsigned int cnt);
void main()
{
	while(1)
	{
		if(KEY == 0) // 判断KEY的值是否等于0
		{
			delay(5); // 延时5ms
			if(KEY == 0)
			{
				LED = ~LED;//LED状态取反
			}
			while(!KEY); //判断按键是否松开
		}
	}
}
void delay(unsigned int cnt)
{
	unsigned int i , j; //变量i , j的范围均为0~65535
	for(i=0;i<183;i++)
	{
		for(j=0;j<cnt;j++);
	}
}
  1. “while(!KEY);”用来判断按键是否松开,设未松开,KEY的值为0,“!”取反,根据项目二中while语句的分析知while(!KEY);一直处于死循环,执行空语句,直到松手后,KEY的值为1,取反后为0,跳出while循环。
  2. C语言中 “!”是逻辑运算符,表示非,即非0是1,非1是0;“~”表示按位取反。例如“!0x01”为0,“~0x01”为0xFE;
  3. 再次强调延时函数延时的1000ms和5ms非准确的延时,但误差较小,准确的延时将在下一项目中讲解。delay函数中的“i”循环次数为什么是183次,会在仿真项目中具体解释。

3.2、多个按键控制LED

#include <reg52.h>
sbit KEY1 = P3^4;
sbit KEY2 = P3^5;
#define LED P0
unsigned char table1[15]={0XEF,0XDF,0XBF,0X7F,0X00,0XFF,0X00,0XFF,0XDB,0XBD,
0X7E,0X00,0XFF,0X00,0XFF};
unsigned char table2[]={0XEF,0XDF,0XBF,0X7F,0XEF,0XDF,0XBF,0X7F,0XFF,0X00,
0XFF,0X7F,0XBF,0XDF,0XEF,0XF7,0XFB,0XFD,0XFE,0XFF,0x01} ;
void  LED_Show1();
void  LED_Show2();
void delay(unsigned int cnt);
void main()
{
	while(1)
	{
		if(KEY1 == 0)
		{
			delay(5);
			if(KEY1 == 0)
			{
				LED_Show1();
			}
			while(!KEY1);
		}
		if(KEY2 == 0)
		{
			delay(5);
			if(KEY2 == 0)
			{
				LED_Show2();
			}
			while(!KEY2);
		}
	}
}
void  LED_Show1()
{
		unsigned char i=0;
	    for(i=0;i<15;i++)
		{
			LED = table1[i];
			delay(100);
		}
}

void  LED_Show2()
{	
		unsigned char j=0;
	    while(table2[j]!=0x01)
		{
			LED = table2[j];
			delay(80);
			j++;
		}
}

void delay(unsigned int cnt)
{
	unsigned int i , j; //变量i , j的范围均为0~65535
	for(i=0;i<183;i++)
	{
		for(j=0;j<cnt;j++);
	}
}
  1. 数组table1中有15个元素,在LED_Show1()函数中通过循环15次来依次来读取数据,如果数组中元素较多则使用不方便,在数组中增减元素整个程序要修改较多处,推荐使用LED_Show1()方式;
  2. “LED_Show2()”函数通过while循环来检测是否有“0X01”元素,一旦检测到停止运行,读者可以在数组table2中看到最后一个元素为“0X01”,称为结束标志符,结束标志符可以任意定义一个十六进制数,但不能与前面元素重复!

(四)小结

1、灵活应用有参函数,达到延时不同时间的目的;

2、掌握编写按键程序,结合LED实现各种功能;

3、掌握本小节涉及到的C语言语法和语句。

可以在B站观看相关视频!!

哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili

;