当前位置:   article > 正文

【STM32F4系列】【自制库】6×6矩阵键盘(软件部分,扫描法和行反转法)_stm32矩阵按键反转法

stm32矩阵按键反转法

请先看硬件和扫描方法的内容,本文章基于此,传送门https://blog.csdn.net/m0_57585228/article/details/125228656

需求

可以分别识别每个按键的按下与松开

扫描法

思路

  1. 初始化
  2. 定时器定时扫描一次
  3. 读取扫描到的按键
  4. 如果这次读取的和上次不同,则标志置位
  5. 将数据暂存起来,利用状态机的思想,当出现按下信号时进入按下状态,当回到松开信号时回到原状态

初始化

需要初始化的外设有,GPIO,定时器中断

GPIO

有2组,输入组设置为上拉输入,输出组设置为开漏上拉输出,详情之前介绍过
GPIOhttps://blog.csdn.net/m0_57585228/article/details/124498831

头文件(KeyBoard.h)

  1. #define Key_L0 GPIO_Pin_0
  2. #define Key_L1 GPIO_Pin_1
  3. #define Key_L2 GPIO_Pin_2
  4. #define Key_L3 GPIO_Pin_3
  5. #define Key_L4 GPIO_Pin_4
  6. #define Key_L5 GPIO_Pin_5
  7. #define Key_L_GPIOx GPIOA
  8. #define Key_L_GPIO_RCC RCC_AHB1Periph_GPIOA
  9. #define Key_R0 GPIO_Pin_0
  10. #define Key_R1 GPIO_Pin_1
  11. #define Key_R2 GPIO_Pin_10
  12. #define Key_R3 GPIO_Pin_3
  13. #define Key_R4 GPIO_Pin_4
  14. #define Key_R5 GPIO_Pin_5
  15. #define Key_R_GPIOx GPIOB
  16. #define Key_R_GPIO_RCC RCC_AHB1Periph_GPIOB

C文件(KeyBoard.c)

  1. void Key_Scan_GPIO_init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStruct; //声明GPIO初始化结构体
  4. RCC_AHB1PeriphClockCmd(Key_L_GPIO_RCC, ENABLE); //打开GPIO时钟
  5. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //设置为输出模式
  6. GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //设置为开漏输出
  7. GPIO_InitStruct.GPIO_Pin = Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5; //设置引脚
  8. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //设置为上拉
  9. GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed; //设置为快速模式
  10. GPIO_Init(Key_L_GPIOx, &GPIO_InitStruct);
  11. Set();
  12. RCC_AHB1PeriphClockCmd(Key_R_GPIO_RCC, ENABLE); //打开GPIO时钟
  13. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //设置为输入模式
  14. GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //
  15. GPIO_InitStruct.GPIO_Pin = Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5; //设置引脚
  16. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //设置为上拉
  17. GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed; //设置为快速模式
  18. GPIO_Init(Key_R_GPIOx, &GPIO_InitStruct); //初始化
  19. }
  20. void Set(void)
  21. {
  22. GPIO_SetBits(Key_L_GPIOx, Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5);
  23. }

定时器

需要开启定时器中断,中断时间设置为几到几十毫秒即可,因为在定时器中使用了1us延迟,我们给定时器中断的个较低的优先级

定时器https://blog.csdn.net/m0_57585228/article/details/124520929

