510
个编辑
更改
无编辑摘要
</syntaxhighlight>
== 04-按键中断实验 ==
=== STM32L476 外部中断简介 ===
首先我们看一下外部中断/事件的GPIO映射图。
[[文件:NBDK-DS-EXTI.png|边框|居中|无框|606x606像素]]
由上面的映射图可以知道,多个GPIO引脚(GPIOA、GPIOB、GPIOC、GPIOD等等的GPIO_Pin_0)都会触发同一个中断线(EXTI line0)。也就是说,当EXTI0被触发时,我们无法判断他是PA0触发,还是PB0触发,因此大家在设计自己的硬件的时候,需要选择合适的中断引脚。
源码中我们配置外部中断的步骤如下:
1.使能GPIO时钟
2.GPIO初始化,配置GPIO的边沿触发条件
3.设置EXTI线,配置GPIO与EXTI的关系
4.中断向量初始化
=== 硬件设计 ===
# 将SW1拨到DBG端,SW2拨到MCU。
# 使用Keil打开基础实验 03-蜂鸣器实验工程。
# 使用Xshell打开Jlink虚拟出的COM口
# 下载程序,并完成功能测试。
==== stm32l4xx_hal_conf.h ====
// 使能的宏
#define HAL_MODULE_ENABLED // 芯片
#define HAL_GPIO_MODULE_ENABLED // GPIO
#define HAL_DMA_MODULE_ENABLED // DMA
#define HAL_UART_MODULE_ENABLED // UART
</syntaxhighlight>
main函数,我们的例程由此处开始执行,首先调用HAL_Init()函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。
<syntaxhighlight lang="c++" line="1" start="36">
// 配置系统时钟(包含振荡器、系统时钟、总线时钟等等)
SystemClock_Config();
// 初始化USART1
MX_USART1_UART_Init();
// 初始化按键引脚
//注册按钮回调函数
KEY_RegisterCb(AppKey_cb);
//
}
}
</syntaxhighlight>在按键的处理回调函数中,我们可以看到,按键S1(UP)按下后,设置蜂鸣器引脚高电平,按键S3(DOWN)按下后,设置蜂鸣器引脚低电平在应用层的按键回调函数中,我们可以看到,当我们分别按下S1、S2、S3、S4按键后,STM32L4会通过串口向外部打印按键信息。<syntaxhighlight lang="c" line="1" start="69">
void AppKey_cb(uint8_t key)
{
if(key & KEY_UP)
{
}
if(key & KEY_LEFT)
{
}
if(key & KEY_DOWN)
{
}
if(key & KEY_RIGHT)
{
}
}
</syntaxhighlight>
==== gyu_buzzergyu_key.c ====蜂鸣器引脚初始化函数,初始化PB2推挽输出低电平。首先我们看一下按键的初始化函数,在按键初始化函数中我们配置按键引脚的状态,四个按键引脚都被配置为默认上拉,下降沿中断触发。并且开启EXTI0、EXTI1、EXTI2、EXTI3这四个外部中断线。<syntaxhighlight lang="c++" line="1" start="3175">void Buzzer_InitMX_KEY_Init(void){ // 定义GPIO结构体 GPIO_InitTypeDef GPIO_InitStruct; // 使能GPIOC引脚时钟(按键引脚:PC0、PC1、PC2、PC3) __HAL_RCC_GPIOC_CLK_ENABLE(); // 配置按键引脚 GPIO_InitStruct.Pin = KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin|KEY_UP_Pin; // 选择PC0、PC1、PC2、PC3 GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿中断触发 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉 HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 初始化引脚 // 配置中断优先级,并且使能中断 { // 配置PC0的中断,也就是EXTI line0 HAL_NVIC_SetPriority(KEY_LEFT_EXTI_IRQn, 10, 0); HAL_NVIC_EnableIRQ(KEY_LEFT_EXTI_IRQn); // 配置PC1的中断,也就是EXTI line1 HAL_NVIC_SetPriority(KEY_DOWN_EXTI_IRQn, 10, 0); HAL_NVIC_EnableIRQ(KEY_DOWN_EXTI_IRQn); // 配置PC2的中断,也就是EXTI line2 HAL_NVIC_SetPriority(KEY_RIGHT_EXTI_IRQn, 10, 0); HAL_NVIC_EnableIRQ(KEY_RIGHT_EXTI_IRQn); // 配置PC3的中断,也就是EXTI line3 HAL_NVIC_SetPriority(KEY_UP_EXTI_IRQn, 10, 0); HAL_NVIC_EnableIRQ(KEY_UP_EXTI_IRQn); }}</syntaxhighlight>如下,是我们在初始化函数中打开的四个中断线。<syntaxhighlight lang="c" line="1" start="43">// EXTI line0 中断函数void EXTI0_IRQHandler(void)
{
}
</syntaxhighlight>蜂鸣器引脚电平设置函数,设置为高电平,蜂鸣器发出蜂鸣声,设置低电平,蜂鸣器停止。按键轮训函数,其实就是用来处理消抖的函数,我们根据从外部中断回调函数中的获取的时间(也就是中断触发的时间,上一个函数中记录的),对比现在实时的时间,判断是否超过20ms,如果超过20ms,则认为按键被按下。我们记录下按键信息,并且触发向应用层回调的函数。<syntaxhighlight lang="c++" line="1" start="54171">void Buzzer_SETKEY_Poll(GPIO_PinState pinSatevoid){ uint8_t key_event = 0; // 如果有按键任务 HAL_GPIO_WritePin if(GPIOBkey_check_press.key_event) { // 获取当前时钟 减去 记录的按键触发时钟,如果大于消抖延时,则继续向下判断 if(HAL_GetTick() - key_check_press.start_tick >= KEY_DELAY_TICK ) { // 如果按键任务记录为KEY_UP if(key_check_press.key_event & KEY_UP) { // 获取当前KEY_UP引脚电平,如果是低电平,则认为UP按键被按下 if(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, GPIO_PIN_2KEY_UP_Pin) == GPIO_PIN_RESET) { key_event |= KEY_UP; // 记录app按键任务 } key_check_press.key_event ^= KEY_UP; // 删除按键中断任务 } // 如果按键任务记录为KEY_LEFT if(key_check_press.key_event & KEY_LEFT) { // 获取当前KEY_LEFT引脚电平,如果是低电平,则认为LEFT按键被按下 if(HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, pinSateKEY_LEFT_Pin)== GPIO_PIN_RESET) { key_event |= KEY_LEFT; // 记录app按键任务 } key_check_press.key_event ^= KEY_LEFT; // 删除按键中断任务 } // 如果按键任务记录为KEY_DOWN if(key_check_press.key_event & KEY_DOWN) { // 获取当前KEY_DOWN引脚电平,如果是低电平,则认为DOWN按键被按下 if(HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port,KEY_DOWN_Pin) == GPIO_PIN_RESET) { key_event |= KEY_DOWN; // 记录app按键任务 } key_check_press.key_event ^= KEY_DOWN; // 删除按键中断任务 } // 如果按键任务记录为KEY_RIGHT if(key_check_press.key_event & KEY_RIGHT) { // 获取当前KEY_RIGHT引脚电平,如果是低电平,则认为RIGHT按键被按下 if(HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port,KEY_RIGHT_Pin) == GPIO_PIN_RESET) { key_event |= KEY_RIGHT; // 记录app按键任务 } key_check_press.key_event ^= KEY_RIGHT; // 设置PB2输出删除按键中断任务 } } } //如果有记录给app的按键任务,代表真的有按钮按下,则执行回调函数 if(key_event && pFkey_cb) { pFkey_cb(key_event); }
}
</syntaxhighlight>