一、简介
在 FreeRTOS 下实现按键中断可以有两种方法:
-
通过事件的触发和等待:可以实现
一对多
,多对多
的同步。即一个任务可以等待多个事件的发生;可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。 -
通过任务通知: FreeRTOS
v8.2.0
以上版本。优点是解除阻塞的任务要快 45%,并且节省 RAM 内存空间。缺点是只能够一对一
。
二、通过事件
2.1 要点
- 创建 EXTI 外部中断,配置 NVIC 中断优先级分组与 FreeRTOS 相同,即
4
。 - 触发 EXTI 外部中断,在中断服务函数中使用事件组置位函数
xEventGroupSetBitsFromISR()
。 - 使用等待事件函数
xEventGroupWaitBits()
,任务阻塞直到等待的事件发生。
2.2 实验
2.2.1 board_gpi.c
/*********************************************************************
* INCLUDES
*/
#include "board_gpi.h"
static void nvicConfig(void);
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief 按键驱动初始化
@param 无
@return 无
*/
void Board_KeyInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE); // 开启按键端口的时钟
// RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK | KEY2_GPIO_CLK, ENABLE); // 开启按键端口的时钟
nvicConfig(); // 配置NVIC中断
/*--------------------- KEY1 配置 ---------------------*/
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; // 选择按键的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置按键的引脚为浮空输入
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); // 使用结构体初始化按键
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, \
KEY1_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; // 选择EXTI的信号源
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // EXTI为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断
EXTI_Init(&EXTI_InitStructure);
/*--------------------- KEY2 配置 ---------------------*/
// GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; // 选择按键的引脚
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置按键的引脚为浮空输入
// GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); // 使用结构体初始化按键
//
// GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, \
// KEY2_INT_EXTI_PINSOURCE);
// EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; // 选择EXTI的信号源
// EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // EXTI为中断模式
// EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿中断
// EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断
// EXTI_Init(&EXTI_InitStructure);
}
/**
@brief 检测是否有按键按下
@param keyNum -[in] 按键编号
@return KEY_OFF(没按下按键)、KEY_ON(按下按键)
*/
uint8_t Board_KeyScan(uint8_t keyNum)
{
switch(keyNum)
{
case KEY1:
{
// 检测是否有按键按下
if(GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
{
// 等待按键释放
while(GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
{
return KEY_ON;
}
}
else
{
return KEY_OFF;
}
}
break;
// case KEY2:
// {
// // 检测是否有按键按下
// if(GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON)
// {
// // 等待按键释放
// while(GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON)
// {
// return KEY_ON;
// }
// }
// else
// {
// return KEY_OFF;
// }
// }
// break;
default:
break;
}
return 0;
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief 配置嵌套向量中断控制器NVIC
@param 无
@return 无
*/
static void nvicConfig(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 嵌套向量中断控制器组选择
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ; // 配置按键1为中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7; // 抢断优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure); // 初始化配置NVIC
// NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ; // 配置按键2为中断源,其他使用上面相关配置
NVIC_Init(&NVIC_InitStructure);
}
/****************************************************END OF FILE****************************************************/
2.2.2 board_gpi.h
#ifndef _BOARD_GPI_H_
#define _BOARD_GPI_H_
/*********************************************************************
* INCLUDES
*/
#include "stm32f10x.h"
/*********************************************************************
* DEFINITIONS
*/
// 按键1
#define KEY1_GPIO_CLK (RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO)
#define KEY1_GPIO_PORT GPIOD
#define KEY1_GPIO_PIN GPIO_Pin_6
#define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOD
#define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource6
#define KEY1_INT_EXTI_LINE EXTI_Line6
#define KEY1_INT_EXTI_IRQ EXTI9_5_IRQn
#define KEY1_IRQHandler EXTI9_5_IRQHandler
// 按键2
#define KEY2_GPIO_CLK (RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO)
#define KEY2_GPIO_PORT GPIOC
#define KEY2_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
#define KEY_ON 1
#define KEY_OFF 0
#define KEY1 0x01
#define KEY2 0x02
#define KEY1_EVENT (0x01 << 0) // 设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1) // 设置事件掩码的位1
/*********************************************************************
* API FUNCTIONS
*/
void Board_KeyInit(void);
uint8_t Board_KeyScan(uint8_t keyNum);
#endif /* _BOARD_GPI_H_ */
2.2.3 stm32f10x_it.c
#include "stm32f10x_it.h"
//FreeRTOS使用
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "board_gpi.h"
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
extern void xPortSysTickHandler(void);
// systick中断服务函数
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1)
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1)
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
extern EventGroupHandle_t g_eventHandle;
void KEY1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
xEventGroupSetBitsFromISR(g_eventHandle, KEY1_EVENT, &xHigherPriorityTaskWoken);
// 如果需要的话进行一次任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR(ulReturn);
}
void KEY2_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
xEventGroupSetBitsFromISR(g_eventHandle, KEY2_EVENT, &xHigherPriorityTaskWoken);
// 如果需要的话进行一次任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR(ulReturn);
}
2.2.4 main.c
/*********************************************************************
* INCLUDES
*/
// FreeRTOS
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/****************** 内核对象句柄 ******************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
EventGroupHandle_t g_eventHandle = NULL;
/**
@brief 按键检测任务主体
@param 无
@return 无
*/
static void keyTask(void *pvParameters)
{
EventBits_t rEvent; // 定义一个事件接收变量
while(1) // 任务都是一个无限循环,不能返回
{
rEvent = xEventGroupWaitBits(g_eventHandle, // 事件对象句柄
KEY1_EVENT, // 接收任务感兴趣的事件
pdTRUE, // 退出时清除事件位
pdTRUE, // 满足感兴趣的所有事件
portMAX_DELAY); // 指定超时事件,一直等
if((rEvent & KEY1_EVENT) == KEY1_EVENT)
{
vTaskDelay(50); // 消抖
if(Board_KeyScan(KEY1) == KEY_ON)
{
// 应用程序
}
}
else
{
printf("Event error!\n");
}
}
}
三、通过任务通知
3.1 要点
- 创建 EXTI 外部中断,配置 NVIC 中断优先级分组与 FreeRTOS 相同,即
4
。 - 触发 EXTI 外部中断,在中断服务函数中使用任务通知发送函数
xTaskNotifyFromISR()
。 - 使用等待通知函数
xTaskNotifyWait()
,任务阻塞直到等待的通知到达。
注意:要在 FreeRTOS v8.2.0
以上版本
3.2 实验
3.2.1 board_gpi.c
/*********************************************************************
* INCLUDES
*/
#include "board_gpi.h"
static void nvicConfig(void);
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief 按键驱动初始化
@param 无
@return 无
*/
void Board_KeyInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE); // 开启按键端口的时钟
// RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK | KEY2_GPIO_CLK, ENABLE); // 开启按键端口的时钟
nvicConfig(); // 配置NVIC中断
/*--------------------- KEY1 配置 ---------------------*/
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; // 选择按键的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置按键的引脚为浮空输入
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); // 使用结构体初始化按键
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, \
KEY1_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; // 选择EXTI的信号源
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // EXTI为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断
EXTI_Init(&EXTI_InitStructure);
/*--------------------- KEY2 配置 ---------------------*/
// GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; // 选择按键的引脚
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置按键的引脚为浮空输入
// GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); // 使用结构体初始化按键
//
// GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, \
// KEY2_INT_EXTI_PINSOURCE);
// EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; // 选择EXTI的信号源
// EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // EXTI为中断模式
// EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿中断
// EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断
// EXTI_Init(&EXTI_InitStructure);
}
/**
@brief 检测是否有按键按下
@param keyNum -[in] 按键编号
@return KEY_OFF(没按下按键)、KEY_ON(按下按键)
*/
uint8_t Board_KeyScan(uint8_t keyNum)
{
switch(keyNum)
{
case KEY1:
{
// 检测是否有按键按下
if(GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
{
// 等待按键释放
while(GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
{
return KEY_ON;
}
}
else
{
return KEY_OFF;
}
}
break;
// case KEY2:
// {
// // 检测是否有按键按下
// if(GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON)
// {
// // 等待按键释放
// while(GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON)
// {
// return KEY_ON;
// }
// }
// else
// {
// return KEY_OFF;
// }
// }
// break;
default:
break;
}
return 0;
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief 配置嵌套向量中断控制器NVIC
@param 无
@return 无
*/
static void nvicConfig(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 嵌套向量中断控制器组选择
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ; // 配置按键1为中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7; // 抢断优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure); // 初始化配置NVIC
// NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ; // 配置按键2为中断源,其他使用上面相关配置
NVIC_Init(&NVIC_InitStructure);
}
/****************************************************END OF FILE****************************************************/
3.2.2 board_gpi.h
#ifndef _BOARD_GPI_H_
#define _BOARD_GPI_H_
/*********************************************************************
* INCLUDES
*/
#include "stm32f10x.h"
/*********************************************************************
* DEFINITIONS
*/
// 按键1
#define KEY1_GPIO_CLK (RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO)
#define KEY1_GPIO_PORT GPIOD
#define KEY1_GPIO_PIN GPIO_Pin_6
#define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOD
#define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource6
#define KEY1_INT_EXTI_LINE EXTI_Line6
#define KEY1_INT_EXTI_IRQ EXTI9_5_IRQn
#define KEY1_IRQHandler EXTI9_5_IRQHandler
// 按键2
#define KEY2_GPIO_CLK (RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO)
#define KEY2_GPIO_PORT GPIOC
#define KEY2_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
#define KEY_ON 1
#define KEY_OFF 0
#define KEY1 0x01
#define KEY2 0x02
#define KEY1_EVENT (0x01 << 0) // 设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1) // 设置事件掩码的位1
/*********************************************************************
* API FUNCTIONS
*/
void Board_KeyInit(void);
uint8_t Board_KeyScan(uint8_t keyNum);
#endif /* _BOARD_GPI_H_ */
3.2.3 stm32f10x_it.c
#include "stm32f10x_it.h"
//FreeRTOS使用
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "board_gpi.h"
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
extern void xPortSysTickHandler(void);
// systick中断服务函数
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1)
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif /* INCLUDE_xTaskGetSchedulerState */
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1)
}
#endif /* INCLUDE_xTaskGetSchedulerState */
}
extern EventGroupHandle_t g_eventHandle;
void KEY1_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
xTaskNotifyFromISR(g_keyTaskHandle, KEY1_EVENT, eSetBits, &xHigherPriorityTaskWoken);
// 如果需要的话进行一次任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
// 清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR(ulReturn);
}
3.3.4 main.c
注意包含 limits.h
/*********************************************************************
* INCLUDES
*/
// FreeRTOS
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "limits.h"
TaskHandle_t g_keyTaskHandle = NULL; // 按键检测任务句柄
/**
@brief 按键检测任务主体
@param 无
@return 无
*/
static void keyTask(void *pvParameters)
{
uint32_t ulInterruptStatus;
while(1) // 任务都是一个无限循环,不能返回
{
// 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)
xTaskNotifyWait(0x00, // 在进入的时候不清除通知值的任何位
ULONG_MAX, // 在退出的时候复位通知值为0
&ulInterruptStatus, // 任务通知值传递到变量
portMAX_DELAY); // 指定超时事件,一直等
if((ulInterruptStatus & KEY1_EVENT) == KEY1_EVENT)
{
vTaskDelay(50); // 消抖
if(Board_KeyScan(KEY1) == KEY_ON)
{
// 应用程序
}
}
else
{
printf("Event error!\n");
}
}
}
• 由 Leung 写于 2021 年 1 月 8 日
网友评论