单片机(二):3个IO扩展n*8个IO,基于74hc595与74hc165的8x8矩阵键盘

文章介绍了如何利用74hc595和74hc165芯片扩展IO口,构建8x8矩阵键盘,并详细阐述了防止鬼键和消抖的实现方法。74hc595作为输出,74hc165作为输入,通过行扫描和列检测来识别按键状态,同时在软件层面进行消抖处理以确保稳定识别。
摘要由CSDN通过智能技术生成

3个IO通过一片74hc595扩展8个输出IO,3个IO通过一片74hc1655扩展8个输入IO,最终成为8X8的矩阵键盘。对于普通的矩阵键盘,再加入防止鬼键、消抖和按下与松开识别。

一、74hc595介绍

74HC595是一个8位串行输入、并行输出的移位缓存器。通俗的来讲就是在输入时钟的上升沿数据输入端的数据可以位移进入芯片内部的位移缓存器,多位数据移位输入完成后,在输出锁存时钟的上升沿时将数据存入并行输出缓存器,在输出使能时数据输出到并行输出端。相当于可以通过3个IO口控制输出并行的8个IO口,甚至通过74hc595的级联扩展更多的IO,相比于74HC138更加的灵活和可扩展。这里介绍怎么使用,具体的性能参数靠参考技术手册。

符号

引脚

描述

Q0--Q7

第15脚,

第1-7脚

8位并行数据输出

GND

第8脚

电源地

Q7’

第9脚

串行数据输出

SCLK#(MR)

第10脚

主复位(低电平有效),可接VCC

SCK(SH_CP)

第11脚

数据输入时钟线

RCK(ST_CP)

第12脚

输出存储器锁存时钟线

G#(OE)

第13脚

输出有效(低电平有效),可接地

SER(DS)

第14脚

串行数据输入

VCC

第16脚

电源正级

表x 引脚定义

图x 74hc595操作示意图

74HC595可以进行级联,级联时共用数据输入时钟、数据锁存时钟和输出数据使能口。不同的是Q7’接下一片74HC595的串行数据输入口,相当于上一片595的数据移位溢出之后,数据就进入了下片595。

二、74HC165介绍

74HC165是一款高速CMOS八位并入串出移位寄存器,功能与74HC595相反。这里介绍怎么使用,具体的性能参数靠参考技术手册。

符号

引脚

描述

SH/LD(PL)

第1脚

数据加载控制,

低电平时读取并行输入端口数据存储在移位寄存器中

高电平时串行数据从DS进入移位寄存器中,用于级联

CLK(CP)

第2脚

时钟输入,上升沿触发。CE使能时,移位输出。

A-F

第11-14

3-6脚

并行数据输入引脚

QH’

第7脚

串行数据互补输出

GND

第8脚

电源地

QH

第9脚

串行数据输出引脚

SER(DS)

第10脚

串行数据输入引脚

CLK_INH(CE)

第15脚

时钟输入使能引脚,低电平触发,可接地

VCC

第16脚

电源正极

表x 引脚定义

图x 74hc165操作示意图

74HC165可以进行级联,级联时共用数据输入时钟、数据锁存时钟和输出数据使能口。不同的是DS接下一片74HC165的QH/QH’,相当于把多片165的移位寄存器串联起来,在时钟信号下数据逐步输出。

三、8x8矩阵键盘

8x8采用一片74hc595、一片74hc165、64个按键和64个二极管组成。其中595负责行扫描按键,165负责扫描选中的这一行的按键。如图595扫描第一行,即输出b01111111,165检测到第3列的电平为0,即1行3列的按键被按下,依次循环扫描检测。这样就是矩阵键盘的设计原理,但是在多个按键被按下的时候,会出现幽灵按键或者鬼键的问题。

图x 矩阵键盘示意图

幽灵按键或者鬼键出现在一个矩形的三个角的按键被按下后,会检测为剩余一个角上的按键被按下,就好像出现了一个幽灵按键。如下图,1行3,4列,2行3列被按下,但是会检测到按下的是2行4列的按键。解决的办法就是在按键的左边或者右边加入一个二极管,使得电流只能单方向的流动。

图x 鬼键示意图

四、软件设计

