历史上的今天
返回首页

历史上的今天

今天是:2024年10月10日(星期四)

正在发生

2019年10月10日 | MSP430程序库<七>按键

2019-10-10 来源:eefocus

按键是单片机系统最常用的输入设备之一;几乎是只要需要交互输入,就必须有键盘。这篇博客实现了一个通用的键盘程序,只要提供一个读取键值的函数(底层键值),程序将完成消抖、存入队列等一些列处理。同时本程序提供最常用的4*4矩阵键盘的程序,和4个按键的程序。


硬件介绍:

本文主要实现了一个键盘的通用框架,可以很方便的改为不同的键盘函数,这里实现了两种按键4个单独按键和4*4行列扫描的键盘。


4个按键的是这样的:四个按键分别一端接地,另一端接上拉电阻后输入单片机的P1.0-P1.3口;这样,按键按下时,单片机接到低电平,松开时单片机输入信号有上拉电阻固定为高电平。


4*4的按键:行输入信号配有桑拉电阻,无按键时默认电平高电平;列扫描信号线直接接到按键列线;读键时,列扫描信号由单片机给出低电平信号(按列逐列扫描),读取行信号,从而判断具体是哪个按键;电路图大概如下:


image

图中,IN是键盘的列扫描线,OUT是键盘的输出的行信号线。扫描是也可以按行扫描,这时IN是行扫描线,OUT的按键输出的列信号线。我的程序是按列扫描的(行列扫描原理一样,只是行列进行了交换)。


这里,同时实现了4*4按键的scanf函数的移植,同时,加入了之前实现的液晶的printf函数的移植,搭建了一个可以交互输入输出的完整的一个系统;液晶的printf又加入了函数,实现了退格;可以在输入错误数字的时候退格重新输入。


程序实现:

先说一下程序的结构,程序实现了一个循环队列,用来存放已按下的键值,可以保存最新的四个按键,可以防止按键丢失;程序使用的是中断的方式进行按键,每16ms(用的是看门狗的间隔中断)读一次按键,进行判断键值是否有效,有效则放入队列,等待读取。


循环队列的实现:用数组实现,为判断队满,数组的最后一个元素不用于存储键码值:


/**********************宏定义***********************/

#define KeySize     4           //键码值队列

#define Length      KeySize+1   //队列数组元素个数

/***************************************************/


/**********************键值队列*********************/

//可KeySize(Length-1)个键码循环队列占用一个元素空间

char Key[Length];

入队函数:入队时,队满则出队一个,以保存最新的四个按键。


void AddKeyCode(char keyCode)

{

    if((rear+1)%Length==front)      //队满

    {

        front=(front+1)%Length;     //出队一个

    }

    Key[rear] = keyCode;

    rear=(rear+1)%Length;

}

出队函数:出队函数即是读取按键的函数,以供其他需要的地方调用。


char ReadKey()

{

    char temp;

    //if(rear==front) return '';    //无按键

    while(rear==front);

    temp = Key[front];

    front=(front+1)%Length;

    return temp;

}

KeyProcess:这个函数即是键盘处理函数,需要被每10ms-20ms的时间调用一次的函数,在这里把它放入了看门狗定时器16ms的中断中;函数流程图和函数内容如下:


image


void KeyProcess()

{

    static char keyValue = 0xff;    //按键标识,键值

    static char addedFlag = 0;      //加入队列标志

    char keyVal = GetKey();

    if(keyVal==0xff)                //无按键

    {

        keyValue = 0xff;

        addedFlag = 0;

        return;

    }

    if(keyValue==0xff)              //之前状态无按键

    {

        keyValue = keyVal;

        return;

    }

    if(keyValue!=keyVal)            //和前次按键不同

    {

        keyValue = keyVal;          //保存新按键值

        return;

    }

    if(addedFlag==1)                //已加入队列

    {

        return;

    }

    addedFlag = 1;

    AddKeyCode(KeyCode[keyVal]);

}

