基于 STM32 的 4×4 矩阵键盘源码

一、硬件连接与原理

1.1 硬件连接图

复制代码
STM32F103         4×4 矩阵键盘
──────────────────────────────
PA0  ────────────  行0 (ROW0)
PA1  ────────────  行1 (ROW1)
PA2  ────────────  行2 (ROW2)
PA3  ────────────  行3 (ROW3)
PA4  ────────────  列0 (COL0)
PA5  ────────────  列1 (COL1)
PA6  ────────────  列2 (COL2)
PA7  ────────────  列3 (COL3)

键盘布局:
        COL0  COL1  COL2  COL3
ROW0     1     2     3     A
ROW1     4     5     6     B
ROW2     7     8     9     C
ROW3     *     0     #     D

1.2 扫描原理

复制代码
1. 行扫描法:逐行输出低电平,读取列状态
2. 列扫描法:逐列输出低电平,读取行状态
3. 中断法:任意键按下产生外部中断

二、完整源码实现

2.1 头文件 (matrix_keyboard.h)

c 复制代码
#ifndef __MATRIX_KEYBOARD_H
#define __MATRIX_KEYBOARD_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>

// 键盘引脚定义
#define KEYBOARD_ROW_PORT    GPIOA
#define KEYBOARD_COL_PORT    GPIOA

#define ROW0_PIN    GPIO_Pin_0
#define ROW1_PIN    GPIO_Pin_1
#define ROW2_PIN    GPIO_Pin_2
#define ROW3_PIN    GPIO_Pin_3

#define COL0_PIN    GPIO_Pin_4
#define COL1_PIN    GPIO_Pin_5
#define COL2_PIN    GPIO_Pin_6
#define COL3_PIN    GPIO_Pin_7

// 键值定义
typedef enum {
    KEY_NONE = 0x00,
    
    // 数字键
    KEY_0 = '0',
    KEY_1 = '1',
    KEY_2 = '2',
    KEY_3 = '3',
    KEY_4 = '4',
    KEY_5 = '5',
    KEY_6 = '6',
    KEY_7 = '7',
    KEY_8 = '8',
    KEY_9 = '9',
    
    // 功能键
    KEY_A = 'A',
    KEY_B = 'B',
    KEY_C = 'C',
    KEY_D = 'D',
    KEY_STAR = '*',
    KEY_HASH = '#',
    
    // 特殊键
    KEY_ENTER = 0x0D,
    KEY_ESC = 0x1B,
    KEY_BACKSPACE = 0x08
} KeyValue_t;

// 键盘状态
typedef enum {
    KEY_RELEASED = 0,
    KEY_PRESSED,
    KEY_LONG_PRESSED,
    KEY_REPEAT
} KeyState_t;

// 键盘配置
typedef struct {
    uint16_t debounce_time;      // 消抖时间 (ms)
    uint16_t long_press_time;    // 长按时间 (ms)
    uint16_t repeat_delay;       // 重复延迟 (ms)
    uint16_t repeat_rate;         // 重复速率 (ms)
    bool interrupt_enable;        // 中断使能
} KeyboardConfig_t;

// 键盘事件
typedef struct {
    KeyValue_t key_value;
    KeyState_t key_state;
    uint32_t press_time;
    uint8_t repeat_count;
} KeyboardEvent_t;

// 函数声明
void MatrixKeyboard_Init(void);
void MatrixKeyboard_Config(KeyboardConfig_t *config);
KeyValue_t MatrixKeyboard_Scan(void);
bool MatrixKeyboard_GetKey(KeyboardEvent_t *event);
void MatrixKeyboard_IRQHandler(void);
void MatrixKeyboard_Process(void);

// 实用函数
const char* MatrixKeyboard_GetKeyName(KeyValue_t key);
bool MatrixKeyboard_IsDigit(KeyValue_t key);
uint8_t MatrixKeyboard_GetDigit(KeyValue_t key);

#endif /* __MATRIX_KEYBOARD_H */

2.2 核心驱动 (matrix_keyboard.c)

c 复制代码
#include "matrix_keyboard.h"
#include "delay.h"

// 静态变量
static KeyboardConfig_t keyboard_config = {
    .debounce_time = 20,      // 20ms消抖
    .long_press_time = 1000,  // 1秒长按
    .repeat_delay = 500,      // 500ms重复延迟
    .repeat_rate = 100,       // 100ms重复速率
    .interrupt_enable = false
};

