4×4矩阵键盘

对于行扫描状态,进入该情状时,应该对行、列的I/O口举办安装。也即,在其enter()
达成中设置行I/O口为input形式,并启用在那之中间上拉电阻;列I/O为output形式,并输出0。其
loop()
完毕则相继检查和测试行I/O口是或不是读数为0,若读数为0,则声明该行有按键按下,记下行号,并离开本状态:

 

 

#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));
}

结构体 App_ScanningState
表示三个意况,当进入该情形时,调用其 (函数指针)成员enter()
。在先后主循环中,则调用其 loop() 成员。loop() 函数重返值为 App_STAY 或
App_LEAVE,若重返前者,则申明应该停留在本场地,下次主循环将再也调用此情状的
loop() 函数;反之,若重回后者,则注解应该切换成下1个场所。

结构体 App_ScanningState
代表三个景况,当进入该景况时,调用其 (函数指针)成员enter()
。在先后主循环中,则调用其 loop() 成员。loop() 函数重回值为 App_STAY 或
App_LEAVE,若重返前者,则声明应该停留在这场地,下次主循环将再也调用此情况的
loop() 函数;反之,若重回后者,则注解应该切换来下贰个意况。

流水灯尝试的硬件基础上,扩大矩阵键盘接口。4×4矩阵键盘共有15个按键,四个LED刚好能够显得17个二进制值(0-0x0F)。

流水灯试验的硬件基础上,扩大矩阵键盘接口。4×4矩阵键盘共有1四个按键,四个LED刚好能够突显17个二进制值(0-0x0F)。

  

率先,调用当前事态的
loop()
函数,其重返值注明是或不是合宜切换成下多少个情景。假若切换成下贰个场合,则调用其
enter()
函数。若是是偏离第一等级,则已检测到1次按键事件(按下并甩手),依据按键键值(0-15)点亮LED。点亮LED的函数定义如下,其无外乎按位依次点亮或消失每3个LED:

而第二阶段无需改变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;
}

注意,在读取pin值时,为了de-bouncing,扩展了叁个5ms的延时复读。一般,de-bouncing延时取5-10ms。

矩阵键盘的按键检查和测试是分等级展开的,因而,程序的关键性布局越发符合利用“状态机”设计方式。下列代码中,5个行I/O口的Label依次为卡宴1:4,列I/O口为C1:4。首先定义状态结构体及二个实例:

STM32的I/O口内部电路中富含有上拉电阻和下拉电阻,能够因而程序启用或剥夺。

关于上拉/下拉电阻,此间有一篇介绍小说。上拉电阻的成效在于,在常态下,按钮开放,IO口被“往上拉”到VDD,读数为1;当按钮闭合,I/O口通过按钮短路到VSS,读数为0;而VDD通过上拉电阻和按钮与VSS连通。若没有上拉电阻的存在,则VDD与VSS短路,会造成劫难性的后果,那分明是必须制止的。使用上拉电阻时,按钮开放时,pin值为1;当按钮闭合时,pin值为0。即,pin值与按钮闭合状态相反,那名叫“负逻辑”。

 

而第2阶段无需变更I/O口设置,只需检查和测试被按下按键所在的列是还是不是读取pin值为1。读取pin值为1标明按键被卸掉,应该离开此情状,切换回行扫描状态:

#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;
}

对于行扫描状态,进入该情形时,应该对行、列的I/O口实行安装。也即,在其enter()
达成中装置行I/O口为input情势,并启用其内部上拉电阻;列I/O为output方式,并输出0。其
loop()
完成则相继检查和测试行I/O口是否读数为0,若读数为0,则申明该行有按键按下,记下行号,并离开本状态:

4×4矩阵键盘实拍照如下图。其构成是4行(L1:4)x
4列(Evoque1:4)共1陆个按键,当第n行、第m列的按钮(n, m)按下时,引脚
Ln 与 Rm 导通:

列扫描状态的兑现与行扫描相就像是,那里便不再给出代码了。供给表达的是,程序中接纳了2个字节型全局变量
key
用来保存键值,其第壹-三人为行号(0-3),第0-一人为列号(0-3),由此,key
的值为0-0x0F,依次对应十五个按键。

图片 1