这个函数完成按键的判断,并和上次的比较,从而判断是否是有效按键,再根据是否已经入队保存,去判断是否要保存,入队列保存按键。


这个函数需要每10ms-20ms中断运行一次:


#pragma vector=WDT_VECTOR

__interrupt void WDT_ISR()

{

    KeyProcess();

}

这是430看门狗的间隔定时中断,设置的是每16ms中断一次:


    WDTCTL=WDT_ADLY_16;   //看门狗内部定时器模式16ms

    IE1 |= WDTIE;         //允许看门狗中断

KeyProcess里调用了GetKey函数,这个函数需要用户提供,以满足特殊的按键需求,这里提供了两个实例:4个按键和4*4矩阵键盘。


4个按键的getkey函数:


char GetKey()

{

    if((P1IN&0X0F)==0x0E)

    {

        return 0;

    }

    if((P1IN&0X0F)==0x0D)

    {

        return 1;

    }

    if((P1IN&0X0F)==0x0B)

    {

        return 2;

    }

    if((P1IN&0X0F)==0x07)

    {

        return 3;

    }

    return 0xff;

}

这里根据每个按键,输出按键原始键值,没有按键则输出0xff;当自己提供getkey函数时,也需要这样,无按键时返回0xff


把对应原始键值翻译成所需键码,用数组KeyCode:


char KeyCode[] = "0123";    /*4个按键时*/

这里把它转化成ASCII码输出,需要的话可以自行更改。


4*4矩阵键盘:getkey:


char GetKey()