软件中包含了行扫描和列检测,其中还增加了按键的消抖检测。当按键按下时,电平会有一段信号不稳定的时间,要等到电平稳定之后才能判断按键确实按下。为了消抖,创建了两个二维数组,一个记录检测到一次按下信号后扫描的次数,一个记录按下信号的次数,比较这两个数,相差较大则不稳定,记录的数据多并且两个数据相差较小时则稳定。


/*
reg:
    KeyScane -> 检测到按下就加1
    KeyCount -> 当检测到一次按键按下后,每扫描一次就加1
    KeyStatus -> 0未按下的检测按下状态,1按下的稳定状态,2未按下或者处于松开状态
    size -> shape of matrix
*/
void ScanKey(unsigned char* KeyScane, unsigned char* KeyCount, unsigned char* KeyStatus,  unsigned char size){
    unsigned char ScanData = 0;
    unsigned char index_scan, index_move;
    // 行扫描
    for(index_scan=0; index_scan<8; index_scan++){
        ScanData = ~(0x01 << index_scan);
        // 595写入数据,从高位开始,也就是先扫描QA,SCK上升沿将数据移入,RCK上升沿数据输出
        KRCK = 0;
        for(index_move=0; index_move<8; index_move++){
            DATA_OUT = (ScanData << index_move) & 0x80;
            KSCK = 0;
            mDelayuS(10);
            KSCK = 1;
        }
        KRCK = 1;
        //列扫描
        // 165检查输入,当PL拉高时,CP给一个上升沿,8位寄存器中的值就通过Q7输出一位
        PL = 0;
        mDelayuS(10);
        PL = 1;
        for(index_move=0; index_move<8; index_move++){
            // 未按下的检测按下状态
            if(KeyStatus[index_scan*size+index_move] == 0){
                if(KeyScane[index_scan*size+index_move] >= 1){
                    KeyCount[index_scan*size+index_move] += 1;
                }
                if(DATA_IN == 0){
                    KeyScane[index_scan*size+index_move] += 1;
                }
            }
            // 不处于检测按下状态
            else{
                if(DATA_IN == 1){
                    KeyStatus[index_scan*size+index_move] = 2;
                }
            }
 
            CP = 0;
            mDelayuS(10);
            CP = 1;
            mDelayuS(10);
        }
    }
}

在app应用中做如下操作:


UINT8X AT(0x00c8) KeyScane[8][8];
UINT8X AT(0x0108) KeyCount[8][8]; 
UINT8X AT(0x0148) KeyStatus[8][8];
                                                             
memset(KeyScane, 0, sizeof(KeyScane));
memset(KeyCount, 0, sizeof(KeyCount));
memset(KeyStatus, 0, sizeof(KeyStatus));    
 
while(1){
    ScanKey((unsigned char*)KeyScane, (unsigned char*)KeyCount, (unsigned char*)KeyStatus, 8);
    // 当keycount比keyscane大n时都置位0,说明是误触;
    // 当keyscane的值大于n,都置位0,说明按下去了
    for(h=0; h<8; h++){
        for(l=0; l<8; l++){
            // 处于检测按下状态
            if(KeyStatus[h][l]==0){
                // 误触,计数清零,状态不变
                if(KeyCount[h][l]-KeyScane[h][l] >= n){
                    KeyCount[h][l] = 0;
                    KeyScane[h][l] = 0;
                }
                // 检测到稳定按下,计数清零,置位按键处于稳定按下状态,将检测松开,处理按下业务
                if(KeyScane[h][l] >= n){
                    KeyStatus[h][l] = 1;
                    KeyCount[h][l] = 0;
                    KeyScane[h][l] = 0;
 
                }
            }
            // 检测到松开,计数清零,置位按键处于检测按下状态,处理松开业务
            else if (KeyStatus[h][l]==2)
            {
                KeyStatus[h][l] = 0;
                KeyCount[h][l] = 0;
                KeyScane[h][l] = 0;
 
            }
        }
    }
}

CH552的GPIO配置:


// 595 通用数据输出
#define DATA_OUT  P1_1
// 按键 595
#define KRCK P3_1
#define KSCK P3_0
// 按键 165
#define CP P1_6
#define PL P1_7
#define DATA_IN P1_5