C文件(KeyBoard.c)

  1. void Key_Scan_Tim_init(void)
  2. {
  3. TIM_TimeBaseInitTypeDef TIM_Init_Struct; //声明定时器初始化结构体
  4. NVIC_InitTypeDef NVIC_Init_Struct; //声明NVIC初始化结构体
  5. RCC_APB1PeriphClockCmd(Key_Tim_RCC, ENABLE); //打开时钟
  6. TIM_Init_Struct.TIM_ClockDivision = TIM_CKD_DIV1; //滤波器不分频
  7. TIM_Init_Struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
  8. //每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
  9. //这里是10ms
  10. TIM_Init_Struct.TIM_Period = 840 - 1;
  11. TIM_Init_Struct.TIM_Prescaler = 1000 - 1;
  12. TIM_Init_Struct.TIM_RepetitionCounter = 0; //高级定时器特有,这里写0就行
  13. TIM_TimeBaseInit(Key_Timx, &TIM_Init_Struct); //调用函数初始
  14. TIM_ITConfig(Key_Timx, TIM_IT_Update, ENABLE); //启用溢出中断
  15. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2
  16. NVIC_Init_Struct.NVIC_IRQChannel = Tim_IRQn; //中断名称
  17. NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE; //使能
  18. NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 2; //主优先级1
  19. NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 3; //副优先级1
  20. NVIC_Init(&NVIC_Init_Struct); //初始化NVIC
  21. TIM_Cmd(Key_Timx, ENABLE); //打开定时器
  22. }

读取和置位函数

C文件(KeyBoard.c)

  1. u8 Key_Scan_Read_IN(void)
  2. {
  3. u8 zj = 0;
  4. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R0))
  5. zj |= 1 << 0;
  6. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R1))
  7. zj |= 1 << 1;
  8. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R2))
  9. zj |= 1 << 2;
  10. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R3))
  11. zj |= 1 << 3;
  12. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R4))
  13. zj |= 1 << 4;
  14. if (GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R5))
  15. zj |= 1 << 5;
  16. return (~zj) & 0x3f;
  17. }
  18. void Key_Scan_ReSet(u8 i)
  19. {
  20. if (i == 0)
  21. GPIO_ResetBits(Key_L_GPIOx, Key_L0);
  22. else if (i == 1)
  23. GPIO_ResetBits(Key_L_GPIOx, Key_L1);
  24. else if (i == 2)
  25. GPIO_ResetBits(Key_L_GPIOx, Key_L2);
  26. else if (i == 3)
  27. GPIO_ResetBits(Key_L_GPIOx, Key_L3);
  28. else if (i == 4)
  29. GPIO_ResetBits(Key_L_GPIOx, Key_L4);
  30. else if (i == 5)
  31. GPIO_ResetBits(Key_L_GPIOx, Key_L5);
  32. }

定时器扫描

每一次触发中断扫描全部输出线,需要间隔一段时间避免频率过高导致误触

然后将其暂存起来、

C文件(KeyBoard.c)

  1. u8 Key_Scan_Dat[6] = {0};
  2. void Key_Scan_Code(void)
  3. {
  4. static u8 Dat1[6] = {0};
  5. static u8 Dat2[6] = {0};
  6. static u8 ins = 0;
  7. if (ins == 0)
  8. {
  9. for (int i = 0; i < 6; i++)
  10. {
  11. Set();
  12. Key_Scan_ReSet(i);
  13. Delay_us(1);
  14. Dat1[i] = Key_Scan_Read_IN();
  15. ins = 1;
  16. }
  17. Set();
  18. }
  19. else if (ins == 1)
  20. {
  21. for (int i = 0; i < 6; i++)
  22. {
  23. Set();
  24. Key_Scan_ReSet(i);
  25. Delay_us(1);
  26. Dat2[i] = Key_Scan_Read_IN();
  27. }
  28. Set();
  29. for (int i = 0; i < 6; i++)
  30. {
  31. if ((Dat2[i] == Dat1[i]))
  32. {
  33. if (Key_Scan_Dat[i] != Dat1[i])
  34. Key_OK = 1;
  35. Key_Scan_Dat[i] = Dat1[i];
  36. }
  37. }
  38. ins = 0;
  39. }
  40. }

动作函数

为了增加通用性,这个函数可以在主函数调用,也可以在中断调用

使用了函数指针,使用时将两个参数传入即可,就j和i分别是行和列的序号

