前述

  • 在闲暇的时候逛B站发现某UP使用了移位寄存器实现1*N矩阵键盘,就想着蛮简单的自己也动手弄一弄才发现踩的坑还挺多的。
  • 今天就从图的设计到程序的编写来实现
  • 今天所使用的单片机型号是MCU51,至于为什么不用STM32等其他主流主控芯片。因为懒的配置GPIO。直接修改寄存器就好了。但是其他单片机也是一样的方法

需要准备的工具

  • Proteus主要用于仿真
  • Keil5主要用于开发编写程序
  • 74HC165芯片手册

芯片手册分析

  • 打开芯片手册就可以直接的看到芯片的基本功能

  • 该芯片工作电压在2v ~ 6v

  • 并行输入转串行输出

  • 主要应用在 视频的驱动,输出扩展,和今天要做的矩阵键盘

  • 内部逻辑图

  • 从图中可以看出使用的是RS锁存器进行数据的存储

  • 前面的了解一下即可,重要的还是芯片的管脚

  • 可以从下图看到每个管脚对应的作用

  • A-G:并行输入端

  • SER:串行输入端

  • Qh:串行输出端

  • Vcc:电源

  • GND:地线

  • CLK:时钟信号

  • 芯片的真值表也是很重要的
  • 真值表可以让我们编写程序来操作芯片工作

仿真部分

  • 仿真使用的是Proteus,从芯片手册可以看到如果需要使用时钟上升沿当作移位信号那么CLK INH就直接接地即可。下面是Proteus的连接

  • 画叉叉的地方就是一个多输出端的电阻

  • 这里我将两个芯片级联了,达到扩展16个输入端。还可以不断级联扩展并且还是只需要3根线也就是说不管级联多少都只需要3个IO口读取数据

  • 上面那个芯片的Qh连接到了P10口,P10就是数据的输入主要用来扫描寄存器里的数据

  • 根据真值表,两个INH连接地线即可,用于配合工作

  • SH/LD非是用来控制输入还是移位,连接到了P12

  • CLK用于控制操作芯片工作,连接到了P11

  • 最上面的示波器只是为了方便我们分析程序找出问题

程序编写

  • 引入头文件和定义宏用于后面方便移植

    1
    2
    3
    4
    5
    6
    7
    8
    #include <REG51RD2.H>

    #define SH_LD P1_2
    #define CLK P1_1
    #define OUT P1_0

    typedef unsigned int u16;
    typedef unsigned char u8;
  • 主要部分,扫描键盘输入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    u16 KEY_Scan(void){

    u8 i;
    u16 key;
    key = 0;

    CLK = 1; //Set clock to 1
    SH_LD = 0; //Read input
    SH_LD = 1; // Stop reead input
    key |= OUT;

    for(i=0;i<15;i++){

    CLK = 0; // Set clock to 0 to read data
    CLK = 1; // Set clock to 1 to prefer next scan for data
    key <<= 1;
    key |= OUT;
    }

    CLK = 0;
    return key;
    }

  • 代码逻辑

    1. 置时钟为高电平
    2. 根据真值表设置SH/LD非获取用户输入的数据
    3. 将数据锁起来,停止用户输入
    4. 获取最高位数据存放到key中
    5. 循环依次获取每个寄存器的值放到key中,每循环一次左移1是为了不让后面的数据把当前数据替换掉。CLK = 0,CLK = 1是为了产生一个上升沿的信号
    6. 最后停止芯片的时钟
    7. 返回key,key存放的就是用户输入键盘的状态每一个比特位为一个按键的状态
  • 这里key我使用了一个u16的数据类型是因为刚好有16个按键,每个按键的状态刚好对应了每一位比特位

  • 如果是要扩展32个按键可以使用unsigned long要是64个按键或更多按键可以使用一个unsigned long的数组存放

  • 主函数,对按键状态取反是因为由于按键是低电平有效,反转后变成到高电平有效方便后期处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main(void){

    while(1){
    u16 key = ~KEY_Scan();

    u8 num = KeyToNum(key);
    if (num != -1)
    LED_ShowNum(num);
    Delayms(10);
    }
    }

  • 完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80

    #include <REG51RD2.H>

    #define SH_LD P1_2
    #define CLK P1_1
    #define OUT P1_0

    typedef unsigned int u16;
    typedef unsigned char u8;

    void Delayms(unsigned char x) //@12.000MHz
    {
    unsigned char i, j;
    do{
    i = 2;
    j = 239;
    do
    {
    while (--j);
    } while (--i);
    } while(--x);
    }


    void LED_ShowNum(u8 num){
    u8 i,j;
    i = num / 10;
    j = num % 10;
    P2 &= 0;
    P2 |= j;
    P2 <<= 4;
    P2 |= i;
    }


    u16 KEY_Scan(void){

    u8 i;
    u16 key;
    key = 0;

    CLK = 1; //Set clock to 1
    SH_LD = 0; //Read input
    SH_LD = 1; // Stop reead input
    key |= OUT;

    for(i=0;i<15;i++){

    CLK = 0; // Set clock to 0 to read data
    CLK = 1; // Set clock to 1 to prefer next scan for data
    key <<= 1;
    key |= OUT;
    }

    CLK = 0;
    return key;
    }

    u8 KeyToNum(u16 key){
    u8 i;
    for(i=0;i<16;i++)
    if (((key >> i) & 0x01) == 0x01)
    return i+1;
    return -1;
    }

    int main(void){

    while(1){
    u16 key = ~KEY_Scan();

    u8 num = KeyToNum(key);
    if (num != -1)
    LED_ShowNum(num);

    Delayms(10);
    }
    }