从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(二十九)中断管理 NO.2 中断管理实验
中断管理实验是在FreeRTOS中创建了两个任务分别获取信号量与消息队列,并且定义了两个按键KEY1与KEY2的触发方式为中断触发,其触发的中断服务函数则跟裸机一样,在中断触发的时候通过消息队列将消息传递给任务,任务接收到消息就将信息通过串口调试助手显示出来。而且中断管理实验也实现了一个串口的DMA传输+空闲中断功能,当串口接收完不定长的数据之后产生一个空闲中断,在中断中将信号量传递给任务,任务在收到信号量的时候将串口的数据读取出来并且在串口调试助手中回显。
实验步骤:
- 硬件配置,外部中断
- 创建任务
- 创建消息队列与信号量
- 在任务中获取消息/信号量
- 在中断中释放消息、信号量
实验代码:
/************************************************************************ @file main.c* @version V1.0* @date 2018-xx-xx* @brief FreeRTOS V9.0.0 + STM32 中断管理*********************************************************************************************************************************************/ /*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_debug_usart.h"
#include "bsp_key.h"
#include "bsp_exti.h"/* 标准库头文件 */
#include <string.h>/**************************** 任务句柄 ********************************/
/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
QueueHandle_t Test_Queue =NULL;
SemaphoreHandle_t BinarySem_Handle =NULL;/******************************* 全局变量声明 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些全局变量。*/extern char Usart_Rx_Buf[USART_RBUFF_SIZE];/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) *//*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */static void LED_Task(void* pvParameters);/* LED_Task任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化 第二步:创建APP应用任务第三步:启动FreeRTOS,开始多任务调度****************************************************************/
int main(void)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("这是一个STM32全系列开发板-FreeRTOS中断管理实验!\r\n");printf("按下KEY1 | KEY2触发中断!\r\n");printf("串口发送数据触发中断,任务处理数据!\r\n");/* 创建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(); /* 启动任务,开启调度 */elsereturn -1; while(1);/* 正常不会执行到这里 */
}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无 * @ 返回值 : 无**********************************************************************/
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");/* 创建 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary(); if(NULL != BinarySem_Handle)printf("BinarySem_Handle二值信号量创建成功!\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");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}/*********************************************************************** @ 函数名 : LED_Task* @ 功能说明: LED_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
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");}LED1_TOGGLE;}
}/*********************************************************************** @ 函数名 : LED_Task* @ 功能说明: LED_Task任务主体* @ 参数 : * @ 返回值 : 无********************************************************************/
static void KEY_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */while (1){//获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */portMAX_DELAY); /* 等待时间 */if(pdPASS == xReturn){LED2_TOGGLE;printf("收到数据:%s\r\n",Usart_Rx_Buf);memset(Usart_Rx_Buf,0,USART_RBUFF_SIZE);/* 清零 */}}
}/************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 : * @ 返回值 : 无*********************************************************************/
static void BSP_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* DMA初始化 */USARTx_DMA_Config();/* 串口初始化 */Debug_USART_Config();/* 按键初始化 */Key_GPIO_Config();/* 按键初始化 */EXTI_Key_Config();}/********************************END OF FILE****************************/
/********************************************************************************* @file bsp_debug_usart.c* @version V1.0* @date 2015-xx-xx* @brief 重定向c库printf函数到usart端口,中断接收模式**************************************************************************************************************************************************************/ #include "bsp_debug_usart.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"/*** @brief 配置嵌套向量中断控制器NVIC* @param 无* @retval 无*/
static void NVIC_Configuration(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* 嵌套向量中断控制器组选择 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* 配置USART为中断源 */NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;/* 抢断优先级为1 */NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;/* 子优先级为1 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;/* 使能中断 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;/* 初始化配置NVIC */NVIC_Init(&NVIC_InitStructure);
}/*** @brief DEBUG_USART GPIO 配置,工作模式配置。115200 8-N-1 ,中断接收模式* @param 无* @retval 无*/
void Debug_USART_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_AHB1PeriphClockCmd(DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK,ENABLE);/* 使能 USART 时钟 */RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);/* GPIO初始化 */GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;/* 配置Tx引脚为复用功能 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN ; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);/* 配置Rx引脚为复用功能 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);/* 连接 PXx 到 USARTx_Tx*/GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE,DEBUG_USART_RX_AF);/* 连接 PXx 到 USARTx__Rx*/GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);/* 配置串DEBUG_USART 模式 *//* 波特率设置:DEBUG_USART_BAUDRATE */USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;/* 字长(数据位+校验位):8 */USART_InitStructure.USART_WordLength = USART_WordLength_8b;/* 停止位:1个停止位 */USART_InitStructure.USART_StopBits = USART_StopBits_1;/* 校验位选择:不使用校验 */USART_InitStructure.USART_Parity = USART_Parity_No;/* 硬件流控制:不使用硬件流 */USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;/* USART模式控制:同时使能接收和发送 */USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;/* 完成USART初始化配置 */USART_Init(DEBUG_USART, &USART_InitStructure); /* 嵌套向量中断控制器NVIC配置 */NVIC_Configuration();// 开启 串口空闲IDEL 中断USART_ITConfig(DEBUG_USART, USART_IT_IDLE, ENABLE); // 开启串口DMA接收USART_DMACmd(DEBUG_USART, USART_DMAReq_Rx, ENABLE); /* 使能串口 */USART_Cmd(DEBUG_USART, ENABLE);
}char Usart_Rx_Buf[USART_RBUFF_SIZE];void USARTx_DMA_Config(void)
{DMA_InitTypeDef DMA_InitStructure;// 开启DMA时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);// DMA复位DMA_DeInit(DEBUG_USART_DMA_STREAM); // 设置DMA通道DMA_InitStructure.DMA_Channel = USART_RX_DMA_CHANNEL; /*设置DMA源:串口数据寄存器地址*/DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADDRESS;// 内存地址(要传输的变量的指针)DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Usart_Rx_Buf;// 方向:从内存到外设 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;// 传输大小 DMA_InitStructure.DMA_BufferSize = USART_RBUFF_SIZE;// 外设地址不增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 内存地址自增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 外设数据单位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// 内存数据单位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // DMA模式,一次或者循环模式//DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 优先级:中 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 禁止内存到内存的传输/*禁用FIFO*/DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; /*存储器突发传输 1个节拍*/DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /*外设突发传输 1个节拍*/DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /*配置DMA2的数据流7*/ DMA_Init(DEBUG_USART_DMA_STREAM, &DMA_InitStructure);// 清除DMA所有标志DMA_ClearFlag(DEBUG_USART_DMA_STREAM,DMA_FLAG_TCIF2);DMA_ITConfig(DEBUG_USART_DMA_STREAM, DMA_IT_TE, ENABLE);// 使能DMADMA_Cmd (DEBUG_USART_DMA_STREAM,ENABLE);
}extern SemaphoreHandle_t BinarySem_Handle;void Uart_DMA_Rx_Data(void)
{BaseType_t pxHigherPriorityTaskWoken;// 关闭DMA ,防止干扰DMA_Cmd(DEBUG_USART_DMA_STREAM, DISABLE); // 清DMA标志位DMA_ClearFlag(DEBUG_USART_DMA_STREAM,DMA_FLAG_TCIF2); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目DMA_SetCurrDataCounter(DEBUG_USART_DMA_STREAM,USART_RBUFF_SIZE); DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE); /* xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);*///给出二值信号量 ,发送接收到新数据标志,供前台程序查询xSemaphoreGiveFromISR(BinarySem_Handle,&pxHigherPriorityTaskWoken); //释放二值信号量//如果需要的话进行一次任务切换,系统会判断是否需要进行切换portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);}/***************** 发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{/* 发送一个字节数据到USART */USART_SendData(pUSARTx,ch);/* 等待发送数据寄存器为空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}/***************** 发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{unsigned int k=0;do {Usart_SendByte( pUSARTx, *(str + k) );k++;} while(*(str + k)!='\0');/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET){}
}/***************** 发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch&0XFF00)>>8;/* 取出低八位 */temp_l = ch&0XFF;/* 发送高八位 */USART_SendData(pUSARTx,temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 发送低八位 */USART_SendData(pUSARTx,temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口 */USART_SendData(DEBUG_USART, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET); return (ch);
}//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USART);
}
/*********************************************END OF FILE**********************/
/********************************************************************************* @file FMC_SDRAM/stm32f4xx_it.c * @author MCD Application Team* @version V1.0.1* @date 11-November-2013* @brief Main Interrupt Service Routines.* This file provides template for all exceptions handler and* peripherals interrupt service routine.******************************************************************************* @attention** <h2><center>© COPYRIGHT 2013 STMicroelectronics</center></h2>** Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");* You may not use this file except in compliance with the License.* You may obtain a copy of the License at:** http://www.st.com/software_license_agreement_liberty_v2** Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.********************************************************************************//* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_it.h"/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_debug_usart.h"
#include "bsp_key.h"
#include "bsp_exti.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 QueueHandle_t Test_Queue;
extern SemaphoreHandle_t BinarySem_Handle;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 );
}/********************************************************************************** @ 函数名 : KEY1_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 );
}/********************************************************************************** @ 函数名 : DEBUG_USART_IRQHandler* @ 功能说明: 串口中断服务函数* @ 参数 : 无 * @ 返回值 : 无********************************************************************************/
void DEBUG_USART_IRQHandler(void)
{uint32_t ulReturn;/* 进入临界段,临界段可以嵌套 */ulReturn = taskENTER_CRITICAL_FROM_ISR();if(USART_GetITStatus(DEBUG_USART,USART_IT_IDLE)!=RESET){ Uart_DMA_Rx_Data(); /* 释放一个信号量,表示数据已接收 */USART_ReceiveData(DEBUG_USART); /* 清除标志位 */} /* 退出临界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
实验现象: