FreeRTOS
简介
FreeRTOS它是免费开源的,可以适用于很多的RAM内核,是一种实时操作系统。
实现流程
对于单核单片机而言,能够利用时间片的轮询调度进行任务间的流转,以实现多任务同时进行。
每个任务可以理解成一个线程,但是任务间的通信又像是进程和线程间通信的综合(不会的建议去看一看进程和线程间的通信)
时间片的流转其实就是任务状态的转换,具体查看如下图所示:
由图可见,FreeRTOS中的任务要是执行的话,只能从就绪态转换成运行态,此外,任务的状态还有挂起态和阻塞态,对于这两个状态比较复杂,使用到了钩子函数和空闲任务(后期讲解)。
不同优先级的任务的调度问题
图片解析:以两个任务为例,如果任务的优先级相同,任务A先执行,任务A将会执行一小段时间,然后就会切换任务进行任务B的执行,来回进行切换。
如果再不理解,可以查看FreeRTOS的任务切换
API函数的应用
此章不讲解关于中断的RTOS
一、创建任务
讲解的不是特别深入
注意点:如果不将宏置1的话,无法进行任务的创建。
(1)动态创建
task. h
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,//函数指针
const char * const pcName,//名称
const configSTACK_DEPTH_TYPE uxStackDepth,//任务的堆栈大小
void *pvParameters,//任务的传参
UBaseType_t uxPriority,//任务的优先级
TaskHandle_t *pxCreatedTask//任务的句柄
);
验证实例:
TaskHandle_t CreatedTask1=NULL;
void Task1(void *pvParameters )
{
/*代码区*/
for(;;)
{
/*任务的循环区*/
}
}
int main(void)
{
prvSetupHardware();
xTaskCreate(Task1,"Check1",128,NULL,3,&CreatedTask1);
vTaskStartScheduler();
return 0;
}
注意任务之间,进行公共资源的访问的话。例如:如果使用串口通信的话,会出现资源的抢占问题,导致串口的数据不完整输出。
(2)静态创建(不建议新手使用)
与动态创建唯一的不同之处是:
puxStackBuffer:用户指定的这块内存地址,作为任务堆栈的地址;
pxTaskBuffer:用户指定的这块地址作为,任务的控制块;
task. h
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer );
二、删除任务
使用删除函数的时候需要将宏INCLUDE_vTaskDelete 必须定义为 1
task. h
void vTaskDelete( TaskHandle_t xTask );
参数:任务的句柄
在实际应用当中基本不会用到。
如果用到了,可以采用以下流程:
- 将宏INCLUDE_vTaskDelete 置一
- 入口参数输入需要删除的任务句柄(NULL代表自身)
- 判断要删除的任务:{删除任务自身,需要添加到等待删除列表,内存释放在空闲任务,删除其他任务,释放内存,任务数量……}
- 更新下一个任务的阻塞时间:更新下一个任务的阻塞超出的时间,以防被删除的任务为下一个阻塞超时的对象
!!!注意:任务不可以重复删除。主要是跟内存的算法相关,(简单理解:删除之后就已经把那部分内存释放掉了,所以一直释放的话会出现一些错误)
(1)动态删除任务:
if(CirticalResourceSemaphoreHandle != NULL)
{
printf("任务LED3ORLED4删除了\r\n");
vTaskDelete(CirticalResourceSemaphoreHandle);//Delete LED3ORLED4
CirticalResourceSemaphoreHandle = NULL;
}
(2)静态删除任务(比较复杂)
/* 空闲任务配置 */
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
/* 软件定时器任务配置 */
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务内存分配 */
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer = &idle_task_tcb;
* ppxIdleTaskStackBuffer = idle_task_stack;
* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
void freertos_demo(void)
{
start_task_handler = xTaskCreateStatic( (TaskFunction_t ) start_task,
(char * ) "start_task",
(uint32_t ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(StackType_t * ) start_task_stack,
(StaticTask_t * ) &start_task_tcb );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
task1_handler = xTaskCreateStatic( (TaskFunction_t ) task1,
(char * ) "task1",
(uint32_t ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(StackType_t * ) task1_stack,
(StaticTask_t * ) &task1_tcb );
task2_handler = xTaskCreateStatic( (TaskFunction_t ) task2,
(char * ) "task2",
(uint32_t ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(StackType_t * ) task2_stack,
(StaticTask_t * ) &task2_tcb );
task3_handler = xTaskCreateStatic( (TaskFunction_t ) task3,
(char * ) "task3",
(uint32_t ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(StackType_t * ) task3_stack,
(StaticTask_t * ) &task3_tcb );
vTaskDelete(start_task_handler);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*删除代码块主要部分*/
if(task1_handler != NULL)
{
printf("删除task1任务\r\n");
vTaskDelete(task1_handler);
task1_handler = NULL;
}
三、信号量
简单来说这个相当于一个开关,进行开启和关闭
configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中被设置为 1。
(1)二进制信号量
简单介绍:先当于一个开关,计数器只有0和1,要么为开,要么为关,1,的时候表示可以执行,0的时候为阻塞状态,不可执行。适用于互斥间访问,确保同一时间只有一个任务被执行。
【1】使用STM32CodeMX进行创建
使用的osSemaphoreCreate()函数,其实调用的是xSemaphoreCreateBinary()函数。
【2】标准库的创建
SemaphoreHandle_t xSemaphore1;//只需要创建一个句柄来使用信号量
/*Test1*/
if(xSemaphoreTake(xSemaphore1,portMAX_DELAY) == pdTRUE)//pdTRUE:1
{
printf("Tast1\r\n");
osSemaphoreRelease(xSemaphore1);
vTaskDelay(1);
}
else
{
printf("Task1 xSemaphoreTake is err\r\n");
}
/*Test2*/
if (xSemaphoreTake(xSemaphore1, portMAX_DELAY) == pdTRUE)
{
printf("Task2\r\n");
xSemaphoreGive(xSemaphore1);
vTaskDelay(1);
}
else
{
printf("Task2 xSemaphoreTake is err\r\n");
}
陌生函数讲解:
获取信号量的函数:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
释放信号量:
osSemaphoreRelease
发送信号量的函数:
xSemaphoreGive
(2)计数信号量
定义:
①取值只有0与1两种状态的信号量称之为二值信号量;取值大于1的信号量称之为计数信号量
②计数信号量是一种长度大于1,消息大小为0的特殊消息队列。
③计数信号量的取值也可以为1,但通常大于1,如果取值为1,相当于只有0与1两种状态,则使用二值信号量即可。
意义:实现实现任务的同步或者资源共享功能。
计数信号量允许多个任务对其进行操作,但限制了任务的数量。
count也为可用的资源数量
API函数:
创建计数信号量 xSemaphoreCreateCounting()
释放计数信号量 xSemaphoreGive() 与 xSemaphoreGiveFromISR()
获取计数信号量 xSemaphoreTake()
删除计数信号量 vSemaphoreDelete()
实现流程:
/*Test02*/
if (xSemaphoreTake(myCountingSem01Handle, portMAX_DELAY) == pdTRUE)
{
printf("Test2 OK\r\n");
vTaskDelay(1);
}
else
{
printf("Test2 ERR\r\n");
}
/*Test01*/
if(xSemaphoreGive(myCountingSem01Handle) == pdTRUE)
{
printf("Test1 OK\r\n");
vTaskDelay(1);
}
else
{
printf("Test1 ERR\r\n");
}
四、互斥信号量
这段比较复杂,不理解的请跳过。
定义:
取值只有0与1两种状态的信号量称之为二进制信号量。 而互斥信号量是一种特殊的二值信号量,具有防止优先级翻转的特性。
应用:
在嵌入式操作系统中,互斥信号量用于临界资源的独占式访问,只能用于任务与任务间,因为其特有的优先级继承机制只能在任务中起作用,在中断的上下文环境毫无意义。
将上述的信号量换成互斥信号量即可使用
应用场景:
比如有两个任务需要通过同一串口发送数据,其硬件资源只有一个,那么两个任务不能同时发送,否则会导致数据错误。此时就可以用互斥信号量对串口资源进行保护,当任务1正在使用串口发送数据时,互斥信号量变为无效,任务2无法使用串口,任务2必须等待互斥信号量有效(任务1释放信号量),才能获得串口使用权,进而发送数据。
递归互斥信号量:递归互斥信号量是一种特殊的互斥信号量,支持拥有该信号量使用权的任务重复多次获取,而不会死锁。 任务成功获取几次递归互斥信号量,就要返还几次,在此之前,递归互斥信号量都处于无效状态。
优先级继承
作用:消除了优先级反转的问题
任务优先级修改的函数
//优先级的修改
//参数:1.句柄---NULL代表自身
//参数:2
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
//获取任务的优先级
//参数:任务的句柄,NULL代表本任务
uxTaskPriorityGet(TaskHandle_t xTask)
实例:
void StartDefaultTask(void const * argument)//低优先级
{
for(;;)
{
printf("this is Task01 %ld\r\n",uxTaskPriorityGet(NULL));
printf("将任务恢复到1\r\n");
vTaskPrioritySet(NULL,1);
osDelay(1);
}
}
void StartTask02(void const * argument)//高优先级
{
for(;;)
{
printf("this is Task02 Task01的优先级%ld\r\n",uxTaskPriorityGet(myTask01Handle));
printf("将任务提升到到4\r\n");
vTaskPrioritySet(myTask01Handle,4);
osDelay(1);
}
}
互斥信号量的实验验证:
此实验选自嵌入式实时操作系统——基于STM32Cube、FreeRTOS和Tracealyzer的应用开-》发课本
创建三个不同优先级的任务,Task01/Task02/Task03,其中Task03的优先级最高,其次是Task02,最小的优先级的是Task01
采用串口输出的方式来进行互斥信号量的检测,串口抢占,属于公共的资源的抢占,也可以使用全局变量来使用(不建议),会导致程序运行不正常,
此实验就会出现高优先级的会等待低优先级的运行完毕
实验代码:
void Task01(void const * argument)
{
for(;;)
{
if(xSemaphoreTake(CirtcalResourceMutexHandle,portMAX_DELAY) == pdTRUE)
printf("TASK01 is ring……\r\n");
xSemaphoreGive(CirtcalResourceMutexHandle);
osDelay(500);
osDelay(1);
}
}
void Task02(void const * argument)
{
for(;;)
{
printf("TASK02 is ring……\r\n");
osDelay(500);
osDelay(1);
}
}
void Task03(void const * argument)
{
for(;;)
{
if(xSemaphoreTake(CirtcalResourceMutexHandle,portMAX_DELAY) == pdTRUE)
printf("TASK03 is ring……\r\n");
osDelay(3000);
xSemaphoreGive(CirtcalResourceMutexHandle);
osDelay(500);
osDelay(1);
}
}
结果:TASK03 is ring…… 3s后
TASK02 is ring…… 持续执行直到Task03解除阻塞
TASK01 is ring…… 500ms后
五、任务挂起和恢复
//挂起函数
//参数:xTaskToSuspend是任务的句柄
// NULL代表自己本任务
vTaskSuspend(TaskHandle_t xTaskToSuspend);
//恢复函数
//参数:xTaskResume是任务的句柄
//NULL代表自己本任务
vTaskResume(TaskHandle_t xTaskToSuspend);
六、直达任务通知
最为使用,且比较难理解的
操作流程
函数 | 描述 |
xTaskNotify() | 发送通知,带有通知值并且不保留接收任务原通知值,用在任务中。 |
xTaskNotifyFromISR() | 发送通知,函数 xTaskNotify()的中断版本。 |
xTaskNotifyWait() | 接收通知 |
xTaskNotifyGive() | 发送通知,函数 xTaskNotifyGive()的中断版本。 |
vTaskNotifyGiveFromISR() | 发送通知,带有通知值并且保留接收任务的原通知值,用在任务中。 |
ulTaskNotifyTake() | 接收通知 |
以下使用这两种方式更加全面:
xTaskNotify()函数功能更强大,可以使用不同参数实现各类功能,兼容xTaskNotifyGive(),也更好用。
ulTaskNotifyTake()更灵活、强大,使用上也就更复杂,兼容xTaskNotifyWait(),也更好用。
//RTOS任务通知的API函数
//uxIndexToWaitOn:索引的值,一般在索引0处等待通知,这个函数不存在
//ulBitsToClearOnEntry:将清除任务通知值中的所有位,有效将值清除为0
//ulBitsToClearOnExit:设置的任何位都将在调用 RTOS 任务通知值中被清除,前提是接收到通知
//pulNotificationValue:RTOS 任务的通知值,不需要时设置为NULL
//xTicksToWait:在阻塞状态下等待接收通知的最长时间不会消耗任何 CPU 时间,时间以RTOS tick周期为单位,宏 pdMS_TO_TICKS() 可以 将以毫秒为单位的时间 转换为以 tick 为单位的时间。
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 );
//函数-->直达任务通知是直接发送至任务的事件, 可以取消阻塞接收任务,并 可以选择通过多种方式更新接收任务的某个通知值。
//uxIndexToWaitOn:调用任务的数组中通知值的索引,调用任务将在该通知值上等待通知 变成非零。xTaskNotifyTake() 没有此参数,总是在索引 0 处等待通知。
//xClearCountOnExit:如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdFALSE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前递减。 这 等同于 通过成功调用 xSemaphoreTake() 而递减计数信号量的值。如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdTRUE,则 RTOS 任务的通知值 在 ulTaskNotifyTake() 退出之前重设为 0。 这 等同于在成功调用 xSemaphoreTake() 后 将二进制信号量的值保留为 0 (或为空,或为“不可用” )。
//xTicksToWait:在阻塞状态下等待接收通知的最长时间, 如果通ulTaskNotifyTake() 被调用时 尚未挂起。处于阻塞状态的 RTOS 任务不会消耗 任何 CPU 时间。而时间以 RTOS tick 周期为单位。 宏 pdMS_TO_TICKS() 可以 将以毫秒为单位的时间 转换为以 tick 为单位的时间。
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
唯一的区别 是 ulTaskNotifyTakeIndexed() 可以在数组内的任何任务通知上运行, 而 ulTaskNotifyTake() 始终在数组索引 0 处的任务通知上运行。
注意:数组中每个通知都独立运行——在数组中的一个通知上一次只能阻塞一个任务,并且该任务不会被发送到任何其他数组索引处的通知取消阻塞。
方法一(二进制信号量)
不接收二进制信号量
实例:
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0,notify_val = 0,ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);//2s
for(;;)
{
printf("xTaskNotifyGiv %d time: ok\r\n", cnot_ok++);
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
//结果:
xTaskNotifyGiv 0 time: ok
xTaskNotifyGiv 1 time: ok
xTaskNotifyGiv 2 time: ok
xTaskNotifyGiv 3 time: ok
xTaskNotifyGiv 4 time: ok
结果的分析:
StartDefaultTask这个任务没有接收到StartTask02的二进制信号
接收二进制信号量
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0,notify_val = 0,ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);//2s
for(;;)
{
if(xTaskNotify( myTask01Handle, (uint32_t)0x00, eNoAction ) == pdPASS)//eNoAction 枚举
printf("xTaskNotifyGiv %d time: ok\r\n", cnot_ok++);
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
//实验结果:
xTaskNotifyGiv 0 time: ok
xTaskNotifyWait OK:0, data:0
xTaskNotifyGiv 1 time: ok
xTaskNotifyWait OK:1, data:0
xTaskNotifyGiv 2 time: ok
xTaskNotifyWait OK:2, data:0
StartDefaultTask这个任务可以接收到StartTask02的二进制信号
方法二(位信号量)
简单解释:通知的值 | ulValue,按位或
实例展示:
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0,notify_val = 0,ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,(uint32_t*)&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
char c;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);
for(;;)
{
for(i = 0;i < 3;i++)
{
c = cnot_tx;
cnot_tx++;
if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetBits ) == pdPASS)
printf("xTaskNotifyGiv %d time:ok,val:%d\r\n", cnot_ok++,c);
else
printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
}
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
结果:
xTaskNotifyGiv 0 time:ok,val:0
xTaskNotifyGiv 1 time:ok,val:1
xTaskNotifyGiv 2 time:ok,val:2
xTaskNotifyWait OK:0, data:3
xTaskNotifyGiv 3 time:ok,val:3
xTaskNotifyGiv 4 time:ok,val:4
xTaskNotifyGiv 5 time:ok,val:5
xTaskNotifyWait OK:1, data:7
结果分析:xTaskNotifyWait(形参1, 形参2, &形参3, portMAX_DELAY);形参1,形参2设置为0,不管有没有用减少影响,形参3(ulValue)是获取的值,形参4:是否使用阻塞态
本次实例使用的为8位
接收ulValue值的变化
方式三(计数信号量)
说明:通知值 = 原来的值 + 1
实例展示:
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0;
uint32_t ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
char c;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);
for(;;)
{
for(i = 0;i < 3;i++)
{
c = cnot_tx;
cnot_tx++;
if(xTaskNotify( myTask01Handle, (uint32_t)c, eIncrement ) == pdPASS)
printf("xTaskNotifyGiv %d time:ok,val:%d\r\n", cnot_ok++,c);
else
printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
}
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
//结果
xTaskNotifyGiv 0 time:ok,val:0
xTaskNotifyGiv 1 time:ok,val:1
xTaskNotifyGiv 2 time:ok,val:2
xTaskNotifyWait OK:0, data:3
xTaskNotifyGiv 3 time:ok,val:3
xTaskNotifyGiv 4 time:ok,val:4
xTaskNotifyGiv 5 time:ok,val:5
xTaskNotifyWait OK:1, data:6
xTaskNotifyGiv 6 time:ok,val:6
xTaskNotifyGiv 7 time:ok,val:7
xTaskNotifyGiv 8 time:ok,val:8
xTaskNotifyWait OK:2, data:9
实例结果分析:
xTaskNotifyWait(参数1,参数2,参数3,portMAX_DELAY),参数1和参数2必须设置为0,否则的话会影响到后边的计数问题。参数3是实际的计数值,参数4表示是否为阻塞态
方式四(不覆盖信号量)
解释说明:如果数据表示“有数据未读”,则此次嗲用xTaskNotify不做任何事情,并且返回pdFAIL,如果通知状态不是"pending"则表示没有新数据。
实例如下:
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0;
uint32_t ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
char c;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);
for(;;)
{
for(i = 0;i < 3;i++)
{
c = 'a'+cnot_tx;
cnot_tx++;
if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetValueWithoutOverwrite ) == pdPASS)
printf("xTaskNotifyGiv %d time:ok,val:%c\r\n", cnot_ok++,c);
else
printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
}
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
结果
xTaskNotifyGiv 0 time:ok,val:a
xTaskNotifyGiv 0 time:err
xTaskNotifyGiv 1 time:err
xTaskNotifyWait OK:0, data:97 ----> a的ASII码值
xTaskNotifyGiv 1 time:ok,val:d
xTaskNotifyGiv 2 time:err
xTaskNotifyGiv 3 time:err
xTaskNotifyWait OK:1, data:100
xTaskNotifyGiv 2 time:ok,val:g
xTaskNotifyGiv 4 time:err
xTaskNotifyGiv 5 time:err
xTaskNotifyWait OK:2, data:103
结果分析:
任务二中循环3次发送任务通知,只有第一次有效,第二第三次没有获取当中的值,发送任务失败,而任务1只接受了一次数值。
xTaskNotifyWait(),中的参数1,参数2设置为0,减少影响。参数3是获取的值,参数4是否使用阻塞态
方式五(覆盖信号量)
说明解释:相当于覆盖,无论如何,不管通知状态是否“表示数据未读”,通知值为&ulValue
实例展示:
void StartDefaultTask(void const * argument)
{
int cnot_ok = 0;
uint32_t ulValue = 0;
BaseType_t xResult;
for(;;)
{
xResult = xTaskNotifyWait(0x00,0x00,&ulValue,portMAX_DELAY);
if(xResult == pdPASS)
printf("xTaskNotifyWait OK:%d, data:%d\r\n",cnot_ok++,(int)ulValue);
osDelay(1);
}
}
void StartTask02(void const * argument)
{
int i = 0,cnot_tx = 0,cnot_ok = 0,cnot_err = 0;
char c;
const TickType_t xTicksToWait = pdMS_TO_TICKS(2000UL);
for(;;)
{
for(i = 0;i < 3;i++)
{
c = 'a'+cnot_tx;
cnot_tx++;
if(xTaskNotify( myTask01Handle, (uint32_t)c, eSetValueWithOverwrite ) == pdPASS)
printf("xTaskNotifyGiv %d time:ok,val:%c\r\n", cnot_ok++,c);
else
printf("xTaskNotifyGiv %d time:err\r\n",cnot_err++);
}
vTaskDelay(xTicksToWait);
osDelay(1);
}
}
结果:
xTaskNotifyGiv 0 time:ok,val:a
xTaskNotifyGiv 1 time:ok,val:b
xTaskNotifyGiv 2 time:ok,val:c
xTaskNotifyWait OK:0, data:99=c
xTaskNotifyGiv 3 time:ok,val:d
xTaskNotifyGiv 4 time:ok,val:e
xTaskNotifyGiv 5 time:ok,val:f
xTaskNotifyWait OK:1, data:102=f
七、任务控制
延时函数(vTaskDelay & vTaskDelayUntil & xTaskDelayUntil & xTaskAbortDelay)
函数解析:
函数:vTaskDelay
相对延时---->延时的ms
INCLUDE_vTaskDelay 定义为 1才可以使用
void vTaskDelay( const TickType_t xTicksToDelay );
函数:vTaskDelayUntil
绝对延时---->定时器
INCLUDE_vTaskDelayUntil 定义为1才可以使用
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );
函数:xTaskDelayUntil
参数1:保存最后一次时间解除的时间
参数2:周期时间段,固定的间隔时间
返回:
一个可用于检查任务是否实际延迟的值: 如果任务延迟,则返回 pdTRUE,否则返回 pdFALSE。 如果下一个预计唤醒时间已过,则任务将不会延迟。
pdMS_TO_TICKS()用来计算毫秒级别的节拍数
INCLUDE_xTaskDelayUntil 必须定义为 1
BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_t xTimeIncrement );
函数:xTaskAbortDelay(TaskHandle_t xTask)
参数:任务的句柄
返回值:如果引用的任务不在阻塞状态则返回pdFAIL,否则为pdPASS
BaseType_t xTaskAbortDelay( TaskHandle_t xTask );
函数分析:
- vTaskDelay()延时函数是相对延时函数,会使任务进入阻塞状态,暂时释放处理器资源,并在指定的时间后重新进行调度执行,而这个时间是相对于当前的时间,举例:如果vTaskDelay(1000);任务将在当前时间的基础上延时1000个节拍。
- vTaskDelayUntil()延时函数是绝对延时延时函数,会使任务进入阻塞状态,并在绝对的时间点后重新被调度执行,此时间是相对于系统的时间,例子:如果调度此函vTaskDelayUntil(&LastWakeTiime,pdMS_TO_TICKS(1000));任务将在LastWakeTiime记录的时间点上延时10000ms。
delay和vTaskDelay的区别:
阻塞方式:vTaskDelay()函数是通过阻塞当前任务的方式来实现延时。它会使当前任务暂时释放处理器资源,并在延时时间结束后重新被调度执行。而传统的delay延时函数是通过循环等待的方式来实现延时,它会占用处理器资源,并在延时时间结束后继续执行后面的代码。
精确性:vTaskDelay()函数是基于FreeRTOS的任务调度器来实现的,可以提供相对较高的延时精确性。它可以在任务级别进行精确的延时,不受其他任务的影响。而传统的delay延时函数通常是基于处理器的时钟周期进行延时,受到处理器速度和其他任务的影响,精确性较差。
多任务处理:vTaskDelay()函数是FreeRTOS多任务操作系统的一部分,可以与其他任务共享处理器资源,并能够在多个任务之间进行切换。而传统的delay延时函数通常是在单任务环境下使用,不适用于多任务操作系统。
总的来说就是任务调度器和时钟节拍的机制
实例展示:
/* 任务一,演示相对延时函数 */
void task1( void * pvParameters )
{
while(1)
{
LED0_TOGGLE(); /* PB1 */
delay_ms(20);
vTaskDelay(500);
}
}
/* 任务二,演示绝对延时函数 */
void task2( void * pvParameters )
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
LED1_TOGGLE(); /* PB0 */
delay_ms(20);
vTaskDelayUntil(&xLastWakeTime,500);
}
}
结果分析:任务一是相对延时函数vTaskDelay(),是相对于当前时间的延时,而任务二是绝对的延时函数vTaskDelayUntil(),是相对于整个系统时间的延时函数。
vTaskDelayUntil和xTaskDelayUntil的区别
xTaskDelayUntil具有返回值,用来判断是否执行了延时函数返回pdTRUE & pdFALSE,vTaskDelayUntil没有任何的返回值
八、获取任务的优先级
参数:任务的句柄,NULL表示本任务的句柄
返回值:任务的优先级
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask)
可以在中断函数中使用此函数获取任务的优先级
参数:任务的句柄,NULL表示会返回调用函数的任务的优先级
返回值:任务的优先级
UBaseType_t uxTaskPriorityGetFromISR( const TaskHandle_t xTask );
实例展示:
uxTaskPriorityGet(myTask01Handle);
uxTaskPriorityGet(NULL);
printf("task01:%d task02:%d\r\n",(int)uxTaskPriorityGet(myTask01Handle),(int)uxTaskPriorityGet(NULL));
if(uxTaskPriorityGet(NULL) > uxTaskPriorityGet(myTask01Handle))
printf("task02 is height\r\n");
结果分析:返回了任务的优先级
九、消息队列(重点)
作用:实现任务间以及中断的通信。通常来说,他们作为安全缓存区使用FIFO缓存区使用,新数据被发送到队列的后边,尽管数据也可以发送到前面。
1.创建消息队列
queue.h
configSUPPORT_DYNAMIC_ALLOCATION在FreeRTOSConfig.h 中被设置成1
动态创建消息队列:
uxQueueLength:队列可同时容纳的最大项目数
uxItemSize:存储队列中的每个数据项所需要的大小(数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 必须大小相同)
返回值:成功获得,使用此队列的句柄,如果创建队列所需要的内存无法分配,则返回NULL
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize);
2.发送消息
发送消息:
xQueue:要发送到消息队列的句柄
pvItemToQueue:指向待入队数据项的指针(创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区域)
xTicksToWait:等待时间
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
3.接收消息
接收消息队列:
xQueue:要发送到消息队列的句柄
pvBuffer:指向缓存区的地址
xTicksToWait:等待时间
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
4.删除消息队列
删除队列:
xQueue:要删除队列的句柄
void vQueueDelete( QueueHandle_t xQueue );
代码展示实例:
void Task1(void *pvParameters )
{
uint32_t dataReceived;
for(;;)
{
if(xQueueReceive(xQueueHandle1, &dataReceived, portMAX_DELAY))
{
printf("%d\n",dataReceived);
}
vTaskSuspend(NULL);
}
}
void Task2(void *pvParameters )
{
uint32_t toDataSend = 100;
xQueueHandle1 = xQueueCreate(QUEUE_LENGTH,ITEM_SIZE);
xQueueSend(xQueueHandle1,&toDataSend,portMAX_DELAY);
for(;;)
{
printf("Task3\r\n");
num1++;
vTaskSuspend(NULL);
}
}
结果:
看到这里你就可以进行进行FreeRTOS的基本使用了。
祝你们越用越顺手。