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\r\n",key_no);
		}
		if(flg_up)
		{
			flg_up = 0;
			printf("key %bu released\r\n",key_no);
		}
	}
}

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM