FreeRTOS实战(三)·外部中断实现消息队列的发送与接收

#新星杯·14天创作挑战营·第11期#

目录

1.  NVIC

1.1  NVIC简介

1.2  NVIC基本结构

1.3  NVIC优先级分组

1.4  NVIC初始化

2.  EXTI

2.1  EXTI简介

2.2  EXTI基本结构

2.3  EXTI初始化

3.  程序设计

3.1  中断初始化配置

3.2  消息队列创建

3.3  中断服务函数配置

3.4  消息队列接收

3.5  实验现象


        通过之前入门系列的学习,我们对FreeRTOS的移植已经有了一个大概的概念,那么我们下面就给其投入到实际的使用当中。

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

1.  NVIC

1.1  NVIC简介

        首先,我们需要知道什么是中断?

        中断是在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。

        什么是中断优先级?

        中断优先级是:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。

        什么是中断嵌套?

        中断嵌套是:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

        STM32F1系列包含最多:68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。

        使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。

1.2  NVIC基本结构

NVIC:嵌套中断向量控制器

统一分配中断优先级,和管理中断

NVIC内核外设,CPU的小助手

        米色圈住部分,意思是:一个外设可能会占用多个中断通道,所以这里有“n”条线,NVIC只有一个输出口,NVIC根据每个中断的优先级进行分配中断的先后顺序。

例如:医院叫号,CPU为医生,NVIC进行排号,中断是病人,NVIC根据病人的紧急程度进行排号,找医生。

1.3  NVIC优先级分组

        NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级。

        抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。

        对于响应优先级,相当于“插队”,此时“1”在看病(程序在进行中),“4”要是比较严重,“4”可以进行插队到“2”前面,但是要等“1”看完(进程走完):

        对于抢占优先级(中断嵌套),相当于“1”在看病(程序在进行中),“4”不等“1”看完直接冲到屋内,把“1”推到一边,“4”先看病(进行),“4”先看完,在进行“1”,再依次进行后续的操作:

在FreeRTOS中我们主要使用的是优先级分组4,即所有优先级都是抢占优先级,这样FreeRTOS能完全控制哪些任务或中断可以抢占当前上下文。

1.4  NVIC初始化

        NVIC的配置非常简单:中断分组、定义结构体、配置中断线路、配置优先级、使能:

	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);	

	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量

	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10_IRQn线
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;	//指定NVIC线路的抢占优先级为6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			//指定NVIC线路的响应优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设	

2.  EXTI

2.1  EXTI简介

        EXTI(Extern Interrupt)外部中断 EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。

简单来说:电平变化,触发中断

支持的触发方式:上升沿/下降沿/双边沿/软件触发

上升沿触发

        数字电平从低电平(数字“0”)变为高电平(数字“1”)的那一瞬间叫作上升沿。 上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。

·下降沿触发

        数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。

那么我们可以很好的理解两种触发:

上升沿触发 就是当电压从低变高时触发中断
下降沿触发 就是当电压从高变低时触发中断

双边沿触发

        可以看做上升沿和下降沿的结合,也就是二者都可以触发中断。

软件触发

        引脚没变化,程序执行代码触发中断。

2.2  EXTI基本结构

        每个GPIO都有16个引脚,每个引脚都能触发中断,但是会通过AFIO中断引脚选择的模块,在GPIOA,GPIOB,GPIOC......在16个引脚中选择一个Pin连接。

        相当于在PA0,PB0,PC0......选个0,PA1,PB1,PC1......选个1,依次类推:

2.3  EXTI初始化

        根据上图我们进行初始化配置,首先先找到我们需要的引脚,这里我们使用PC13,然后定义一个结构体变量,然后因为我们配置的是PC13,那么就选择13为中断线,将中断线配置为中断模式,选择触发模式,使能:

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);//选择想要中断的引脚
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量

	EXTI_InitStructure.EXTI_Line = EXTI_Line13 ;		        //选择配置外部中断线
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;		//指定外部中断线为上升沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设	

3.  程序设计

        上面做了一个大概的了解,那么开始设计一个程序验证一下。

        在 FreeRTOS 中创建了两个任务获取消息队列,并且定义了两个按键 KEY1 与 KEY2 的触发方式为中断触发,其触发的中断服务函数则跟裸机一样,在中断触发的时候通过消息队列将消息传递给任务,任务接收到消息就将信息通过串口调试助手显示出来。

这里我们使用之前移植好的空白工程:

基于STM32F1系列FreeRTOS移植模版资源-CSDN文库

3.1  中断初始化配置

        我们先对中断文件进行配置,这里主要是对两个按键使能外部中断,首先我们对两个按键进行宏定义封装一下,方便后续更改:

//引脚定义
#define KEY1_INT_GPIO_PORT         GPIOA
#define KEY1_INT_GPIO_CLK          (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN          GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE    GPIO_PinSource0
#define KEY1_INT_EXTI_LINE         EXTI_Line0
#define KEY1_INT_EXTI_IRQ          EXTI0_IRQn

#define KEY1_IRQHandler            EXTI0_IRQHandler


#define KEY2_INT_GPIO_PORT         GPIOC
#define KEY2_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_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

        然后首先进行引脚的初始化操作:

//GPIO口配置
void GPIO_Configuration(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE);		//开启KEY1_INT_GPIO_CLK的时钟
	RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK, ENABLE);		//开启KEY2_INT_GPIO_CLK的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟

	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);			

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);			
}

        然后是对两个引脚的外部中断线路配置:

void EXTI_Configuration(void)
{
	/*AFIO选择中断引脚*/
	GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE);
	GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE);
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量

	EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;		//选择配置外部中断的0号线和1号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;		//指定外部中断线为上升沿触发
	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设	

	EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;		//选择配置外部中断的0号线和1号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发
	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设	
}

        然后是对NVIC的一些配置:

//NVIC配置
static void NVIC_Configuration(void)
{
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);	//配置NVIC为分组4
																                  //此分组配置在整个工程中仅需调用一次
																                  //若有多个中断,可以把此代码放在main函数内,while循环之前
																                  //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量

	//KEY1配置
	NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;			//选择配置NVIC的KEY1_INT_EXTI_IRQ线
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;	//指定NVIC线路的抢占优先级为6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			//指定NVIC线路的响应优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设	

	//KEY2配置
	NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;			//选择配置NVIC的KEY1_INT_EXTI_IRQ线
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;	//指定NVIC线路的抢占优先级为6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			//指定NVIC线路的响应优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设	
}

        配置完将上面函数放到一个函数中,回头主函数直接调用这个函数进行初始化操作即可:


//初始化文件
void EXTI_Config(void)
{
	GPIO_Configuration();
	NVIC_Configuration();
	EXTI_Configuration();
}

3.2  消息队列创建

        队列又称消息队列,是一种常用于任务间通信的数据结构,消息队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息。

对于消息队列这里不做过多的阐述,详细了解可以参考之前的入门系列讲解:

FreeRTOS菜鸟入门(十)·消息队列-CSDN博客

        首先既然是任务,那就需要任务句柄,来到主函数:

static TaskHandle_t LED_Task_Handle = NULL;/* 任务句柄 */

        然后对于消息队列,也要为其内核对象创建一个句柄,方便后续对消息队列的操作:

QueueHandle_t Test_Queue =NULL;

        对于创建一个队列,我们需要规定好队列的长度,以及队列中每个消息的大小:

#define  QUEUE_LEN    4   /* 队列的长度,最大可包含多少个消息 */
#define  QUEUE_SIZE   4   /* 队列中每个消息大小(字节) */

        准备完成后,我们开始在AppTaskCreate()函数进行对消息队列的创建,调用xQueueCreate()函数进行创建:

  /* 创建Test_Queue */
  Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                            (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
  
	if(NULL != Test_Queue)
    printf("Test_Queue消息队列创建成功!\r\n");

3.3  中断服务函数配置

        我们先看一下裸机的中断服务函数,通过调用EXTI_GetITStatus()函数查看中断是否被触发,在进行后续的操作:

void EXTI0_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)		//PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
			{
				Encoder_Count --;					//此方向定义为反转,计数变量自减
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断0号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

        我们对其更改一下,首先对于KEY1,为了防止其他中断打断操作我们进入临界段进行操作:

void KEY1_IRQHandler(void)
{
	BaseType_t pxHigherPriorityTaskWoken;
  //确保是否产生了EXTI Line中断
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();
  
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 
	{
		
		//清除中断标志位
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     
	}  
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

        当确认产生中断,我们就地调用消息队列发送函数xQueueSendFromISR()进行数据的发送:

    /* 将数据写入(发送)到队列中,等待时间为 0  */
		xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
											&send_data1,/* 发送的消息内容 */
											&pxHigherPriorityTaskWoken);

        发送完后,进行一次任务切换,如果有更高优先级的任务进行一次任务切换:

		//如果需要的话进行一次任务切换
		portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);

        这些配置我保存在stm32f10x_it.c文件,方便后续维护,完整配置:

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_it.c 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Main Interrupt Service Routines.
  *          This file provides template for all exceptions handler and 
  *          peripherals interrupt service routine.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_it.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"					 
#include "task.h" 
#include "queue.h"

//硬件外设
#include "LED.h"
#include "Usart.h"
#include "Key.h"  
#include "Exti.h"  

/** @addtogroup STM32F10x_StdPeriph_Template
  * @{
  */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
/*            Cortex-M3 Processor Exceptions Handlers                         */
/******************************************************************************/

/**
  * @brief  This function handles NMI exception.
  * @param  None
  * @retval None
  */
void NMI_Handler(void)
{
}

/**
  * @brief  This function handles Hard Fault exception.
  * @param  None
  * @retval None
  */
void HardFault_Handler(void)
{
  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Memory Manage exception.
  * @param  None
  * @retval None
  */
void MemManage_Handler(void)
{
  /* Go to infinite loop when Memory Manage exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Bus Fault exception.
  * @param  None
  * @retval None
  */
void BusFault_Handler(void)
{
  /* Go to infinite loop when Bus Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Usage Fault exception.
  * @param  None
  * @retval None
  */
void UsageFault_Handler(void)
{
  /* Go to infinite loop when Usage Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles SVCall exception.
  * @param  None
  * @retval None
  */
//void SVC_Handler(void)
//{
//}

/**
  * @brief  This function handles Debug Monitor exception.
  * @param  None
  * @retval None
  */
void DebugMon_Handler(void)
{
}

/**
  * @brief  This function handles PendSVC exception.
  * @param  None
  * @retval None
  */
//void PendSV_Handler(void)
//{
//}

/**
  * @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 */
}

/******************************************************************************/
/*                 STM32F10x Peripherals Interrupt Handlers                   */
/*  Add here the Interrupt Handler for the used peripheral(s) (PPP), for the  */
/*  available peripheral interrupt handler's name please refer to the startup */
/*  file (startup_stm32f10x_xx.s).                                            */
/******************************************************************************/

/**
  * @brief  This function handles PPP interrupt request.
  * @param  None
  * @retval None
  */
/*void PPP_IRQHandler(void)
{
}*/

/**
  * @}
  */ 

/* 声明引用外部队列 */
extern QueueHandle_t Test_Queue;

static uint32_t send_data1 = 1;
static uint32_t send_data2 = 2;

//KEY1_IRQHandler中断服务函数
void KEY1_IRQHandler(void)
{
	BaseType_t pxHigherPriorityTaskWoken;
  //确保是否产生了EXTI Line中断
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();
  
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 
	{
    /* 将数据写入(发送)到队列中,等待时间为 0  */
		xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
											&send_data1,/* 发送的消息内容 */
											&pxHigherPriorityTaskWoken);
		
		//如果需要的话进行一次任务切换
		portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
		
		//清除中断标志位
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     
	}  
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

//KEY2_IRQHandler中断服务函数
void KEY2_IRQHandler(void)
{
	BaseType_t pxHigherPriorityTaskWoken;
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();
  
  //确保是否产生了EXTI Line中断
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
	{
    /* 将数据写入(发送)到队列中,等待时间为 0  */
		xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
											&send_data2,/* 发送的消息内容 */
											&pxHigherPriorityTaskWoken);
		
		//如果需要的话进行一次任务切换
		portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
		
		//清除中断标志位
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     
	}  
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}


/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

3.4  消息队列接收

        配置完回到主函数,既然消息队列已经有发送了,那么下面我们在主函数创建一个接收任务:

static void LED_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t r_queue;	/* 定义一个接收消息的变量 */
  while (1)
  {
    /* 队列读取(接收),等待时间为一直等待 */
    xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
                             &r_queue,      /* 发送的消息内容 */
                             portMAX_DELAY); /* 等待时间 一直等 */
								
		if(pdPASS == xReturn)
		{
			printf("触发中断的是 KEY%d !\r\n",r_queue);
		}
		else
		{
			printf("数据接收出错!\r\n");
		}
		
    Toggle_LED_R();
  }
}

        同时对任务进行创建:

  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED_Task任务成功!\r\n");  

        完整主函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  
#include "Exti.h"  

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* 标准库头文件 */
#include <string.h>

/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
QueueHandle_t Test_Queue =NULL;

/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
#define  QUEUE_LEN    4   /* 队列的长度,最大可包含多少个消息 */
#define  QUEUE_SIZE   4   /* 队列中每个消息大小(字节) */

//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED_Task(void* pvParameters);/* LED_Task任务实现 */

static void All_Function_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建AppTaskCreate任务 */
		xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
													(const char*    )"AppTaskCreate",/* 任务名字 */
													(uint16_t       )512,  /* 任务栈大小 */
													(void*          )NULL,/* 任务入口函数参数 */
													(UBaseType_t    )1, /* 任务的优先级 */
													(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
		/* 启动任务调度 */           
		if(pdPASS == xReturn)
			vTaskStartScheduler();   /* 启动任务,开启调度 */
		else
			return -1;  
	}
}

//任务创建函数
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区

  /* 创建Test_Queue */
  Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                            (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
  
	if(NULL != Test_Queue)
    printf("Test_Queue消息队列创建成功!\r\n");
	
  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED_Task任务成功!\r\n");  
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void LED_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t r_queue;	/* 定义一个接收消息的变量 */
  while (1)
  {
    /* 队列读取(接收),等待时间为一直等待 */
    xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
                             &r_queue,      /* 发送的消息内容 */
                             portMAX_DELAY); /* 等待时间 一直等 */
								
		if(pdPASS == xReturn)
		{
			printf("触发中断的是 KEY%d !\r\n",r_queue);
		}
		else
		{
			printf("数据接收出错!\r\n");
		}
		
    Toggle_LED_R();
  }
}


//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

	//按键初始化
	Key_GPIO_Config();
  
	//外部中断初始化
	EXTI_Config();
}



3.5  实验现象

        可以看到数据正常传输。

完整工程:

FreeRTOS使用外部中断EXTI实现消息队列的发送与接收资源-CSDN文库

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

FreeRTOS实战系列_时光の尘的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值