让人工智能帮我写一个矩阵按键扫描程序
1.前言
嘉立创做了一块编程小车的蓝牙按键遥控器,按键是4*4矩阵的,通过蓝牙发送按键编码值给蓝牙小车(外围设备)。
原理图如下:
板子回来后,因为懒得写按键矩阵扫描程序,想想还是交给人工智能帮我写吧。
1.开始提一个大概的需求
浏览器打开通义千问,国产免费的对话人工智能,同时选择代码模式
提问:使用C语言写一个矩阵按键的扫描程序,要求记录按下和松开事件。
模型回答的代码:
#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
代码实现
#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矩阵按键扫描程序,加入了简单的软件延迟去抖动机制。
改进后的代码
#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.补充需求
我仔细检查了一下代码,发现他的按键扫描逻辑和我想的有点不一样。
于是接着提问:输出引脚逐行输出低电平,输入引脚没有按下是高电平,有按键按下是低电平,按照这个要求你再检查下代码
调整后的代码
#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
的初始状态与实际的按键状态匹配。
以下是修正后的代码:
#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,那为什么不在一开始避免这个问题?
这就是人的思维和人工智能的思维不一样的,我们尽可能提供详细的问题。
修复后,代码可以直接运行。
其实,我们可以继续完善,比如 多按键组合方式。
不过,目前功能对我已经够用了,就不再问他了。
相关文章:
让人工智能帮我写一个矩阵按键扫描程序
1.前言 嘉立创做了一块编程小车的蓝牙按键遥控器,按键是4*4矩阵的,通过蓝牙发送按键编码值给蓝牙小车(外围设备)。 原理图如下: 板子回来后,因为懒得写按键矩阵扫描程序,想想还是交给人工智能…...
基于MindSpore NLP的PEFT微调
创建notebook 登录控制台 创建notebook 如果出现提示按如下操作 回到列表页面创建notebook参数如下: 配置mindnlp环境 打开GitHub - mindspore-lab/mindnlp: Easy-to-use and high-performance NLP and LLM framework based on MindSpore, compatible with model…...
2024年12月CCF-GESP编程能力等级认证C++编程八级真题解析
本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。 一、单选题(每题 2 分,共 30 分) 第 1 题 小杨家响应国家“以旧换新”政策,将自家的汽油车置换为新能源汽车,正在准备自编车牌。自编车牌包括5位数字或英文字母,要求…...
基于微信小程序的小区疫情防控ssm+论文源码调试讲解
第2章 程序开发技术 2.1 Mysql数据库 为了更容易理解Mysql数据库,接下来就对其具备的主要特征进行描述。 (1)首选Mysql数据库也是为了节省开发资金,因为网络上对Mysql的源码都已进行了公开展示,开发者根据程序开发需…...
moment()获取时间
moment 是一个 JavaScript 日期处理类库。 使用: //安装 moment npm install moment -- save引用 //在main.js中全局引入 import moment from "moment"设定moment区域为中国 //import 方式 import moment/locale/zh-cn moment.locale(zh-cn); 挂载全…...
CAD学习 day3
细节问题 快捷键X 分解单独进行操作如果需要制定字体样式选择 gdcbig.shx快捷键AA 算面积 平面布置图 客户沟通 - 会面笔记 - 客户需求(几个人居住、生活方式、功能需求(电竞房、家政柜)、书房、佛龛、儿童房、风格方向)根据客户需求 - 平面方案布置 (建议做三个以上方案) -…...
windows免登录linux
windows 生成秘钥文件 ssh-keygen -t rsa 将公钥传送到服务器 scp C:\Users\xx/.ssh/id_rsa.pub xxxx:/home/ruoyi/id_rsa.pub linux 使用ssh-copy-id -i ~/.ssh/id_rsa.pub userhost 如果禁用root登录,先开启 vim /etc/ssh/sshd_config PermitRootLogin yes …...
边缘计算的方式
做边缘计算这个行业要想赚得到收益,那一定要找到适合自己参与的一种方式。目前参与边缘计算的话,它主要有两个渠道。 第一个就是用盒子来跑,这个盒子的话包括光猫、路由器、摄像头等等,盒子是一条网线带动一个盒子,它…...
Android GO 版本锁屏声音无效问题
问题描述 Android go版本 在设置中打开锁屏音开关,息屏灭屏还是无声音 排查 vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\keyguard\KeyguardViewMediator.java private void setupLocked() {...String soundPath Settings.G…...
Android之RecyclerView显示数据列表和网格
一、RecyclerView的优势 RecyclerView 的最大优势在于,它对大型列表来说非常高效: 默认情况下,RecyclerView 仅会处理或绘制当前显示在屏幕上的项。例如,如果您的列表包含一千个元素,但只有 10 个元素可见࿰…...
汽车发动机电控系统-【传感器】篇
燃油:喷油控制(不多不少) 进气 主传感器MAP:进气压力传感器(微型车)、空气流量传感器MAF 辅助传感器:节气门传感器、水温传感器(提供暖机工况)、进气温度传感器 反馈…...
牛客周赛 Round 72 题解
本次牛客最后一个线段树之前我也没碰到过,等后续复习到线段树再把那个题当例题发出来 小红的01串(一) 思路:正常模拟,从前往后遍历一遍去统计即可 #include<bits/stdc.h> using namespace std; #define int lo…...
Python AI后台服务器
把数据训练放在后台,首先碰到的一个问题是如何高效地从数据库把数据请求下来。 分别试了几个库 modin 号称和pandas能够无缝衔接,试了下,确实pd.read_sql蛮快的,但是下来后数据格式就变了,不太好进行后续处理了conne…...
音视频入门基础:MPEG2-TS专题(19)——FFmpeg源码中,解析TS流中的PES流的实现
一、引言 FFmpeg源码在解析完PMT表后,会得到该节目包含的视频和音频信息,从而找到音视频流。TS流的音视频流包含在PES流中。FFmpeg源码通过调用函数指针tss->u.pes_filter.pes_cb指向的回调函数解析PES流的PES packet: /* handle one TS…...
Qt Q_ENUM enum 转 QString 枚举字符串互转; C++模板应用
Part1: Summary 项目中我们常用到命名,使用 enum 转成 string ,方便简洁;Qt给我们提供了一个很方便的功能 Q_ENUM,可以实现枚举字符串互转; Q_ENUM宏将枚举注册到元对象系统中; QMetaEnum::fromType获取枚…...
Mac配置 Node镜像源的时候报错解决办法
在Mac电脑中配置国内镜像源的时候报错,提示权限问题,无法写入配置文件。本文提供解决方法,青测有效。 一、原因分析 遇到的错误是由于 .npm 目录下的文件被 root 用户所拥有,导致当前用户无法写入相关配置文件。 二、解决办法 在终端输入以下命令,输入管理员密码即可。 su…...
windows C#-为枚举创建新方法
可使用扩展方法添加特定于某个特定枚举类型的功能。 示例 在下面的示例中,Grades 枚举表示学生可能在班里收到的字母等级分。 该示例将一个名为 Passing 的扩展方法添加到 Grades 类型中,以便该类型的每个实例现在都“知道”它是否表示合格的等级分。 …...
ISCTF复现-misc
File_Format 下载附件后用010打开查看文件头会发现是个exe文件 格式:文件描述(后缀名),文件头(hex):文件头标识(十六进制)PNG (png),文件头(hex):89504E47 PNGImageFile…...
知道一个服务器IP地址如果attack服务器地址
CSDN提醒:亲爱的用户:你好! 你的账号于2024-12-17 19:04:04在美国美国登录,登录IP为:47.238.159.124。若非本人登录,请及时修改密码。 莫名其妙显示美国登录了我的CSDN博客 卧槽 服务器的IP地址是一个用于…...
【泛微系统】查询角色所在流程操作组
查询角色所在流程操作组 -----sqlserver-------SELECT CASE WHEN a.workflowname LIKE ~`~`% THEN SUBSTRING(a.workflowname, CHARINDEX(`~`7, a.workflowname...
ceph的RGW对象存储网关部署和使用
RGW对象存储网关部署和使用 Rados GateWay简称rgw,底层采用亚马逊的S3作为对象存储实现。 参考链接: https://docs.ceph.com/en/reef/radosgw/ https://docs.ceph.com/en/reef/radosgw/bucketpolicy/ https://docs.aws.amazon.com/zh_cn/AmazonS3/lat…...
智慧公交指挥中枢,数据可视化 BI 驾驶舱
随着智慧城市的蓬勃发展,公共交通作为城市运营的核心枢纽,正朝着智能化和数据驱动的方向演进。通过整合 CAN 总线技术(Controller Area Network,控制器局域网总线)、车载智能终端、大数据分析及处理等尖端技术,构建的公交“大脑”…...
fastjson 字符串转对象 boolean类型字段转换失败
fastjson 字符串转对象 boolean类型字段转换失败 问题出现场景问题出现的原因问题解决方式1方式2 问题出现场景 有一个json字符串,需要将这个字符串转换为对象,字符串中有个字段是布尔类型,对应的value是true,使用阿里的fastjson…...
浏览器事件循环机制
JavaScript 是单线程运行的语言,同一时间只能执行一个任务。单线程意味着: 如果某个任务执行时间过长,后续任务会被阻塞。 同步任务和异步任务的调度需要一种机制来管理。 为了解决这个问题,事件循环应运而生,它可以…...
Redis缓存穿透与缓存击穿
Redis缓存穿透与缓存击穿 缓存穿透 在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀…...
WEB自动化测试(selenium工具)框架、面试题
一、什么是web自动化测试 让程序员代替人为去验证web项目功能的过程 二、什么web项目适合自动化测试 1)需求变动不频繁 测试脚本的稳定性决定了自动化测试的维护成本。如果软件需求变动过于频繁,测试人员需要根据变动的需求来更新测试用例以及相关的测试脚本&…...
Spring Boot 3.X:Unable to connect to Redis错误记录
一.背景 最近在搭建一个新项目,本着有新用新的原则,项目选择到了jdk17SpringBoot3.4。但是在测试Redis连接的时候却遇到了以下问题: redis连不上了。于是我先去检查了配置文件的连接信息,发现没问题;再去检查配置类&am…...
ISP算法之黑电平BLC校正
黑电平形成原因 Sensor本身问题 CMOS Sensor本身的电路会存在暗电流(dark current),在没有光照条件下,感光器件等也会存在流动的电流,在光敏二极管器的伏安特性曲线中反向截止区电流并不为零(如下图)。导致在Sensor遮…...
《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(四)
《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(四) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…...
【Python】利用函数模拟创建【栈】的数据结构操作
知识解读:来自:https://fishc.com.cn[#FwSB,M 9xKOA!^6fP)_EC(nsd什么是栈呢?Powered by https://fishc.com.cn3>A?5JXL#_}YBGD"FWdubKeyhQP栈是一种具有 FILO 特性的数据结构,即先放入的数据反而后取出。e&"%b…...
10 JVM内置锁
我们先想明白一个问题,什么是锁? 我们去给自己家锁门的时候,只有对应的一把钥匙能开锁。当用钥匙去开锁的时候,锁孔的内置型号会验证钥匙能不能对的上。能对上就能把锁打开,然后进到家里使用家里的资源。否则就在外面等…...
前端项目部署方法
ngnix服务器部署 下载nignx,我下的是windows版本的 下载链接:[https://nginx.org/en/download.html](https://nginx.org/en/download.html) 解压文件 如果原本的80端口号被占用了,可以改为其他的端口号 可以点击nginx.exe文件启动nginx,它可能…...
【1.排序】
排序 笔记记录 1.排序的基本概念1.1 排序的定义 2. 插入排序2.1 直接插入排序2.2 折半插入排序2.3 希尔排序 3. 交换排序3.1 冒泡排序3.2 快速排序 4. 选择排序4.1 简单选择排序4.2 堆排序 5. 归并排序、基数排序和计数排序5.1 归并排序4.2 基数排序4.3 计数排序 6. 各种内部排…...
visual studio添加滚动条预览
如何在vs中添加如图的滚动条呢? 打开VS 工具栏 选项 - 文本编辑器 - C/C - 滚动条 行为-使用缩略图 -- 确定...
[SAP ABAP] ALV状态栏GUI STATUS的快速创建
使用事务码SE38进入到指定程序,点击"显示对象列表"按钮 鼠标右键,选择"GUI状态" 弹出【创建状态】窗口,填写状态以及短文本描述以后,点击按钮 点击"调整模板",复制已有程序的状态栏 填…...
鸿蒙元服务项目实战:备忘录UI页面开发
前言 之前写过一篇关于元服务项目的上架流程,为了更好的了解及开发元服务,准备从0到1简单开发一个小项目,也希望能够帮助到刚刚介入到鸿蒙开发的同学,具体项目呢,也是十分的简单,就是一个小巧的备忘录项目&…...
【Java基础面试题022】什么是Java内部类?有什么作用?
回答重点 Java内部类是指:在一个类的内部定义的类,Java支持多种类型的内部类,包括成员内部类、局部内部类、匿名内部类和静态内部类。实际上内部类是一个编译层面的概念,像一个语法糖一样,经过编译器之后其实内部类会…...
量子芯片在新医疗领域的应用研究展望
一、引言 (一)研究背景 随着科技的不断发展,量子芯片在新医疗领域有望在多个技术方面实现创新突破,进而持续拓展其应用范围与深度。 在提高纠错能力方面,当前量子比特的易错性是制约量子芯片广泛应用的关键因素之一…...
不可重入锁与死锁
不可重入锁确实可能导致死锁,特别是在同一线程尝试多次获取同一把锁时。如果锁是不可重入的,那么线程在第二次尝试获取锁时会永远阻塞,从而导致死锁。 不可重入锁与死锁的关系 不可重入锁不允许同一个线程多次获取同一把锁。在以下情况下&am…...
Linux(Ubuntu)命令大全——已分类整理,学习、查看更加方便直观!(2024年最新编制)
Hello! 认真好学的小伙伴们,大家好呀(Respect~)!我是 H u a z z i Huazzi Huazzi,欢迎观看本篇博客,接下来让我们一起来学习 Ubuntu命令大全 吧!祝你有所收获! 文章目录 前言&#x…...
画一颗随机数
代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>codePen - Random Tree</title> </head> <body><canvas></canvas><script>const canvas doc…...
机器人变换关系
rigid 刚性变换(平移、旋转): 只改变位置和方向,拥有6个自由度,保持变换物体的形状。 affine 仿射变换(平移、旋转、缩放、倾斜等): 改变物体形状、位置、方向,拥有12…...
2024年企业中生成式 AI 的现状报告
从试点到生产,企业 AI 格局正在被实时改写。我们对 600 名美国企业 IT 决策者进行了调查,以揭示新兴的赢家和输家。 从试点到生产 2024 年标志着生成性人工智能成为企业关键任务的一年。这些数字讲述了一个戏剧性的故事:今年人工智能支出飙升…...
共创共建!葡萄城 SpreadJS 完成 HarmonyOS NEXT 操作系统兼容认证
最新技术资源(建议收藏) https://www.grapecity.com.cn/resources/ 近日,华为“企业工作必备应用鸿蒙化论坛”在北京圆满落幕,论坛汇聚了众多行业精英和合作伙伴,聚焦讨论企业数字化转型与原生鸿蒙生态融合等话题。葡萄…...
webAPI clean architecture
文章目录 项目地址一、Restaurants.Domain 核心业务层1.1 Entities实体层1.2 Repositories 数据操作EF的接口二、Restaurants.Infrastructure 基础设施层2.1 Persistence 数据EF CORE配置2.2 Repositories 数据查询实现2.3 Extensions 服务注册三、Restaurants.Application用例…...
JS使用random随机数实现简单的四则算数验证
1.效果图 2.代码实现 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</ti…...
在Linux中使用`scp`进行远程目录文件复制
在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令。它允许在远程主机之间复制文件和目录,具有很强的安全性,是一种常用的文件传输工具。以下是如何使用…...
【C++游记】string的使用和模拟实现
枫の个人主页 你不能改变过去,但你可以改变未来 算法/C/数据结构/C Hello,这里是小枫。C语言与数据结构和算法初阶两个板块都更新完毕,我们继续来学习C的内容呀。C是接近底层有比较经典的语言,因此学习起来注定枯燥无味…...
解决Linux访问HuggingFace的问题(操作记录)
目录 一、软件下载 二、软件使用 1、启动软件 2、设置网络 3、注意事项 三、linux命令行访问 1、问题分析 2、设置命令行代理 一、软件下载 MrQiguan/cfw-linux 二、软件使用 1、启动软件 ./cfw 注:先选择节点,再开启Start with Linux&#…...
《向量数据库指南》——Milvus Cloud 2.5:Sparse-BM25引领全文检索新时代
Milvus Cloud BM25:重塑全文检索的未来 在最新的Milvus Cloud 2.5版本中,我们自豪地引入了“全新”的全文检索能力,这一创新不仅巩固了Milvus Cloud在向量数据库领域的领先地位,更为用户提供了前所未有的灵活性和效率。作为大禹智库的向量数据库高级研究员,以及《向量数据…...