打开主菜单

谷雨文档中心 β

更改

NBDK-L4:基础实验教程

添加6,393字节2019年1月22日 (二) 11:14
无编辑摘要
</syntaxhighlight>
== 04-按键中断实验 ==
按键中断实验,是通过外部引脚中断来判断是否有按键被按下,这个在马达以及蜂鸣器实验中其实已经展示过了,这边给大家做一个详细的讲解。我们分别选择PC0、PC1、PC2、PC3这4个引脚作为我们的按键引脚,按键中断实验,是通过外部引脚中断来判断是否有按键被按下,按键部分的代码这个在马达以及蜂鸣器实验中其实已经展示过了,这边给大家做一个详细的讲解。我们分别选择PC0、PC1、PC2、PC3这4个引脚作为我们的按键引脚,对应EXTI line0、EXTI line1、EXTI line2、EXTI line3。
=== 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 ====
此文件位于“03此文件位于“04-蜂鸣器实验按键中断实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件,一般情况下,我们默认需要使用的为前5个,包含芯片、flash、电源、时钟以及NVIC。
此例程因为我们需要展示IO的使用,所以我们额外使能 HAL_GPIO_MODULE_ENABLED。此例程我们只要展示的是外部GPIO中断,所以我们额外使能 HAL_GPIO_MODULE_ENABLED。另外为了辅助展示按键信息,我们额外添加了串口相关的DMA、UART这两个宏定义。<syntaxhighlight lang="c" line="1" start="103">
// 使能的宏
#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部分有详细说明。
接下来是初始化按键,有关按键的部分会在04-按键实验中给大家讲解。接下来我们初始化了串口部分,目的是打印按键按下的调试信息。
最后我们初始化蜂鸣器引脚,配置蜂鸣器引脚为默认输出低电平。接下来是初始化按键,并且注册了按键回调函数(回调函数负责的是不同层之间的数据传输)。 在最后的while()循环中,我们调用按键轮训函数,这样一旦有外部中断触发,我们首先会进行一下按键消抖,确认是否为误判。如果判断是正常触发,则认为是有按键按下,此时按键处理文件gyu_key.c中会将按键信息,通过上面说的回调函数,传到应用层(mian.c)中进行处理。
<syntaxhighlight lang="c++" line="1" start="36">
// 配置系统时钟(包含振荡器、系统时钟、总线时钟等等)
SystemClock_Config();
// 初始化USART1
MX_USART1_UART_Init();
// 初始化按键引脚
MX_KEY_Init();
//注册按钮回调函数
KEY_RegisterCb(AppKey_cb);
//初始化蜂鸣器
Buzzer_Init();
//
}
}
</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)
{
Motor_SETprintf(GPIO_PIN_SET"key_up press\r\n");
}
if(key & KEY_LEFT)
{
//printf("key_left press\r\n");
}
if(key & KEY_DOWN)
{
Motor_SETprintf(GPIO_PIN_RESET"key_down press\r\n");
}
if(key & KEY_RIGHT)
{
//printf("key_right press\r\n");
}
}
</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)
{
GPIO_InitTypeDef GPIO_InitStructure HAL_GPIO_EXTI_IRQHandler(KEY_LEFT_Pin); // 定义引脚参数结构体}
__HAL_RCC_GPIOB_CLK_ENABLE// EXTI line1 中断函数void EXTI1_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(KEY_DOWN_Pin); // 使能GPIOB时钟}
GPIO_InitStructure// EXTI line2 中断函数void EXTI2_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(KEY_RIGHT_Pin);} // EXTI line3 中断函数void EXTI3_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(KEY_UP_Pin);}</syntaxhighlight>我们配置好上面所说的引脚外部中断后,一旦有按键被按下,则会触发中断,最终会跑到如下的HAL_GPIO_EXTI_Callback()函数中。我们在这个函数中,判断一下是哪一个中断线触发的中断,并且记录一下按键信息,以及触发的时间(记录触发的时间,是为了进行按键消抖,防止误操作)。<syntaxhighlight lang="c++" line="1" start="119">void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ // 如果是UP键被触发,记录按键任务为KEY_UP,并记录当前时钟 if(GPIO_Pin == KEY_UP_Pin) { key_check_press.Pinkey_event = GPIO_PIN_2KEY_UP; // 引脚编号为2 GPIO_InitStructure key_check_press.Mode start_tick = GPIO_MODE_OUTPUT_PPHAL_GetTick(); } // 推挽输出如果是LEFT键被触发,记录按键任务为KEY_LEFT,并记录当前时钟 GPIO_InitStructure if(GPIO_Pin == KEY_LEFT_Pin) { key_check_press.Speed key_event = GPIO_SPEED_FREQ_LOWKEY_LEFT; key_check_press.start_tick = HAL_GetTick(); } // 低频率如果是DOWN键被触发,记录按键任务为KEY_DOWN,并记录当前时钟 if(GPIO_Pin == KEY_DOWN_Pin) GPIO_InitStructure { key_check_press.Pull key_event = GPIO_PULLUPKEY_DOWN; // 上拉 HAL_GPIO_Init key_check_press.start_tick = HAL_GetTick(GPIOB, &GPIO_InitStructure); } // 初始化PB2如果是RIGHT键被触发,记录按键任务为KEY_RIGHT,并记录当前时钟 if(GPIO_Pin == KEY_RIGHT_Pin) { key_check_press.key_event = KEY_RIGHT; HAL_GPIO_WritePin key_check_press.start_tick = HAL_GetTick(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); // 设置PB2默认输出低电平 }
}
</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>
510
个编辑