网站首页
手机版

第8章 函数进阶与按键(8.3 8.4)

更新时间:作者:小小条

8.3 函数的形式参数和实际参数

上一个例程中在进行函数调用的时候,不需要任何参数传递,所以函数定义和调用时括号内都是空的,但是更多的时候需要在主调函数和被调用函数之间传递参数。在调用一个有参数的函数时,函数名后边括号中的参数叫做实际参数,简称实参。而被调用的函数在进行定义时,括号里的参数叫做形式参数,简称形参,用一个简单程序例子做说明。

unsigned char add(unsigned char x, unsigned char y); //函数声明

第8章 函数进阶与按键(8.3 8.4)

void main()

{

unsigned char a = 1;

unsigned char b = 2;

unsigned char c = 0;

c = add(a, b); //调用时,a和b就是实参,把函数的返回值赋给c

//执行完后,c的值就是3

while(1);

}

unsigned char add(unsigned char x, unsigned char y) //函数定义

{ //这里括号中的x和y就是形参

unsigned char z = 0;

z = x + y;

return z; //返回值z的类型就是函数add的类型

}

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

1、函数定义中指定的形参,在未发生函数调用时不占内存,只有函数调用时,函数add中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放,这个前边讲过了,形参是局部变量。

2、实参可以是常量,也可以是简单或者复杂的表达式,但是要求必须有确定的值,在调用发生时将实参的值传递给形参。如上边这个程序也可以写成:c = add(1, a+b);

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

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

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

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

8.4 按键

8.4.1 独立按键

常用的按键电路有两种形式,独立式按键和矩阵式按键,独立式按键比较简单,它们各自与独立的输入线相连接,如图8-6所示。

图8-6 独立式按键原理图

4条输入线接到单片机的I/O口,当按键K1按下时,+5V通过电阻R1再通过按键K1最终进入GND形成一条通路,这条线路的电压都加到R1这个电阻上,KeyIn1这个引脚就是个低电平。当松开按键,线路断开,就不会有电流通过,KeyIn1和+5V就应该是等电位,是一个高电平。就可以通过KeyIn1这个I/O口的高低电平来判断是否有按键按下。

实际上在单片机I/O口内部,也有一个上拉电阻的存在。Kingst51开发板的按键接到P2口上,P2口上电默认是准双向I/O口,来了解一下这个准双向I/O口的电路,如图8-7所示。

图8-7 准双向I/O口结构图

首先说明一点,现在绝大多数单片机的I/O口都是使用MOS管而非三极管,但用在这里的MOS管其原理和三极管是一样的,因此在这里用三极管来进行原理讲解,把前面讲过的三极管的知识搬过来,一切都是适用的,有助于理解。

图8-7方框内的电路都是指单片机内部部分,方框外的就是外接的上拉电阻和按键。这个地方要注意一下,就是当单片机要读取外部按键信号的时候,必须先给该引脚写“1”,也就是高电平,这样才能正确读取到外部按键信号。

当“内部输出”是高电平,经过一个反向器变成低电平,NPN三极管不会导通,那么单片机I/O口从内部来看,由于上拉电阻R的存在,所以是一个高电平。当外部没有按键按下将电平拉低的话,VCC也是+5V,它们之间虽然有2个电阻,但是没有压差,就不会有电流,线上所有的位置都是高电平,这个时候就可以正常读取到按键的状态了。

当“内部输出”是个低电平,经过一个反相器变成高电平,NPN三极管导通,那么单片机的内部I/O口就是个低电平,这个时候,外部虽然也有上拉电阻的存在,但是两个电阻是并联关系,不管按键是否按下,单片机的I/O口上输入到单片机内部的状态都是低电平,就无法正常读取到按键的状态了。

和水流类似,内部和外部,只要有一个点是低电位,电流就会顺流而下,由于只有上拉电阻没有下拉电阻分压,直接到GND上,线路上就是低电平了。

可以得出一个结论,这种具有上拉的准双向I/O口,如果要正常读取外部信号的状态,得保证内部输出1,如果内部输出0,无论外部信号是1还是0,这个引脚读进来的都是0。

8.4.2 矩阵按键

在某一个系统设计中,如果需要使用很多的按键时,做成独立按键会大量占用I/O口,因此引入了矩阵按键的设计。如图8-8所示,是Kingst51开发板上的矩阵按键电路原理图,使用8个I/O口来实现了16个按键。

图8-8 矩阵按键原理图

