FreeRTOS学习笔记

官方机器翻译版本

官方参考手册:

开发板整体布局AN385 - ARM Cortex-M3 SMM on V2M-MPS2

子系统相关Cortex-M System Design Kit Technical Reference Manual r1p0

代码为官方示例程序:FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC

系统结构

配置文件

FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/FreeRTOSConfig.h工程下的系统配置文件

  • configSUPPORT_DYNAMIC_ALLOCATION 栈内存管理

关键组件

1
2
3
4
5
6
7
8
FreeRTOS
|-Source
|-tasks.c # 必备;提供任务管理
list.c # 必备;提供列表管理
queue.c # 基本必备;提供队列管理
timers.c # 可选;提供定时器管理
event_groups.c # 可选;提供事件组管理
croutine.c # 可选;提供协程管理

FreeRTOS/Source/portable/MemMang提供heap_1~5堆申请实现

头文件路径:

  • FreeRTOS/Source/include系统内核头文件
  • FreeRTOS/Source/portable/[compiler]/[architecture]BSP头文件
  • FreeRTOSConfig.h配置文件

BSP

FreeRTOS/Source/portable/[compiler]/[architecture]特定编译器和架构的文件

M3内核寄存器

r0~r12

sp

lr

pc

xpsr

初始化

重置入口Reset_Handler

初始化pc寄存器指向0x80(应该有默认引导指向这里)

mps2_m3.ld定义了内存分布和关键变量分布,ld是ARM的链接配置文件,在编译时有-T ./scripts/mps2_m3.ld选项指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 文件FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/scripts/mps2_m3.ld

MEMORY
{
FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M /* to 0x00003FFF = 0x007FFFFF*/
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4M /* to 0x21FFFFFF = 0xFFFFFF */
}
ENTRY(Reset_Handler)

_estack = ORIGIN(RAM) + LENGTH(RAM);

_sidata = LOADADDR(.data);

.data : /* AT ( _sidata ) */
{
. = ALIGN(4);
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > RAM AT > FLASH

.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = _sbss;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >RAM

ENTRY(Reset_Handler)指定汇编入口

这里_sidata变量为代码的.data起始处数据(地址为0x56e8)。_sdata_edata分别是4字节对齐(ARM处理器没对齐会出现异常)用于存放.data数据的内存起止数据处(地址为0x200001000x20000178)。

_sbss_ebss分别是内存.bss区域起止数据(地址为0x200001800x200064a0)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c
/* Prevent optimization so gcc does not replace code with memcpy */
__attribute__((optimize("O0")))
__attribute__((naked)) void
Reset_Handler(void)
{
/* set stack pointer */
__asm volatile("ldr r0, =_estack");
__asm volatile("mov sp, r0");

/* copy .data section from flash to RAM */
for (uint32_t *src = &_sidata, *dest = &_sdata; dest < &_edata;)
{
*dest++ = *src++;
}

/* zero out .bss section */
for (uint32_t *dest = &_sbss; dest < &_ebss;)
{
*dest++ = 0;
}

/* jump to board initialisation */
void _start(void);
_start();
}

设置栈指针

入口函数__attribute__((optimize("O0"))) __attribute__((naked))定义避免优化,这两个是属于GNU C的编译属性控制编译过程。

__asm volatile("ldr r0, =_estack");__asm封装为asm,标注C内联汇编,volatile直接从Flash上读取_estack的值,这个值在就是0x20400000,即将栈指针指向内存最高处。

拷贝.data区域

将Flash的.data区域拷贝到内存中,内存中数据起止地址4字节对齐。

清空.bss区域

将内存.bss区域数据清零。

跳转启动函数

跳转到C语言入口处函数_start

启动函数_start

首先初始化串口,再进入主函数

1
2
3
4
5
6
7
8
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/init/startup.c

void _start(void)
{
uart_init();
main(0, 0);
exit(0);
}

串口初始化uart_init

配置波特率并开启发送

1
2
3
4
5
6
7
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/syscall.c

void uart_init()
{
UART0_ADDR->BAUDDIV = 16;
UART0_ADDR->CTRL = UART_CTRL_TX_EN;
}

主函数main

这里也只负责调用不同实现

1
2
3
4
5
6
7
// FreeRTOSv202112.00/FreeRTOS/Demo/CORTEX_M3_MPS2_QEMU_GCC/main_blinky.c

int main()
{
main_blinky();
return 0;
}

示例函数main_blinky

首先创建队列用于两任务进行通信。再创建收发队列任务,启动任务调度器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void main_blinky( void )
{
/* Create the queue. */
xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

if( xQueue != NULL )
{
/* Start the two tasks as described in the comments at the top of this
* file. */
xTaskCreate( prvQueueReceiveTask, /* The function that implements the task. */
"Rx", /* The text name assigned to the task - for debug only as it is not used by the kernel. */
configMINIMAL_STACK_SIZE, /* The size of the stack to allocate to the task. */
NULL, /* The parameter passed to the task - not used in this case. */
mainQUEUE_RECEIVE_TASK_PRIORITY, /* The priority assigned to the task. */
NULL ); /* The task handle is not required, so NULL is passed. */

xTaskCreate( prvQueueSendTask,
"TX",
configMINIMAL_STACK_SIZE,
NULL,
mainQUEUE_SEND_TASK_PRIORITY,
NULL );

/* Start the tasks and timer running. */
vTaskStartScheduler();
}

/* If all is well, the scheduler will now be running, and the following
* line will never be reached. If the following line does execute, then
* there was insufficient FreeRTOS heap memory available for the Idle and/or
* timer tasks to be created. See the memory management section on the
* FreeRTOS web site for more details on the FreeRTOS heap
* http://www.freertos.org/a00111.html. */
for( ; ; )
{
}
}

系统函数

队列-创建

1
2
3
4
5
6
7
8
9
10
11
12
// FreeRTOSv202112.00/FreeRTOS/Source/queue.c
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )

// FreeRTOSv202112.00/FreeRTOS-Plus/Source/AWS/ota/test/cbmc/FreeRTOS-Kernel/include/queue.h
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )

输入参数

  • uxQueueLength队列长度
  • uxItemSize队列元素大小
  • ucQueueType队列类型,参考头文件可以填写queueQUEUE_TYPE_xxx

输出参数

  • QueueHandle_t队列指针

内部调用逻辑

1
2
3
4
5
6
7
8
9
xQueueGenericCreate
|-pvPortMalloc // 申请足够的内存
|-vTaskSuspendAll // 进入关键区域,将调度器挂起uxSchedulerSuspended加1,
|-portSOFTWARE_BARRIER // 等待内部中断xInsideInterrupt处理完
portMEMORY_BARRIER // 内存屏障,M3不需要??
xTaskResumeAll
malloc // C库函数,申请内存
prvInitialiseNewQueue // 初始化队列结构
|-xQueueGenericReset // 队列通用初始化,为结构体赋值

其他

队列内存结构为:Queue_t头部信息数据+元素数据列表

队列-发送数据

队列-接收数据

任务-全部恢复

1
BaseType_t xTaskResumeAll( void )

注意

函数xTaskResumeAll与函数vTaskSuspendAll需要成对出现

内部调用逻辑

1
2
xTaskResumeAll
|-vPortEnterCritical # 宏包装taskENTER_CRITICAL

列表

1
2
// FreeRTOSv202112.00/FreeRTOS/Source/list.c
void vListInitialise( List_t * const pxList )

关键结构

时钟滴答

TickType_t

configUSE_16_BIT_TICKS时钟滴答计数值类型。1–16位uint16_t;0–32位uint32_t

基本数据

BaseType_t

定义为架构中执行效率最高的数据类型。它的典型的是,32位架构上为32位,16位架构上为16位,8位架构上为8位。

布尔型pdTRUE/pdFALSE也被定义为BaseType_t

队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef xQUEUE Queue_t;

