ca88亚洲城网站则该列线与置为低电平的行线交叉处的开关正是密闭的按钮,列的按键(

4×4矩阵键盘实拍照如下图。其重组是4行(L1:4)x
4列(福特Explorer1:4)共17个开关,当第n行、第m列的按键(n, m)按下时,引脚
Ln 与 Rm 导通:

前日我们上学了矩阵开关,矩阵键盘是单片机外界设备中所使用的排布类似于矩阵的键盘组。矩阵式结构的键盘显明比直接法要复杂一些,识别也要复杂一些,列线通过电阻接正电源,并将行线所接的单片机的I/O口作为输出端,而列线所接的I/O口则作为输入。在键盘中按钮数量较多时,为了削减I/O口的占用,平时将按钮排列成矩阵情势。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过贰个开关加以连接。那样,三个端口就足以组成4*4=17个按钮,比之直接将端口线用于键盘多出了一倍,而且线数越来越多,分裂越刚强,比方再多加一条线即可组合20键的键盘,而向来用端口线则不得十分少出一键(9键)。简单的讲,在须求的键数比较多时,采纳矩阵法来做键盘是合情的。那样,当开关未有按下时,全体的输入端都以高电平,代表无键按下。行线输出是低电平,一旦有键按下,则输入线就能被拉低,那样,通过读入输入线的处境就可得知是不是有键按下了。行扫描法
行扫描法又叫做逐行(或列)扫描查询法,是一种最常用的开关识别方法。

ca88亚洲城网站 1

1、推断键盘中有无键按下
将全部行线Y0-Y3置低电平,然后检验列线的意况。只要有一列的电平为低,则意味键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4个按钮之中。若有所列线均为高电平,则键盘中无键按下。

 

2、推断闭合键所在的地方在分明有键按下后,就可以进入显著具体闭合键的进程。其艺术是:依次将行线置为低电平,即在置某根行线为低电平常,此外线为高电平。在规定某根行线位置为低电平后,再逐行检验各列线的电平状态。若某名列低,则该列线与置为低电平的行线交叉处的开关正是虚掩的按钮。

 

一篇文章,对矩阵键盘的接口疏解得很详细。归纳起来讲,按钮质量评定分为3个阶段。第贰个品级,扫描行。行I/O口设为input方式,使用上拉电阻。列I/O口设为output形式,输出0。逐行扫描,某一行若没有开关按下,则在上拉电阻的作用下pin值读取为1;若该行任一开关按下,则被开关短路到列I/O口,因而pin值读为0。检查实验到有按钮被按下后,进入第二阶段,列扫描,以显著被按下的按钮的列。列扫描阶段,行/列的I/O形式交换,即:行I/O口设置为output方式,输出0;列I/O口设为input方式,使用上拉电阻。类似于行扫描,逐列进行围观,当读取到pin值为0则表明被按下的按钮属于该列。通过第一、二品级,就能够分明被按下的按钮。第三阶段,监听被按下的开关的列I/O口,直到pin值为1,即证明按钮被甩手。

关于上拉/下拉电阻,此地有一篇介绍小说。上拉电阻的法力在于,在常态下,开关开放,IO口被“往上拉”到VDD,读数为1;当按键闭合,I/O口通过按键短路到VSS,读数为0;而VDD通过上拉电阻和开关与VSS连通。若未有上拉电阻的存在,则VDD与VSS短路,会变成灾祸性的结局,那明明是必须制止的。使用上拉电阻时,开关开放时,pin值为1;当按键闭合时,pin值为0。即,pin值与开关闭合状态相反,这称之为“负逻辑”。

在详谈矩阵键盘
的接口算法中,四个阶段都利用了上拉电阻。其检查实验逻辑为负逻辑。

STM32的I/O口内部电路中蕴含有上拉电阻和下拉电阻,可以经进度序启用或剥夺。

流水灯实验的硬件基础上,扩充矩阵键盘接口。4×4矩阵键盘共有十四个开关,4个LED刚好可以显示14个二进制值(0-0x0F)。

矩阵键盘的开关检查测试是分品级举行的,由此,程序的关键性布局特别吻合利用“状态机”设计格局。下列代码中,4个行I/O口的Label依次为纳瓦拉1:4,列I/O口为C1:4。首先定义状态结构体及3个实例:

typedef struct {
    void (*enter)();
    uint8_t (*loop)();
} App_ScanningState;

#define App_STAY 0
#define App_LEAVE 1

void rowScanningEnter();
uint8_t rowScanningLoop();
void colScanningEnter();
uint8_t colScanningLoop();
void colScanningPressedEnter();
uint8_t colScanningPressedLoop();

App_ScanningState rowScanning = { rowScanningEnter, rowScanningLoop };
App_ScanningState colScanning = { colScanningEnter, colScanningLoop };
App_ScanningState colScanningPressed = { colScanningPressedEnter, colScanningPressedLoop };

App_ScanningState *currState = &rowScanning;

 

结构体 App_ScanningState
表示1个情景,当进入该景况时,调用其 (函数指针)成员enter()
。在程序主循环中,则调用其 loop() 成员。loop() 函数重临值为 App_STAY 或
App_LEAVE,若重返前者,则评释应该停留在该情状,后一次主循环将再一次调用此情状的
loop() 函数;反之,若重返后者,则申明应该切换成下叁个气象。

rowScanning,
colScanning, colScanningPressed
3个App_ScanningState实例,分别为行扫描阶段、列扫描阶段及第三品级(检查实验按钮松手)。程序开首时为行扫描状态,比方,使用CubeMX自动生成的初阶化代码。程序主循环内的代码为:

    if (App_LEAVE != currState->loop()) {
        return;
    }

    // Button released
    if (currState == &colScanningPressed) {
        lightLedsUp(key);
    }

    // Next state
    currState = currState == &rowScanning ? &colScanning //
            :
                currState == &colScanning ? &colScanningPressed //
                        : &rowScanning;
    currState->enter();

  

首先,调用当前事态的
loop()
函数,其再次来到值注解是不是应当切换来下三个情景。假若切换成下几个情景,则调用其
enter()
函数。假诺是偏离第三阶段,则已检验到贰遍按钮事件(按下并甩手),依据开关键值(0-15)点亮LED。点亮LED的函数定义如下,其无外乎按位依次点亮或消亡每一种LED:

#define BIT_TO_PIN_VALUE(key, bit) ( (1 & (key >> bit)) ? GPIO_PIN_SET : GPIO_PIN_RESET )

void lightLedsUp(uint8_t key) {
    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, BIT_TO_PIN_VALUE(key, 3));
    HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, BIT_TO_PIN_VALUE(key, 2));
    HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, BIT_TO_PIN_VALUE(key, 1));
    HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, BIT_TO_PIN_VALUE(key, 0));
}

 