/*******************************************************************************
* Function Name  : PortCfg()
* Description    : 端口配置
* Input          : PortN 1,3
                   Mode  0 = 浮空输入,无上拉
                         1 = 推挽输入输出
                         2 = 开漏输入输出,无上拉
                         3 = 类51模式,开漏输入输出,有上拉,内部电路可以加速由低到高的电平爬升
                   ,UINT8 Pin    (0-7)
* Output         : None
* Return         : None
*******************************************************************************/
void PortCfg(unsigned char PortN, unsigned char Mode, unsigned char Pin){
    if(PortN==1){
        switch(Mode){
            case 0:
                P1_MOD_OC = P1_MOD_OC & ~(1<<Pin);
                P1_DIR_PU = P1_DIR_PU &    ~(1<<Pin);
                break;
            case 1:
                P1_MOD_OC = P1_MOD_OC & ~(1<<Pin);
                P1_DIR_PU = P1_DIR_PU |    (1<<Pin);
                break;
            case 2:
                P1_MOD_OC = P1_MOD_OC | (1<<Pin);
                P1_DIR_PU = P1_DIR_PU &    ~(1<<Pin);
                break;
            case 3:
                P1_MOD_OC = P1_MOD_OC | (1<<Pin);
                P1_DIR_PU = P1_DIR_PU |    (1<<Pin);
                break;
            default:
                break;
        }
    }
    else if(PortN==3){
        switch(Mode){
            case 0:
                P3_MOD_OC = P3_MOD_OC & ~(1<<Pin);
                P3_DIR_PU = P3_DIR_PU &    ~(1<<Pin);
                break;
            case 1:
                P3_MOD_OC = P3_MOD_OC & ~(1<<Pin);
                P3_DIR_PU = P3_DIR_PU |    (1<<Pin);
                break;
            case 2:
                P3_MOD_OC = P3_MOD_OC | (1<<Pin);
                P3_DIR_PU = P3_DIR_PU &    ~(1<<Pin);
                break;
            case 3:
                P3_MOD_OC = P3_MOD_OC | (1<<Pin);
                P3_DIR_PU = P3_DIR_PU |    (1<<Pin);
                break;
            default:
                break;
        }
    }
}


void Init_GPIO_HC(void){
    PortCfg(1, 3, 1);
    PortCfg(1, 3, 6);
    PortCfg(1, 3, 7);
    PortCfg(1, 3, 5);

    PortCfg(3, 3, 0);
    PortCfg(3, 3, 1);
    PortCfg(3, 3, 4);
    PortCfg(3, 3, 3);
}

以下是基于STM32单片机74HC595驱动8个LED灯的代码: ```c #include "stm32f10x.h" #define HC595_PORT GPIOA #define HC595_DS_PIN GPIO_Pin_0 #define HC595_SHCP_PIN GPIO_Pin_1 #define HC595_STCP_PIN GPIO_Pin_2 void HC595_Init(void); void HC595_Write(uint8_t dat); int main(void) { HC595_Init(); uint8_t led_data = 0x01; while (1) { HC595_Write(led_data); led_data = (led_data << 1) | (led_data >> 7); //循环移位 for (uint16_t i = 0; i < 10000; i++); //延时 } } void HC595_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = HC595_DS_PIN | HC595_SHCP_PIN | HC595_STCP_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(HC595_PORT, &GPIO_InitStructure); GPIO_ResetBits(HC595_PORT, HC595_DS_PIN | HC595_SHCP_PIN | HC595_STCP_PIN); } void HC595_Write(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { if (dat & 0x80) GPIO_SetBits(HC595_PORT, HC595_DS_PIN); else GPIO_ResetBits(HC595_PORT, HC595_DS_PIN); GPIO_SetBits(HC595_PORT, HC595_SHCP_PIN); GPIO_ResetBits(HC595_PORT, HC595_SHCP_PIN); dat <<= 1; } GPIO_SetBits(HC595_PORT, HC595_STCP_PIN); GPIO_ResetBits(HC595_PORT, HC595_STCP_PIN); } ``` 在此代码中,我们使用了STM32的GPIO模块控制74HC595芯片,来实现控制8个LED灯的功能。具体实现方式为,通过循环移位来得到要显示的数据,并将数据写入到74HC595芯片中,然后通过锁存时钟信号(STCP)将数据传给输出寄存器,最后通过移位时钟信号(SHCP)将输出寄存器中的数据传给移位寄存器,然后通过数据信号(DS)将数据传入到移位寄存器中,完成LED灯的控制。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值