写在前面,这个算是我第一个小项目,从头到尾的完成题目,花了一天写,花了一天调试改BUG,因为我是一个奋发向上的菜菜。

首先读题,看关键字,中断,控制LED,继电器,电机。题目要求用中断按键控制,板子没有那么多按键,这个要用到扩展模块。长下面这个样子。


| ROW1 | ROW2 | COL1 | COL2 | COL3 | |
| 引脚 | PB6 | PB7 | PB1 | PB0 | PA8 |
| MODE | RISING_FALLING | RISING_FALLING | OUTPUT_PP | OUTPUT_PP | OUTPUT_PP |
| Pull | PULLUP | PULLUP | NOPULL | NOPULL | NOPULL |
| Speed | LOW | LOW | LOW | LOW | LOW |
板子外设限制,模块只有两行三列,设置行作为中断输入口,配置为默认上拉,速率为LOW;列为输出口,设置为推挽输出模式,无上拉无下拉。使能中断线,两个口共用中断线EXIT4_15,所以只需要使能该中断线该模块就配置好了。
根据题目,我们需要配置USER按键,LD5,继电器,电机
原理图:

| USER | LD5 | P1 | P2 | K1 | K2 | |
| 引脚 | PC14 | PC15 | PA0 | PA1 | PA11 | PA12 |
| Mode | FALLING | OUTPUT_PP | OUTPUT_PP | OUTPUT_PP | OUTPUT_PP | OUTPUT_PP |
| Pull | PULLUP | NOPULL | NOPULL | NOPULL | NOPULL | NOPULL |
| Speed | LOW | LOW | LOW | LOW | LOW | LOW |
这部分配置注意原理图中,USER引脚被上拉了,按键按下,低电平为1;LD5在实验中发现是高电平熄灭,低电平点亮;其他都是高电平驱动,低电平断开。看好原理图会对后面代码编写省很多事,这是我调代码调了老久的领悟。
注意中断引脚为 ROW1:PB6 ROW2:PB7 USER:PC14
| USER | ROW1 | ROW2 |
| PC14 | PB6 | PB7 |
设置好时钟为 32MHz 后就进行代码初始化;
- void MX_GPIO_Init(void)
- {
-
- GPIO_InitTypeDef GPIO_InitStruct = {0};
-
- /* GPIO Ports Clock Enable */
- __HAL_RCC_GPIOC_CLK_ENABLE();
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_GPIOB_CLK_ENABLE();
-
- /*Configure GPIO pin Output Level */
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_SET);
-
- /*Configure GPIO pin Output Level */
- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|COL3_Pin|GPIO_PIN_11
- |GPIO_PIN_12, GPIO_PIN_RESET);
-
- /*Configure GPIO pin Output Level */
- HAL_GPIO_WritePin(GPIOB, COL1_Pin|COL2_Pin, GPIO_PIN_RESET);
-
- /*Configure GPIO pin : PC14 */
- GPIO_InitStruct.Pin = GPIO_PIN_14;
- GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;//?
- GPIO_InitStruct.Pull = GPIO_PULLDOWN;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
-
- /*Configure GPIO pin : PC15 */
- GPIO_InitStruct.Pin = GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
-
- /*Configure GPIO pins : PA0 PA1 PAPin PA11
- PA12 */
- GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|COL3_Pin|GPIO_PIN_11
- |GPIO_PIN_12;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
-
- /*Configure GPIO pins : PBPin PBPin */
- GPIO_InitStruct.Pin = COL1_Pin|COL2_Pin;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
-
- /*Configure GPIO pins : PBPin PBPin */
- GPIO_InitStruct.Pin = ROW1_Pin|ROW2_Pin;
- GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;//?
- GPIO_InitStruct.Pull = GPIO_PULLUP;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
-
- /* EXTI interrupt init*/
- HAL_NVIC_SetPriority(EXTI4_15_IRQn, 2, 0);
- HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
-
- }
为了方便代码的编写,对引脚进行了宏定义。代码如下:
- #define ROW1 GPIOB,GPIO_PIN_6
- #define ROW2 GPIOB,GPIO_PIN_7
- #define COL1 GPIOB,GPIO_PIN_1
- #define COL2 GPIOB,GPIO_PIN_0
- #define COL3 GPIOA,GPIO_PIN_8
- #define LD5 GPIOC,GPIO_PIN_15
- #define K1_LED GPIOA,GPIO_PIN_11
- #define K2_LED GPIOA,GPIO_PIN_12
- #define P1 GPIOA,GPIO_PIN_0
- #define P2 GPIOA,GPIO_PIN_1
三个引脚配置成中断引脚,所以在回调函数中,要编写三种中断信号的处理
题目要求USER按键开关所有外设,所以我直接在回调函数中翻转LD5,K1,K2,P1,P2的电平
- /* USER CODE BEGIN 1 */
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- delay_ms(10);
- switch(GPIO_Pin)
- {
- case GPIO_PIN_14:
- HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_15);
- HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_11|GPIO_PIN_12);
- break;
- case ROW1_Pin:
- Key_Val=Scan_KeyRow1();
- break;
- case ROW2_Pin:
- Key_Val=Scan_KeyRow2();
- break;
- }
- }
- /* USER CODE END 1 */
在行1中断中,改变中断引脚配置,关闭中断后,将列1,列2,列3的电平拉高,再依次拉低,读取ROW1的引脚电平,如果为低电平,说明该行,该列的按键按下,返回键值,再拉低每一列的电平,打开中断。代码如下:
- uint16_t Scan_KeyRow1(void)
- {
- uint16_t ucKey_val=0; //设立变量存键值
- HAL_GPIO_WritePin(COL1,GPIO_PIN_SET);//拉高3列的电平
- HAL_GPIO_WritePin(COL2,GPIO_PIN_SET);
- HAL_GPIO_WritePin(COL3,GPIO_PIN_SET);
- //遍历列
- for(uint8_t i=0;i<=2;i++)
- {
- HAL_NVIC_DisableIRQ(ROW1_EXTI_IRQn);//关闭ROW1的中断
- switch(i){
- case 0:
- HAL_GPIO_WritePin(COL1,GPIO_PIN_RESET);//拉低列1
- delay_ms(10);//消抖
- if(HAL_GPIO_ReadPin(ROW1) == 0)//读取键值
- ucKey_val = '1';
- HAL_GPIO_WritePin(COL1,GPIO_PIN_SET);//拉高列1
- break;
- case 1:
- HAL_GPIO_WritePin(COL2,GPIO_PIN_RESET);//拉低列2
- delay_ms(10);//消抖
- if(HAL_GPIO_ReadPin(ROW1) == 0)//读取键值
- ucKey_val = '2';
- HAL_GPIO_WritePin(COL2,GPIO_PIN_SET);//拉高列2
- break;
- case 2:
- HAL_GPIO_WritePin(COL3,GPIO_PIN_RESET);//拉低列3
- delay_ms(10);//消抖
- if(HAL_GPIO_ReadPin(ROW1) == 0)//读取键值
- ucKey_val = '3';
- HAL_GPIO_WritePin(COL3,GPIO_PIN_SET);//拉高列3
- break;
- }
- }
- //拉低三列的电平
- HAL_GPIO_WritePin(COL1,GPIO_PIN_RESET);
- HAL_GPIO_WritePin(COL2,GPIO_PIN_RESET);
- HAL_GPIO_WritePin(COL3,GPIO_PIN_RESET);
- //打开ROW1中断
- HAL_NVIC_EnableIRQ(ROW1_EXTI_IRQn);
-
- return ucKey_val;
- }
行2的逻辑和行1一样,拉高电平,关闭中断,依次拉低,返回键值,打开中断。代码如下:
- uint16_t Scan_KeyRow2(void)
- {
- uint16_t ucKey_val=0;
- HAL_GPIO_WritePin(COL1,GPIO_PIN_SET);
- HAL_GPIO_WritePin(COL2,GPIO_PIN_SET);
- HAL_GPIO_WritePin(COL3,GPIO_PIN_SET);
- for(uint8_t i=0;i<=2;i++)
- {
- HAL_NVIC_DisableIRQ(ROW2_EXTI_IRQn);
- switch(i){
- case 0:
- HAL_GPIO_WritePin(COL1,GPIO_PIN_RESET);
- delay_ms(10);
- if(HAL_GPIO_ReadPin(ROW2)==0)
- ucKey_val = '4';
- HAL_GPIO_WritePin(COL1,GPIO_PIN_SET);
- break;
- case 1:
- HAL_GPIO_WritePin(COL2,GPIO_PIN_RESET);
- delay_ms(10);
- if(HAL_GPIO_ReadPin(ROW2)==0)
- ucKey_val = '5';
- HAL_GPIO_WritePin(COL2,GPIO_PIN_SET);
- break;
- case 2:
- HAL_GPIO_WritePin(COL3,GPIO_PIN_RESET);
- delay_ms(10);
- if(HAL_GPIO_ReadPin(ROW2)==0)
- ucKey_val = '6';
- HAL_GPIO_WritePin(COL3,GPIO_PIN_SET);
- break;
- }
- }
- HAL_GPIO_WritePin(COL1,GPIO_PIN_RESET);
- HAL_GPIO_WritePin(COL2,GPIO_PIN_RESET);
- HAL_GPIO_WritePin(COL3,GPIO_PIN_RESET);
- HAL_NVIC_EnableIRQ(ROW2_EXTI_IRQn);
-
- return ucKey_val;
- }
对返回的键值就可以实现对应的功能,实现题目要求。代码如下:
- void LED_Control(uint16_t ucState)
- {
-
- switch(ucState)
- {
- case '1':
- HAL_GPIO_TogglePin(LD5);//LD翻转
- HAL_GPIO_WritePin(K1_LED,GPIO_PIN_SET);//K1_LED ON
- HAL_GPIO_WritePin(K2_LED,GPIO_PIN_RESET);//K2_LED OFF
- HAL_GPIO_WritePin(P1,GPIO_PIN_SET);//P1 ON
- HAL_GPIO_WritePin(P2,GPIO_PIN_RESET);//P2 OFF
- break;
- case '2':
- HAL_GPIO_TogglePin(LD5);//LD翻转
- HAL_GPIO_WritePin(K1_LED,GPIO_PIN_RESET);//K1_LED OFF
- HAL_GPIO_WritePin(K2_LED,GPIO_PIN_SET);//K2_LED ON
- HAL_GPIO_WritePin(P1,GPIO_PIN_RESET);//P1 OFF
- HAL_GPIO_WritePin(P2,GPIO_PIN_SET);//P2 ON
- break;
- case '3':
- HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_SET);//LD5 OFF
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET);//ALL OFF
- break;
- case '4':
- HAL_GPIO_WritePin(LD5,GPIO_PIN_SET);//LD5 OFF
- break;
- case '5':
- HAL_GPIO_WritePin(K1_LED,GPIO_PIN_RESET);//K1_LED OFF
- HAL_GPIO_WritePin(K2_LED,GPIO_PIN_RESET);//K2_LED OFF
- break;
- case '6':
- HAL_GPIO_WritePin(P1,GPIO_PIN_RESET);//P1 OFF
- HAL_GPIO_WritePin(P2,GPIO_PIN_RESET);//P2 OFF
- break;
- }
- }
最后,在头文件中声明函数,在主函数中显示扫描LED_Control()。代码如下:
- int main(void)
- {
- /* USER CODE BEGIN 1 */
-
- /* USER CODE END 1 */
-
- /* MCU Configuration--------------------------------------------------------*/
-
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
- /* USER CODE BEGIN Init */
-
- /* USER CODE END Init */
-
- /* Configure the system clock */
- SystemClock_Config();
-
- /* USER CODE BEGIN SysInit */
-
- /* USER CODE END SysInit */
-
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_SPI1_Init();
- MX_USART2_UART_Init();
- /* USER CODE BEGIN 2 */
-
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- delay_ms(50);
- LED_Control(Key_Val);
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
- void delay_ms(uint16_t ms)
- {
- uint16_t i=0;
- while(ms--)
- {
- i=12000;
- while(i--);
- }
- }
在代码编写的时候,把思路变成代码的过程,写的时候嘎嘎快,调的时候嘎嘎难受。在中断按键扫描的那点,卡住很长时间,一开始卡在中断,到后面一点一点,发现HAL_Delay会卡死中断,就自己编写delay函数,发现中断过后会再次读取引脚,两次引脚信号导致板子要按两次才能实现功能,调延时,最后调整ROW1中断,ROW2中断引脚为上升沿和下降沿都触发。解决问题,实现了题目要求。