1.前言
嘉立创做了一块编程小车的蓝牙按键遥控器,按键是4*4矩阵的,通过蓝牙发送按键编码值给蓝牙小车(外围设备)。
原理图如下:
板子回来后,因为懒得写按键矩阵扫描程序,想想还是交给人工智能帮我写吧。
1.开始提一个大概的需求
浏览器打开通义千问,国产免费的对话人工智能,同时选择代码模式
提问:使用C语言写一个矩阵按键的扫描程序,要求记录按下和松开事件。
模型回答的代码:
cpp
#include <stdio.h>
#include <stdbool.h>
#define ROWS 4
#define COLS 4
// 模拟按键状态数组
bool keyMatrix[ROWS][COLS] = {
{false, false, false, false},
{false, false, false, false},
{false, false, false, false},
{false, false, false, false}
};
// 上一次按键状态数组
bool lastKeyMatrix[ROWS][COLS];
void initKeyboard() {
// 初始化上一次按键状态数组
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
lastKeyMatrix[i][j] = keyMatrix[i][j];
}
}
}
void scanKeyboard() {
// 这里应该是从硬件读取按键状态的代码
// 这里我们用随机数模拟按键状态变化
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
keyMatrix[i][j] = rand() % 2;
}
}
// 检测按键按下和松开事件
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
if (keyMatrix[i][j] && !lastKeyMatrix[i][j]) {
printf("Key (%d,%d) pressed\n", i, j);
} else if (!keyMatrix[i][j] && lastKeyMatrix[i][j]) {
printf("Key (%d,%d) released\n", i, j);
}
lastKeyMatrix[i][j] = keyMatrix[i][j];
}
}
}
int main() {
srand(time(NULL));
initKeyboard();
while (1) {
scanKeyboard();
usleep(100000); // 延迟100ms
}
return 0;
}
他已经帮我写了一些逻辑,写自动举例是4*4的按键矩阵,但是一看好像是一段和平台无关的代码,于是我又补充问题。
2.补充具体平台代码
提问:使用stm32单片机来编写这段代码
回答:
好的,下面是一个使用STM32单片机编写的矩阵按键扫描程序示例。假设你有一个4x4的矩阵键盘,并且使用的是STM32的GPIO接口来读取按键状态。我们将使用HAL库来进行GPIO操作。
硬件连接
- 行(ROW):PA0, PA1, PA2, PA3
- 列(COL):PB0, PB1, PB2, PB3
代码实现
cpp
#include "main.h"
#include "stm32f4xx_hal.h"
#define ROWS 4
#define COLS 4
// 定义行和列的GPIO引脚
GPIO_TypeDef* rowPorts[ROWS] = {GPIOA, GPIOA, GPIOA, GPIOA};
uint16_t rowPins[ROWS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
GPIO_TypeDef* colPorts[COLS] = {GPIOB, GPIOB, GPIOB, GPIOB};
uint16_t colPins[COLS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
bool keyMatrix[ROWS][COLS];
bool lastKeyMatrix[ROWS][COLS];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 初始化按键状态数组
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
lastKeyMatrix[i][j] = false;
}
}
while (1) {
scanKeyboard();
HAL_Delay(100); // 延迟100ms
}
}
void scanKeyboard() {
for (int r = 0; r < ROWS; r++) {
// 设置当前行为低电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_RESET);
for (int c = 0; c < COLS; c++) {
// 读取列的状态
keyMatrix[r][c] = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_SET;
// 检测按键按下和松开事件
if (keyMatrix[r][c] && !lastKeyMatrix[r][c]) {
printf("Key (%d,%d) pressed\n", r, c);
} else if (!keyMatrix[r][c] && lastKeyMatrix[r][c]) {
printf("Key (%d,%d) released\n", r, c);
}
lastKeyMatrix[r][c] = keyMatrix[r][c];
}
// 将当前行恢复为高电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_SET);
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置行引脚为输出模式
for (int i = 0; i < ROWS; i++) {
GPIO_InitStruct.Pin = rowPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rowPorts[i], &GPIO_InitStruct);
HAL_GPIO_WritePin(rowPorts[i], rowPins[i], GPIO_PIN_SET); // 初始化为高电平
}
// 配置列引脚为输入模式
for (int i = 0; i < COLS; i++) {
GPIO_InitStruct.Pin = colPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(colPorts[i], &GPIO_InitStruct);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
它根据我提出的使用STM32单片机来写这段代码,并使用HAL库来操作。
因为机械按键有机械抖动,所以还要考虑按键抖动滤波。
3. 考虑按键抖动滤波
提问:考虑按键抖动
回答:
按键抖动是一个常见的问题,特别是在机械按键中。为了处理按键抖动,我们可以引入去抖动机制。一种常用的方法是使用软件延迟或状态计数器来过滤掉短暂的抖动信号。
下面是一个改进后的STM32矩阵按键扫描程序,加入了简单的软件延迟去抖动机制。
改进后的代码
cpp
#include "main.h"
#include "stm32f4xx_hal.h"
#define ROWS 4
#define COLS 4
// 定义行和列的GPIO引脚
GPIO_TypeDef* rowPorts[ROWS] = {GPIOA, GPIOA, GPIOA, GPIOA};
uint16_t rowPins[ROWS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
GPIO_TypeDef* colPorts[COLS] = {GPIOB, GPIOB, GPIOB, GPIOB};
uint16_t colPins[COLS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
bool keyMatrix[ROWS][COLS];
bool lastKeyMatrix[ROWS][COLS];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void delayMs(uint32_t ms) {
HAL_Delay(ms);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 初始化按键状态数组
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
lastKeyMatrix[i][j] = false;
}
}
while (1) {
scanKeyboard();
delayMs(50); // 延迟50ms以减少CPU占用
}
}
void scanKeyboard() {
for (int r = 0; r < ROWS; r++) {
// 设置当前行为低电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_RESET);
for (int c = 0; c < COLS; c++) {
// 读取列的状态
bool currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_SET;
// 使用简单去抖动机制
if (currentState != lastKeyMatrix[r][c]) {
delayMs(20); // 去抖动延迟
currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_SET;
if (currentState != lastKeyMatrix[r][c]) {
keyMatrix[r][c] = currentState;
// 检测按键按下和松开事件
if (keyMatrix[r][c] && !lastKeyMatrix[r][c]) {
printf("Key (%d,%d) pressed\n", r, c);
} else if (!keyMatrix[r][c] && lastKeyMatrix[r][c]) {
printf("Key (%d,%d) released\n", r, c);
}
lastKeyMatrix[r][c] = keyMatrix[r][c];
}
}
}
// 将当前行恢复为高电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_SET);
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置行引脚为输出模式
for (int i = 0; i < ROWS; i++) {
GPIO_InitStruct.Pin = rowPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rowPorts[i], &GPIO_InitStruct);
HAL_GPIO_WritePin(rowPorts[i], rowPins[i], GPIO_PIN_SET); // 初始化为高电平
}
// 配置列引脚为输入模式
for (int i = 0; i < COLS; i++) {
GPIO_InitStruct.Pin = colPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(colPorts[i], &GPIO_InitStruct);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
4.补充需求
我仔细检查了一下代码,发现他的按键扫描逻辑和我想的有点不一样。
于是接着提问:输出引脚逐行输出低电平,输入引脚没有按下是高电平,有按键按下是低电平,按照这个要求你再检查下代码
调整后的代码
cpp
#include "main.h"
#include "stm32f4xx_hal.h"
#define ROWS 4
#define COLS 4
// 定义行和列的GPIO引脚
GPIO_TypeDef* rowPorts[ROWS] = {GPIOA, GPIOA, GPIOA, GPIOA};
uint16_t rowPins[ROWS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
GPIO_TypeDef* colPorts[COLS] = {GPIOB, GPIOB, GPIOB, GPIOB};
uint16_t colPins[COLS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
bool keyMatrix[ROWS][COLS];
bool lastKeyMatrix[ROWS][COLS];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void delayMs(uint32_t ms) {
HAL_Delay(ms);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 初始化按键状态数组
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
lastKeyMatrix[i][j] = true; // 初始状态为高电平(无按键按下)
}
}
while (1) {
scanKeyboard();
delayMs(50); // 延迟50ms以减少CPU占用
}
}
void scanKeyboard() {
for (int r = 0; r < ROWS; r++) {
// 设置当前行为低电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_RESET);
for (int c = 0; c < COLS; c++) {
// 读取列的状态
bool currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_RESET;
// 使用简单去抖动机制
if (currentState != lastKeyMatrix[r][c]) {
delayMs(20); // 去抖动延迟
currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_RESET;
if (currentState != lastKeyMatrix[r][c]) {
keyMatrix[r][c] = currentState;
// 检测按键按下和松开事件
if (keyMatrix[r][c] && !lastKeyMatrix[r][c]) {
printf("Key (%d,%d) pressed\n", r, c);
} else if (!keyMatrix[r][c] && lastKeyMatrix[r][c]) {
printf("Key (%d,%d) released\n", r, c);
}
lastKeyMatrix[r][c] = keyMatrix[r][c];
}
}
}
// 将当前行恢复为高电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_SET);
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置行引脚为输出模式
for (int i = 0; i < ROWS; i++) {
GPIO_InitStruct.Pin = rowPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rowPorts[i], &GPIO_InitStruct);
HAL_GPIO_WritePin(rowPorts[i], rowPins[i], GPIO_PIN_SET); // 初始化为高电平
}
// 配置列引脚为上拉输入模式
for (int i = 0; i < COLS; i++) {
GPIO_InitStruct.Pin = colPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(colPorts[i], &GPIO_InitStruct);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
他在引脚配置上做了调整,在判断引脚电平上也做了调整。
阅读一下代码,感觉问题不大,开始复制-粘贴 ,编译,下载,运行。
注:因为我使用的平台不是STM32,所以引脚配置相关的我做了修改,不过逻辑完全和它写的一致。
5.修复BUG
在代码运行的时候,我发现每次上电的时候,所有的按键都会打印一次释放事件。
于是我把这个问题告诉了它。
提问:这段代码有一个bug,第一次运行scanKeyboard()时,即使没有按键按下或释放,也会打印按键释放信息。
回答:
你说得对,第一次运行 scanKeyboard()
时,由于 lastKeyMatrix
数组初始化为 true
(高电平),而实际的按键状态可能是 true
或 false
,这会导致在第一次扫描时可能会错误地检测到按键释放事件。我们需要确保 lastKeyMatrix
的初始状态与实际硬件状态一致。
为了修复这个问题,我们可以在初始化时先读取一次按键状态,并将其存储到 lastKeyMatrix
中。这样可以确保 lastKeyMatrix
的初始状态与实际的按键状态匹配。
以下是修正后的代码:
cpp
#include "main.h"
#include "stm32f4xx_hal.h"
#define ROWS 4
#define COLS 4
// 定义行和列的GPIO引脚
GPIO_TypeDef* rowPorts[ROWS] = {GPIOA, GPIOA, GPIOA, GPIOA};
uint16_t rowPins[ROWS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
GPIO_TypeDef* colPorts[COLS] = {GPIOB, GPIOB, GPIOB, GPIOB};
uint16_t colPins[COLS] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3};
bool keyMatrix[ROWS][COLS];
bool lastKeyMatrix[ROWS][COLS];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void delayMs(uint32_t ms) {
HAL_Delay(ms);
}
void initializeKeyMatrix() {
for (int r = 0; r < ROWS; r++) {
// 设置当前行为低电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_RESET);
for (int c = 0; c < COLS; c++) {
// 读取列的状态
bool currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_RESET;
keyMatrix[r][c] = currentState;
lastKeyMatrix[r][c] = currentState;
}
// 将当前行恢复为高电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_SET);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
// 初始化按键状态数组
initializeKeyMatrix();
while (1) {
scanKeyboard();
delayMs(50); // 延迟50ms以减少CPU占用
}
}
void scanKeyboard() {
for (int r = 0; r < ROWS; r++) {
// 设置当前行为低电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_RESET);
for (int c = 0; c < COLS; c++) {
// 读取列的状态
bool currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_RESET;
// 使用简单去抖动机制
if (currentState != lastKeyMatrix[r][c]) {
delayMs(20); // 去抖动延迟
currentState = HAL_GPIO_ReadPin(colPorts[c], colPins[c]) == GPIO_PIN_RESET;
if (currentState != lastKeyMatrix[r][c]) {
keyMatrix[r][c] = currentState;
// 检测按键按下和松开事件
if (keyMatrix[r][c] && !lastKeyMatrix[r][c]) {
printf("Key (%d,%d) pressed\n", r, c);
} else if (!keyMatrix[r][c] && lastKeyMatrix[r][c]) {
printf("Key (%d,%d) released\n", r, c);
}
lastKeyMatrix[r][c] = keyMatrix[r][c];
}
}
}
// 将当前行恢复为高电平
HAL_GPIO_WritePin(rowPorts[r], rowPins[r], GPIO_PIN_SET);
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置行引脚为输出模式
for (int i = 0; i < ROWS; i++) {
GPIO_InitStruct.Pin = rowPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rowPorts[i], &GPIO_InitStruct);
HAL_GPIO_WritePin(rowPorts[i], rowPins[i], GPIO_PIN_SET); // 初始化为高电平
}
// 配置列引脚为上拉输入模式
for (int i = 0; i < COLS; i++) {
GPIO_InitStruct.Pin = colPins[i];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(colPorts[i], &GPIO_InitStruct);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage */
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
哈哈,居然他自己知道这个BUG,那为什么不在一开始避免这个问题?
这就是人的思维和人工智能的思维不一样的,我们尽可能提供详细的问题。
修复后,代码可以直接运行。
其实,我们可以继续完善,比如 多按键组合方式。
不过,目前功能对我已经够用了,就不再问他了。