static uint8_t current_row = 0;
static uint8_t key_buffer[16];
static uint8_t buffer_head = 0;
static uint8_t buffer_tail = 0;

// 键值映射表
static const KeyValue_t key_map[4][4] = {
    {KEY_1, KEY_2, KEY_3, KEY_A},
    {KEY_4, KEY_5, KEY_6, KEY_B},
    {KEY_7, KEY_8, KEY_9, KEY_C},
    {KEY_STAR, KEY_0, KEY_HASH, KEY_D}
};

// GPIO初始化
static void Keyboard_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置行引脚为推挽输出
    GPIO_InitStructure.GPIO_Pin = ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(KEYBOARD_ROW_PORT, &GPIO_InitStructure);
    
    // 配置列引脚为上拉输入
    GPIO_InitStructure.GPIO_Pin = COL0_PIN | COL1_PIN | COL2_PIN | COL3_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
    GPIO_Init(KEYBOARD_COL_PORT, &GPIO_InitStructure);
    
    // 初始状态:所有行输出高电平
    GPIO_SetBits(KEYBOARD_ROW_PORT, ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN);
}

// 设置指定行为低电平
static void SetRowLow(uint8_t row) {
    switch(row) {
        case 0: GPIO_ResetBits(KEYBOARD_ROW_PORT, ROW0_PIN); break;
        case 1: GPIO_ResetBits(KEYBOARD_ROW_PORT, ROW1_PIN); break;
        case 2: GPIO_ResetBits(KEYBOARD_ROW_PORT, ROW2_PIN); break;
        case 3: GPIO_ResetBits(KEYBOARD_ROW_PORT, ROW3_PIN); break;
    }
}

// 设置所有行为高电平
static void SetAllRowsHigh(void) {
    GPIO_SetBits(KEYBOARD_ROW_PORT, ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN);
}

// 读取列状态
static uint8_t ReadColumns(void) {
    uint8_t col_state = 0;
    
    if (GPIO_ReadInputDataBit(KEYBOARD_COL_PORT, COL0_PIN) == 0) col_state |= 0x01;
    if (GPIO_ReadInputDataBit(KEYBOARD_COL_PORT, COL1_PIN) == 0) col_state |= 0x02;
    if (GPIO_ReadInputDataBit(KEYBOARD_COL_PORT, COL2_PIN) == 0) col_state |= 0x04;
    if (GPIO_ReadInputDataBit(KEYBOARD_COL_PORT, COL3_PIN) == 0) col_state |= 0x08;
    
    return col_state;
}

// 扫描单个按键
static KeyValue_t ScanSingleKey(uint8_t row, uint8_t col) {
    KeyValue_t key = KEY_NONE;
    
    // 设置指定行为低电平
    SetRowLow(row);
    Delay_us(10);  // 等待电平稳定
    
    // 读取列状态
    uint8_t col_state = ReadColumns();
    
    // 检查是否有按键按下
    if (col_state & (1 << col)) {
        key = key_map[row][col];
    }
    
    // 恢复行电平
    SetAllRowsHigh();
    Delay_us(10);
    
    return key;
}

// 键盘扫描主函数
KeyValue_t MatrixKeyboard_Scan(void) {
    KeyValue_t key = KEY_NONE;
    uint8_t col_state;
    
    // 逐行扫描
    for (uint8_t row = 0; row < 4; row++) {
        SetRowLow(row);
        Delay_us(10);
        
        col_state = ReadColumns();
        
        if (col_state != 0) {
            // 检测哪一列有按键按下
            for (uint8_t col = 0; col < 4; col++) {
                if (col_state & (1 << col)) {
                    key = key_map[row][col];
                    
                    // 等待按键释放(可选,根据需求)
                    while (ReadColumns() != 0) {
                        Delay_ms(10);
                    }
                    
                    SetAllRowsHigh();
                    return key;
                }
            }
        }
        
        SetAllRowsHigh();
        Delay_us(10);
    }
    
    return key;
}

// 带消抖的键盘扫描
KeyValue_t MatrixKeyboard_ScanDebounced(void) {
    static KeyValue_t last_key = KEY_NONE;
    static uint32_t last_scan_time = 0;
    KeyValue_t current_key;
    
    // 检查消抖时间
    if (HAL_GetTick() - last_scan_time < keyboard_config.debounce_time) {
        return KEY_NONE;
    }
    
    current_key = MatrixKeyboard_Scan();
    
    if (current_key != last_key) {
        last_scan_time = HAL_GetTick();
        last_key = current_key;
        return current_key;
    }
    
    return KEY_NONE;
}

// 初始化键盘
void MatrixKeyboard_Init(void) {
    Keyboard_GPIO_Init();
    current_row = 0;
    buffer_head = buffer_tail = 0;
    
    printf("Matrix Keyboard Initialized\r\n");
}

// 配置键盘参数
void MatrixKeyboard_Config(KeyboardConfig_t *config) {
    if (config != NULL) {
        keyboard_config = *config;
    }
}

// 获取键盘事件(带状态检测)
bool MatrixKeyboard_GetKey(KeyboardEvent_t *event) {
    static KeyValue_t last_key = KEY_NONE;
    static KeyState_t last_state = KEY_RELEASED;
    static uint32_t press_start_time = 0;
    static uint32_t last_repeat_time = 0;
    static uint8_t repeat_count = 0;
    
    KeyValue_t current_key = MatrixKeyboard_ScanDebounced();
    
    if (event == NULL) return false;
    
    event->key_value = current_key;
    event->key_state = KEY_RELEASED;
    event->press_time = 0;
    event->repeat_count = 0;
    
    if (current_key != KEY_NONE) {
        if (last_key == KEY_NONE) {
            // 新按键按下
            last_state = KEY_PRESSED;
            press_start_time = HAL_GetTick();
            repeat_count = 0;
            event->key_state = KEY_PRESSED;
            event->press_time = press_start_time;
        } else if (current_key == last_key) {
            // 持续按下
            uint32_t press_duration = HAL_GetTick() - press_start_time;
            
            if (press_duration >= keyboard_config.long_press_time) {
                last_state = KEY_LONG_PRESSED;
                event->key_state = KEY_LONG_PRESSED;
                event->press_time = press_duration;
            }
            
            // 重复按键检测
            if (press_duration >= keyboard_config.long_press_time + keyboard_config.repeat_delay) {
                if (HAL_GetTick() - last_repeat_time >= keyboard_config.repeat_rate) {
                    last_state = KEY_REPEAT;
                    event->key_state = KEY_REPEAT;
                    event->repeat_count = ++repeat_count;
                    last_repeat_time = HAL_GetTick();
                }
            }
        }
    } else {
        last_state = KEY_RELEASED;
        last_key = KEY_NONE;
    }
    
    last_key = current_key;
    return (event->key_state != KEY_RELEASED);
}

// 键盘中断处理函数
void MatrixKeyboard_IRQHandler(void) {
    KeyValue_t key = MatrixKeyboard_Scan();
    
    if (key != KEY_NONE) {
        // 将按键存入缓冲区
        uint8_t next_head = (buffer_head + 1) % 16;
        if (next_head != buffer_tail) {
            key_buffer[buffer_head] = (uint8_t)key;
            buffer_head = next_head;
        }
    }
}

// 处理键盘事件
void MatrixKeyboard_Process(void) {
    KeyboardEvent_t event;
    
    if (MatrixKeyboard_GetKey(&event)) {
        switch(event.key_state) {
            case KEY_PRESSED:
                printf("Key Pressed: %c\r\n", event.key_value);
                break;
                
            case KEY_LONG_PRESSED:
                printf("Key Long Pressed: %c (Duration: %lu ms)\r\n", 
                       event.key_value, event.press_time);
                break;
                
            case KEY_REPEAT:
                printf("Key Repeat: %c (Count: %d)\r\n", 
                       event.key_value, event.repeat_count);
                break;
                
            default:
                break;
        }
    }
}

// 实用函数实现
const char* MatrixKeyboard_GetKeyName(KeyValue_t key) {
    switch(key) {
        case KEY_0: return "0";
        case KEY_1: return "1";
        case KEY_2: return "2";
        case KEY_3: return "3";
        case KEY_4: return "4";
        case KEY_5: return "5";
        case KEY_6: return "6";
        case KEY_7: return "7";
        case KEY_8: return "8";
        case KEY_9: return "9";
        case KEY_A: return "A";
        case KEY_B: return "B";
        case KEY_C: return "C";
        case KEY_D: return "D";
        case KEY_STAR: return "*";
        case KEY_HASH: return "#";
        default: return "None";
    }
}

