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 | FreeRTOS |
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数据的内存起止数据处(地址为0x20000100和0x20000178)。
_sbss和_ebss分别是内存.bss区域起止数据(地址为0x20000180和0x200064a0)。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
37void 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 | // FreeRTOSv202112.00/FreeRTOS/Source/queue.c |
输入参数
uxQueueLength队列长度uxItemSize队列元素大小ucQueueType队列类型,参考头文件可以填写queueQUEUE_TYPE_xxx
输出参数
QueueHandle_t队列指针
内部调用逻辑1
2
3
4
5
6
7
8
9xQueueGenericCreate
|-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
2xTaskResumeAll
|-vPortEnterCritical # 宏包装taskENTER_CRITICAL
列表
1 | // FreeRTOSv202112.00/FreeRTOS/Source/list.c |
关键结构
时钟滴答
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 | typedef xQUEUE Queue_t; |
任务控制块
1 | typedef tskTCB TCB_t; |
目标
移植CORTEX_LM3S811_GCC为QEMU中运行
问题
内联汇编指令寄存器重排
__asm volatile("push {r1,r0,lr}");实际产生的汇编是push {r0,r1,lr},容易造成pop {r3,r4,lr}理解偏差。
官方手册简介
所有变量类型都显示声明为signed或unsigned;
变量命名规则:c代表char,s代表int16_t,l代表int32_t(long),x代表BaseType_t;前缀u代表unsigned,p代表指针;
函数命名规则:使用前缀标识返回值类型;在变量命名规则上,增加v代表void;
宏名称命名规则:使用小写单词开头标明宏定义位置;
堆管理
基本情况
C标准库的内存管理不适合嵌入式的原因:
- 在小型嵌入式系统中并不是都是可用的;
- 实现体积比较大,会消耗可贵的代码空间;
- 很少是线程安全的;
- 运行不确定性;执行函数所花费的时间在不同调用会有差异;
- 会遭受内存碎片侵蚀;
- 会使链接器配置变复杂;
- 若允许栈空间增长到正在使用的内存中,它们可能称为难以调试错误的来源;
pvPortMalloc
vPortFree
FreeRTOS/Source/portable/MemMang中定义了5中栈管理方式:heap_1~heap_5
configTOTAL_HEAP_SIZE配置栈空间大小
创建每个任务都需要从内存分配任务控制块(Task Control Block, TCB)和栈空间。
申请内存
Heap_1
适合仅仅创建任务和其他内核对象的场景。只实现内存申请,而没有实现内存释放接口。
Heap_2
提前分配内存块,大小由参数决定
configTOTAL_HEAP_SIZE
适合重复创建任务,但任务大小一致的场景
非确定性,当运行速度比标准库块
推荐使用Heap_4代替Heap_2
Heap_3
使用标准库函数管理内存,通过暂停调度来保障线程安全
Heap_4
与Heap_1和Heap_4一样,将内存划分为小块。
拥有合并相邻空闲内存的功能,减少内存碎片的风险。
有时需要确保使用的是内部内存,可以配置configAPPLICATION_ALLOCATED_HEAP为1,内存数据将由应用程序声明的数据替换。数据类型为uint8_t的ucHeap数组,大小为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_t1
2
3
4
5typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;
显示声明时,内存地址pucStartAddress必须从低到高的顺序声明,并且有结束的区域pucStartAddress=NULL。
例如有三块内存区域:
使用Heap_5需要显示初始化这几块内存1
2
3
4
5
6
7
8
9
10
11
12const 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
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
11void ATaskFunction( void *pvParameters )
{
int32_t lVariableExample = 0;
/* 一般就是无限循环 */
for( ;; )
{
/* 任务功能代码逻辑. */
}
/* 一定要返回,需要显示调用删除 */
vTaskDelete( NULL );
}
顶层任务状态
简化模型,假定系统中只有一个核,有许多任务需要运行。当任务为运行状态时,处理器会执行任务代码。当任务为非运行状态时,任务被休眠,运行上下文将会被保存。当任务恢复运行状态时,运行上下文也需要恢复。
由非运行态转为运行态的任务称为被切入或换入,反之,称为被切出或换出。系统中调度器是唯一能切换任务的实体。
创建任务
xTaskCreate
函数原型:1
2
3
4
5
6BaseType_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 | void vTask1( void *pvParameters ) |
1 | void vTask2( void *pvParameters ) |
1 | int main( void ) |

