FreeRTOS

FreeRTOS 目录结构

目录结构如下

image-20231212210055753

RTOS的概念

普通单片机程序.

1
2
3
4
5
6
7
void main()
{
while (1) {
fun_A();
fun_B();
}
}

RTOS程序.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void task_A()
{
while (1) {
fun_A();
}
}

void task_B()
{
while (1) {
fun_B();
}
}

void main()
{
create_task(task_A);
create_task(task_B);
start_scheduler();

while (1) {
sleep()
}
}

创建任务

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
void task_1(void* arg)
{
while (1) {
printf("task_1\r\n");
}
}

void task_2(void* arg)
{
while (1) {
printf("task_2\r\n");
}
}

int main(void)
{
//创建任务句柄
TaskHandle_t xHandlerTask1;
TaskHandle_t xHandlerTask2;

//创建任务
//任务函数 任务名 栈大小 参数 优先级 任务句柄
xTaskCreate(task_1, "task_1", 100, NULL, 1, &xHandlerTask1);
xTaskCreate(task_2, "task_2", 100, NULL, 1, &xHandlerTask2);

/* Start the scheduler. */
vTaskStartScheduler();
}

创建静态任务

在FreeRTOSConfig.h文件中需要配置下列宏定义.

1
#define configSUPPORT_STATIC_ALLOCATION 1

还需要定义vApplicationGetIdleTaskMemory函数.

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
TaskHandle_t xHandleTask3;

void Task3Function(void* arg)
{
while (1) {
printf("3");
}
}

//手动提供的栈空间100*4字节 和 任务句柄结构体的空间
StackType_t xTask3Stack[100];
StaticTask_t xTask3TCB;

//提供给下文函数vApplicationGetIdleTaskMemory函数使用
StackType_t xIdleTaskStack[100];
StaticTask_t xIdleTaskTCB;

//vApplicationGetIdleTaskMemory函数
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
uint32_t * pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
*ppxIdleTaskStackBuffer = xIdleTaskStack;
*pulIdleTaskStackSize = 100;
}

int main(void)
{
//后两个参数为自己提供的栈空间和任务句柄结构体空间
//返回值为任务句柄
xHandleTask3 = xTaskCreateStatic(task_3, "task_3", 100, NULL, 1, xTask3Stack, &xTask3TCB);
}

vTaskDelete

删除任务.

1
2
3
4
5
6
void vTaskDelete(TaskHandle_t xTaskToDelete);
/*
1.自删:vTaskDelete(NULL)
2.被删:vTaskDelete(pvTaskCode), pvTaskCode是自己的任务句柄
3.删人:vTaskDelete(pvTaskCode), pvTaskCode是别人的任务句柄
*/

任务状态

RTOS中共有四种状态,阻塞(Blocked),暂停(Suspended),就绪(Ready),运行(Running),其转换过程可以参考下图.
image-20231213133018217

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
void task_1(void* arg)
{
//获取起始tick 一个tick 1ms, 可在FreeRTOSConfig.h配置
TickType_t Start = xTaskGetTickCount();
TickType_t t;
int flag = 0;

while (1) {
t = xTaskGetTickCount();

task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;
printf("1");

if (!flag && (t > Start + 10)) {
//运行10个Tick后,将task_3挂起
//即进入Suspended状态
vTaskSuspend(xHandleTask3);
flag = 1;
}
if (t > Start + 20) {
//在运行10个Tick后,将task_3唤醒
//即进入Ready状态
vTaskResume(xHandleTask3);
}
}
}

void task_2(void* arg)
{
while (1) {
task1flagrun = 0;
task2flagrun = 1;
task3flagrun = 0;
printf("2");

//每执行一次,将自己Blocked 10个Tick
vTaskDelay(10);
}
}

void task_3(void* arg)
{
while (1) {
task1flagrun = 0;
task2flagrun = 0;
task3flagrun = 1;
printf("3");
}
}

image-20231213134156397

vTaskDelay

两种不同的Delay,vTaskDelay和vTaskDelayUntil.

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
//如下为有变动的部分
void task_1(void* arg)
{
TickType_t Start = xTaskGetTickCount();
int i = 0;
int j = 0;

while (1) {
task1flagrun = 1;
task2flagrun = 0;
task3flagrun = 0;

//确保每次task_1执行不同的时间
for (i = 0; i < rand[j]; i++)
printf("1");

j++;
if (j == 5)
j = 0;

#if 0
vTaskDelay(20);
#else
vTaskDelayUntil(&Start, 20);
#endif

}
}

void main()
{
//提高task_1的优先级
xTaskCreate(task_1, "task_1", 100, NULL, 2, &xHandleTask1);
}

vTaskDelay

每次执行结束下次执行开始之间的时间间隔相同. 间隔即位所填参数.

image-20231213135219765

vTaskDelayUntil

每次执行开始下次执行开始之间的时间间隔相同.间隔即位所填参数

image-20231213135326314

空闲函数与其钩子函数

空闲函数用于帮助自删的任务函数处理后续工作如内存回收等,同时还可以定义钩子函数,在空闲函数中调用. 钩子函数的出现是便于我们利用空闲函数,但不破坏其本身的功能,所以为了保证其原本的功能能正常执行,即处理自删任务的内存回收,不能使空闲函数进入Suspended和Blocked状态,以及建议钩子函数处理的工作需要优先级低且简单高效.

为了使用钩子函数,首先需要在FreeRTOSConfig.h中定义如下宏定义:

1
#define configUSE_IDLE_HOOK 1

然后实现如下函数:

1
2
3
4
5
void vApplicationIdleHook(void)
{
//执行你所需代码
printf("Idle Task Running...\r\n");
}

注意不要在钩子函数中执行死循环.

任务调度算法

  1. 是否允许抢占:
    允许抢占的话,高优先级的任务先执行;不允许抢占的话,大家都是平等,谁先执行谁就一致霸占住,除非主动放弃CPU资源.
  2. 允许抢占时,是否允许时间片轮转:
    允许时间片轮转的话,同优先级的任务交替执行;不允许时间片轮转的话,谁先运行谁就一直运行.
  3. 允许抢占时,允许时间片轮转时,空闲任务是否让步:
    即空闲任务中存在一条判断语句,如果定义了该宏定义,执行一次调度.
    image-20231213150216638