bool MatrixKeyboard_IsDigit(KeyValue_t key) {
    return (key >= KEY_0 && key <= KEY_9);
}

uint8_t MatrixKeyboard_GetDigit(KeyValue_t key) {
    if (MatrixKeyboard_IsDigit(key)) {
        return (uint8_t)(key - KEY_0);
    }
    return 0xFF;  // 无效值
}

2.3 主程序示例 (main.c)

c 复制代码
#include "stm32f10x.h"
#include "matrix_keyboard.h"
#include "usart.h"
#include "delay.h"
#include "led.h"

// 密码验证示例
typedef struct {
    uint8_t password[4];
    uint8_t input_index;
    bool password_correct;
} PasswordSystem_t;

PasswordSystem_t pwd_system = {
    .password = {1, 2, 3, 4},  // 预设密码:1234
    .input_index = 0,
    .password_correct = false
};

// 键盘回调函数
void Keyboard_Callback(KeyboardEvent_t *event) {
    if (event->key_state == KEY_PRESSED) {
        LED_Toggle(LED0);  // 按键提示
        
        if (MatrixKeyboard_IsDigit(event->key_value)) {
            // 数字输入
            if (pwd_system.input_index < 4) {
                uint8_t digit = MatrixKeyboard_GetDigit(event->key_value);
                printf("Input digit: %d\r\n", digit);
                
                // 验证密码
                if (digit == pwd_system.password[pwd_system.input_index]) {
                    pwd_system.input_index++;
                    
                    if (pwd_system.input_index >= 4) {
                        printf("Password Correct! Access Granted.\r\n");
                        LED_On(LED1);  // 绿灯表示正确
                        pwd_system.password_correct = true;
                        pwd_system.input_index = 0;
                    }
                } else {
                    printf("Password Wrong! Try again.\r\n");
                    LED_On(LED2);  // 红灯表示错误
                    pwd_system.input_index = 0;
                    Delay_ms(1000);
                    LED_Off(LED2);
                }
            }
        } else if (event->key_value == KEY_HASH) {
            // #键确认
            printf("Confirm pressed\r\n");
            pwd_system.input_index = 0;
        } else if (event->key_value == KEY_STAR) {
            // *键清除
            printf("Clear pressed\r\n");
            pwd_system.input_index = 0;
        }
    }
}

int main(void) {
    // 系统初始化
    System_Init();
    Delay_Init();
    USART_Init(115200);
    LED_Init();
    
    // 初始化矩阵键盘
    MatrixKeyboard_Init();
    
    // 配置键盘参数
    KeyboardConfig_t config = {
        .debounce_time = 30,      // 30ms消抖
        .long_press_time = 2000,  // 2秒长按
        .repeat_delay = 800,      // 800ms重复延迟
        .repeat_rate = 150,       // 150ms重复速率
        .interrupt_enable = false
    };
    MatrixKeyboard_Config(&config);
    
    printf("4x4 Matrix Keyboard Test Started\r\n");
    printf("Password: 1234 (# to confirm, * to clear)\r\n");
    
    while(1) {
        // 方法1:简单扫描
        KeyValue_t key = MatrixKeyboard_ScanDebounced();
        if (key != KEY_NONE) {
            printf("Key pressed: %s\r\n", MatrixKeyboard_GetKeyName(key));
        }
        
        // 方法2:事件驱动(推荐)
        KeyboardEvent_t event;
        if (MatrixKeyboard_GetKey(&event)) {
            Keyboard_Callback(&event);
        }
        
        // 方法3:处理键盘事件
        MatrixKeyboard_Process();
        
        Delay_ms(50);  // 主循环延时
    }
}

2.4 中断方式实现 (keyboard_interrupt.c)

c 复制代码
#include "matrix_keyboard.h"
#include "stm32f10x_exti.h"

// 外部中断初始化
void Keyboard_EXTI_Init(void) {
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置列引脚为外部中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7);
    
    // 配置EXTI线
    EXTI_InitStructure.EXTI_Line = EXTI_Line4 | EXTI_Line5 | EXTI_Line6 | EXTI_Line7;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    // 配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 为其他EXTI线配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    NVIC_Init(&NVIC_InitStructure);
}

// 外部中断服务函数
void EXTI4_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line4) != RESET) {
        MatrixKeyboard_IRQHandler();
        EXTI_ClearITPendingBit(EXTI_Line4);
    }
}

