引入队列:
在实际的运用和开发过程中,经常用到一个任务和中断服务函数或者是另外一个任务之间的“沟通交流”,又称之为消息或信息的传递和交互, 没有操作系统的时候通过全局变量的方式来进行, 但是FreeRTOS用全局变量的进行传递可能会造成“资源管理的问题”, 因为个个任务都是一个循环体,所以采用队列的方式来进行消息的传递在任务与任务之间。
1.队列的简化操如入下图所示,从此图可知:
- 队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
- 每个数据大小固定
- 创建队列时就要指定长度、数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
- 也可以强制写队列头部:覆盖头部数据
2.传输数据的两种方式(传输地址和传输实际值) FreeRTos采用的是值传递的方式,但也可以用地址的传输方式(当传输的数据较大的时候)
值传递的优劣处:
(1).可以灵活的调用局部变量(局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据)局部变量可以马上再次使用.
(2)无需分配buffer来保存数据,队列中有buffer
3.队列的阻塞的问题
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间到之后它也会进入就绪态。
当多个任务写消息给一个“满队列”时, 这些任务就会阻塞状态, 也就是又多个任务在等待同一个队列的空间, 那么当队列中有多余的空间的话:
(1)优先级最高的任务,(2)优先级相同的情况下,等待时间较久的先进
4.队列的相关的结构体和API函数分析:
队列结构体的定义:
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /*< 存储区域的起始地址. */
int8_t * pcWriteTo; /*< 下一个写入的地址(位置) */
union
{
QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */
SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
} u;
List_t xTasksWaitingToSend; /*< 等待发送的列表 */
List_t xTasksWaitingToReceive; /*< 等待接收的列表*/
volatile UBaseType_t uxMessagesWaiting; /*< 非空闲队列项目的数目*/
UBaseType_t uxLength; /*< 队列的长度 */
UBaseType_t uxItemSize; /*< 队列项目的大小*/
volatile int8_t cRxLock; /*< 读取上锁计数器(一般设置为开启)*/
volatile int8_t cTxLock; /*< 写入上锁计数器(一般设置为开启)*/
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
使用队列的主要流程: 创建队列->写队列->读队列
(1),队列的创建有两种方法:动态分配内存、静态分配内存(一般采用动态分配,比较简单)
函数原型如下:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
但是该函数仅仅是一个宏定义,其引用的为:
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
对于上面的宏定义知识熟悉和知道,对于下面的二值信号和计数型信号和互斥信号等的创建都是该函数,只是参数queueQUEUE_TYPE_BASE的修改。
(2),队列的写可以写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
/*
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
这些函数用到的参数是类似的,统一说明如下:
(3)队列的读取使用xQueueReceive()
函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken
);
参数说明如下:
(4)可以查询队列中有多少个数据、有多少空余空间。函数原型如下:
/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
4.队列的实验,(举出的例子为正点原子视频的视频中的)
设计四个任务,start_task , task1, task2, task3, task4
start_task: 创建其它的任务:
task1: 指示灯的闪烁显示系统的正常进行
task2: 按下按键一和二,键值入队key_queue,按下key_up(高电平触发的按键),拷贝大数据进入队列big_date_queue(通过传输地址的方式来进行)
task3: 读出key_queue的数据信息
task4:读出big_date_queue的数据信息
#include "freertos_demo.h"
/*FreeRTOS*********************************************************************************************************************/
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "malloc.h"
#include "queue.h"
#include "semphr.h"
#include "FreeRTOSConfig.h"
/**************************************************************************************************************************************************************/
/*FreeRTOS的配置*/
/* START_TASK的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
/* TASK1的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK1_PRIO 2 //任务优先级
#define TASK1_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1_Handler; //任务句柄
void task1(void *pvParameters); //任务函数
/* TASK2的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK2_PRIO 3 //任务优先级
#define TASK2_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2_Handler; //任务句柄
void task2(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK3_PRIO 4 //任务优先级
#define TASK3_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task3_Handler; //任务句柄
void task3(void *pvParameters); //任务函数
/* TASK4的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK4_PRIO 5 //任务优先级
#define TASK4_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task4_Handler; //任务句柄
void task4(void *pvParameters); //任务函数
/**************************************************************************************************************************************************************/
QueueHandle_t key_queue;
QueueHandle_t big_date_queue;
char buff[300] = {"fvcdxaasassass"};
/**
* @brief FreeRTOS例程入库函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
key_queue = xQueueCreate( 2, /*队列可同时容纳的最大项目数*/
sizeof( uint8_t) ); /*存储队列中的每个数据项所需的大小(以字节为单位)*/
if(key_queue != NULL) printf("key_queue队列创建成功\r\n");
else printf("key_queue队列创建失败\r\n");
big_date_queue = xQueueCreate( 1, /*队列可同时容纳的最大项目数*/
sizeof( char*) ); /*存储队列中的每个数据项所需的大小(以字节为单位)*/
if(big_date_queue != NULL) printf("big_date_queue队列创建成功\r\n");
else printf("big_date_queue队列创建失败\r\n");
xTaskCreate((TaskFunction_t ) start_task, /*指向任务函数的指针*/
(const char* )"start_task", /*任务名字(默认为16)*/
(uint16_t )START_STK_SIZE, /*任务的堆栈大小:单位为字*/
(void* )NULL, /*传递给任务函数的参数*/
(UBaseType_t )START_TASK_PRIO, /*任务的优先级*/
(TaskHandle_t* )&StartTask_Handler); /*任务句柄(任务的控制块:删除和添加)*/
vTaskStartScheduler(); //开启任务的调度函数
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区(关闭中断:就是那些不想被打断的程序段)
/*该函数的用于先退出中断,任务创建完毕后在开始从高优先级到低优先级的执行*/
xTaskCreate((TaskFunction_t ) task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t ) task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
xTaskCreate((TaskFunction_t ) task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
xTaskCreate((TaskFunction_t ) task4,
(const char* )"task4",
(uint16_t )TASK4_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK4_PRIO,
(TaskHandle_t* )&Task4_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*任务一, 实现LED0的每500ms反转一次*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务二,释放二值信号量 */
void task2(void *pvParameters)
{
uint8_t key = 0;
char *buf;
buf = buff;
while(1)
{
key = KEY_Scan(0);
switch(key)
{
case WKUP_PRES:
if(xQueueSend(
big_date_queue,
&buf,
portMAX_DELAY
) == pdTRUE) printf("big_date_queue队列发送成功\r\n");
else printf("big_date_queue队列发送失败\r\n");
break;
case KEY0_PRES:
if(xQueueSend(
key_queue,
&key,
portMAX_DELAY
) == pdTRUE) printf("key_queue队列发送成功\r\n");
else printf("key_queue队列发送失败\r\n");
break;
case KEY1_PRES:
if(xQueueSend(
key_queue,
&key,
portMAX_DELAY
) == pdTRUE) printf("key_queue队列发送成功\r\n");
else printf("key_queue队列发送失败\r\n");
break;
}
vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task3(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
if(xQueueReceive(
key_queue,
&key,
portMAX_DELAY
) == pdTRUE) printf("kcey_queue队列的数据key = %d\r\n", key);
//vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task4(void *pvParameters)
{
char* buf;
while(1)
{
if(xQueueReceive(
big_date_queue,
&buf,
portMAX_DELAY
) == pdTRUE) printf("big_date_queue队列的数据key = %s\r\n", buf);
//vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
执行效果:
上电初始化显示一和二行,创建队列成功,当按下wk_up按键,显示字符串,按下key1和key2显示对应的键值。
引入信号量:
前面介绍的队列(queue)可以用于传输数据:在任务之间、任务和中断之间。
有时候我们只需要传递状态,并不需要传递具体的信息,这个时候就有了信号量的概念。
引入信号的概念类似于我们在操作裸机的时候使用中断,众所周知,中断服务函数的代码应尽量的少,避免代码过多引起程序的不良。
信号分为:二值信号量:只取0和1
计数型信号量:取值没有限制
二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
信号量跟队列的对比
二值信号相关API函数
使用过程: 创建二值信号量->释放二值信号量->获取二值信号量
创建二值信号量:SemaphoreHandle_t xSemaphoreCreateBinary( void );
和上面的队列相同,是一个宏定义, 其声明为:(区别在于最后一个参数)
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
注意:调用这个函数的时候要导入#include "semphr.h"文件,否则会声明未定义。
SemaphoreHandle_t xSemaphoreCreateBinary( void );
xSemaphoreGive的函数原型如下:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
xSemaphoreGive函数的参数与返回值列表如下:
xSemaphoreTake函数的参数与返回值列表如下:
实验:使用二进制信号量来同步创建四个任务:
start_task: 用于创建其他的任务
task1:用于指示灯显示系统的运行状态
task2:用于释放信号量
task3: 用于获取二值信号量,并打印信息
#include "freertos_demo.h"
/*FreeRTOS*********************************************************************************************************************/
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "malloc.h"
#include "queue.h"
#include "semphr.h"
#include "FreeRTOSConfig.h"
/**************************************************************************************************************************************************************/
/*FreeRTOS的配置*/
/* START_TASK的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
/* TASK1的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK1_PRIO 2 //任务优先级
#define TASK1_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1_Handler; //任务句柄
void task1(void *pvParameters); //任务函数
/* TASK2的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK2_PRIO 3 //任务优先级
#define TASK2_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2_Handler; //任务句柄
void task2(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK3_PRIO 4 //任务优先级
#define TASK3_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task3_Handler; //任务句柄
void task3(void *pvParameters); //任务函数
/**************************************************************************************************************************************************************/
SemaphoreHandle_t binary_handle;
/**
* @brief FreeRTOS例程入库函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
binary_handle = xSemaphoreCreateBinary();
if(binary_handle != NULL) printf("二值信号量创建成功\r\n");
else printf("二值信号量创建失败\r\n");
xTaskCreate((TaskFunction_t ) start_task, /*指向任务函数的指针*/
(const char* )"start_task", /*任务名字(默认为16)*/
(uint16_t )START_STK_SIZE, /*任务的堆栈大小:单位为字*/
(void* )NULL, /*传递给任务函数的参数*/
(UBaseType_t )START_TASK_PRIO, /*任务的优先级*/
(TaskHandle_t* )&StartTask_Handler); /*任务句柄(任务的控制块:删除和添加)*/
vTaskStartScheduler(); //开启任务的调度函数
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区(关闭中断:就是那些不想被打断的程序段)
/*该函数的用于先退出中断,任务创建完毕后在开始从高优先级到低优先级的执行*/
xTaskCreate((TaskFunction_t ) task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t ) task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
xTaskCreate((TaskFunction_t ) task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*任务一, 实现LED0的每500ms反转一次*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务二,释放二值信号量 */
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
if(xSemaphoreGive( binary_handle) == pdTRUE) printf("二值信号量释放成功\r\n");
else printf("二值信号量释放失败\r\n");
}
vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task3(void *pvParameters)
{
while(1)
{
xSemaphoreTake( binary_handle,portMAX_DELAY); printf("二值信号量获取成功\r\n");
//vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
实验现象:
优先级反转的问题:
高优先级的后被执行,而低优先级的先被执行, 在实时系统中,是不允许的,可能会导致严重的后果。
高优先级和低优先级执行相同的操作,当低优先级的延时函数delay_ms大于高优先级的话,并在信号量获取不到的情况下,就会产生阻塞,导致高优先级任务迟迟得不到调度,但中优先级的任务却得到资源进而执行,这种情况类似优先级的反转,举例演示:
实验:使用二进制信号量来同步创建五个任务:
start_task: 用于创建其他的任务
task1:用于指示灯显示系统的运行状态
task2:低优先级的任务,获取二值信号量,获取成功以后打印提示信息吗,处理完毕后马上进行释放
task3: 中等优先级任务,用于提示和简单的任务
task4: 高优先级的任务,同低优先级的任务一样,不同之处在于延时的大小
#include "freertos_demo.h"
/*FreeRTOS*********************************************************************************************************************/
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "malloc.h"
#include "queue.h"
#include "semphr.h"
#include "FreeRTOSConfig.h"
/**************************************************************************************************************************************************************/
/*FreeRTOS的配置*/
/* START_TASK的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
/* TASK1的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK1_PRIO 2 //任务优先级
#define TASK1_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1_Handler; //任务句柄
void task1(void *pvParameters); //任务函数
/* TASK2的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK2_PRIO 3 //任务优先级
#define TASK2_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2_Handler; //任务句柄
void task2(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK3_PRIO 4 //任务优先级
#define TASK3_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task3_Handler; //任务句柄
void task3(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK4_PRIO 5 //任务优先级
#define TASK4_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task4_Handler; //任务句柄
void task4(void *pvParameters); //任务函数
/**************************************************************************************************************************************************************/
SemaphoreHandle_t Binary_handle;
/**
* @brief FreeRTOS例程入库函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Binary_handle = xSemaphoreCreateBinary();
if(Binary_handle == NULL) printf("二值信号量创建失败\r\n");
else printf("二值信号量创建成功\r\n");
xSemaphoreGive(Binary_handle);
xTaskCreate((TaskFunction_t ) start_task, /*指向任务函数的指针*/
(const char* )"start_task", /*任务名字(默认为16)*/
(uint16_t )START_STK_SIZE, /*任务的堆栈大小:单位为字*/
(void* )NULL, /*传递给任务函数的参数*/
(UBaseType_t )START_TASK_PRIO, /*任务的优先级*/
(TaskHandle_t* )&StartTask_Handler); /*任务句柄(任务的控制块:删除和添加)*/
vTaskStartScheduler(); //开启任务的调度函数
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区(关闭中断:就是那些不想被打断的程序段)
/*该函数的用于先退出中断,任务创建完毕后在开始从高优先级到低优先级的执行*/
xTaskCreate((TaskFunction_t ) task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t ) task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
xTaskCreate((TaskFunction_t ) task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
xTaskCreate((TaskFunction_t ) task4,
(const char* )"task4",
(uint16_t )TASK4_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK4_PRIO,
(TaskHandle_t* )&Task4_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*任务一, 实现LED0的每500ms反转一次*/
void task1(void *pvParameters)
{
while(1)
{
//HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务二,释放二值信号量 */
void task2(void *pvParameters)
{
while(1)
{
printf("low_task获取信号量\r\n");
xSemaphoreTake(Binary_handle,portMAX_DELAY);
printf("low_task运行\r\n");
delay_xms(3000);
printf("low_task释放信号量\r\n");
xSemaphoreGive(Binary_handle);
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task3(void *pvParameters)
{
while(1)
{
printf("middle_task正在运行\r\n");
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task4(void *pvParameters)
{
while(1)
{
printf("high_task获取信号量\r\n");
xSemaphoreTake(Binary_handle,portMAX_DELAY);
printf("high_task运行\r\n");
delay_xms(1000);
printf("high_task释放信号量\r\n");
xSemaphoreGive(Binary_handle);
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
实验现象:
从现象中可以看出,并结合上面的图: 高优先级的任务先执行,当执行到延时的函数时候,产生任务调度,此时中优先级的任务执行,随后中等优先级的延时函数作用,产生调度,则来到图的位置,获取信号量,当延时的时候,信号量并未释放,则高优先级会阻塞,中优先级执行,直到低优先级的延时过后,并释放信号量后,高优先级的任务再次执行。
互斥量信号:
用于尽可能的消除优先级反转的问题,并不能一定的消除,只是在很大的程度上。对于优先级的继承的问题,当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞,知道低优先级的互斥信号量被释放以后,并且这个高优先级的任务将低优先级任务的优先级提升到和自己相同的优先级。不会在像上面的中优先级的任务执行下去。
注意:互斥信号量不能用于中断中: 原因为:
(1)互斥信号量又任务优先级继承的机制,但是中断不是任务,没有任务的优先级,
(2)中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞状态。
并且互斥信号量在创建过程中会自动释放信号量。
相关API函数:
使用流程:创建互斥信号量, 获取信号, 释放信号
创建互斥信号量:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
创建互斥锁 ,并返回 一个该互斥锁可以引用的句柄。 中断服务例程中, 不能使用互斥锁。
返回:
如果已成功创建互斥锁类型信号量,则返回创建的 互斥锁的句柄。 如果 由于 创建递归互斥锁, 则返回 NULL。 |
实验:在上面的中断优先级反转的实验做出修改,
将优先级反装的二值信号量修改未互斥信号量,通过窗口观测其现象。
代码:
#include "freertos_demo.h"
/*FreeRTOS*********************************************************************************************************************/
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "malloc.h"
#include "queue.h"
#include "semphr.h"
#include "FreeRTOSConfig.h"
/**************************************************************************************************************************************************************/
/*FreeRTOS的配置*/
/* START_TASK的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
/* TASK1的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK1_PRIO 2 //任务优先级
#define TASK1_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1_Handler; //任务句柄
void task1(void *pvParameters); //任务函数
/* TASK2的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK2_PRIO 3 //任务优先级
#define TASK2_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2_Handler; //任务句柄
void task2(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK3_PRIO 4 //任务优先级
#define TASK3_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task3_Handler; //任务句柄
void task3(void *pvParameters); //任务函数
/* TASK3的任务配置
* 包括: 任务句柄,任务优先级,任务堆栈大小,创建任务
*/
#define TASK4_PRIO 5 //任务优先级
#define TASK4_STACK_SIZE 128 //任务堆栈大小
TaskHandle_t Task4_Handler; //任务句柄
void task4(void *pvParameters); //任务函数
/**************************************************************************************************************************************************************/
SemaphoreHandle_t Mutex_handle;
/**
* @brief FreeRTOS例程入库函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Mutex_handle = xSemaphoreCreateMutex();
if(Mutex_handle == NULL) printf("Mutex_handle信号量创建失败\r\n");
else printf("Mutex_handle信号量创建成功\r\n");
xTaskCreate((TaskFunction_t ) start_task, /*指向任务函数的指针*/
(const char* )"start_task", /*任务名字(默认为16)*/
(uint16_t )START_STK_SIZE, /*任务的堆栈大小:单位为字*/
(void* )NULL, /*传递给任务函数的参数*/
(UBaseType_t )START_TASK_PRIO, /*任务的优先级*/
(TaskHandle_t* )&StartTask_Handler); /*任务句柄(任务的控制块:删除和添加)*/
vTaskStartScheduler(); //开启任务的调度函数
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区(关闭中断:就是那些不想被打断的程序段)
/*该函数的用于先退出中断,任务创建完毕后在开始从高优先级到低优先级的执行*/
xTaskCreate((TaskFunction_t ) task1,
(const char* )"task1",
(uint16_t )TASK1_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t ) task2,
(const char* )"task2",
(uint16_t )TASK2_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
xTaskCreate((TaskFunction_t ) task3,
(const char* )"task3",
(uint16_t )TASK3_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3_Handler);
xTaskCreate((TaskFunction_t ) task4,
(const char* )"task4",
(uint16_t )TASK4_STACK_SIZE,
(void* )NULL,
(UBaseType_t )TASK4_PRIO,
(TaskHandle_t* )&Task4_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*任务一, 实现LED0的每500ms反转一次*/
void task1(void *pvParameters)
{
while(1)
{
//HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(10); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务二,释放二值信号量 */
void task2(void *pvParameters)
{
while(1)
{
printf("low_task获取信号量\r\n");
xSemaphoreTake(Mutex_handle,portMAX_DELAY);
printf("low_task运行\r\n");
delay_xms(3000);
printf("low_task释放信号量\r\n");
xSemaphoreGive(Mutex_handle);
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task3(void *pvParameters)
{
while(1)
{
printf("middle_task正在运行\r\n");
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
/*任务三, 获取信号量 */
void task4(void *pvParameters)
{
while(1)
{
printf("high_task获取信号量\r\n");
xSemaphoreTake(Mutex_handle,portMAX_DELAY);
printf("high_task运行\r\n");
delay_xms(1000);
printf("high_task释放信号量\r\n");
xSemaphoreGive(Mutex_handle);
vTaskDelay(1000); /* 每个任务函数都是一个死循环,必须加xTaskDelay()函数,用于任务的切换*/
}
}
实验现象:
可以看出,避免了任务优先级的反转。
队列集的应用:
一个队列只允许传递的消息为同一数据类型(二值信号量,互斥信号量, 计数型信号量,队列) ,如果需要在任务之间传递不同数据类型的信息时候,就可以使用队列集。
相关API的函数的分析
(1) QueueSetHandle_t xQueueCreateSet ( const UBaseType_t uxEventQueueLength );
必须在 FreeRTOSConfig.h 中将 configUSE_QUEUE_SETS 设置为 1,xQueueCreateSet() API 函数才可用。
uxEventQueueLength | 队列集存储集合中包含的队列和 信号量上发生的事件。 uxEventQueueLength 指定一次可以排队的最大事件数 。 |
- 如果队列集要保存一个长度为 5 的队列, 另一个长度为 12 的队列和一个二进制信号量, 则 uxEventQueueLength 应设置为 (5 + 12 + 1) 或 18。
- 如果队列集要容纳三个二进制信号量, 则 uxEventQueueLength 应设置为 (1 + 1 + 1) 或 3。
- 如果队列集要保持最大计数为 5 的计数信号量 和最大计数为 3 的计数信号量, 则 uxEventQueueLength 应设置为 (5 + 3) 或 8。
返回:
如果成功创建队列集,则返回所创建队列集的句柄 。 否则返回 NULL。
(2) BaseType_t xQueueAddToSet ( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );
必须在 FreeRTOSConfig.h 中将 configUSE_QUEUE_SETS 设置为 1,这样 xQueueAddToSet () API 函数才可用。
参数:
xQueueOrSemaphore | 正在添加到队列集的队列或信号量的句柄 (转换为 QueueSetMemberHandle_t 类型)。 |
xQueueSet | 正在添加队列或信号量的队列集句柄 。 |
返回:
如果队列或信号量成功添加到队列集 那么返回 pdPASS。 如果队列无法成功添加到 队列集,因为它已经是其他队列集的成员,那么返回 pdFAIL 。
(3) QueueSetMemberHandle_t xQueueSelectFromSet ( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait ); //从队列集成员中选择队列或信号量
参数:
xQueueSet | 任务(可能)阻塞的队列集。 |
xTicksToWait | 调用任务保持阻塞状态(其他任务正在执行), 等待队列集成员做好准备 以便成功读取队列或获取信号量所需的最长时间, 以滴答为单位。 |
返回:
xQueueSelectFromSet() 将返回 队列集中包含数据的队列的句柄(转换为 QueueSetMemberHandle_t 类型) 或队列集中可用信号量的句柄(转换为 QueueSetMemberHandle_t 类型), 如果在指定的阻塞时间到期之前不存在这样的队列或信号量, 则返回 NULL。
实验:
创建四个任务 start_task, task1,task2,task3
start_task: 创建其他的任务
task1: led的闪烁提示系统的正常运行
task2: 用于按键的扫描,当KEY0按下,往队列中写入数据,当KEY1按下,释放二值信号量
task3: 读取队列的信息,并打印
代码:
#include "freertos_demo.h"
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "queue.h"
#include "semphr.h"
/*********************************************************************************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/* TASK4 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK5_PRIO 5 /* 任务优先级 */
#define TASK5_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task5Task_Handler; /* 任务句柄 */
void task5(void *pvParameters); /* 任务函数 */
/*********************************************************************************************************************************************/
static QueueSetHandle_t xQueueSet; //队列集的句柄的定义
QueueHandle_t xQueue1,xSemaphore; //队列的句柄的创建
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
xQueueSet = xQueueCreateSet(2); //队列集的创建
if(xQueueSet != NULL) printf("队列集创建成功\r\n");
else printf("队列集创建失败\r\n");
xQueue1 = xQueueCreate( 1, sizeof(uint8_t) ); //队列的创建
if(xQueue1 != NULL) printf("队列创建成功\r\n");
else printf("队列创建失败\r\n");
xSemaphore = xSemaphoreCreateBinary(); //二值信号量的创建
if(xSemaphore != NULL) printf("二值信号量创建成功\r\n");
else printf("二值信号量创建失败\r\n");
if(xQueueAddToSet( xQueue1, xQueueSet ) == pdTRUE) printf("队列添加队列集成功\r\n"); //队列和信号量的添加
else printf("队列添加队列集失败\r\n");
if(xQueueAddToSet( xSemaphore, xQueueSet ) == pdTRUE) printf("二值信号量添加队列集成功\r\n");
else printf("二值信号量添加队列集失败\r\n");
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务 3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 延时 1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
if(xQueueSend(
xQueue1,
&key,
portMAX_DELAY
) == pdTRUE) printf("队列信息发送成功\r\n");
else printf("队列的信息发送失败\r\n");
}
else if(key == KEY0_PRES)
{
if(xSemaphoreGive(xSemaphore) == pdTRUE) printf("二值信号量释放成功\r\n");
else printf("二值信号量释放失败\r\n");
}
vTaskDelay(10); /* 延时 1000ticks */
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
QueueSetMemberHandle_t xActivatedMember;
uint8_t key = 0;
while(1)
{
xActivatedMember = xQueueSelectFromSet(
xQueueSet,
portMAX_DELAY
); //从队列集成员中选择队列或信号量
if( xActivatedMember == xQueue1 )
{
xQueueReceive( xQueue1, &key, portMAX_DELAY);
printf("队列的项目接受成功,key = %d\r\n", key);
}
else if( xActivatedMember == xSemaphore )
{
xSemaphoreTake( xSemaphore, portMAX_DELAY );
printf("二值信号量发送成功\r\n");
}
// vTaskDelay(500); /* 延时 1000ticks */
}
}
实验现象:
事件标志组的简介:
张三:我到了
李四:我到了
王五:我到了
组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。
在这个日常生活场景中:
出发:要等待这3个人都到齐,他们是"与"的关系
交报告:只需等待这3人中的任何一个,他们是"或"的关系
事件标志组是一个整数,用整数的每一位来表示一个任务,通过位的修改和添加来确定相应的任务的执行。(事件标志组的前八位不能用,用来表示内核的相关的操作)
和队列,信号量的不同点在于:
功能 | 唤醒对象 | 事件清除 |
队列,信号量 | 事件发生过后,只会唤醒有一个对象 | 是消耗性的资源,队列的数据一但读走就没了,信号量获取就减少了。 |
事件标志组 | 事件发生后,会唤醒所有符合条件的任务 | 被唤醒的任务,可以让事件保留,或者让事件消失 |
相关API函数的使用:
(1)EventGroupHandle_t xEventGroupCreate( void ); 创建一个新的 RTOS 事件组
返回:
如果创建了事件组, 则返回事件组的句柄。 如果没有足够的 FreeRTOS 堆可用于创建事件组, 则返回 NULL。
(2)EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ); 在 RTOS 事件组中设置位(标志)。
参数:
xEventGroup | 要设置位的事件组。 该 事件组必须已通过调用 通过调用 xEventGroupCreate() 创建。 |
uxBitsToSet | 指定要在事件组中设置的一个或多个位的按位值。 例如,将 uxBitsToSet 设置为 0x08,可仅设置位 3。 将 uxBitsToSet 设置 为 0x09,可设置位 3 和位 0。 |
返回:
调用 xEventGroupSetBits() 返回时事件组的值。
(3)EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ); //读取 RTOS 事件组中的位,选择性地进入“已阻塞”状态(已设置 超时值)以等待设置单个位或一组位。
参数:
xEventGroup | 正在测试位的事件组。 此前 必须已通过 xEventGroupCreate() 创建好事件组。 |
uxBitsToWaitFor | 等待的事件标志位 例如,要等待第 0 位和/或第 2 位, 请将 uxBitsToWaitFor 设置为 0x05。 要等待第 0 位和/或第 1 位和/或第 2 位, 请设置 uxBitsToWaitFor 为 0x07, 以此类推。uxBitsToWaitFor 不得设置为 0。 |
xClearOnExit | 是否清除相应的标志位。 如果 xClearOnExit 设置为 pdTRUE,清除 如果 xClearOnExit 设置为 pdFALSE,事件组中设置的位不会改变。 |
xWaitForAllBits | 等待时间组的标志位: 如果 xWaitForAllBits 设置为 pdTRUE,所有位 均已在事件组中设置好,或指定的阻塞时间已过期,则 xEventGroupWaitBits() 会返回相应值。 如果 xWaitForAllBits 设置为 pdFALSE,任何位已在事件组中设置好, 或指定的阻塞时间已过期,则 xEventGroupWaitBits() 会返回相应值。 |
xTicksToWait | 等待 uxBitsToWaitFor 指定的一个/所有(取决于 xWaitForAllBits 的值)位完成设置的最大时间 (单位:tick)。 |
返回:
返回值 | 描述 |
等待的事件标志位 | 等待事件标志位成功,返回事件标志位 |
其他值 | 等待事件标志位失败,返回事件标志位 |
实验:
创建四个任务 start_task, task1,task2,task3
start_task: 创建其他的任务
task1: led的闪烁提示系统的正常运行
task2: 用于按键的扫描,根据 不同键值将事件标志组相应的标志位至于一,模拟事件的发生
task3: 同时等待事件标志组中的多个事件位,当这些事件的标志位都置于一的话执行相应的处理。
代码:
#include "freertos_demo.h"
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
/*********************************************************************************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/* TASK4 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK5_PRIO 5 /* 任务优先级 */
#define TASK5_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task5Task_Handler; /* 任务句柄 */
void task5(void *pvParameters); /* 任务函数 */
/*********************************************************************************************************************************************/
EventGroupHandle_t xCreatedEventGroup; //创建事件标志组
#define BIT_0 ( 1 << 0 ) //创建事件标志位
#define BIT_1 ( 1 << 1 )
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
xCreatedEventGroup = xEventGroupCreate();
if( xCreatedEventGroup != NULL ) printf("事件组创建成功\r\n");
else printf("事件组创建失败\r\n");
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务 3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 延时 1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
xEventGroupSetBits( xCreatedEventGroup,
BIT_0);
}
else if(key == KEY0_PRES)
{
xEventGroupSetBits( xCreatedEventGroup,
BIT_1);
}
vTaskDelay(10); /* 延时 1000ticks */
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
EventBits_t uxBits;
while(1)
{
uxBits = xEventGroupWaitBits(
xCreatedEventGroup,
BIT_0|BIT_1,
pdTRUE,
pdTRUE,
portMAX_DELAY );
printf("等待到的事件标志位值为: %#x\r\n", uxBits);
// vTaskDelay(500); /* 延时 1000ticks */
}
}
实验结果:
任务通知:
任务通知值的更新方式
是否覆盖接收任务的通知值 (类似于队列的操作)
更新接收任务通知的一个或多个bit (类似于事件标志组)
增加接收任务的通知值 (类似于计数型信号量)
但是在一些场合中无法替代队列,信号量, 事件标志组
优缺点:效率高,节省内存(不用创建结构体变量) 缺点是无法在中断接收数据,无法在中断发送数据,任务的交互只能一对一,无法像队列那样可以一对多
每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:
一个是uint8_t类型,用来表示通知状态
一个是uint32_t类型,用来表示通知值
typedef struct tskTaskControlBlock
{
......
/* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
......
} tskTCB;
通知状态有3种取值:
- taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
- taskWAITING_NOTIFICATION:任务在等待通知
- taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 也是初始状态 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
通知值可以有很多种类型:
- 计数值
- 位(类似事件组)
- 任意数值
任务通知的相关函数的讲解:
发送函数:
(1)BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
(2)BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
(3)BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );
上面的三个函数就来源于
BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue ); 不同在于参数的配置参数的不一致
(1)发送通知,不带通知值 (2)发送通知,带通知值,(3),发送通知,带通知值,并保留原始值
参数:
xTaskToNotify | 正在通知的 RTOS 任务的句柄。 |
uxIndexToNotify | 目标任务通知值数组中的索引, 通知值将发送给该索引。 uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES。为0 |
ulValue | 任务的通知值。 |
eAction | 一种枚举类型,可以采用 下表中记录的值之一来执行相关操作。 |
pulPreviousNotifyValue | 用于保存更新之前的值, 如不需要,可设置为 NULL。 |
eAction 设置 | 已执行的操作 |
eNoAction | 不使用 ulValue。 |
eSetBits | 目标任务的通知值 使用 ulValue 按位或运算。 例如, 如果 ulValue 设置为 0x01,则将在 目标任务的通知值中设置位 0。 同样,如果 ulValue 为 0x04,则将在 目标任务的通知值中设置位 2。 通过这种方式,RTOS 任务通知机制 可以用作 事件组的轻量级替代方案。 |
eIncrement | 目标任务的通知值 自增 1,使得调用 xTaskNotify() 相当于调用 xTaskNotifyGive()。 在这种情况下,不使用 ulValue。 类似与计数型信号量 |
eSetValueWithOverwrite | 覆写的方式更新通知值 |
eSetValueWithoutOrwrite | 不覆写通知值 |
接收通知
(1)uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
(2)BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
上面的两个函数就来源于:
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );不同在于参数的配置参数的不一致
(1)获取任务通知,可以设置在退出函数的时候将任务通知清零或者减一(用于模拟信号量和计数型信号量)
(2)功能强
在调用 xTaskNotifyWait() 时,如果收到通知, 或通知已经挂起,则返回 pdTRUE。
如果调用 xTaskNotifyWait() 在收到通知之前超时, 则返回 pdFALSE。
参数:
uxIndexToWaitOn | 调用任务的数组中通知值的索引, 为0 |
xClearCountOnExit | 如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdFALSE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前递减。 这 等同于 通过成功调用 xSemaphoreTake() 而递减计数信号量的值。 如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdTRUE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前重设为 0。 这 等同于在成功调用 xSemaphoreTake() 后 将二进制信号量的值保留为 0 (或为空,或为“不可用” )。 |
xTicksToWait | 在阻塞状态下等待接收通知的最长时间, 如果通知在 ulTaskNotifyTake() 被调用时 尚未挂起。 处于阻塞状态的 RTOS 任务不会消耗 任何 CPU 时间。 |
返回:
被递减或清楚之前的任务通知值的值 (重点在于上表xClearCountOnExit的设置)。
实验(任务通知模拟信号量的实验)
创建四个任务 start_task, task1,task2,task3
start_task: 创建其他的任务
task1: led的闪烁提示系统的正常运行
task2: 用于按键的扫描,当检测到KEY0按下的时候,发送任务通知
task3: 用于接收任务的通知,并打印相关的信息
代码
#include "freertos_demo.h"
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
/*********************************************************************************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/* TASK4 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK5_PRIO 5 /* 任务优先级 */
#define TASK5_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task5Task_Handler; /* 任务句柄 */
void task5(void *pvParameters); /* 任务函数 */
/*********************************************************************************************************************************************/
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务 3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 延时 1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
printf("任务通知模拟二值信号量的释放\r\n");
xTaskNotifyGive(Task3Task_Handler);
}
vTaskDelay(10); /* 延时 1000ticks */
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
uint32_t value = 0;
while(1)
{
value = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
printf("接收的信息位value = %d\r\n", value);
// vTaskDelay(500); /* 延时 1000ticks */
}
}
实验现象:
实验(任务通知模拟队列的实验)
创建四个任务 start_task, task1,task2,task3
start_task: 创建其他的任务
task1: led的闪烁提示系统的正常运行
task2: 用于按键的扫描,当检测按键按下的时候,发送相应的键值
task3: 用于接收任务的通知,并打印相关的信息
代码
#include "freertos_demo.h"
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
/*********************************************************************************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/* TASK4 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK5_PRIO 5 /* 任务优先级 */
#define TASK5_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task5Task_Handler; /* 任务句柄 */
void task5(void *pvParameters); /* 任务函数 */
/*********************************************************************************************************************************************/
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务 3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 延时 1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if((key != 0)&&(Task3Task_Handler != NULL))
{
xTaskNotify( Task3Task_Handler,key,eSetValueWithOverwrite);
}
vTaskDelay(10); /* 延时 1000ticks */
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
uint32_t value = 0;
while(1)
{
xTaskNotifyWait( 0,0xffffffff, &value, portMAX_DELAY);
if(value == KEY0_PRES) printf("接收到的通知值为: %d\r\n", value);
else if(value == KEY1_PRES) printf("接收到的通知值为: %d\r\n", value);
// vTaskDelay(500); /* 延时 1000ticks */
}
}
实验结果:
实验(任务通知模拟事件标志组的实验)
创建四个任务 start_task, task1,task2,task3
start_task: 创建其他的任务
task1: led的闪烁提示系统的正常运行
task2: 用于按键的扫描,当检测按键按下的时候,发送相应的键值(标志位)
task3: 用于接收任务的通知,并打印相关的信息
代码
#include "freertos_demo.h"
#include "FreeRTOS.h" //os 使用
#include "task.h"
#include "led.h"
#include "key.h"
#include "usart.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
/*********************************************************************************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/* TASK4 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK5_PRIO 5 /* 任务优先级 */
#define TASK5_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task5Task_Handler; /* 任务句柄 */
void task5(void *pvParameters); /* 任务函数 */
/*********************************************************************************************************************************************/
#define BIT_0 ( 1 << 0 )
#define BIT_1 ( 1 << 1 )
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务 3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(500); /* 延时 1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == KEY0_PRES)
{
printf("bit0位置一\r\n");
xTaskNotify( Task3Task_Handler,BIT_0,eSetBits);
}
else if(key == KEY1_PRES)
{
printf("bit1位置一\r\n");
xTaskNotify( Task3Task_Handler,BIT_1,eSetBits);
}
vTaskDelay(10); /* 延时 1000ticks */
}
}
/**
* @brief task3
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
uint32_t value = 0;
uint32_t flag = 0;
while(1)
{
xTaskNotifyWait( 0,0xffffffff, &value, portMAX_DELAY);
if(value & BIT_0) flag |= BIT_0;
if(value & BIT_1) flag |= BIT_1;
if(flag == (BIT_0 | BIT_1))
{
printf("任务通知模拟事件标志组接收成功!!\r\n");
flag = 0;
}
}
}
实验结果: