排的按钮(列的按钮(则该列线及贩否没有电平的行线交叉处的按键就是合的按键。

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

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

今日咱们念了矩阵按键,矩阵键盘是单片机外部设备中所运用的排布类似于矩阵的键盘组。矩阵式结构的键盘显然比直接法要复杂一些,识别为要是复杂一些,列线通过电阻接正电源,并以行线所联网的单片机的I/O口作为输出端,而列线所联网的I/O口则当输入。在键盘中按键数量比较多时,为了减小I/O口的挤占,通常以按键排列成矩阵形式。在矩阵式键盘中,每条水平线与垂直线在交叉处不直接入,而是经一个按键加以连接。这样,一个端口就可以组合4*4=16只按键,比之直拿端口线用于键盘多出了扳平倍增,而且线数更是多,区别越明白,比如更多加同长线便可以做20键的键盘,而直白用端口线则只能多发生同键(9键)。由此可见,在急需的键数比较多时,采用矩阵法来开键盘是理所当然的。这样,当按键没有按下经常,所有的输入端都是赛电平,代表无键按下。行线输出是低电平,一旦闹键按下,则输入线就见面让牵涉低,这样,通过读入输入线的状态就只是获悉是否来键按下了。行扫描法
行扫描法又称之为逐行(或列)扫描查询法,是一致种植最常用之按键识别方法。

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,即表明按键被松开。

有一致首文章,对矩阵键盘的接口讲解得十分详细。概括起来说,按键检测分为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值与按钮闭合状态相反,这名“负逻辑”。

关于上拉/下拉电阻,此出雷同首介绍文章。上拉电阻的意图在于,在常态下,按钮开放,IO口被“往上拖累”到VDD,读数为1;当按钮闭合,I/O口通过按钮短路到VSS,读数为0;而VDD通过达成拉电阻和按钮与VSS连通。若无上拉电阻的是,则VDD与VSS短路,会促成灾难性的名堂,这明明是必须避免的。使用及拉电阻时,按钮开放时,pin值为1;当按钮闭合时,pin值为0。即,pin值与按钮闭合状态相反,这叫做“负逻辑”。

以前述矩阵键盘
的接口算法中,三独号还下了上拉电阻。其检测逻辑吗负逻辑。

以详谈矩阵键盘
的接口算法中,三个阶段都施用了上拉电阻。其检测逻辑吗负逻辑。

STM32之I/O口内部电路中包含有上拖累电阻和下拉电阻,可以经序启用或剥夺。

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

在流水灯试验的硬件基础及,增加矩阵键盘接口。4×4矩阵键盘共有16只按键,4独LED刚好可以来得16个伯仲迈入制值(0-0x0F)。

在流水灯尝试的硬件基础及,增加矩阵键盘接口。4×4矩阵键盘共有16只按键,4只LED刚好可以来得16独次前行制值(0-0x0F)。

矩阵键盘的按键检测是劈路展开的,因此,程序的重点布局特别吻合下“状态机”设计模式。下列代码中,4个行I/O口的Label依次为R1:4,列I/O口为C1:4。首先定义状态结构体及3独实例:

矩阵键盘的按键检测是分路展开的,因此,程序的核心结构特别吻合用“状态机”设计模式。下列代码中,4独行I/O口的Label依次为R1: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;
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() 函数;反之,若返回后者,则表明该切换到下一个状态。

结构体 App_ScanningState
代表1个状态,当进入该状态时,调用其 (函数指针)成员enter()
。在次主循环中,则调整用该 loop() 成员。loop() 函数返回值为 App_STAY 或
App_LEAVE,若返回前者,则表明该留于拖欠状态,下次主循环将更调整用是态的
loop() 函数;反之,若返回后者,则表明该切换到下一个态。

rowScanning,
colScanning, colScanningPressed
3个App_ScanningState实例,分别吗实施扫描等、列扫描等及第三号(检测按键松开)。程序开始时也履行扫描状态,例如,使用CubeMX自动生成的初始化代码。程序主循环内的代码为:

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

率先,调用当前状态的
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));
}
#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,则表明该行有按键按下,记下行号,并离开本状态:

于执行扫描状态,进入该状态时,应该对行、列的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;
}
#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。

只顾,在读取pin值时,为了de-bouncing,增加了一个5ms之延时复读。一般,de-bouncing延时取5-10ms。

列扫描状态的贯彻同实践扫描相类似,这里虽不再受来代码了。需要征的凡,程序中行使了一个字节型全局变量
key
用来保存键值,其第2-3号呢行号(0-3),第0-1各类也列号(0-3),因此,key
的值为0-0x0F,依次对应16只按键。

排扫描状态的兑现和执行扫描相仿佛,这里虽不再被有代码了。需要说明的是,程序中使用了一个字节型全局变量
key
用来保存键值,其第2-3各类吗行号(0-3),第0-1各项呢列号(0-3),因此,key
的值为0-0x0F,依次对应16单按键。

若第三品无需转I/O口设置,只需要检测为据下按键所当的排列是否读取pin值为1。读取pin值为1标志按键被扒,应该去这态,切换回行扫描状态:

若果第三阶段无需改变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;
}
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;
}

 

 

相关文章