typedef struct QueueDefinition
{
int8_t *pcHead; /*< 指向存储数据区开始处,即跳过头部区域 */
int8_t *pcWriteTo; /*< 指向存储数据区可使用处,跳过已经入队的元素 */

union
{
QueuePointers_t xQueue; /*< 队列数据,pcTail指向存储数据区结束处;pcReadFrom指向存储数据区最后一元素区域 */
SemaphoreData_t xSemaphore; /*< 信号量使用 */
} u;

List_t xTasksWaitingToSend; /*< 阻塞等待写入数据的任务列表,按优先级排序 */
List_t xTasksWaitingToReceive; /*< 阻塞等待读取数据的任务列表,按优先级排序. */

volatile UBaseType_t uxMessagesWaiting; /*< 当前队列中元素个数 */
UBaseType_t uxLength; /*< 队列元素个数. */
UBaseType_t uxItemSize; /*< 队列元素大小 */

volatile int8_t cRxLock; /*< 上锁时,存储从队列移除元素数;无锁时,值为queueUNLOCKED */
volatile int8_t cTxLock; /*< 上锁时,存储向队列添加元素数;无锁时,值为queueUNLOCKED */

#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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
typedef tskTCB TCB_t;

typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif

ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif

#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif

#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif

#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif

#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif

#if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif

#if ( configUSE_NEWLIB_REENTRANT == 1 )

/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif

/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif

#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif

#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;

目标

移植CORTEX_LM3S811_GCC为QEMU中运行

问题

内联汇编指令寄存器重排

__asm volatile("push {r1,r0,lr}");实际产生的汇编是push {r0,r1,lr},容易造成pop {r3,r4,lr}理解偏差。

官方手册简介

所有变量类型都显示声明为signedunsigned

变量命名规则:c代表chars代表int16_tl代表int32_t(long)x代表BaseType_t;前缀u代表unsignedp代表指针;

函数命名规则:使用前缀标识返回值类型;在变量命名规则上,增加v代表void

宏名称命名规则:使用小写单词开头标明宏定义位置;

堆管理

基本情况

C标准库的内存管理不适合嵌入式的原因:

  • 在小型嵌入式系统中并不是都是可用的;
  • 实现体积比较大,会消耗可贵的代码空间;
  • 很少是线程安全的;
  • 运行不确定性;执行函数所花费的时间在不同调用会有差异;
  • 会遭受内存碎片侵蚀;
  • 会使链接器配置变复杂;
  • 若允许栈空间增长到正在使用的内存中,它们可能称为难以调试错误的来源;

pvPortMalloc

vPortFree

FreeRTOS/Source/portable/MemMang中定义了5中栈管理方式:heap_1~heap_5

configTOTAL_HEAP_SIZE配置栈空间大小

创建每个任务都需要从内存分配任务控制块(Task Control Block, TCB)和栈空间。

申请内存

Heap_1

适合仅仅创建任务和其他内核对象的场景。只实现内存申请,而没有实现内存释放接口。

image-20230219214120657

Heap_2

提前分配内存块,大小由参数决定

configTOTAL_HEAP_SIZE

适合重复创建任务,但任务大小一致的场景

image-20230222092456582

非确定性,当运行速度比标准库块

推荐使用Heap_4代替Heap_2

Heap_3

使用标准库函数管理内存,通过暂停调度来保障线程安全

Heap_4

与Heap_1和Heap_4一样,将内存划分为小块。

拥有合并相邻空闲内存的功能,减少内存碎片的风险。

image-20230222093624554

有时需要确保使用的是内部内存,可以配置configAPPLICATION_ALLOCATED_HEAP为1,内存数据将由应用程序声明的数据替换。数据类型为uint8_tucHeap数组,大小为configTOTAL_HEAP_SIZE。

不同编译器的语法不一样。

使用GCC语法声明heap_4使用的数组,数组将会在.my_heap区域:

1
uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((section(".my_heap")));

使用IAR语法声明heap_4使用的数组,数组将会在绝对地址0x20000000处:

1
uint8_t ucHeap[configTOTAL_HEAP_SIZE] @ 0x20000000;

Heap_5

分配算法与Heap_4一样,不同的是,Heap_5可以使用非连续的内存区域。

在使用前,必须显示调用vPortDefineHeapRegions()完成内存初始化。函数参数是区域结构体数组,每块区域结构体类型为HeapRegion_t

1
2
3
4
5
typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;

显示声明时,内存地址pucStartAddress必须从低到高的顺序声明,并且有结束的区域pucStartAddress=NULL

例如有三块内存区域:

image-20230222102857053

使用Heap_5需要显示初始化这几块内存

1
2
3
4
5
6
7
8
9
10
11
12
const HeapRegion_t xHeapRegion[] = 
{
{(uint8_t *)0x00010000, 65*1024},
{(uint8_t *)0x00020000, 32*1024},
{(uint8_t *)0x00030000, 32*1024},
{NULL, 0},
};

int main(void)
{
vPortDefineHeapRegions(xHeapRegion);
}

若真按上面代码初始化,就没有内存给其他变量使用。在项目中,链接阶段将会分配每个变量内存地址。可以将RAM1划分为两个区域,0x0001nnn以上区域由系统内存管理。优化后的GCC版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define RAM1_HEAP_SIZE (30*1024)
static uint8_t ucHeap[RAM1_HEAP_SIZE];

const HeapRegion_t xHeapRegion[] =
{
{ucHeap, RAM1_HEAP_SIZE},
{(uint8_t *)0x00020000, 32*1024},
{(uint8_t *)0x00030000, 32*1024},
{NULL, 0},
};

int main(void)
{
vPortDefineHeapRegions(xHeapRegion);
}

堆相关函数

xPortGetFreeHeapSize

函数原型:

1
size_t xPortGetFreeHeapSize( void );

返回堆中可用字节数。

使用heap_3时,函数不可用。

xPortGetMinimumEverFreeHeapSize

函数原型:

1
size_t xPortGetMinimumEverFreeHeapSize( void );

返回应用开始执行后,历史最小可用字节数。

只有heap_4和heap_5可使用此函数。

内存申请失败钩子函数

当调用pvPortMalloc没有足够内存时,将会返回NULL,即内存申请失败。若应用需要处理这种场景,需要配置configUSE_MALLOC_FAILED_HOOK,然后应用需要实现内存申请失败钩子函数,原型如下:

1
void vApplicationMallocFailedHook( void );

任务管理

3.1 章节介绍和范围

范围

本章旨在让读者很好地理解:

  • FreeRTOS如何为应用程序中的每个任务分配处理时间。

  • FreeRTOS如何在任何给定时间内选择任务执行。

  • 每个任务的相对优先级如何影响系统行为。

  • 任务可以存在的状态。

读者还可以容易理解:

  • 如何实现任务。
  • 如何创建一个或多个任务的实例。
  • 如何使用任务参数。
  • 如何更改已经创建的任务的优先级。
  • 如何删除一个任务。
  • 如何使用任务实现周期性处理(软件计时器将在后面的一章中讨论)。
  • 空闲任务的执行时间以及如何使用它。

本章中介绍的概念是理解FreeRTOS的基础:如何使用系统和系统应用程序的行为。所以,这也是本书中最详细章节。

3.2 任务函数

任务都是由C语言函数实现。它们唯一特殊之处在于函数原型,它必须返回void类型并接受一个void类型指针。Listing 11展示了函数原型:

1
2
// Listing 11
void ATaskFunction( void *pvParameters );

每个任务本身都是一个小程序。它有一个入口,通常会在无限循环中永久运行,并且不会退出。Listing 12展示了典型的任务结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Listing 12
void ATaskFunction( void *pvParameters )
{
/* 变量可以像普通函数一样进行声明。使用此示例函数创建的每个任务实例,都有其lVariableExample变量的副本。如果变量声明为静态,这就是错误的。在这种情况下,变量只有一个副本,并且副本将被创建的任务实例共享。(变量名称中的前缀参考章节1.5 数据类型和编码风格指南。) */
int32_t lVariableExample = 0;
/* 任务通常被实现为无限循环。 */
for( ;; )
{
/* 实现任务功能的代码写到这里。 */
}
/* 即使任务跳出循环,那么必须在其实现函数结束之前将任务删除。传递给vTaskDelete() API函数的NULL参数,它表示要删除的是调用(此)任务。API函数命名规范在0章节中已描述。 */
vTaskDelete( NULL );
}

任务不需要,应该调用删除任务

1
2
3
4
5
6
7
8
9
10
11
void ATaskFunction( void *pvParameters )
{
int32_t lVariableExample = 0;
/* 一般就是无限循环 */
for( ;; )
{
/* 任务功能代码逻辑. */
}
/* 一定要返回,需要显示调用删除 */
vTaskDelete( NULL );
}

顶层任务状态

简化模型,假定系统中只有一个核,有许多任务需要运行。当任务为运行状态时,处理器会执行任务代码。当任务为非运行状态时,任务被休眠,运行上下文将会被保存。当任务恢复运行状态时,运行上下文也需要恢复。

由非运行态转为运行态的任务称为被切入或换入,反之,称为被切出或换出。系统中调度器是唯一能切换任务的实体。

image-20230222111533707

创建任务

xTaskCreate

函数原型:

1
2
3
4
5
6
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, 
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );

这可能是所有API中最复杂的,但任务也是多任务系统的最基本组件。

参数表

参数名描述
pvTaskCode任务函数入口
pcName任务的描述名称。configMAX_TASK_NAME_LEN配置名称最大长度。超过会被截断
usStackDepth任务使用的栈大小,单位是字(words)。configMINIMAL_STACK_SIZE配置空闲任务和任务最小栈大小。
pvParameters任务使用的void*类型参数指针。
uxPriority指定任务运行优先级,范围0-(configMAX_PRIORITIES – 1),最低优先级为0。
pxCreatedTask创建的任务句柄,可以会被其他接口引用。没有需要可以设置为NULL。

返回值

pdPASS创建成功;pdFAIL创建失败;

Example 1 创建任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void vTask2( void *pvParameters )
{
const char *pcTaskName = "Task 2 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main( void )
{
/* Create one of the two tasks. Note that a real application should check
the return value of the xTaskCreate() call to ensure the task was created
successfully. */
xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
"Task 1",/* Text name for the task. This is to facilitate
debugging only. */
1000, /* Stack depth - small microcontrollers will use much
less stack than this. */
NULL, /* This example does not use the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* This example does not use the task handle. */
/* Create the other task in exactly the same way and at the same priority. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();

/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}
image-20230222175228346

Task2可以被Task1创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* If this task code is executing then the scheduler must already have
been started. Create the other task before entering the infinite loop. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}

Example 2 使用任务参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* The string to print out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* Delay for a period. */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* Define the strings that will be passed in as the task parameters. These are
defined const and not on the stack to ensure they remain valid when the tasks are
executing. */
static const char *pcTextForTask1 = "Task 1 is running\r\n";
static const char *pcTextForTask2 = "Task 2 is running\r\n";
int main( void )
{
/* Create one of the two tasks. */
xTaskCreate( vTaskFunction, /* Pointer to the function that
implements the task. */
"Task 1", /* Text name for the task. This is to
facilitate debugging only. */
1000, /* Stack depth - small microcontrollers
will use much less stack than this. */
(void*)pcTextForTask1, /* Pass the text to be printed into the
task using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* The task handle is not used in this
example. */
/* Create the other task in exactly the same way. Note this time that multiple
tasks are being created from the SAME task implementation (vTaskFunction). Only
the value passed in the parameter is different. Two instances of the same
task are being created. */
xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
/* Start the scheduler so the tasks start executing. */
vTaskStartScheduler();

/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}

任务优先级

configMAX_PRIORITIES

configUSE_PORT_OPTIMISED_TASK_SELECTION

时间尺度和滴答中断

configTICK_RATE_HZ

100

image-20230223094018410

pdMS_TO_TICKS()

Example 3. Experimenting with priorities

image-20230223094342546

扩展非运行状态

阻塞状态(Blocked)

暂停状态(Suspended)

就绪状态(Ready)

完整的状态切换图

image-20230223094909681

Example 4. Using the Blocked state to create a delay

INCLUDE_vTaskDelay

1
void vTaskDelay( TickType_t xTicksToDelay );
image-20230223095233646

vTaskDelayUntil()函数

1
void vTaskDelayUntil( TickType_t * pxPreviousWakeTime, TickType_t xTimeIncrement );

Example 5. Converting the example tasks to use vTaskDelayUntil()

Example 6. Combining blocking and non-blocking tasks

image-20230223100440966

空闲任务和钩子函数

configIDLE_SHOULD_YIELD

清理内核资源

空闲任务钩子函数

使用场景

空闲任务钩子函数的限制

使用事项

1
void vApplicationIdleHook( void );

configUSE_IDLE_HOOK

改变任务优先级

vTaskPrioritySet()

1
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );

uxTaskPriorityGet()

1
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );

Example 8. Changing task priorities

删除任务

vTaskDelete()

INCLUDE_vTaskDelete

1
void vTaskDelete( TaskHandle_t pxTaskToDelete );

Example 9. Deleting tasks

线程本地存储

调度算法

任务状态和事件回顾

Running

Ready

Blocked

Suspended

配置调度算法

configUSE_PREEMPTION

configUSE_TIME_SLICING

configUSE_TICKLESS_IDLE

Round Robin Scheduling

Fixed Priority Pre-emptive Scheduling with Time Slicing

基于时间片优先级抢占调度

image-20230224092259473
image-20230224092550083

configIDLE_SHOULD_YIELD=1

image-20230224093117397

优先级抢占调度(无时间片)

image-20230224094055809

合作调度

image-20230224095111125

队列管理

队列特性

数据存储

First In First Out FIFO

image-20230224095944873

多任务访问

队列读取阻塞

队列写入阻塞

多队列阻塞

使用队列

xQueueCreate

QueueHandle_t

1
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

xQueueSendToBack/xQueueSendToFront

xQueueSend

1
2
3
4
5
6
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );

