FreeRTOS学习笔记(12)——按键中断

一、简介

在 FreeRTOS 下实现按键中断可以有两种方法:

  • **通过事件的触发和等待:**可以实现一对多多对多的同步。即一个任务可以等待多个事件的发生;可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
  • 通过任务通知: FreeRTOS v8.2.0 以上版本。优点是解除阻塞的任务要快 45%,并且节省 RAM 内存空间。缺点是只能够一对一

二、通过事件

2.1 要点

  1. 创建 EXTI 外部中断,配置 NVIC 中断优先级分组与 FreeRTOS 相同,即 4
  2. 触发 EXTI 外部中断,在中断服务函数中使用事件组置位函数 xEventGroupSetBitsFromISR()
  3. 使用等待事件函数 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 要点

  1. 创建 EXTI 外部中断,配置 NVIC 中断优先级分组与 FreeRTOS 相同,即 4
  2. 触发 EXTI 外部中断,在中断服务函数中使用任务通知发送函数 xTaskNotifyFromISR()
  3. 使用等待通知函数 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 日

猜你喜欢

转载自blog.csdn.net/qq_36347513/article/details/112355870