rowScanning,
colScanning, colScanningPressed
3个App_ScanningState实例,分别为行扫描阶段、列扫描阶段及第③阶段(检查和测试按键松开)。程序起首时为行扫描状态,例如,使用CubeMX自动生成的初阶化代码。程序主循环内的代码为:

#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));
}

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

rowScanning,
colScanning, colScanningPressed
3个App_ScanningState实例,分别为行扫描阶段、列扫描阶段及第③阶段(检测按键松手)。程序初始时为行扫描状态,例如,使用CubeMX自动生成的伊始化代码。程序主循环内的代码为:

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

 

STM32的I/O口内部电路中含有有上拉电阻和下拉电阻,能够由此程序启用或剥夺。

    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();
    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();

矩阵键盘的按键检查和测试是分等级展开的,由此,程序的珍视点布局特别适合利用“状态机”设计情势。下列代码中,五个行I/O口的Label依次为大切诺基1:4,列I/O口为C1:4。首先定义状态结构体及三个实例:

图片 2

至于上拉/下拉电阻,此处有一篇介绍小说。上拉电阻的功用在于,在常态下,按钮开放,IO口被“往上拉”到VDD,读数为1;当按钮闭合,I/O口通过按钮短路到VSS,读数为0;而VDD通过上拉电阻和按钮与VSS连通。若没有上拉电阻的存在,则VDD与VSS短路,会招致灾害性的后果,那鲜明是必须防止的。使用上拉电阻时,按钮开放时,pin值为1;当按钮闭合时,pin值为0。即,pin值与按钮闭合状态相反,那称为“负逻辑”。

 

首先,调用当前意况的
loop()
函数,其再次回到值表明是或不是应当切换成下多少个场所。倘若切换成下三个场合,则调用其
enter()
函数。假诺是距离第叁品级,则已检查和测试到二回按键事件(按下并放手),依据按键键值(0-15)点亮LED。点亮LED的函数定义如下,其无外乎按位依次点亮或没有每四个LED:

 

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;
}

  

在详谈矩阵键盘
的接口算法中,多个等级都应用了上拉电阻。其检查和测试逻辑为负逻辑。

 

 

在前述矩阵键盘
的接口算法中,多个阶段都使用了上拉电阻。其检查和测试逻辑为负逻辑。

 

 

一篇作品,对矩阵键盘的接口讲解得很详细。回顾起来说,按键检查和测试分为一个级次。第二个阶段,扫描行。行I/O口设为input形式,使用上拉电阻。列I/O口设为output方式,输出0。逐行扫描,某一行若没有按键按下,则在上拉电阻的功效下pin值读取为1;若该行任一按键按下,则被按键短路到列I/O口,由此pin值读为0。检测到有按键被按下后,进入第2阶段,列扫描,以鲜明被按下的按键的列。列扫描阶段,行/列的I/O形式交流,即:行I/O口设置为output情势,输出0;列I/O口设为input方式,使用上拉电阻。类似于行扫描,逐列进行围观,当读取到pin值为0则申明被按下的按键属于该列。通过第③ 、二阶段,就能分明被按下的按键。第②等级,监听被按下的按键的列I/O口,直到pin值为1,即注脚按键被甩手。

#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;
}
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;

 

 

一篇作品,对矩阵键盘的接口讲解得很详细。归纳起来说,按键检查和测试分为二个阶段。第③个阶段,扫描行。行I/O口设为input方式,使用上拉电阻。列I/O口设为output方式,输出0。逐行扫描,某一行若没有按键按下,则在上拉电阻的效用下pin值读取为1;若该行任一按键按下,则被按键短路到列I/O口,因而pin值读为0。检查和测试到有按键被按下后,进入第贰阶段,列扫描,以分明被按下的按键的列。列扫描阶段,行/列的I/O方式交流,即:行I/O口设置为output方式,输出0;列I/O口设为input格局,使用上拉电阻。类似于行扫描,逐列进行围观,当读取到pin值为0则表明被按下的按键属于该列。通过第3、二阶段,就能显著被按下的按键。第3等级,监听被按下的按键的列I/O口,直到pin值为1,即表明按键被松手。

列扫描状态的完成与行扫描相接近,那里便不再给出代码了。要求验证的是,程序中采纳了一个字节型全局变量
key
用来保存键值,其第贰-三位为行号(0-3),第0-1人为列号(0-3),由此,key
的值为0-0x0F,依次对应16个按键。

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;