portMAX_DELAY

INCLUDE_vTaskSuspend

pdPASS

errQUEUE_FULL

xQueueReceive

1
2
3
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

pdPASS

errQUEUE_EMPTY

uxQueueMessagesWaiting

1
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );

Example 10. Blocking when receiving from a queue

image-20230224140047388

从多个源接收数据

image-20230224140141858

Example 11. Blocking when sending to a queue, and sending structures on a queue

image-20230224140630606

传输大量或可变大小的数据

队列指针

使用队列传输不同类型和长度数据

IPStackEvent_t

从多个队列接收

队列集

xQueueCreateSet

1
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );

uxEventQueueLength 每个队列最大长度和

xQueueAddToSet

1
2
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, 
QueueSetHandle_t xQueueSet );

xQueueSelectFromSet

1
2
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait );

Example 12. Using a Queue Set

More Realistic Queue Set Use Cases

使用队列构建邮箱

xQueueOverwrite

1
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );

xQueuePeek

1
2
3
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

软件计时器管理

FreeRTOS/Source/timers.c

configUSE_TIMERS

软件计时器回调函数

1
void ATimerCallback( TimerHandle_t xTimer );

不能调用可能进入阻塞状态的API

软件计时器的属性和状态

软件计时器的周期

单次和自动重载计时器