独立按键理解了,矩阵按键也不难理解。图8-8中,一共有4组按键,如果只看其中一组,如图8-9所示。KeyOut1输出一个低电平,KeyOut1就相当于是GND,是否相当于4个独立按键呢。当然这时候KeyOut2、KeyOut3、KeyOut4都必须输出高电平,才能保证与它们相连的三路按键不会对这一路产生干扰,可以对照两张原理图分析一下。

图8-9 矩阵按键变独立按键示意图

8.4.3 独立按键的扫描

原理搞清楚了,下面就先编写一个独立按键的程序,把最基本的功能验证一下。

#include <reg52.h>

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit LED9 = P0^7;

sbit LED8 = P0^6;

sbit LED7 = P0^5;

sbit LED6 = P0^4;

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

void main()

{

ENLED = 0; //选择独立LED进行显示

ADDR3 = 1;

ADDR2 = 1;

ADDR1 = 1;

ADDR0 = 0;

P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平

while (1)

{

//将按键扫描引脚的值传递到LED上

LED9 = KEY1; //按下时为0,对应的LED点亮

LED8 = KEY2;

LED7 = KEY3;

LED6 = KEY4;

}

}

本程序在KeyOut1上输出低电平,而KeyOut2~4保持高电平,就相当于是把矩阵按键的第一行,即K1~K4作为4个独立按键来处理,然后把这4个按键的状态直接送给LED9~6这4个LED小灯。当按键按下时,对应按键的输入引脚是0,对应小灯控制信号是低电平,于是灯就亮了,这说明上述关于按键检测的理论都是可实现的。

绝大多数情况下,按键是不会一直按住的,所以通常检测按键的动作并不是检测一个固定的电平值,而是检测电平值的变化,即按键在按下和弹起这两种状态之间的变化,只要发生了这种变化就说明现在按键产生动作了。如何判断按键被按下事件发生?

假设代表按键的IO口的状态x,x为0代表按下,x为1代表弹起。某一t时刻读取一次按键IO口的状态,x为1;而t+1时刻再次读取一次按键IO口状态,x为0,通过x的变化得知按键状态发生了变化,即按键被按下的事件发生。同理如果t时刻读到的x为0,而t+1时刻读取到IO口状态x为1,则按键弹起的事件发生。

每次按键动作都会包含一次“按下”和一次“弹起”,可以任选其一来执行程序,或者两个都用,以执行不同的功能程序。把每次t时刻扫描到的按键状态都保存起来,当t+1时刻按键状态扫描进来的时候,与前一次t时刻扫描的状态进行比较,如果这两次按键状态不一致,就说明按键产生动作了。下面用程序实现这个功能,程序只取按键K4为例。

#include <reg52.h>

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

void main()

{

bit backup = 1; //定义位变量,保存t时刻扫描的按键值,默认按键状态为弹起

unsigned char cnt = 0; //定义一个计数变量,记录按键按下的次数

ENLED = 0; //选择数码管DS1进行显示

ADDR3 = 1;

ADDR2 = 0;

ADDR1 = 0;

ADDR0 = 0;

P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平

P0 = LedChar[cnt]; //显示按键次数初值

while (1)

{

if (KEY4 != backup) //t+1时刻KEY4与t时刻不相等说明此时按键有动作

{

if (backup == 0) //如果t时刻值为0,则说明当前是由0变1,即按键弹起

{

cnt++; //按键次数+1

if (cnt >= 10)

{ //只用1个数码管显示,所以加到10就清零重新开始

cnt = 0;

}

P0 = LedChar[cnt]; //计数值显示到数码管上

}

backup = KEY4; //更新备份按键状态,以备进行下次比较

}

}

}

先来介绍出现在程序中的一个新知识点,变量类型——bit,这个在标准C语言是没有的。51单片机有一种特殊的变量类型就是bit型。unsigned char型是定义了一个无符号的8位的数据,它占用一个字节(Byte)的内存,而bit型是1位数据,只占用1个位(bit)的内存,用法和标准C中其他的基本数据类型是一致的。它的优点就是节省内存空间,8个bit型变量才相当于1个char型变量所占用的空间。虽然它只有0和1两个值,但也已经可以表示很多东西了,比如:按键的按下和弹起、LED灯的亮和灭、三极管的导通与关断等等。

