API函数 | 描述 |
xTaskCreate() | 动态方式创建任务,FreeRTOS自动管理内存 |
xTaskCreateStatic() | 静态方式创建任务,手动分配内存,适用精细控制 |
vTaskDelete() | 删除任务 |
API函数 | 描述 |
vTaskSuspend() | 挂起任务, 类似暂停,可恢复 |
vTaskResume() | 恢复被挂起的任务 |
xTaskResumeFromISR() | 在中断中恢复被挂起的任务 |
启动调度器:
vTaskStartScheduler();
//自动帮我们创建空闲调度和软件定时器调度等
/*问是每一次创建新任务都要写一次启动调度器吗?不是的,启动调度器就相当去给FreeRTOS开机,用一次即行*/
动态创建任务函数:
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
/*最小栈大小(128*4个字节内存空间),单位4字节,
32位平台单位就是4字节,如果是16位平台单位就是2字节*/
- 使用动态分配的前提
- 将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1。
- 定义函数入口参数。(准备用xTaskCreate服务的函数)
- 编写任务函数。(创建对应的xTaskCreate函数)
- 此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
在keil5工程中,ctrl+f填写对应内容找到配置支持动态分配的宏定义,发现其是有默认值1的
void start_task(void * pvParameters){}
/*1.创建一个启动任务*/
xTaskCreate((TaskFunction_t)start_task,//FreeRTOS为start_task函数服务
(char *)"stare_task", //任务别名,我一般选择同名好记住
(uint16_t) START_TASK_STACK, //给函数创建的资源任务栈的大小
(void *) NULL,//为start_tack函数形参,不传参就置空NULL
(UBaseType_t) START_TASK_Priority, //执行优先级,数值越大优先级越高
(TaskHandle_t *) &start_task_handle);//叫句柄其实就是会将TCP地址赋值给它,即启动任务的地址
返回值:
pdPASS:任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任务创建失败。
任务删除函数:
在函数体内写该函数:传入参数NULL表示执行完自己的所有代码后删除自己,传入其他函数的句柄表示删除其他函数
用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除
注意:使用删除函数,空闲任务会负责释放被删除任务中由系统分配的内存(例如:动态创建任务的内存由FreeRTOS分配算法实现就属于系统内存和静态创建任务内存),但是由用户在任务删除前申请的内存(静态创建任务中我们手动分配的内存),则需要由用户在任务被删除前提前释放,否则将导致内存泄露。
如果删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行(目的:实现先执行完自身的所有代码后再实现彻底的删除自身);如果删除其他任务(直接删除),释放内存,任务数量--。
void vTaskDelete( TaskHandle_t xTaskToDelete )
使用前提和方法:
需将宏INCLUDE_vTaskDelete 配置为 1(默认值是关闭的)
入口参数输入需要删除的任务句柄(NULL代表删除本身)
代码例子:(涵盖动态创建任务,删除任务,进入/退出临界区,启用调度器)
思路:
1.创建启动任务(创建其他任务)
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
/*注意FREERTOS的优先级和32优先级的数值定义不同,FREERTOS是数大的优先级高*/
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "main.h"
#include "stdio.h"
void freertos_start(void);
void task1(void * pvParameters);
void task2(void * pvParameters);
void task3(void * pvParameters);
void start_task(void * pvParameters);
#endif
注意创建任务传参我们不需要记,知道如何用通过跳转到定义,复制定义内容到实际操作文件,复制后根据形参提示更改实参即可
#include "FreeRTOS_demo.h"
#define START_TASK_STACK 128//栈内存空间
#define START_TASK_Priority 1//优先级,FreeRTOS的优先级是数值越高优先级越大
TaskHandle_t start_task_handle;//定义句柄变量
#define START_TASK_STACK 128
#define TASK1_Priority 3
TaskHandle_t task1_handle;
#define START_TASK_STACK 128
#define TASK2_Priority 4
TaskHandle_t task2_handle;
#define START_TASK_STACK 128
#define TASK3_Priority 5
TaskHandle_t task3_handle;
/**
* @description: 启动Freertos
* @return: {*}
*/
void freertos_start(void){
/*1.创建一个启动任务*/
//强转实参确保安全性
xTaskCreate((TaskFunction_t)start_task,//创建任务其对应执行函数是
(char *)"stare_task", //任务别名
(uint16_t) START_TASK_STACK, //资源任务栈的大小
(void *) NULL,//函数形参
(UBaseType_t) START_TASK_Priority, //执行优先级
(TaskHandle_t *) &start_task_handle);//叫句柄其实就是会将TCP地址赋值给它,即启动任务的地址
/*2.启动调度器*/
vTaskStartScheduler();//自动帮我们创建空闲调度和软件定时器调度等
/*问是每一次创建新任务都要写一次启动调度器吗?不是的,启动调度器就相当去给FreeRTOS开机,用一次即行*/
}
/**
* @description:开始任务就起到个创建多任务的作用,用完就丢掉
* @return: 返回值说明
*/
void start_task(void * pvParameters){
taskENTER_CRITICAL();/* 进入临界区 ,避免顺序乱套*/
xTaskCreate((TaskFunction_t)task1,//创建任务其对应执行函数是
(char *)"task1", //任务别名
(uint16_t) START_TASK_STACK, //资源任务栈的大小
(void *) NULL,//函数形参
(UBaseType_t) TASK1_Priority, //执行优先级
(TaskHandle_t *) &task1_handle);//叫句柄其实就是会将TCP地址赋值给它,即启动任务的地址
xTaskCreate((TaskFunction_t)task2,//创建任务其对应执行函数是
(char *)"task2", //任务别名
(uint16_t) START_TASK_STACK, //资源任务栈的大小
(void *) NULL,//函数形参
(UBaseType_t) TASK2_Priority, //执行优先级
(TaskHandle_t *) &task2_handle);//叫句柄其实就是会将TCP地址赋值给它,即启动任务的地址
xTaskCreate((TaskFunction_t)task3,//创建任务其对应执行函数是
(char *)"task3", //任务别名
(uint16_t) START_TASK_STACK, //资源任务栈的大小
(void *) NULL,//函数形参
(UBaseType_t) TASK3_Priority, //执行优先级
(TaskHandle_t *) &task3_handle);//叫句柄其实就是会将TCP地址赋值给它,即启动任务的地址
vTaskDelete(NULL);//删除自身是逻辑删除,执行完自身一遍时才会真正删除
taskEXIT_CRITICAL();/* 退出临界区 */
}
2.写具体实现函数体
/**
* @description: 实现LED1每500ms闪烁一次
* @return: 返回值说明
*/
void task1(void * pvParameters){
while(1){
char *msg1 = "任务1\r\n";
HAL_UART_Transmit(&huart1,(uint8_t *)msg1,sizeof(msg1)-1,0xff);
HAL_GPIO_TogglePin(GPIOA,LED1_Pin);
// //开中断
// if(HAL_GPIO_ReadPin(GPIOA,KEY4_Pin)==0){
// HAL_Delay(10);//阻塞调度不让时间片
// if(HAL_GPIO_ReadPin(GPIOA,KEY4_Pin)==0){
// printf("关中断\r\n");
// portDISABLE_INTERRUPTS();//会将5~15的中断优先级屏蔽掉
}
vTaskDelay(1000);//让出时间片,实现任务调度
}
/**
* @description: 实现LED2每500ms闪烁一次
* @return: 返回值说明
*/
void task2(void * pvParameters){
while(1){
char *msg2 = "任务2";
HAL_UART_Transmit(&huart1,(uint8_t *)msg2,sizeof(msg2)-1,0xff);
HAL_GPIO_TogglePin(GPIOA,LED2_Pin);
vTaskDelay(1000);
}
}
/**
* @description: 按下KEY1删除task1
* @return: 返回值说明
*/
void task3(void * pvParameters){
while(1){
char *msg2 = "T3";
HAL_UART_Transmit(&huart1,(uint8_t *)msg2,sizeof(msg2)-1,0xff);
if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==0){
HAL_Delay(10);//阻塞调度不让时间片
if(HAL_GPIO_ReadPin(GPIOA,KEY1_Pin)==0){
if(task1_handle!=NULL){
vTaskDelete(task1_handle);//删除后让句柄置空,就不会反复执行删除函数
task1_handle=NULL;
}}
开中断
// char *msg2 = "开中断....\r\n";
// HAL_UART_Transmit(&huart1,(uint8_t *)msg2,sizeof(msg2)-1,0xff);
//portENABLE_INTERRUPTS();
}
vTaskDelay(10);//让时间片,其底层逻辑是进入临界区(关中断)和退出临界区(通过开中断实现开中断)
}
}
进入临界区:
进入临界区,避免顺序乱套,例如创建启动函数去创建3个任务,成功创建任务1,由于任务1 比启动任务的优先级高,所以启动任务就被挂起,任务1就开始运行,等到释放出时间片时,才会轮到启动任务去创建任务2和3,但是我们原先设定的逻辑的优先级任务3>任务2>任务1>启动任务,你还没有创建完所有任务,就开始工作了,赶鸭子上架,不符合逻辑,所以要用临界区来限制
taskENTER_CRITICAL();/* 进入临界区 ,避免顺序乱套*/
退出临界区:
taskEXIT_CRITICAL();/* 退出临界区*/
静态创建任务函数:
TaskHandle_t xTaskCreateStatic
(
TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */
const char * const pcName, /* 任务函数名 */
const uint32_t ulStackDepth, /* 任务堆栈大小,单位是4字节 */
void * const pvParameters, /* 传递的任务函数参数 */
UBaseType_t uxPriority, /* 任务优先级 */
StackType_t * const puxStackBuffer, /* 任务堆栈,一般为数组,由用户分配 */
StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配 */
)
返回值:
NULL:用户没有提供相应的内存,任务创建失败。
其他值:任务句柄,任务创建成功。
实现步骤:
(1)将宏configSUPPORT_STATIC_ALLOCATION 配置为 1。
(2)定义空闲任务&定时器任务的任务堆栈及TCB。
(3)实现接口函数:
vApplicationGetIdleTaskMemory()应用获取空闲任务内存
vApplicationGetTimerTaskMemory()(如果开启软件定时器)应用获取定时器任务内存
(4)定义函数入口参数。
(5)编写任务函数。 此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
按要求定义变量还要注意数据类型 我们可以查看下:转到声明可以看到StackType_t的数据类型是uint32_t
以此类推StaticTask_t类型是一个结构体类型
数值长度就是栈深度,FreeRTOS的configFreeRTOS.h文件(里面都是配置FreeRTOS的要求)有要求
3
那么我们就将任务堆栈的大小也定成FreeRTOS可以接收的最小值,具体分配看你业务要求
一下代码和动态创建的业务逻辑一致,所以例子只举了需要手动创建内存的部分
在FreeRTOS.h文件中开启配置支持静态创建任务
#define configSUPPORT_STATIC_ALLOCATION 1
静态创建方式需要实现的两个接口函数:
关于创建静态任务还要开启vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );函数:一下是函数的要求
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize );
使用软件定时器,会创建定时器任务,帮助我们创建静态任务
在FreeRTOS.h文件中开启,开启后启动任务调度器(vTaskStartScheduler();),就会自动创建空闲任务和软件定时器
//开启软件定时器
#define configUSE_TIMERS 1
关于创建静态任务还要开启vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );函数:一下是函数的要求
vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
StackType_t ** ppxTimerTaskStackBuffer,
uint32_t * pulTimerTaskStackSize );
在FreeRTOS.h文件中添加软件定时器相关定义:
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0 */
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) /* 定义软件定时器任务的优先级, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小, 无默认configUSE_TIMERS为1时需定义 */
//按要求定义变量还要注意数据类型
//StackType_t * const puxStackBuffer, /* 任务堆栈,一般为数组,由用户分配 */
StackType_t buffer[configMINIMAL_STACK_SIZE];
// StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配 */
StaticTask_t * stack_task_tcb;
task1_handle = xTaskCreateStatic((TaskFunction_t)task1,//创建任务其对应执行函数是
(char *)"task1", //任务别名
(uint16_t) START_TASK_STACK, //资源任务栈的大小
(void *) NULL,//函数形参
(UBaseType_t) TASK1_Priority, //执行优先级
(StackType_t *) buffer, //静态任务的任务栈
(StaticTask_t *) stack_task_tcb //静态任务的TCB结构体 /* 任务控制块指针,由用户分配, */
)
//这两个函数创建出来即可,不用在主函数中调用,就像中断处理函数一样,存在即会被调用
//分配空闲任务的资源
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
* ppxIdleTaskTCBBuffer=&buffer;
* ppxIdleTaskStackBuffer=&stack_task_tcb
* pulIdleTaskStackSize=configMINIMAL_STACK_SIZE
}
//按要求定义变量还要注意数据类型
//StackType_t * const puxStackBuffer, /* 任务堆栈,一般为数组,由用户分配 */
StackType_t timer_task[ configTIMER_TASK_STACK_DEPTH ]
// StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配 */
StaticTask_t * stack_timer_task_tcb;
//分配软件定时器任务的资源
vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
StackType_t ** ppxTimerTaskStackBuffer,
uint32_t * pulTimerTaskStackSize )
{
* ppxTimerTaskTCBBuffer=&timer_task;
* ppxTimerTaskStackBuffer=&stack_timer_task_tcb;
pulTimerTaskStackSize= configTIMER_TASK_STACK_DEPTH ;
}
任务挂起:
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
使用方式:
xTaskToSuspend:待挂起任务的任务句柄,为NULL表示挂起任务自身。
需将宏INCLUDE_vTaskSuspend配置为 1
任务恢复和任务挂起搭配使用:
void vTaskResume( TaskHandle_t xTaskToResume )//传入任务句柄
不论任务被使用 vTaskSuspend() 挂起多少次,只需调用 vTaskResume() 一次,即可使其继续执行。被恢复的任务会重新进入就绪状态
中断中恢复
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )//传入任务句柄
使用条件:INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必须定义为 1。
在中断服务程序中调用FreeRTOS的API函数时,中断的优先级不能高于FreeRTOS所管理的最高中断优先级。
调度器的恢复挂起和恢复:
vTaskSuspendAll():挂起任务调度器,调度器不会进行任务切换,当前任务一直运行。
xTaskResumeAll():恢复任务调度器,调度器继续任务切换。
查看任务状态:
void vTaskList( char * pcWriteBuffer )
使用条件:
在FreeRTOSconfig.h中定义宏,并将其置1:
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1