image-20230227140145505

软件计时器状态

休眠

运行

image-20230227140355152
image-20230227140419033

软件计时器上下文

RTOS守护任务

configTIMER_TASK_PRIORITY

configTIMER_TASK_STACK_DEPTH

不能调用可能进入阻塞状态的API

计时器命令队列

configTIMER_QUEUE_LENGTH

image-20230227140850116

守护任务调度

image-20230227141013158
image-20230227141229467

创建并启动一个软件计时器

xTimerCreate

1
2
3
4
5
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );

xTimerStart

1
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

INCLUDE_vTaskSuspend

portMAX_DELAY

Example 13. Creating one-shot and auto-reload timers

计时器编号

vTimerSetTimerID

1
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );

pvTimerGetTimerID

1
void *pvTimerGetTimerID( TimerHandle_t xTimer );

Example 14. Using the callback function parameter and the software timer ID

改变计时器周期

xTimerChangePeriod

1
2
3
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, 
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );

重置软件计时器

image-20230228092540839

xTimerReset

1
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

Example 15. Resetting a software timer

中断管理

ISR中使用系统API

中断安全API

FromISR

使用独立的中断安全API的优势

使用独立的中断安全API的劣势

xHigherPriorityTaskWoken参数

一般用pbFALSE

portYIELD_FROM_ISR和portEND_SWITCHING_ISR宏

taskYIELD

1
2
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

延时中断处理

image-20230228134407138

用于同步的二进制信号量

image-20230228135032388
image-20230228135534394

xSemaphoreCreateBinary

1
SemaphoreHandle_t xSemaphoreCreateBinary( void );

xSemaphoreTake

1
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

xSemaphoreGiveFromISR

1
2
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, 
BaseType_t *pxHigherPriorityTaskWoken );

Example 16. Using a binary semaphore to synchronize a task with an interrupt

vPortGenerateSimulatedInterrupt

vPortSetInterruptHandler

image-20230228140557184

Improving the Implementation of the Task Used in Example 16

image-20230228140947975
image-20230228141049239

计数信号量

configUSE_COUNTING_SEMAPHORES

image-20230301092559990

xSemaphoreCreateCounting

1
2
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );

Example 17. Using a counting semaphore to synchronize a task with an interrupt

延时工作到系统守护任务

xTimerPendFunctionCallFromISR

1
2
3
4
5
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );

Example 18. Centralized deferred interrupt processing

image-20230301093446475

在中断服务函数(ISR)中使用队列

xQueueSendToFrontFromISR和xQueueSendToBackFromISR

1
2
3
4
5
6
7
8
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, 
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);

考虑合适使用ISR队列

DMA

Example 19. Sending and receiving on a queue from within an interrupt

image-20230301094553407

中断嵌套

configMAX_SYSCALL_INTERRUPT_PRIORITY

configMAX_API_CALL_INTERRUPT_PRIORITY

configKERNEL_INTERRUPT_PRIORITY

数字优先级

逻辑优先级

image-20230301100213478

ARM Cortex-M1和GIC的用户说明

configASSERT

image-20230301101125569

资源管理

互斥现象

临界段和暂停调度器

临界段基础

taskENTER_CRITICAL

taskEXIT_CRITICAL

configMAX_SYSCALL_INTERRUPT_PRIORITY

taskENTER_CRITICAL_FROM_ISR

taskEXIT_CRITICAL_FROM_ISR

暂停调度器

vTaskSuspendAll

1
void vTaskSuspendAll( void );

xTaskResumeAll

1
BaseType_t xTaskResumeAll( void );

互斥体(二进制信号量)

image-20230302134248107

xSemaphoreCreateMutex

1
SemaphoreHandle_t xSemaphoreCreateMutex( void );

Example 20. Rewriting vPrintString() to use a semaphore

image-20230302134426159

优先级反转

image-20230302134556524

优先级继承

image-20230302134936848

死锁

递归互斥锁

xSemaphoreCreateRecursiveMutex

xSemaphoreTakeRecursive

xSemaphoreGiveRecursive

互斥锁和任务调度

image-20230303091409614
image-20230303092336449

守护任务

只有守护任务才能直接访问资源

Example 21. Re-writing vPrintString() to use a gatekeeper task

configUSE_TICK_HOOK

1
void vApplicationTickHook( void );

事件组

事件组特性

EventBits_t

image-20230306093213414
image-20230306093348768

EventBits_t数据类型细节

configUSE_16_BIT_TICKS

多任务访问

使用事件组的一个实际实例

FreeRTOS+TCP TCP/IP

FreeRTOS_Socket_t

使用事件组的事件管理

xEventGroupCreate

1
EventGroupHandle_t xEventGroupCreate( void );

xEventGroupSetBits

1
2
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );

xEventGroupSetBitsFromISR

1
2
3
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );

xEventGroupWaitBits

1
2
3
4
5
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );

Example 22. Experimenting with event groups

使用事件组的任务同步

xEventGroupSync

1
2
3
4
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );

Example 23. Synchronizing tasks

任务通知

通过中间对象通信

image-20230307094905368

任务通知——直接通信方式

image-20230307101348461

configUSE_TASK_NOTIFICATIONS

任务通知:优势和限制

性能优势

内存消耗优势

限制

使用任务通知

任务通知API选项

xTaskNotifyGive

1
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

vTaskNotifyGiveFromISR

1
2
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, 
BaseType_t *pxHigherPriorityTaskWoken );