在这个程序中,以K4为例,按一次按键,就会产生“按下”和“弹起”两个动态的动作,程序选择在“弹起”时对数码管进行加1操作。理论虽是如此,但是经过多次实验是否发现了这样一种现象:有的时候明明只按了一下按键,但数字却加了不止1,而是2或者更多?但是程序并没有逻辑上的错误,这是怎么回事呢?这是一个按键抖动和消抖的问题。

8.4.4 按键消抖

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图8-10所示。

图8-10 按键抖动状态图

按键稳定闭合时间长短是由操作人员决定的,通常都会在100ms以上,刻意快速按的话能达到40-50ms左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。

最简单的消抖,就是当检测到按键状态变化后,先等待一个10ms左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。将上一个的程序稍加改动,得到新的带消抖功能的程序如下。

#include <reg52.h>

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

void delay();

void main()

{

bit keybuf = 1; //按键值暂存,临时保存按键的扫描值

bit backup = 1; //按键值备份,保存前一次的扫描值

unsigned char cnt = 0; //按键计数,记录按键按下的次数

ENLED = 0; //选择数码管DS1进行显示

ADDR3 = 1;

ADDR2 = 0;

ADDR1 = 0;

ADDR0 = 0;

P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平

P0 = LedChar[cnt]; //显示按键次数初值

while (1)

{

keybuf = KEY4; //把当前扫描值暂存

if (keybuf != backup) //当前值与前次值不相等说明此时按键有动作

{

delay(); //延时大约10ms

if (keybuf == KEY4) //判断扫描值有没有发生改变,即按键抖动

{

if (backup == 0) //如果前次值为0,则说明当前是弹起动作

{

cnt++; //按键次数+1

if (cnt >= 10)

{ //只用1个数码管显示,所以加到10就清零重新开始

cnt = 0;

}

P0 = LedChar[cnt]; //计数值显示到数码管上

}

backup = keybuf; //更新备份为当前值,以备进行下次比较

}

}

}

}

/* 软件延时函数,延时约10ms */

void delay()

{

unsigned int i = 1000;

while (i--);

}

作为消抖的功能演示程序可以采用延时的办法,但是实际做开发的时候,程序量往往很大,各种状态值也很多,while(1)这个主循环要不停的扫描各种状态值是否有发生变化,及时的进行任务调度,如果程序中间加了这种delay延时操作后,很可能某一事件发生了,但是程序还在进行delay延时操作中,当这个事件发生完了,程序还在delay操作中,当delay完事再去检查的时候,已经检测不到那个事件了。为了避免这种情况的发生,要尽量缩短while(1)循环一次所用的时间,而需要进行长时间延时的操作,必须想其它的办法来处理。

那么消抖该采用什么办法呢?介绍一种作者在实际工程中常常采用的一种办法:启用一个定时中断,每2ms进一次中断,扫描一次按键状态并且存储起来,连续扫描8次后,看看这连续8次的按键状态是否是一致的。8次按键的时间大概是16ms,这16ms内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段,如图8-12。

图8-12 按键连续扫描判断

假如8-12图中t时刻检测到了某一个按键[1,2,3,4,5,6,7,8]这8个状态,t+1(t+2ms)时刻检测[2,3,4,5,6,7,8,9]这8个状态,t+2(t+4ms)时刻检测[3,4,5,6,7,8,9,10]这8个状态... ...,随着时间的推移,检测按键也不断更新连续的8次按键状态。按键状态分为弹起、抖动和按下三种状态,当程序检测到连续8次按键状态全为1时,则代表按键状态为弹起;当程序检测到连续8次按键状态全为0时,则代表按键状态为按下;当检测连续8次按键状态是0和1交错,则代表按键为抖动。

利用这种方法,就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键状态判定而非按键过程判定,只对当前按键的连续16ms的8次状态进行判断,而不再关心它在这16ms内都做了什么,下面就按照这种思路用程序实现出来,同样只以K4为例。

#include <reg52.h>

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

bit KeySta = 1; //当前按键状态,初始默认为弹起

void main()

{

bit backup = 1; //按键值备份,保存前一次的扫描值,默认弹起

unsigned char cnt = 0; //按键计数,记录按键按下的次数

EA = 1; //使能总中断

ENLED = 0; //选择数码管DS1进行显示

ADDR3 = 1;

ADDR2 = 0;

ADDR1 = 0;

ADDR0 = 0;

TMOD = 0x01; //设置T0为模式1

TH0 = 0xF8; //为T0赋初值0xF8CD,定时2ms

TL0 = 0xCD;

ET0 = 1; //使能T0中断

TR0 = 1; //启动T0

P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平

P0 = LedChar[cnt]; //显示按键次数初值

while (1)

{

if (KeySta != backup) //当前值与前次值不相等说明此时按键有动作

{

if (backup == 0) //如果前次值为0,则说明当前是弹起动作

{

cnt++; //按键次数+1

if (cnt >= 10)

{ //只用1个数码管显示,所以加到10就清零重新开始

cnt = 0;

}

P0 = LedChar[cnt]; //计数值显示到数码管上

}

backup = KeySta; //更新备份为当前值,以备进行下次比较

}

}

}

