写在前面,这个算是我第一个小项目,从头到尾的完成题目,花了一天写,花了一天调试改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 后就进行代码初始化;
引脚初始化后CubeMax生成的代码
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
题目要求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 */
ROW1 行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;
}
ROW2 行2 中断信号的处理
行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;
}
LED控制函数
对返回的键值就可以实现对应的功能,实现题目要求。代码如下:
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中断引脚为上升沿和下降沿都触发。解决问题,实现了题目要求。