ulTaskNotifyTake

1
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

Example 24. Using a task notification in place of a semaphore, method 1

image-20230307134340180

Example 25. Using a task notification in place of a semaphore, method 2

xTaskNotify和xTaskNotifyFromISR

1
2
3
4
5
6
7
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );

xTaskNotifyWait

1
2
3
4
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );

Task Notifications Used in Peripheral Device Drivers: UART Example

Task Notifications Used in Peripheral Device Drivers: ADC Example

Task Notifications Used Directly Within an Application

image-20230307135357762

低功耗支持

开发者支持

configASSERT

默认无效:

1
assert( pxMyPointer != NULL );

需要改为

configASSERT()

configASSERT定义示例

FreeRTOS+跟踪

image-20230308092623605

调试相关钩子(回调)函数

内存申请失败

栈溢出

查看运行时和任务状态信息

任务运行时统计

运行时统计时钟

10~100倍频率

配置应用以收集运行时统计

configGENERATE_RUN_TIME_STATS

portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()

portGET_RUN_TIME_COUNTER_VALUE()

portALT_GET_RUN_TIME_COUNTER_VALUE(Time)

uxTaskGetSystemState

1
2
3
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
uint32_t * const pulTotalRunTime );
1
2
3
4
5
6
7
8
9
10
11
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle;
const char *pcTaskName;
UBaseType_t xTaskNumber;
eTaskState eCurrentState;
UBaseType_t uxCurrentPriority;
UBaseType_t uxBasePriority;
uint32_t ulRunTimeCounter;
uint16_t usStackHighWaterMark;
} TaskStatus_t;

vTaskList辅助函数

configUSE_TRACE_FACILITY

configUSE_STATS_FORMATTING_FUNCTIONS

1
void vTaskList( signed char *pcWriteBuffer );
image-20230308094211231

vTaskGetRunTimeStats辅助函数

configGENERATE_RUN_TIME_STATS

configUSE_STATS_FORMATTING_FUNCTIONS

1
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );
image-20230308095815972

产生和显示运行时统计示例

跟踪钩子宏

traceTASK_INCREMENT_TICK(xTickCount)

traceTASK_SWITCHED_OUT()

traceTASK_SWITCHED_IN()

traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue)

traceBLOCKING_ON_QUEUE_SEND(pxQueue)

traceQUEUE_SEND(pxQueue)

traceQUEUE_SEND_FAILED(pxQueue)

traceQUEUE_RECEIVE(pxQueue)

traceQUEUE_RECEIVE_FAILED(pxQueue)

traceQUEUE_SEND_FROM_ISR(pxQueue)

traceQUEUE_SEND_FROM_ISR_FAILED(pxQueue)

traceQUEUE_RECEIVE_FROM_ISR(pxQueue)

traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue)

traceTASK_DELAY_UNTIL()

traceTASK_DELAY()

定义跟踪钩子宏

FreeRTOSConfig.h

FreeRTOS感知调试插件

Eclipse (StateViewer)

Eclipse (ThreadSpy)

IAR

ARM DS-5

Atollic TrueStudio

Microchip MPLAB

iSYSTEM WinIDEA

问题解答

中断优先级

configMAX_SYSCALL_INTERRUPT_PRIORITY

栈溢出

uxTaskGetStackHighWaterMark

1
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

运行时栈检查——概述

configCHECK_FOR_STACK_OVERFLOW

1
void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );

运行时栈检查——方法1

configCHECK_FOR_STACK_OVERFLOW 1

运行时栈检查——方法2

configCHECK_FOR_STACK_OVERFLOW 2

不适当地使用printf()和sprintf()

Printf-stdarg.c

其他常见的错误来源

症状:向演示中添加一个简单的任务会导致演示崩溃

症状:在中断中使用API函数会导致应用程序崩溃

症状:有时应用程序会在中断服务例程中崩溃

症状:尝试启动第一个任务时崩溃

症状:意外禁用中断,或者临界段未正确嵌套

症状:应用程序甚至在调度程序启动之前就崩溃了

症状:当调度程序挂起时,或从关键部分内部调用API函数,会导致应用程序崩溃