对此行扫描状态,进入该意况时,应该对行、列的I/O口举行设置。也即,在其enter()
实现中安装行I/O口为input格局,并启用其里面上拉电阻;列I/O为output模式,并输出0。其
loop()
落成则相继检测行I/O口是或不是读数为0,若读数为0,则申明该行有开关按下,记下行号,并离开本状态:

#define configInputPullUp(port, pin, GPIO_InitStruct) { \
/*        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET); */ \
        (GPIO_InitStruct)->Pin = pin ; \
        (GPIO_InitStruct)->Mode = GPIO_MODE_INPUT ; \
        (GPIO_InitStruct)->Pull = GPIO_PULLUP ; \
        (GPIO_InitStruct)->Speed = GPIO_SPEED_FREQ_LOW; \
        HAL_GPIO_Init(port, GPIO_InitStruct) ; \
}

#define configOutputLow(port, pin, GPIO_InitStruct) { \
        (GPIO_InitStruct)->Pin = pin ; \
        (GPIO_InitStruct)->Mode = GPIO_MODE_OUTPUT_PP ; \
        (GPIO_InitStruct)->Pull = GPIO_NOPULL ; \
        (GPIO_InitStruct)->Speed = GPIO_SPEED_FREQ_LOW; \
        HAL_GPIO_Init(port, GPIO_InitStruct) ; \
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);  \
}

#define DEBOUNCE_DELAY 5

void rowScanningEnter() {

    GPIO_InitTypeDef GPIO_InitStruct;

    // Row pins: input, pull-up enabled
    configInputPullUp(R1_GPIO_Port, R1_Pin, &GPIO_InitStruct);
    configInputPullUp(R2_GPIO_Port, R2_Pin, &GPIO_InitStruct);
    configInputPullUp(R3_GPIO_Port, R3_Pin, &GPIO_InitStruct);
    configInputPullUp(R4_GPIO_Port, R4_Pin, &GPIO_InitStruct);

    // Col pins: output 0
    configOutputLow(C1_GPIO_Port, C1_Pin, &GPIO_InitStruct);
    configOutputLow(C2_GPIO_Port, C2_Pin, &GPIO_InitStruct);
    configOutputLow(C3_GPIO_Port, C3_Pin, &GPIO_InitStruct);
    configOutputLow(C4_GPIO_Port, C4_Pin, &GPIO_InitStruct);

}

GPIO_PinState checkPressedLow(GPIO_TypeDef *port, uint16_t pin) {
    if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(port, pin)) {
        // Delay & read again
        HAL_Delay(DEBOUNCE_DELAY);
        return HAL_GPIO_ReadPin(port, pin);
    }

    return GPIO_PIN_SET;
}

uint8_t rowScanningLoop() {

    if (GPIO_PIN_RESET == checkPressedLow(R1_GPIO_Port, R1_Pin)) {
        key = 0;
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R2_GPIO_Port, R2_Pin)) {
        key = 1 << 2;
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R3_GPIO_Port, R3_Pin)) {
        key = 2 << 2;key
        return App_LEAVE;
    }
    if (GPIO_PIN_RESET == checkPressedLow(R4_GPIO_Port, R4_Pin)) {
        key = 3 << 2;
        return App_LEAVE;
    }

    return App_STAY;
}

 

瞩目,在读取pin值时,为了de-bouncing,扩张了贰个5ms的延时复读。一般,de-bouncing延时取5-10ms。

列扫描状态的完毕与行扫描左近似,这里便不再给出代码了。要求证实的是,程序中动用了一个字节型全局变量
key
用来保存键值,其第2-3位为行号(0-3),第0-1位为列号(0-3),由此,key
的值为0-0x0F,依次对应14个开关。

而第三品级没有须要更改I/O口设置,只需检查评定被按下开关所在的列是还是不是读取pin值为1。读取pin值为1标识开关被卸掉,应该离开此情况,切换回行扫描状态:

uint8_t colScanningPressedLoop() {

    int col = 3 & key;

    if (0 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C1_GPIO_Port, C1_Pin)) {
            return App_LEAVE;
        }
    } else if (1 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C2_GPIO_Port, C2_Pin)) {
            return App_LEAVE;
        }
    } else if (2 == col) {
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C3_GPIO_Port, C3_Pin)) {
            return App_LEAVE;
        }
    } else { // 3== col
        if (GPIO_PIN_SET == HAL_GPIO_ReadPin(C4_GPIO_Port, C4_Pin)) {
            return App_LEAVE;
        }
    }

    return App_STAY;
}

 

相关文章