Task2可以被Task1创建1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20void 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 | void vTaskFunction( void *pvParameters ) |
1 | /* Define the strings that will be passed in as the task parameters. These are |
任务优先级
configMAX_PRIORITIES
configUSE_PORT_OPTIMISED_TASK_SELECTION
时间尺度和滴答中断
configTICK_RATE_HZ
100
pdMS_TO_TICKS()
Example 3. Experimenting with priorities

扩展非运行状态
阻塞状态(Blocked)
暂停状态(Suspended)
就绪状态(Ready)
完整的状态切换图

Example 4. Using the Blocked state to create a delay
INCLUDE_vTaskDelay1
void vTaskDelay( TickType_t xTicksToDelay );

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

空闲任务和钩子函数
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_vTaskDelete1
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
基于时间片优先级抢占调度

configIDLE_SHOULD_YIELD=1
优先级抢占调度(无时间片)
合作调度
队列管理
队列特性
数据存储
First In First Out FIFO
多任务访问
队列读取阻塞
队列写入阻塞
多队列阻塞
使用队列
xQueueCreate
QueueHandle_t1
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
xQueueSendToBack/xQueueSendToFront
xQueueSend1
2
3
4
5
6BaseType_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 | BaseType_t xQueueReceive( QueueHandle_t xQueue, |
pdPASS
errQUEUE_EMPTY
uxQueueMessagesWaiting
1 | UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue ); |
Example 10. Blocking when receiving from a queue

从多个源接收数据

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

传输大量或可变大小的数据
队列指针
使用队列传输不同类型和长度数据
IPStackEvent_t
从多个队列接收
队列集
xQueueCreateSet
1 | QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ); |
uxEventQueueLength 每个队列最大长度和
xQueueAddToSet
1 | BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, |
xQueueSelectFromSet
1 | QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, |
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 | BaseType_t xQueuePeek( QueueHandle_t xQueue, |
软件计时器管理
FreeRTOS/Source/timers.c
configUSE_TIMERS
软件计时器回调函数
1 | void ATimerCallback( TimerHandle_t xTimer ); |
不能调用可能进入阻塞状态的API
软件计时器的属性和状态
软件计时器的周期
单次和自动重载计时器

软件计时器状态
休眠
运行

软件计时器上下文
RTOS守护任务
configTIMER_TASK_PRIORITY
configTIMER_TASK_STACK_DEPTH
不能调用可能进入阻塞状态的API
计时器命令队列
configTIMER_QUEUE_LENGTH
守护任务调度


创建并启动一个软件计时器
xTimerCreate
1 | TimerHandle_t xTimerCreate( const char * const pcTimerName, |
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 | BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, |
重置软件计时器

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宏
taskYIELD1
2portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
延时中断处理

用于同步的二进制信号量


xSemaphoreCreateBinary
1 | SemaphoreHandle_t xSemaphoreCreateBinary( void ); |
xSemaphoreTake
1 | BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait ); |
xSemaphoreGiveFromISR
1 | BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, |
Example 16. Using a binary semaphore to synchronize a task with an interrupt
vPortGenerateSimulatedInterrupt
vPortSetInterruptHandler
Improving the Implementation of the Task Used in Example 16


计数信号量
configUSE_COUNTING_SEMAPHORES
xSemaphoreCreateCounting
1 | SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, |
Example 17. Using a counting semaphore to synchronize a task with an interrupt
延时工作到系统守护任务
xTimerPendFunctionCallFromISR
1 | BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, |
Example 18. Centralized deferred interrupt processing

在中断服务函数(ISR)中使用队列
xQueueSendToFrontFromISR和xQueueSendToBackFromISR
1 | BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, |
考虑合适使用ISR队列
DMA
Example 19. Sending and receiving on a queue from within an interrupt

中断嵌套
configMAX_SYSCALL_INTERRUPT_PRIORITY
configMAX_API_CALL_INTERRUPT_PRIORITY
configKERNEL_INTERRUPT_PRIORITY
数字优先级
逻辑优先级
ARM Cortex-M1和GIC的用户说明
configASSERT
资源管理
互斥现象
临界段和暂停调度器
临界段基础
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 ); |
互斥体(二进制信号量)

xSemaphoreCreateMutex
1 | SemaphoreHandle_t xSemaphoreCreateMutex( void ); |
Example 20. Rewriting vPrintString() to use a semaphore

优先级反转

优先级继承

死锁
递归互斥锁
xSemaphoreCreateRecursiveMutex
xSemaphoreTakeRecursive
xSemaphoreGiveRecursive
互斥锁和任务调度


守护任务
只有守护任务才能直接访问资源
Example 21. Re-writing vPrintString() to use a gatekeeper task
configUSE_TICK_HOOK1
void vApplicationTickHook( void );
事件组
事件组特性
EventBits_t

EventBits_t数据类型细节
configUSE_16_BIT_TICKS
多任务访问
使用事件组的一个实际实例
FreeRTOS+TCP TCP/IP
FreeRTOS_Socket_t
使用事件组的事件管理
xEventGroupCreate
1 | EventGroupHandle_t xEventGroupCreate( void ); |
xEventGroupSetBits
1 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, |
xEventGroupSetBitsFromISR
1 | BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, |
xEventGroupWaitBits
1 | EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, |
Example 22. Experimenting with event groups
使用事件组的任务同步
xEventGroupSync
1 | EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, |
Example 23. Synchronizing tasks
任务通知
通过中间对象通信
任务通知——直接通信方式
configUSE_TASK_NOTIFICATIONS
任务通知:优势和限制
性能优势
内存消耗优势
限制
使用任务通知
任务通知API选项
xTaskNotifyGive
1 | BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); |
vTaskNotifyGiveFromISR
1 | void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, |
ulTaskNotifyTake
1 | uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); |
Example 24. Using a task notification in place of a semaphore, method 1

Example 25. Using a task notification in place of a semaphore, method 2
xTaskNotify和xTaskNotifyFromISR
1 | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, |
xTaskNotifyWait
1 | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, |
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

低功耗支持
开发者支持
configASSERT
默认无效:1
assert( pxMyPointer != NULL );
需要改为
configASSERT()
configASSERT定义示例
FreeRTOS+跟踪

调试相关钩子(回调)函数
内存申请失败
栈溢出
查看运行时和任务状态信息
任务运行时统计
运行时统计时钟
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 | UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, |
1 | typedef struct xTASK_STATUS |
vTaskList辅助函数
configUSE_TRACE_FACILITY
configUSE_STATS_FORMATTING_FUNCTIONS1
void vTaskList( signed char *pcWriteBuffer );

vTaskGetRunTimeStats辅助函数
configGENERATE_RUN_TIME_STATS
configUSE_STATS_FORMATTING_FUNCTIONS1
void vTaskGetRunTimeStats( signed char *pcWriteBuffer );

产生和显示运行时统计示例
跟踪钩子宏
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_OVERFLOW1
void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );
运行时栈检查——方法1
configCHECK_FOR_STACK_OVERFLOW 1
运行时栈检查——方法2
configCHECK_FOR_STACK_OVERFLOW 2