历史上的今天
今天是:2025年05月06日(星期二)
2018年05月06日 | 单片机C语言实现独立按键检测与矩阵键盘操作
2018-05-06 来源:eefocus
所有的电子产品几乎到涉及到按键操作。所以微控制器是如何识别一个按键是否被按下,按下后又该如何做出反应,又如何防止按键抖动呢?更深入一点,微控制器又是如何识别矩阵键盘的?本文将详细阐述如何用C语言实现独立按键的检测和矩阵键盘操作。
完成本文所需硬件:基于C51系列单片机的开发板(本文是基于STC12C5A60S2处理器的一款开发板),带中文版windows操作系统的电脑。
完成本文所需软件:KEIL系列平台(本文选取Keil uVision4), STC烧写软件-ISP-V6.82E 。
一、独立按键检测
这里我要实现用按键K1去控制发光二极管LD4。同时为了试验按键过程中与其他事件的冲突性,引入两个事件即LD1与LD8分别以不同频率闪烁。先上程序吧。另外利用keil软件新建工程和文件部分这里就略过了,总之所有的代码都放在main文件里执行。
代码:
-----------------------------------------------------------------------------------------------
#include
sbit K1=P2^4; //定义按键K1的检测口
sbit LD4=P1^3; //定义控制LD4的输出口
sbit LD1=P1^0; //定义控制LD1的输出口
sbit LD8=P1^7; //定义控制LD8的输出口
void LD1_flash() //LD1闪烁
{
static unsigned int a; //定义a为静态局部变量
a = a + 1;
if(a == 20000) //当a达到20000次CPU计数次数
{
a=0; //a归零
LD1 =! LD1; //LD1取反,如果先前是灭,则取反后亮,反之。
}
}
void LD8_flash()//LD8 flash
{
static unsigned int b;
b++;
if(b==40000)
{
b=0;
LD8 =! LD8;
}
}
void delay(unsigned int x)
{
while(x)
{
x = x - 1;
}
}
void Key1()
{
static char st;
if(K1==0) //按键K1是否被按下
{
if(st==0) //按键K1是否是被刚刚按下,假设st为1,则按键还处于被按下的状态,则不用执行if里面语句。
{
delay(5000); //延时防抖动
if(K1==0) //查看按键K1是否还处于被按下状态
{
LD4=!LD4;
st=1; //等待放手
}
}
}
else
{
st=0;
}
}
void main() //C语言执行入口
{
while(1)
{
LD1_flash(); //ld1 flash
LD8_flash(); //ld8 flash
Key1();
}
}
要理解上面的代码,我们需要结合C语言的一些特性来分析。
1、static unsigned int a,为什么不把变量a与b定义成局部变量,却定义成静态局部变量?
我们知道局部变量在函数执行完时候其值归零,而static关键字修饰的局部变量在函数结束时,其值会保留到下一次该函数被调用。
2、为什么a和b没被定义成全局变量?
全局变量是指该变量可被任何函数使用,而局部变量只有定义该变量的函数可以使用。局部变量的优点是安全,但缺点是函数结束其值随之归零。与之相对,全局变量则不安全。
3、为什么a和b并未赋初始值?
这是因为startup.A51程序执行时使其赋值为0。C语言执行是从函数开始的,但是真正的程序运行是从汇编语言开始,即startup.A51文件。因为C语言无法访问寄存器如R0, 所以只能由汇编程序来执行。下面截取一段startup.A51的程序进行分析:
IF IDATALEN <> 0
MOV R0,#IDATALEN - 1
CLR A
IDATALOOP: MOV @R0,A
DJNZ R0,IDATALOOP
ENDIF
这段程序会将内存区域清零,其中A代表累加器ACC,即内存中224号地址。
另外解释下赋初值的情况,例如unsigned int a = 8, 我们知道RAM在断电后数据丢失,因此单片机就利用ROM来保存这个初始值。在下次上电时,进入C语言之前,汇编程序将ROM中的a调到RAM中。这样也就保证了a的初值。
4、LD1与LD8的闪烁为什么不适用delay函数?
如果使用delay函数,那么单片机在执行到LD1_flash函数时候,只能停滞在delay的这个时间段内,浪费时钟资源。而引入a和b后,程序进入LD1_flash函数时候,只需判断a的值,然后再加一或者清零,几乎不占用CPU时间。
5、key1函数中st的作用?
首先,st可以判断程序在每次被调用的前后按键状态,这样,可以保证键被按住的时候,发光二极管不会闪烁。另外,也可以避免在操作K1键的时候影响LD1和LD8的运行。另外如果不想在key1函数中使用delay函数,可以采用如下代码:
void Key1()
{
static unsigned int a,b;
if(K1==0)
{
if(a==0) //检查按键是否被刚刚按下
{
b++;
if(b >= 1000) //看看按键时间是否维持了1000次CPU执行周期
{
LD4=!LD4;
//...
a=1;
}
}
}
else
{
a=0;
b=0;
}
}
6、如何将不同的函数独立到不同的文件?
为了保证代码的可维护性,如果要将不同的代码区块分配到不同文件,可以使用extern,它的作用是实现不同文件间的函数调用。例如:
本例中可将main函数精简成:
#include
extern void delay(unsigned int x);
extern void LD1_flash();
extern void LD8_flash();
extern void Key1();
void main()
{
while(1)
{
LD1_flash(); //ld1 flash
LD8_flash(); //ld8 flash
Key1();
}
}
二、矩阵键盘
上图中,P1口初始值从高位到低位为11111110,当2和3键被按下,此时P1.0口连通P1.5和P1.6口,即P1口状态变为10011110。由此我们可以用P1口的高四位来判断有没有键被按下,如果没有那么其值为1111,也就是0x0F。那如果被按下了,具体是哪个键呢?这时候我们可以建立一个矩阵表,然后通过每次读取P1口的状态,与矩阵表相对应,便可以知道哪个键被按下。这个矩阵表见下(选用了P0口代替P1口):
原理就解释到这吧,直接上程序:
-----------------------------------------------------------------------------------------------------------------
#include "reg51.h"
void delay(unsigned int x)
{
while(x)
{
x=x-1;
}
}
void key_exc(unsigned char k)//unsigned 表示最高位不当做符号用
{
switch(k)
{
case 0xee: P1=~P1; break; //1键按下
case 0xed: break; //2键按下
case 0xeb: break;
case 0xe7: break;
case 0xde: break;
case 0xdd: break;
case 0xdb: break;
case 0xd7: break;
case 0xbe: break;
case 0xbd: break;
case 0xbb: break;
case 0xb7: break;
case 0x7e: break;
case 0x7d: break;
case 0x7b: break;
case 0x77: P1=~P1; break;
}
}
void key_scan()
{
char i;
char t; //用来存放读取P0口的状态
// if((P0>>4) != 15)
// {
// return; //直接跳出函数
// }
for(i=0;i<4;i++) //for循环执行,首先i赋值,再判断i是否小于4,如果是的话,开始执行下面的程序,程序执行完加1,再判断i是否小于4,这样一直到结束
{
P0 = ~(1<
t=P0; //将P0的值赋给t
if((t>>4)!=0x0F) //本行是否有按钮被按下
{
delay(5000);
if(P0==t) //如果相等则表示延时前后的按键状态是一致的
{
key_exc(t);//将局部变量传给函数key-exc,并分析按下的按钮
while((P0>>4)!=15)//等待放手
{
}
}
}
}
}
void main()
{
while(1)
{
key_scan();
}
}
-----------------------------------------------------------------------------------------------------------------------------------
利用上面的程序,可以判断哪个按钮被按下。例如当K1按下时候,P1口的发光二极管都被点亮。
上一篇:单片机C语言实现数码管控制
史海拾趣
|
这是在我们的MCF52259开发板上跑的一个简单的SD卡读写程序,SD卡是用MCF52259的QSPI模块操作的。MCF52259是飞思卡尔coldfire v2 core的mcu,具有以太网控制器,CAN, USB OTG,UART,RTC,QSPI,IIC等功能,比较全。 例子在codewarrior for coldfire v7 ...… 查看全部问答> |
|
本帖最后由 jameswangsynnex 于 2015-3-3 19:59 编辑 用稳压集成功放制作的功率放大器,对电子爱好者来说,作为开拓思路的一种尝试不无积极意义。该电路为纯甲类工作,又用低噪声管作电压放大,所以THD,NF等指标都不错,输出功率可达到30W 电路 ...… 查看全部问答> |
|
新产品快递Allegro’s LED Backlight Driver IC for Large Displays Allegro’s LED Backlight Driver IC for Large Displays The new device integrates a scalable-output boost controller operating in constant-frequency current mode control – adjustable between 300kHz and 1MHz - drivin ...… 查看全部问答> |
|
本帖最后由 jameswangsynnex 于 2015-3-3 20:01 编辑 今年以来,随着海尔推出物联网冰箱、小天鹅推出物联网洗衣机以及美的在世博会上展示全套的物联网家电产品,家电的智能化热潮正悄然袭来。4月底,中国轻工业联合会嵌入式系统应用委员会也宣布成 ...… 查看全部问答> |
|
我把整个的NAND FLASH读出来,然后通过一些办法把NK文件读出来了。。 但是我发现里面的都是系统文件,里面原先写入NAND FLASH文件的文件不能导出来。请问有什么办法把这些导出来。。 我写个示意图: 00000000H:xx xx xx xx xx xx xx xx xx xx ...… 查看全部问答> |
|
embedded visual c++4.0安装过程中 sp4安装不成功 尝试很多次,一直安不成功。 本人使用电脑为联想的笔记本,自带操作系统,是不是因为他里面装了一些软件导致冲突造成。 错误提示 Internal Error2349! 非常感谢各位大虾! 另,我是一初学者,还请各位指教本书来尽快上手。… 查看全部问答> |
|
红外线遥控器控制继电器的程序 #include #include #define uchar unsigned char #define uint unsigned int #pragma interrupt_handler IceInt:6 中断程序说明 uint bitcnt,data0=0,data1=0; void ICEInit(void) //T/C1 初 ...… 查看全部问答> |
|
【M4开发板Hanker试用狂-Shower.Xu】9、有容乃大-基于USB设备的SD读卡器实现 失败了无数次之后,一瞬间竟然可以了。成功和失败只隔了一层纸,但是个中滋味却相差甚远...简单记录一下移植过程:1、在SD实现读写的移植基础上展开。2、usb端口初始化和USB鼠标一样,无需改变。3、在此基础上加入下图几个文件usb_msc_structs.c是 ...… 查看全部问答> |
|
#include typedef unsigned char uchar; void Bcstime() { WDTCTL=WDTPW+WDTHOLD; } /************定时器A初始化*******************/ void TimerA() { P2DIR |=0X04; ...… 查看全部问答> |




