1.矩阵键盘vs独立按键

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

本文讨论矩阵键盘的工作原理,并提供了一种结构清晰、简单易用的矩阵键盘驱动程序。

2.矩阵键盘工作原理



如图所示,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);
}
}
}

MCU软件最佳实践——矩阵键盘驱动的更多相关文章

  1. MCU软件最佳实践——独立按键

    1. 引子 在进行mcu驱动和应用开发时,经常会遇到独立按键驱动的开发,独立按键似乎是每一个嵌入式工程师的入门必修课.笔者翻阅了许多书籍(包括上大学时候用的书籍)同时查阅了网上许多网友的博客,无一例外 ...

  2. MCU软件最佳实践——使用printf打印数据

    在mcu上开发应用时,使用串口打印调试信息是最常用的调试手段之一.printf是c标准库提供的函数,可以方便输出格式化的信息.但针对不同的mcu芯片,printf函数要能正常工作,需要做一些移植和适配 ...

  3. 基于FPGA的4x4矩阵键盘驱动调试

    好久不见,因为博主最近两个月有点事情,加上接着考试,考完试也有点事情要处理,最近才稍微闲了一些,这才赶紧记录分享一篇博文.FPGA驱动4x4矩阵键盘.这个其实原理是十分简单,但是由于博主做的时候遇到了 ...

  4. 读《软件需求最佳实践》YOUGAN

    这几天在看<软件需求最佳实践>作者徐锋老师的软件需求培训,三天的课程,虽然原来对需求也关注了很多,自己也做过需求分析和开发的工作,但是这次培训感觉收获还是很多.三天的培训先做个记录,后续多 ...

  5. 敏捷遇上UML-需求分析及软件设计最佳实践(郑州站 2014-6-7)

      邀请函: 尊敬的阁下:我们将在郑州为您奉献高端知识大餐,当敏捷遇上UML,会发生怎样的化学作用呢?首席专家张老师将会为您分享需求分析及软件设计方面的最佳实践,帮助您掌握敏捷.UML及两者相结合的实 ...

  6. Atitit. 软件设计 模式 变量 方法 命名最佳实践 vp820 attilax总结命名表大全

    Atitit. 软件设计 模式 变量 方法 命名最佳实践 vp820 attilax总结命名表大全 1. #====提升抽象层次1 2. #----使用通用单词1 3. #===使用术语..1 4.  ...

  7. 基于开源软件在Azure平台建立大规模系统的最佳实践

    作者 王枫 发布于2014年5月28日 前言 Microsoft Azure 是微软公有云的唯一解决方案.借助这一平台,用户可以以多种方式部署和发布自己的应用. 这是一个开放的平台,除了对于Windo ...

  8. 移动App測试实战:顶级互联网企业软件測试和质量提升最佳实践

    这篇是计算机类的优质预售推荐>>>><移动App測试实战:顶级互联网企业软件測试和质量提升最佳实践> 国内顶级互联网公司測试实战经验总结.阿里.腾讯.京东.携程.百 ...

  9. Atitit.软件开发概念说明--io系统区--特殊文件名称保存最佳实践文件名称编码...filenameEncode

    Atitit.软件开发概念说明--io系统区--特殊文件名称保存最佳实践文件名称编码...filenameEncode 不个网页title保存成个个文件的时候儿有无效字符的问题... 通常两个处理方式 ...

随机推荐

  1. 初探Windows用户态调试机制

    我们在感叹Onlydbg强大与便利的同时,是否考虑过它实现的原理呢? 作为一个技术人员知其然必知其所以然,这才是我们追求的本心. 最近在学习张银奎老师的<软件调试>,获益良多.熟悉Wind ...

  2. CF17A Noldbach problem 题解

    Content 若一个素数可以用比它小的相邻的两个素数的和加 \(1\) 表示,那么称这个素数为"好素数". 给定两个正整数 \(n,k\),问从 \(2\) 到 \(n\) 的好 ...

  3. mysql三种提交类型

    mysql提交分显式提交.隐式提交及自动提交. (1) 显式提交用COMMIT命令直接完成的提交为显式提交.其格式为:SQL>COMMIT: (2) 隐式提交用SQL命令间接完成的提交为隐式提交 ...

  4. 『与善仁』Appium基础 — 30、操作微信小程序

    目录 1.测试微信小程序前提 2.获取微信小程序的进程 3.代码示例 4.补充:(了解) 微信小程序和微信公众号的测试方式基本上是一样的. 微信的小程序越来越多了,随之带来的问题是:小程序如何做自动化 ...

  5. 【LeetCode】1437. 是否所有 1 都至少相隔 k 个元素 Check If All 1s Are at Least Length K Places Away

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 指针 日期 题目地址:https://leetcode ...

  6. javascript原始值和引用值类型及区别

    原始值和引用值类型及区别 首先,原始值和引用值类型都是js中的数据类型,为了充分利用存储空间,定义了不同的数据类型,而且js是弱类型,动态语言,数据类型可变. 原始值(简单数据类型) 存储在栈中的简单 ...

  7. Normalized Cuts and Image Segmentation

    目录 概 主要内容 求解 相似度 总的算法流程 skimage.future.graph.cut Shi J. and Malik J. Normalized cuts and image segme ...

  8. 从源码看全局异常处理器@ExceptionHandler&@ExceptionHandler的生效原理

    1.开头在前 日常开发中,几乎我们的项目都会用到异常处理器,我们通常会定制属于自己的异常处理器,来处理项目中大大小小.各种各样的异常.配置异常处理器目前最常用的方式应该是使用@ControllerAd ...

  9. Java高级程序设计作业目录(作业笔记)

    持续更新中............. Java高级程序设计笔记 • [目录] 我的大学笔记>>> 第1章 IO流>>> 1.1.3 编写Java程序,在电脑硬盘里, ...

  10. 编写Java程序,实现对兵营类的封装,将兵营类中的所有属性设置为私有访问权限,方法设置为公有访问权限

    返回本章节 返回作业目录 需求说明: 实现对兵营类的封装 将兵营类中的所有属性设置为私有访问权限. 将兵营类中所有属性的赋值方法设置为公有访问权限. 要求兵营名称的长度在4-8位之间. 要求兵营士兵的 ...