• MCU软件最佳实践——矩阵键盘驱动


    1.矩阵键盘vs独立按键

    在mcu应用开发过程中,独立按键比较常见,但是在需要的按键数比较多时,使用矩阵键盘则可以减少io占用,提高系统资源利用率。例如,某mcu项目要求有16个按钮,如果采用独立按键方案,则需要占用16个mcu引脚,如果采用4x4矩阵键盘,则只需要4+4个mcu引脚,节省了一倍io资源占用,如图所示。但是矩阵键盘也有其缺点,相较与独立按键,程序设计稍显复杂。
    本文讨论矩阵键盘的工作原理,并提供了一种结构清晰、简单易用的矩阵键盘驱动程序。

    2.矩阵键盘工作原理

    image
    如图所示,4x4矩阵键盘,连接到mcu的P2口8个pin,P2[3:0]为行线,P2[7:4]为列线。

    当编程设置P2 = 0Xfe:
    则P2.0被设置为0,即第一行所有按钮的一端接地,单片机通过检测P2的高四位,可以判断按下的按钮在哪一列。假如检测到P2 == 0Xee则是第一个按钮被按下,检测到P2 == 0Xde则是第二个按钮被按下,检测到P2 == be则是第三个按钮按下,检测P2 == 7e则是第四个按钮被按下。

    当编程设置P2=0Xfd,P2=0Xfb,0Xff:
    则以此类推,可分别判定第二行、第三行、第四行哪个按钮被按下。

    矩阵键盘常见的检测程序,是逐行检测,即:

    // 伪代码
    void matrixkbd_scan()
    {
    	unsigned char t;
    	// 检测第一行
    	P2 = 0XFE;
    	t = P2;
    	if(t != 0xFE) // 说明第一行有按钮被按下
    	{
    		delay_10ms(); // 延时10ms,以防止抖动,避免误判断
    		t = P2;
    		if(t != 0XFE)
    		{
    			switch(t)
    			{
    			case 0xee:num = 0;break;
    			case 0xde:num = 1;break;
    			case 0xbe:num = 2;break;
    			case 0xfe:num = 3;break;
    			}
    		}
    		
    	}
    	//检测第二行
    	P2 = 0XFD;
    	...
    	
    	//检测第三行
    	P2 = 0XFB;
    	...
    	
    	//检测第四行
    	P2 = 0XFF;
    	...
    }
    

    上述代码的缺点:

    1. 代码冗余,每一行的检测几乎是重复的代码;
    2. 每检测一行都需要延时消除抖动,增加了延时,有可能漏检测

    3.本文矩阵键盘驱动

    3.1 实现思路

    思路:
    第一步:让行线全部为0,读取列线的值,存储在th中,这个值反映了当前按下按钮所在的列
    第二步:让列线全部为0,读取行线的值,存储在tl中,这个值反映了当前按下按钮所在的行
    第三步:将th和tl或操作,这个值反映了当前按下按钮的行和列。
    例如:
    让P2 = 0xf0,然后读取P2的值为0x70,则说明第四列有键按下
    让P2 = 0X0F,然后读取P2的值为0x07,则说明第四行有键按下
    或操作后,得到0x77,就是第四行第四列这个按钮的按键码

    或操作后,得到0xff,说明没有键按下。可以作为判定是否有键按下的条件。

    3.2 matrixkbd.c

    /**
     * @file:  matrixkbd.c
     * @brief: 矩阵键盘驱动程序
     */
    #include <reg52.h>
    #include <stdio.h>
    unsigned char key_no;
    unsigned char flg_down;
    unsigned char flg_up;
    
    static unsigned char timer;
    static unsigned stat = 1;
    // 按键扫描
    // 第一步:让行线全部为0,读取列线的值,存储在th中,这个值反映了当前按下按钮所在的列
    // 第二步:让列线全部为0,读取行线的值,存储在tl中,这个值反映了当前按下按钮所在的行
    // 第三步:将th和tl或操作,这个值反映了当前按下按钮的行和列。
    static unsigned char keyfn()
    {
    	unsigned char th,tl;
    	
    	P2 = 0xf0;
    	th = P2;
    	P2 = 0x0f;
    	tl = P2;
    	return th | tl;
    }
    
    // 键值转换
    static unsigned char decode(unsigned t)
    {
    	unsigned char key_no;
    	switch(t)
    	{
    		case 0xee:key_no = 0;break;
    		case 0xde:key_no = 1;break;
    		case 0xbe:key_no = 2;break;
    		case 0x7e:key_no = 3;break;
    		case 0xed:key_no = 4;break;
    		case 0xdd:key_no = 5;break;
    		case 0xbd:key_no = 6;break;
    		case 0x7d:key_no = 7;break;
    		case 0xeb:key_no = 8;break;
    		case 0xdb:key_no = 9;break;
    		case 0xbb:key_no = 10;break;
    		case 0x7b:key_no = 11;break;
    		case 0xe7:key_no = 12;break;
    		case 0xd7:key_no = 13;break;
    		case 0xb7:key_no = 14;break;
    		case 0x77:key_no = 15;break;
    		default:key_no = 16;break;
    	}
    	return key_no;
    }
    
    // 外部调用:保证每10ms调用1次
    void key_scan()
    {
    	unsigned char t;
    	t = keyfn();
    	if(t != 0xff)
    	{
    		if(timer < 1)
    		{
    			timer++;
    			return;
    		}
    		if(stat == 1)
    		{
    			stat = 0;
    			// keydown
    			key_no = decode(t);
    			flg_down = 1;
    		}
    	}
    	else
    	{
    		if(timer > 0)
    		{
    			timer--;
    			return;
    		}
    		if(stat == 0)
    		{
    			stat = 1;
    			flg_up = 1;
    		}
    	}
    }
    

    3.3 对外接口matrixkbd.h

    /**
     * @file: matrixkbd.h
     */
    // 键盘模块
    extern unsigned char key_no;
    extern unsigned char flg_down;
    extern unsigned char flg_up;
    extern void key_scan();
    

    4.实验

    检测矩阵键盘事件;
    按下和弹起时,串口打印输出键编号

    4.1 定时器驱动

    4.1.1 定时器驱动timer0.c

    为了方便调用,采用时间触发方式,添加定时器驱动:

    /**
     * @file: timer0.c
     * @brief: 产生10ms事件
     */
    #include <reg52.h>
    int systick;
    unsigned char flg_10ms;
    unsigned char flg_50ms;
    unsigned char flg_sec;
    void timer_init(unsigned char ms)
    {
    	TMOD = (TMOD & 0XF0);  // 模式0:13bit 定时器模式,最大计数值8192
    	TH0 = (8192 - ms * 1000) / 32; // TH0的8位保存13bit初值的高8bit
    	TL0 = (8192 - ms * 1000) % 32; // TL0的低5位用来存储13bit初值得低5bit
    	
    	TR0 = 1;
    	
    	ET0 = 1;
    	EA = 1;
    }
    
    
    void timer_isr(void) interrupt 1
    {
    	TR0 = 0;
    	timer_init(1);
    	
    	systick++;
    	if(systick % 10 == 0)
    	{
    		flg_10ms = 1;
    		if(systick % 50 == 0)
    		{
    			flg_50ms = 1;
    			if(systick % 1000 == 0)
    			{
    				flg_sec = 1;
    			}
    		}
    	}
    }
    

    4.1.2 定时器对外接口timer0.h

    /**
     * @file:timer0.h
     */
    // 定时器模块
    extern unsigned char flg_10ms;
    extern unsigned char flg_50ms;
    extern unsigned char flg_sec;
    extern void timer_init(unsigned char ms);
    

    4.2 串口驱动

    4.2.1 串口驱动实现uart.c

    为了方便打印,查看调试信息,实现串口驱动程序:

    /**
     * @file: uart.c
     * @brief: 串口驱动,波特率9600bps,10bit模式
     *
     */
    #include <reg52.h>
    
    void uart_init(void)
    {
    	SCON = 0X50; // 10bit 可变波特率模式
    	
    	//T1: SM1SM0=10,8bit auto reload,波特率9600bps
    	TMOD = (TMOD & 0X0F) | (1 << 5);
    	TH1 = TL1 = 0XFD;
    	TR1 = 1;
    	
    	ES = 1;
    	EA = 1;
    	TI = 1; // start transmit if using putchar provided by c51 lib
    }
    
    void uart_isr(void) interrupt 4
    {
    	if(RI)
    	{
    		RI = 0;
    	}
    	if(TI)
    	{
    	}
    }
    

    4.2.2 串口驱动对外接口uart.h

    /**
     * @file: uart.h
     */
    // 串口模块
    extern void uart_init(void);
    

    4.3 主程序

    主程序中:

    1. 在后台按照驱动程序要求调用驱动:10ms为周期调用key_scan
    2. 实时监测事件:flg_up,flg_down,flg_10ms
    /**
     * @file: main.c
     * @brief: 主程序
     */
    #include "uart.h"
    #include "timer0.h"
    #include "matrixkbd.h"
    void main(void)
    {
    	timer_init(1);
    	uart_init();
    	while(1)
    	{
    		if(flg_10ms)
    		{
    			flg_10ms = 0;
    			key_scan();
    		}
    		if(flg_down)
    		{
    			flg_down = 0;
    			printf("key %bu pressed
    ",key_no);
    		}
    		if(flg_up)
    		{
    			flg_up = 0;
    			printf("key %bu released
    ",key_no);
    		}
    	}
    }
    

    image

  • 相关阅读:
    hibernate建表默认为UTF-8编码
    XML和JSON
    chrome 模拟发送请求的方法
    什么时候需要使用缓存?
    eclipse中查找类、方法及变量被引用的地方
    用户内容与商业
    2019第48周日
    ajax与重定向
    ifream
    Windows下找到JVM占用资源高的线程
  • 原文地址:https://www.cnblogs.com/uil2liu/p/14696975.html
Copyright © 2020-2023  润新知