首先感谢 江科大 的stm32入门课程 受益匪浅。推荐有兴趣的朋友去看看。
先看看我用的矩阵键盘是啥样的(很常见的一种)

接线如图(其他型号根据自己需求接上GPIO口)
代码基于stm大善人的代码修改而来,讲的很详细,非常感谢。
直接上代码:
头文件Key4x4.h
#ifndef __KEY4x4_H
#define __KEY4x4_H
void KEY_4x4_Init(void);
void KEY_Scan(void);
u16 Key_Read(void);
#endif
主体文件Key4x4.c
#include "stm32f10x.h"
#include "Delay.h"
u8 anxia = 0;
u8 key = 1;
u16 line[4] = {0x00fe , 0x00fd , 0x00fb ,0x00f7};
u16 off = 0x00ff; // 全部引脚置为1
u16 keys[16] = {
49, 50, 51, 65,
52, 53, 54, 66,
55, 56, 57, 67,
42, 48, 35, 68,
};
void KEY_4x4_Init(void){
GPIO_InitTypeDef GPIO_InitStructre;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); // 使能 GPIOA 的时钟
// 第一组
GPIO_InitStructre.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructre.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA , &GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
// 第二组数据
GPIO_InitStructre.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA , &GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
}
void Do_Click(uint16_t gpio_pin_x , u8 num){
anxia = 1;
key = num;
while(!GPIO_ReadInputDataBit(GPIOA , gpio_pin_x));
}
void KEY_Click_Listener(u8 num){
if((GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)==0)){
Delay_ms(10);
if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)){
Do_Click(GPIO_Pin_4 , num+0);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)){
Do_Click(GPIO_Pin_5 , num+1);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)){
Do_Click(GPIO_Pin_6 , num+2);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)){
Do_Click(GPIO_Pin_7 , num+3);
}else {
anxia = 0;
GPIO_Write(GPIOA , off);
}
}
}
void KEY_Scan(){
// 第一行 1111 1110
GPIO_Write(GPIOA , line[0]);
KEY_Click_Listener(1);
// 第二行
GPIO_Write(GPIOA , line[1]);
KEY_Click_Listener(5);
// 第三行
GPIO_Write(GPIOA , line[2]);
KEY_Click_Listener(9);
// 第四行
GPIO_Write(GPIOA , line[3]);
KEY_Click_Listener(13);
}
u16 Key_Read(){
return keys[key-1];
}
主要代码说明:
初始化配置 (KEY_4x4_Init):
- 使能GPIOA模块的时钟。
- 配置GPIOA的前四个引脚(GPIO_Pin_0至GPIO_Pin_3)为推挽输出模式,用于键盘行线的扫描。
- 设置GPIOA的后四个引脚(GPIO_Pin_4至GPIO_Pin_7)为上拉输入模式,用于检测键盘列线的状态。
// 第一组
GPIO_InitStructre.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructre.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA , &GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
// 第二组数据
GPIO_InitStructre.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA , &GPIO_InitStructre);
GPIO_SetBits(GPIOA , GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
按键检测 (KEY_Click_Listener):
- 检测列线状态,如果有键按下,则调用
Do_Click函数记录按键信息并等待按键释放。 - 使用延时函数
Delay_ms来消除抖动。
void KEY_Click_Listener(u8 num){
if((GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)==0)
||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)==0)){
Delay_ms(10);
if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_4)){
Do_Click(GPIO_Pin_4 , num+0);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_5)){
Do_Click(GPIO_Pin_5 , num+1);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_6)){
Do_Click(GPIO_Pin_6 , num+2);
}else if(0 == GPIO_ReadInputDataBit(GPIOA , GPIO_Pin_7)){
Do_Click(GPIO_Pin_7 , num+3);
}else {
anxia = 0;
GPIO_Write(GPIOA , off);
}
}
}
void Do_Click(uint16_t gpio_pin_x , u8 num){
anxia = 1;
key = num;
while(!GPIO_ReadInputDataBit(GPIOA , gpio_pin_x));
}
扫描过程 (KEY_Scan):
- 循环扫描每一行,通过改变行线的状态来检测是否有键按下。
- 调用
KEY_Click_Listener函数来处理每一行的按键检测。
读取按键值 (Key_Read):
- 返回当前按下的键对应的数值。
补充说明:
u16 line[4] = {0x00fe , 0x00fd , 0x00fb ,0x00f7};
u16 off = 0x00ff; // 全部引脚置为1
u16 keys[16] = {
49, 50, 51, 65,
52, 53, 54, 66,
55, 56, 57, 67,
42, 48, 35, 68,
};
line 定义 了4个16进制的数值分别转成二进制
0000 0000 1111 1110 0x00fe
0000 0000 1111 1101 0x00fd
0000 0000 1111 1011 0x00fb
0000 0000 1111 0111 0x00f7
低4位为行 高四位为列
设置0就是给对应行设置低电平
这样我们在scan的代码就能看出来 扫描的做法就是先给传入的行linex 设置低电平
所有列都是高电平当扫描到某一列为低电平时就说明这一列被点击了。

循环从第一列到第四列设置低电平直到检测到某一列也变成低电平
假设1被点击 则1这一列也是低电平
就变成第一行 第一列低电平

这样就能确定1被点击。给对应参数赋值1即可
再根据定义的keys (askII 码表对应数值 )
主要代码就是上面这些,其他代码只要复制 江科大OLED的课件源码即可
使用方式 man.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key4x4.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
KEY_4x4_Init();
/*OLED显示*/
OLED_ShowString(1, 1, "in put:"); //1行1列显示字符A
u8 num = 0;
while (1)
{
KEY_Scan();
num = Key_Read();
OLED_ShowChar(1 ,8 ,num);
}
}