void EXTI9_5_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line5) != RESET) {
        MatrixKeyboard_IRQHandler();
        EXTI_ClearITPendingBit(EXTI_Line5);
    }
    if (EXTI_GetITStatus(EXTI_Line6) != RESET) {
        MatrixKeyboard_IRQHandler();
        EXTI_ClearITPendingBit(EXTI_Line6);
    }
    if (EXTI_GetITStatus(EXTI_Line7) != RESET) {
        MatrixKeyboard_IRQHandler();
        EXTI_ClearITPendingBit(EXTI_Line7);
    }
}

三、工程配置

3.1 Keil工程配置

复制代码
Project Structure:
├── User/
│   ├── main.c
│   ├── matrix_keyboard.c
│   └── keyboard_interrupt.c
├── Hardware/
│   ├── led.c
│   ├── usart.c
│   └── delay.c
├── System/
│   ├── sys.c
│   └── delay.c
└── Libraries/
    └── STM32F10x_StdPeriph_Driver/

3.2 编译选项

makefile 复制代码
# 宏定义
USE_STDPERIPH_DRIVER
STM32F10X_MD

# 包含路径
-I../User
-I../Hardware
-I../System
-I../Libraries/CMSIS/CM3/CoreSupport
-I../Libraries/STM32F10x_StdPeriph_Driver/inc

参考代码 基于STM32的4X4矩阵键盘源码 www.youwenfan.com/contentcsu/60559.html

四、使用示例

4.1 基本使用

c 复制代码
// 1. 初始化
MatrixKeyboard_Init();

// 2. 在主循环中扫描
while(1) {
    KeyValue_t key = MatrixKeyboard_Scan();
    if(key != KEY_NONE) {
        // 处理按键
        switch(key) {
            case KEY_1: // 处理数字1
                break;
            case KEY_A: // 处理功能键A
                break;
        }
    }
    Delay_ms(100);
}

4.2 高级功能

c 复制代码
// 密码锁系统
typedef struct {
    char input_buffer[16];
    uint8_t input_len;
    bool locked;
} LockSystem_t;

LockSystem_t lock_sys = {.locked = true, .input_len = 0};

void ProcessLockSystem(KeyValue_t key) {
    if(lock_sys.locked) {
        if(MatrixKeyboard_IsDigit(key)) {
            if(lock_sys.input_len < 16) {
                lock_sys.input_buffer[lock_sys.input_len++] = key;
            }
        } else if(key == KEY_HASH) {  // #确认
            if(ValidatePassword(lock_sys.input_buffer, lock_sys.input_len)) {
                lock_sys.locked = false;
                printf("Unlocked!\r\n");
            } else {
                printf("Wrong password!\r\n");
                lock_sys.input_len = 0;
            }
        } else if(key == KEY_STAR) {  // *清除
            lock_sys.input_len = 0;
        }
    }
}

五、常见问题解决

问题 原因 解决方案
按键无反应 GPIO配置错误 检查引脚定义和模式
按键抖动 消抖时间不够 增加消抖延时到30-50ms
多键同时触发 扫描速度太快 降低扫描频率
中断不响应 中断未使能 检查EXTI和NVIC配置
键值错误 映射表不对 核对key_map数组
相关推荐
国产芯片设计1 小时前
【LCD驱动实战】单颗YL1621脚位不足?双芯片联动驱动方案详解
stm32·单片机·mcu·51单片机·硬件工程
笨笨小乌龟114 小时前
单片机的半主机模式与 MicroLib 机制(Keil UseMicroLIB)
stm32·单片机·嵌入式硬件
旧梦吟8 小时前
5.9 电工考试-易错题
stm32·嵌入式硬件
foundbug9998 小时前
STM32 + SHT20 温湿度测试 TFT 显示方案
stm32·单片机·嵌入式硬件
星夜夏空999 小时前
STM32单片机学习(3)——前置知识学习
stm32·单片机·学习
渣渣灰958710 小时前
基于STM32F03ZET6移植FreeRTOS
数据库·stm32·嵌入式硬件
星夜夏空9910 小时前
STM32单片机学习(5) —— STM32的一些名词解释
stm32·单片机·学习
yuan1999710 小时前
STM32 速度控制器:PWM + PID 无级调速实现
stm32·单片机·嵌入式硬件