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

1.矩阵键盘vs独立按键

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

2.矩阵键盘工作原理

MCU软件最佳实践——矩阵键盘驱动
如图所示,4×4矩阵键盘,连接到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
#include
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
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

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软件最佳实践——矩阵键盘驱动

Original: https://www.cnblogs.com/arminker/p/14696975.html
Author: 流云的博客
Title: MCU软件最佳实践——矩阵键盘驱动

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/573752/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • springboot集成rabbitmq

    主题交换机有fanout(扇形)、direct(直连)、topic(主题)三种。而主题交换机是可以通过配置包含前两种的,所以,本篇主要介绍主题交换机。 topic路由器的关键在于定…

    Java 2023年5月30日
    074
  • Java开发之@PostConstruct和@PreConstruct注解

    Java开发之@PostConstruct和@PreConstruct注解从Java EE5规范开始,Servlet增加了两个影响Servlet生命周期的注解(Annotation…

    Java 2023年5月29日
    070
  • SpringBoot集成Thymeleaf发送Html邮件报错

    由于业务需求需要使用Thymeleaf作为模板发送Html邮件,开发调试过程中发生以下错误 开始以为是Classpath下不存在这个文件或者解析时候传入参数不对等等原因,排查了半天…

    Java 2023年6月13日
    085
  • 从Spring中学到的【1】–读懂继承链

    最近看了一些 Spring 源码,发现源码分析的文章很多,而底层思想分析的文章比较少,这个系列文章准备总结一下Spring中给我的启示,包括设计模式思想、SOLID设计原则等,涉及…

    Java 2023年6月16日
    093
  • 那些年我一直在用的高效开发者工具-Typora

    今天跟大家介绍一款我平时一直在用的本地Markdown工具,对比了国内外几款相似工具,Typora简洁、干练、清爽、功能完备特性深深吸引了我。我平时一般用它记录一些学习文章撰写,工…

    Java 2023年6月15日
    093
  • Idea2019.3 :一直卡在Resolving Maven dependencies

    maven仓库是阿里的 问题 如图,下载jar包挺快,一直卡在解析那一步。。。。导致写注解老是爆红 解决 修改maven Importing的jvm参数, 默认为700多, 直接修…

    Java 2023年6月7日
    078
  • 4 信息的表示和处理_信息存储

    开头:本章研究在计算机上如何表示数字和其它形式数据的基本属性,以及计算机对这些数据执行操作的属性。 注意:这部分谈到的内存,并不是指硬件中的内存条,而是在《计算机系统漫游》章节中的…

    Java 2023年6月7日
    081
  • Linux命令拾遗-使用blktrace分析io情况

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介 一般来说,想检查磁盘I/O情况,可以使用iostat、iotop、sar等,但这些命令只能做一…

    Java 2023年6月7日
    081
  • 解决IDEA 读取properties配置文件中文乱码问题

    方法一查看文件编码类型是不是 utf-8 如果不是 修改为uft-8 然后就设置读取时的编码类型 InputStream resourceAsStream = equalsDemo…

    Java 2023年6月7日
    080
  • 关于docker创建了mysql容器但却启动不了的解决办法

    使用以下命令挂载mysql配置文件目录和数据文件 docker run -p 3306:3306 –name mysql57 \ -v /wfd/mysql/conf:/etc/…

    Java 2023年6月5日
    0119
  • Liunx-CentOS安装Nginx

    0 卸载Nginx 查看nginx是否运行 ps -ef | grep nginx 停止用stop、或者用kill /usr/local/nginx/sbin/nginx -s s…

    Java 2023年6月9日
    079
  • 计算java字符串中某个字符出现的次数

    使用HashMap实现 https://www.csdn.net/tags/MtzaAgwsMDczNTYtYmxvZwO0O0OO0O0O.html 使用Char查找匹配 htt…

    Java 2023年5月29日
    060
  • X-Y问题

    什么是X-Y问题 X-Y问题就是有人想解决问题X,他觉得Y可能是解决X的方法但不知道Y怎么做;在我们的工作中,需求方给出的来的是Y,而软件工程师不知道需要解决的X是什么。 我理解的…

    Java 2023年6月15日
    067
  • Spring中ApplicationContextAware接口使用

    一、接口介绍 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。换句话说,就是…

    Java 2023年6月5日
    091
  • Oracle 相关的专业术语 说明

    2PC: (See two-phase commit) ACID:The properties of a reliable transactional system: Atomic…

    Java 2023年5月30日
    061
  • Spring自定义解析的集中方式

    springMVC 、springboot中返回前端JSON 时候,经常需要不同的格式 实现方式有几种 一 、自己实现JSON序列化器 二、 自定义注释 Original: htt…

    Java 2023年5月30日
    074
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球