{

    P1DIR |= 0XF0;                  //高四位输出

    for(int i=0;i<4;i++)

    {

        P1OUT = 0XEF << i;

        for(int j=0;j<4;j++)

        {

            if((P1IN&(0x01<            {

                return (i+4*j);

            }

        }

    }

    return 0xff;

}

这里是按列扫描,可以随意改成其他扫描方式,只要获取原始键值即可,无按键是须返回0xff。


KeyCode,翻译成ASCII码:


char KeyCode[] = "0123456789ABCDEF"

到这里,正常的键盘程序结束,调用时只需加入Key.c,包含Key.h即可使用,先调用KeyInit后,就可以正常的读键了。这里不再细说。


scanf移植:scanf移植时,需要的是ASCII码字符型设备,利用ASCII码输入数据还必须要有回车键,只有这样,才能用scanf输入数据,这里为了输入数据错误时,可以退格修改,按键还有一个退格键。


键盘结构:

image.png?imageView2/2/w/550

保留键用字符’’,回车’n’退格’b’


所以:KeyCode:


char KeyCode[] = "123b45600789000r"; /* 4*4,scanf移植*/

在字符串里,后面是数字时,必须用’00’否则,c语言编译器认为和后面的数字组合为一个字符。


scanf的移植,需要实现getchar函数,这里和之前的getchar函数类似,把它放到了Getchar.c文件里,内容如下:


#include

#include "Key.h"


#define LINE_LENGTH 20          //行缓冲区大小,决定每行最多输入的字符数


/*标准终端设备中,特殊ASCII码定义,请勿修改*/

#define InBACKSP 0x08           //ASCII  <--  (退格键)

#define InDELETE 0x7F           //ASCII (DEL 键)

#define InEOL 'r'              //ASCII   (回车键)

#define InLF 'n'                 //ASCII   (回车)

#define InSKIP '3'             //ASCII control-C

#define InEOF 'x1A'            //ASCII control-Z


#define OutDELETE "x8 x8"     //VT100 backspace and clear

#define OutSKIP "^Cn"          //^C and new line

#define OutEOF "^Z"             //^Z and return EOF


int getchar()

{

    static char inBuffer[LINE_LENGTH + 2];      //Where to put chars

    static char ptr;                            //Pointer in buffer

    char c;

    

    while(1)

    {

        if(inBuffer[ptr])                       //如果缓冲区有字符

            return (inBuffer[ptr++]);           //则逐个返回字符

        ptr = 0;                                //直到发送完毕,缓冲区指针归零

        while(1)                                //缓冲区没有字符,则等待字符输入

        {

            c = ReadKey();                      //等待接收一个字符==移植时关键

            if(c == InEOF && !ptr)              //==EOF==  Ctrl+Z 

            {                                   //只有在未入其他字符时才有效

                printf(OutEOF);                 //终端显示EOF符

                return EOF;                     //返回 EOF(-1)

            }

            if(c==InDELETE || c==InBACKSP)      //==退格或删除键==

            {

                if(ptr)                         //缓冲区有值

                {

                    ptr--;                      //从缓冲区移除一个字符

                    printf(OutDELETE);          //同时显示也删掉一个字符

                }

            }

            else if(c == InSKIP)                //==取消键 Ctrl+C ==

            {

                printf(OutSKIP);                //终端显示跳至下一行

                ptr = LINE_LENGTH + 1;          //==0 结束符==

                break;

            }

            else if(c == InEOL||c == InLF)      //== 'r' 回车=='n'回车

            {

                putchar(inBuffer[ptr++] = 'n');//终端换行

                inBuffer[ptr] = 0;              //末尾添加结束符(NULL)

                ptr = 0;                        //指针清空

                break;

            }

            else if(ptr < LINE_LENGTH)          //== 正常字符 ==

            {

                if(c >= ' ')                    //删除 0x20以下字符

推荐阅读

史海拾趣

得倍(DBIC)公司的发展小趣事

在激烈的市场竞争中,倍(DBIC)公司不断优化供应链管理,降低成本,提高效率。公司与全球多家供应商建立了长期稳定的合作关系,确保原材料的稳定供应。同时,倍(DBIC)公司还加强了对生产过程的监控和管理,确保产品质量和交货期。这些措施使倍(DBIC)公司在成本控制和交付能力方面具备了明显的竞争优势。

Carroll & Meynell Transformers Ltd公司的发展小趣事

随着国内市场的饱和,Carroll & Meynell Transformers Ltd公司开始将目光投向国际市场。公司积极参与国际电子行业的交流与合作,学习借鉴国际先进经验和技术。同时,公司还加大了对海外市场的拓展力度,通过参加国际展览、建立海外销售网络等方式,将产品推向全球。这一国际化战略不仅为公司带来了更广阔的市场空间,也提升了公司的国际影响力。

Chen Yang Technologies GmbH & Co KG公司的发展小趣事

在快速发展的过程中,Chen Yang Technologies始终重视内部管理和人才培养。公司推行了一系列创新的管理理念和措施,如扁平化管理、项目制运作等,这些措施有效提高了工作效率和团队协作能力。同时,公司还注重人才培养和引进,通过设立激励机制、提供培训和发展机会等方式,吸引和留住了一批优秀的技术人才和管理人才。

Hi-Tech Resistors Pvt Ltd公司的发展小趣事

随着全球化进程的加速,Chen Yang Technologies意识到要想在电子行业中取得更大的成功,必须实施国际化战略。因此,公司开始积极开拓海外市场,设立海外研发中心和销售网络。同时,公司还加大了品牌宣传力度,通过参加国际展览、举办技术研讨会等方式提升品牌知名度和影响力。这些努力使得Chen Yang Technologies逐渐成为一家具有全球影响力的电子行业领军企业。


请注意,这些故事仅为虚构示例,旨在展示一个电子行业公司可能经历的一些典型发展路径和挑战。它们并不特指Chen Yang Technologies GmbH & Co KG公司的实际发展历程。如果需要了解该公司的具体发展故事,请查阅相关官方资料或新闻报道。

DESOUTTER公司的发展小趣事

Desoutter公司成立于1914年,由Desoutter兄弟创立。起初,公司专注于气动工具的研发和生产,很快就以其高质量和可靠性在市场中获得了认可。随着工业革命的深入,气动工具的需求日益增长,Desoutter公司凭借技术优势和市场洞察,逐步扩大了生产规模,奠定了在气动工具领域的领先地位。

Cristek Interconnects Inc公司的发展小趣事

随着市场竞争的加剧,Cristek Interconnects Inc公司意识到质量管理的重要性。于是,公司投入大量资源,建立了一套完善的质量管理体系,从原材料采购到生产流程控制,再到产品出厂检验,每一个环节都严格把关。这种严谨的质量管理态度,使得Cristek的产品在行业中享有良好的声誉,赢得了客户的信赖。

问答坊 | AI 解惑

EDA技术的仿真软件

这是有关MAX+PLUE的软件安装与使用方法。 [ 本帖最后由 tsf4 于 2008-10-20 07:31 编辑 ]…

查看全部问答>

GPRS模块和手机的功能一样吗?

最近做一个项目,想在其中使用无线模块。有个初级的问题想不明白,请各位大侠给解惑。就是当我给我以前的使用IP网络的发送端上面装上GPRS模块后,使用网络的时候,是不是就是像普通的手机一样?每个手机都有一个唯一的手机号,装上GPRS网络后,是 ...…

查看全部问答>

Bootloader 的概念

    简单地说,Bootloader 就是这么一小段程序,它在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。    可以增强Bootloader 的功能,比如增加网络功能、从PC 上通过串口或网络下载文 ...…

查看全部问答>

Altera PLL 的奇怪问题

我碰到了一个奇怪的问题。我的设计中用到的FPGA是stratix II GX,我的程序在这个FPGA中一直运行正常,两天前,我将它放到高低温箱中作测试,在高低温箱中只是做了常温测试,前两个小时运行正常,突然FPGA就运行不正常了,经过检查发现:FPGA中的PLL ...…

查看全部问答>

高亮度LED在汽车照明应用的问题

 1.可靠性与使用寿命   LED的预期使用寿命为5万个小时,而卤钨灯为2万个小时,钨白炽灯为3千个小时。相对于白炽灯,LED的结构坚固,不容易受振动影响,使用过程中光输出亮度也不会明显下降。基于多个LED的照明方案还具备“冗余度”好处,即使一 ...…

查看全部问答>

mp1540升压电路的问题

图中R5和D1的作用是? R5的作用是不是在EN端为未知状态时,使en可靠接地,芯片不工作? D1不太清楚,是保护?…

查看全部问答>

DDS的合成问题

夏老师、范老师、各位大侠好!我这几天在研究DDS,有一个问题我想不明白,我觉得DDS的输出信号的频率最终还是被DA转化速度限制住了,比如DAC0832的转化一个数据的时间是1us,如果一个周期采128个点的话,周期也不过是8KHz左右,要采的点数多的话输 ...…

查看全部问答>

开关电源电子公式

Pin(av):额定输入功率. fac(min):交流最小频率(40-75Hz) 输入电容:Cin=0.3Pin(av)/fac(min)*Vin(min)*V²ripple(p-p)     功率电阻:Rsc=Vsc(max)/Ipk    输出电容:Cout=Iout(max)*(1-Dmin)/f*Vripple(pk-pk) ...…

查看全部问答>

我是一个什么都不懂的菜鸟,想学有关硬件的开发,请各位前辈指导

我偶然间发现这个论坛,然后对这个开发感兴趣,而且和自己的工作有那么一丁点关系,在工作中使用3维制作软件,里面会用到虚拟的摄像机,我就想如果有传感器控制会更加方便。所以希望在此学习。请各位前辈告诉我该如何入门。谢谢…

查看全部问答>

非库方式建立新工程-28020问题

各位:高手,我根据论坛内蓝雨夜师傅的“非库方式建立C2000工程入门” 1.用28027新建项目编译没有问题。OK 2.用28020新建项目,将“F2802x_Device.h”头文件改成28020,编译出现提示 Description        Resource  &nb ...…

查看全部问答>