0 FreeRTOS源码概述
该STM32F103Z开发板提供了一个freertos.c文件,由STM32CubeMX软件生成,其结构如下:
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle; // 声明任务句柄
const osThreadAttr_t defaultTask_attributes = { // 定义任务属性
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
void StartDefaultTask(void *argument); // 声明默认任务入口
void MX_FREERTOS_Init(void); // 初始化函数
void MX_FREERTOS_Init(void) {
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); // 创建任务
}
void StartDefaultTask(void *argument){ // 定义默认任务
LCD_Init();
LCD_Clear();
for(;;)
{
}
}
后续的程序开发,都会在这个模板上进行修改
1 基本概念
对于整个单片机程序,称其为程序(application)。我们可以在程序中创建多个任务(task)或称为线程(thread)
以一个同时要做喂饭和回信息两件事的人举例:
- 任务状态(State):
◼ 当前正在喂饭,它是 running 状态;另一个"回信息"的任务就是"not running"状态
◼ "not running"状态还可以细分:
◆ ready:就绪,随时可以运行
◆ blocked:阻塞,卡住了,母亲在等待同事回信息
◆ suspended:挂起,同事废话太多,不管他了 - 优先级(Priority)
◼ 我工作生活兼顾:喂饭、回信息优先级一样,轮流做
◼ 我忙里偷闲:还有空闲任务,休息一下
◼ 厨房着火了,什么都别说了,先灭火:优先级更高 - 栈(Stack)
◼ 喂小孩时,我要记得上一口喂了米饭,这口要喂青菜了
◼ 回信息时,我要记得刚才聊的是啥
◼ 做不同的任务,这些细节不一样,对于人来说,当然是记在脑子里,对于程序,是记在栈里。每个任务有自己的栈 - 事件驱动
◼ 孩子吃饭太慢:先休息一会,等他咽下去了、等他提醒我了,再喂下一口 - 协助式调度(Co-operative Scheduling)
◼ 你在给同事回信息
◆ 同事说:好了,你先去给小孩喂一口饭吧,你才能离开
◆ 同事不放你走,即使孩子哭了你也不能走
◼ 你好不容易可以给孩子喂饭了
◆ 孩子说:好了,妈妈你去处理一下工作吧,你才能离开
◆ 孩子不放你走,即使同事连发信息你也不能走
2 内存管理
2.1 栈
栈(stack):一个内存空间,CPU的SP寄存器指向它,可以用于函数调用、局部变量、多任务系统里面保存现场。
以下代码,用于理解栈。与FreeRTOS的使用无关
int g_cnt = 0;
void b_func(int a)
{
a += 2;
return a;
}
void c_func(int a)
{
a += 3;
return a;
}
void a_func(int a)
{
g_cnt = b_func(a);
g_cnt = c_func(g_cnt);
}
int main(void)
{
int i = 99;
a_func();
}
以上代码,main调用函数a,函数a先后调用函数b和c。调用其他函数的本质是使用BL汇编指令,实现
LR = 返回地址(下一条指令的地址)
PC = 函数地址。往PC寄存器存地址,就会跳过去执行那段代码
每次调用函数,都会执行一次BL,即LR和PC经过一次上述的变化,就有以下问题:
- LR是否被覆盖了,怎么会事呢?
- 局部变量在栈中分配,如何分配?
- 为何每个RTOS任务都有自己的栈?
解答:
- 编译器会在函数入口自动PUSH保存LR以及一些必要的寄存器 到栈里,从而避免上一个函数被覆盖。函数执行完后,栈区会被POP回收。
- 局部变量优先分配到寄存器中,空间不足了(或用volatile指定)再放栈里
- 切换任务时,会保存当前函数的现场,恢复目标函数的现场。保存线程时,将所有寄存器(除了SP)压到到当前函数对应的栈里,并且记录SP;恢复现场时,先找到SP,POP寄存器信息。各个任务使用各自的栈,方便内存管理和现场恢复。
2.2 堆
堆(heap):一块空闲的内存。可以从里面分配一块空间用作栈
malloc:从堆中画出一块空间给程序用。
free:释放他
2.3 FreeRTOS的内存管理方法
内存管理函数为pvPortMalloc, vPortFree,对应C中的malloc和free
源码中默认提供了5个文件,对应内存管理的5种方法
文件 | 优点 | 缺点 |
---|---|---|
heap_1.c | 分配简单,时间确定 | 只分配不回收 |
heap_2.c | 动态分配,最佳分配 | 碎片,时间不定 |
heap_3.c | 调用标准库 | 速度慢,时间不定 |
heap_4.c | 相邻空闲内存可合并 | 可解决碎片问题,时间不定 |
heap_5.c | 在heap_4基础上支持分隔的内存块 | 可解决碎片问题,时间不定 |
一般不用heap_3,heap_4和heap_5常用。heap_5可以通过链表沟通片内和片外RAM
以下列举了一些内存管理
void * pvPortMalloc(size_t xWantedSize); //分配内存,如果失败则返回NULL
void vPortFree(void * pv); //释放内存
size_t xPortGetFreeHeapSize(void); //返回空闲内存容量。heap3无法用
size_t xPortGetMinimumEverFreeHeapSize(void); //返回程序运行过程中空闲内存容量的最小值。只有heap4,5能用