/* T0中断服务函数,用于按键状态的扫描并消抖 */

void InterruptTimer0() interrupt 1

{

static unsigned char keybuf = 0xFF; //扫描缓冲区,保存一段时间内的扫描值,默认按键弹起为全‘1’

TH0 = 0xF8; //重新加载初值,定时2ms

TL0 = 0xCD;

keybuf = (keybuf<<1) | KEY4; //缓冲区左移一位,将当前按键状态更新至最低位

if (keybuf == 0x00)

{ //连续8次扫描值都为0,即16ms内都只检测到按下状态时,可认为按键已按下

KeySta = 0;

}

else if (keybuf == 0xFF)

{ //连续8次扫描值都为1,即16ms内都只检测到弹起状态时,可认为按键已弹起

KeySta = 1;

}

else

{} //其它情况则说明按键状态尚未稳定,则不对KeySta变量值进行更新

}

8.4.5 矩阵按键的扫描

介绍独立按键扫描的时候,已经简单认识了矩阵按键是什么样子了。矩阵按键相当于4组每组各4个独立按键,一共是16个按键。那如何区分这些按键呢?

前边讲过,按键按下通常都会保持100ms以上。如果在按键扫描中断程序中,每次让矩阵按键的一个KeyOut输出低电平,其它三个输出高电平,判断当前4个KeyIn的状态,下次中断时再让下一个KeyOut输出低电平,其它三个输出高电平,再次判断所有KeyIn,通过快速的中断不停的循环进行判断,就可以最终确定哪个按键按下了,扫描原理是不是跟数码管动态扫描有点类似?数码管在动态赋值,而按键这里在动态读取状态。至于扫描间隔时间和消抖时间,由于现在有4个KeyOut输出,要中断4次才能完成一次全部按键的扫描,显然再采用2ms中断判断8次扫描值的方式时间就太长了(2*4*8=64ms),那么就改用1ms中断判断4次采样值,这样消抖时间还是16ms(1*4*4)。下面就用程序实现程序循环扫描板子上的K1~K16这16个矩阵按键,在按键按下时把当前按键的编号显示在一位数码管上(用0~F表示,显示值=按键编号-1)。

#include <reg52.h>

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit KEY_IN_1 = P2^4;

sbit KEY_IN_2 = P2^5;

sbit KEY_IN_3 = P2^6;

sbit KEY_IN_4 = P2^7;

sbit KEY_OUT_1 = P2^3;

sbit KEY_OUT_2 = P2^2;

sbit KEY_OUT_3 = P2^1;

sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[] = { //数码管显示字符转换表

0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态,1代表弹起,0表示按下

{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

};

void main()

{

unsigned char i, j;

unsigned char backup[4][4] = { //按键值备份,保存前一次的值

{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

};

EA = 1; //使能总中断

ENLED = 0; //选择数码管DS1进行显示

ADDR3 = 1;

ADDR2 = 0;

ADDR1 = 0;

ADDR0 = 0;

TMOD = 0x01; //设置T0为模式1

TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms

TL0 = 0x67;

ET0 = 1; //使能T0中断

TR0 = 1; //启动T0

P0 = LedChar[0]; //默认显示0

while (1)

{

for (i=0; i<4; i++) //循环检测4*4的矩阵按键,比较16个按键状态是否发生变化

{

for (j=0; j<4; j++)

{

if (backup[i][j] != KeySta[i][j]) //检测按键动作

{

if (backup[i][j] != 0) //按键按下时执行动作

{

P0 = LedChar[i*4+j]; //将编号显示到数码管

}

backup[i][j] = KeySta[i][j]; //更新前一次的备份值

}

}

}

}

}

/* T0中断服务函数,扫描矩阵按键状态并消抖 */

void InterruptTimer0() interrupt 1

{

unsigned char i = 0;

static unsigned char keyout = 0; //矩阵按键扫描输出索引

static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区

{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},

{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}

};

TH0 = 0xFC; //重新加载初值

TL0 = 0x67;

//将keyout = x(x为1,2,3,4中其中一个)时对应一行的4个按键值移入缓冲区

keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

//消抖后更新按键状态

for (i=0; i<4; i++) //每行4个按键,所以循环4次

{

if ((keybuf[keyout][i] & 0x0F) == 0x00)

{ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下

KeySta[keyout][i] = 0;

}

else if ((keybuf[keyout][i] & 0x0F) == 0x0F)

{ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起

KeySta[keyout][i] = 1;

}

}

//执行下一次的扫描输出

keyout++; //输出索引递增

keyout = keyout & 0x03; //索引值加到4即归零

switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚

{

case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;

case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;

case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;

case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;

default: break;

}

}

这个程序完成了矩阵按键的扫描、消抖、动作分离的全部内容,还有两点值得说明。

首先,中断函数中扫描KeyIn输入和切换KeyOut输出的顺序与前面提到的不同,程序中先对所有的KeyIn输入做了扫描、消抖,然后才切换到了下一次的KeyOut输出,也就是说中断每次扫描的实际是上一次KeyOut输出选择的那行按键,这是为什么呢?任何信号从输出到稳定都需要时间,有时它足够快而有时却不够快,这取决于具体的电路设计,如果keyout为0时立即读取与其对应的四个按键的KeyIn值,虽然程序上已经让keyout为0了,但是电路反应并没有那么快,电路中对应的keyout引脚还没有完全拉成低电平的话,读取到的值就有可能出现错误。这里的输入输出顺序的颠倒就是为了让输出信号有足够的时间(一次中断间隔)来稳定,并有足够的时间来完成它对输入的影响,虽然这样使得程序理解起来有点绕,但它的适应性是最好的,换个说法就是,这段程序足够“健壮”,足以应对各种恶劣情况。

其次,是一点小小的编程技巧。注意看keyout = keyout & 0x03;这一行,这里是要让keyout在0~3之间变化,加到4就自动归零,按照常规可以用前面讲过的if语句轻松实现,但是现在看一下这样程序是不是同样可以做到这一点呢?因为0、1、2、3这四个数值正好占用两个二进制的位,所以把一个字节的高6位一直清零的话,这个字节的值自然就是一种到4归零的效果了。看一下,这样一句代码比if语句要更为简洁吧,而效果完全一样。

版权声明:本文转载于今日头条,版权归作者所有,如果侵权,请联系本站编辑删除

为您推荐

如何真正掌握教育教学

根据多年的布点教学法的研修经历,真正掌握教育教学就几句话:始终围绕学生展开教学,把自己自学的过程优化为教学过程,不断积累 则自然而然会真正懂教育教学。这短短几句话,既是我

2026-01-15 15:38

破解初中函数学习困境:跨越“思维断层”是关键

在初中数学学习中,函数堪称“分水岭”式的难点。数据显示,超80%的初中生曾在函数学习中遭遇滑铁卢,部分学生甚至出现数学成绩的断崖式下跌。这一现象令许多家长困惑:为何此前数

2026-01-15 15:37

四校六区,石室北湖vs石室联合vs石室天府,你更青睐谁?

今天的主角是大名鼎鼎的“四系初中”我们先来看看四系家谱捋捋关系。 四系家族有多庞大,大家也见识过了,咱们今天的重点就只放在那些较为优质、在家长中口碑比较好的初中学校!

2026-01-15 15:36

太原市致远实验学校2023年招聘小初高各科教师简章

“逐梦奋进,踏浪远方”——太原市致远实验学校用汗水浇灌收获,以实干笃定前行,做团队、泽文化,确品牌。学校以“致力于培养有信仰、有理想、有道德、有才干、有担当的国家栋梁”

2026-01-15 15:36

市一中天宁分校官宣:落户青龙街道、36个班、2022年建成使用

11月9日上午,常州市第一中学天宁分校项目签约仪式在天宁科技促进中心举行,这意味着天宁区将新建一所高中,按照预期,市一中天宁分校将于2022年投入使用。副市长陈正春,市教育局局

2026-01-15 15:35

四川新高考首年分析:从政策变化到录取情况,对高一选科的影响!

一、 新高考主要变化—不只是不再分文理2025年是四川推行“新高考”的第一年,这次改革不仅仅是简单地将文理分科改为物理类和历史类,而是涉及考试、计分、录取全方位的变革。

2026-01-15 15:35