C文件(KeyBoard.c)

  1. void Key_Scan_Action(void (*Down)(u8 R, u8 L), void (*Up)(u8 R, u8 L))
  2. {
  3. if (Key_OK)
  4. {
  5. for (int i = 0; i < 6; i++)
  6. {
  7. for (int j = 0; j < 6; j++)
  8. {
  9. if (Key_Scan_Dat[i] & (1 << j))
  10. {
  11. Key_Key[j][i] = 1;
  12. Key_OK = 0;
  13. Down(j, i);
  14. }
  15. if ((Key_Key[j][i] == 1) && ((Key_Scan_Dat[i] & (1 << j)) == 0))
  16. {
  17. Key_Key[j][i] = 0;
  18. Up(j, i);
  19. }
  20. }
  21. }
  22. }
  23. }

行反转法

GPIO

输入和输出均设置为开漏上拉输出

C文件(KeyBoard.c)

  1. void Key_RLreverse_GPIO_init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStruct; //声明GPIO初始化结构体
  4. RCC_AHB1PeriphClockCmd(Key_R_GPIO_RCC, ENABLE); //打开GPIO时钟
  5. RCC_AHB1PeriphClockCmd(Key_L_GPIO_RCC, ENABLE); //打开GPIO时钟
  6. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //设置为输出模式
  7. GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //设置为开漏输出
  8. GPIO_InitStruct.GPIO_Pin = Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5; //设置引脚
  9. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //设置为上拉
  10. GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed; //设置为快速模式
  11. GPIO_Init(Key_R_GPIOx, &GPIO_InitStruct);
  12. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //设置为输出模式
  13. GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; //设置为开漏输出
  14. GPIO_InitStruct.GPIO_Pin = Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5; //设置引脚
  15. GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //设置为上拉
  16. GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed; //设置为快速模式
  17. GPIO_Init(Key_L_GPIOx, &GPIO_InitStruct); //初始化
  18. }

定时器

定时器设置和扫描法基本相同

C文件(KeyBoard.c)

  1. void Key_RLreverse_Tim_init(void)
  2. {
  3. TIM_TimeBaseInitTypeDef TIM_Init_Struct; //声明定时器初始化结构体
  4. NVIC_InitTypeDef NVIC_Init_Struct; //声明NVIC初始化结构体
  5. RCC_APB1PeriphClockCmd(Key_Tim_RCC, ENABLE); //打开时钟
  6. TIM_Init_Struct.TIM_ClockDivision = TIM_CKD_DIV1; //滤波器不分频
  7. TIM_Init_Struct.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
  8. //每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
  9. //这里是5ms
  10. TIM_Init_Struct.TIM_Period = 840 - 1;
  11. TIM_Init_Struct.TIM_Prescaler = 1000 - 1;
  12. TIM_Init_Struct.TIM_RepetitionCounter = 0; //高级定时器特有,这里写0就行
  13. TIM_TimeBaseInit(Key_Timx, &TIM_Init_Struct); //调用函数初始
  14. TIM_ITConfig(Key_Timx, TIM_IT_Update, ENABLE); //启用溢出中断
  15. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2
  16. NVIC_Init_Struct.NVIC_IRQChannel = Tim_IRQn; //中断名称
  17. NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE; //使能
  18. NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 3; //主优先级1
  19. NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 3; //副优先级1
  20. NVIC_Init(&NVIC_Init_Struct); //初始化NVIC
  21. TIM_Cmd(Key_Timx, ENABLE); //打开定时器
  22. }

读取和置位函数

C文件(KeyBoard.c)

  1. void Key_RLreverse_Set(u8 R_L, u8 bool)
  2. {
  3. if (R_L == 1)
  4. {
  5. if (bool == 1)
  6. GPIO_SetBits(Key_L_GPIOx, (Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5));
  7. else if (bool == 0)
  8. GPIO_ResetBits(Key_L_GPIOx, (Key_L0 | Key_L1 | Key_L2 | Key_L3 | Key_L4 | Key_L5));
  9. }
  10. else if (R_L == 0)
  11. {
  12. if (bool == 1)
  13. GPIO_SetBits(Key_R_GPIOx, (Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5));
  14. else if (bool == 0)
  15. GPIO_ResetBits(Key_R_GPIOx, (Key_R0 | Key_R1 | Key_R2 | Key_R3 | Key_R4 | Key_R5));
  16. }
  17. }
  18. u8 Key_RLreverse_Read(u8 R_L)
  19. {
  20. u8 zj = 0;
  21. if (R_L == 0)
  22. {
  23. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R5);
  24. zj <<= 1;
  25. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R4);
  26. zj <<= 1;
  27. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R3);
  28. zj <<= 1;
  29. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R2);
  30. zj <<= 1;
  31. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R1);
  32. zj <<= 1;
  33. zj |= GPIO_ReadInputDataBit(Key_R_GPIOx, Key_R0);
  34. }
  35. else if (R_L == 1)
  36. {
  37. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L5);
  38. zj <<= 1;
  39. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L4);
  40. zj <<= 1;
  41. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L3);
  42. zj <<= 1;
  43. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L2);
  44. zj <<= 1;
  45. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L1);
  46. zj <<= 1;
  47. zj |= GPIO_ReadInputDataBit(Key_L_GPIOx, Key_L0);
  48. }
  49. return zj;
  50. }

行反转函数

这个函数是用来扫描的,需要在定时器中调用

也可以稍微更改一下放入外部中断中(可能需要额外硬件或者占用多个中断源,不推荐)

C文件(KeyBoard.c)

  1. void Key_RLreverse_Code()
  2. {
  3. static u8 zj_R;
  4. static u8 zj_L;
  5. u16 zj = 0;
  6. static u8 Key_Code_ins;
  7. static u16 Bef_Dat;
  8. if (Key_Code_ins == 1) //去抖
  9. {
  10. zj_R = Key_RLreverse_Read(Key_R);
  11. Key_RLreverse_Set(Key_R, 0); //行设为
  12. Key_RLreverse_Set(Key_L, 1); //列设成
  13. Key_Code_ins = 2;
  14. }
  15. else if (Key_Code_ins == 2)
  16. {
  17. zj_L = Key_RLreverse_Read(Key_L);
  18. zj = zj_L | (zj_R << 8);
  19. if (zj != Bef_Dat)
  20. {
  21. Key_OK = 1;
  22. Key_Data[Key_L] = zj_L;
  23. Key_Data[Key_R] = zj_R;
  24. Key_RLreverse_Decode();
  25. }
  26. Bef_Dat = zj;
  27. Key_Code_ins = 0;
  28. }
  29. else
  30. {
  31. Key_RLreverse_Set(Key_L, 0); //行设为1
  32. Key_RLreverse_Set(Key_R, 1); //列设为0
  33. if (Key_RLreverse_Read(Key_R) != 0x3F)
  34. Key_Code_ins = 1;
  35. else
  36. Key_Code_ins = 0;
  37. }
  38. }

动作函数

和扫描法类似

C文件(KeyBoard.c)

  1. void Key_RLreverse_Action(void (*Down)(u8 R, u8 L), void (*Up)(u8 R, u8 L))
  2. {
  3. if (Key_OK)
  4. {
  5. Key_OK = 0;
  6. for (int i = 0; i < 6; i++)
  7. {
  8. for (int j = 0; j < 6; j++)
  9. {
  10. if (Key_Key[i][j] == Key_Down)
  11. {
  12. Down(i, j);
  13. }
  14. else if (Key_Key[i][j] == Key_Up)
  15. {
  16. Key_Key[i][j] = Key_Keep;
  17. Up(i, j);
  18. }
  19. }
  20. }
  21. }
  22. }

成品

矩阵键盘演示,可以识别按键按下与松开

GitHubicon-default.png?t=M4ADhttps://github.com/HZ1213825/STM32F4_Matrix_KeyBoard百度网盘icon-default.png?t=M4ADhttps://pan.baidu.com/s/1U1B8jIfiXDHqmPbITeSRmw?pwd=mtlu 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/302206
推荐阅读
相关标签
  

闽ICP备14008679号