FreeRTOS内核实现与应用开发实战指南:基于STM32
上QQ阅读APP看书,第一时间看更新

第3章 裸机系统与多任务系统

在真正开始动手写FreeRTOS内核之前,我们先来讲解单片机编程中的裸机系统和多任务系统(不仅限于FreeRTOS)的区别。

3.1 裸机系统

裸机系统通常分成轮询系统和前后台系统,有关这两者的具体实现方式请参见下面的讲解。

3.1.1 轮询系统

轮询系统即在裸机编程时,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地处理各种事件,大概的伪代码具体参见代码清单3-1。轮询系统是一种非常简单的软件结构,通常只适用于仅需要顺序执行代码且不需要外部事件来驱动就能完成的事件。在代码清单3-1中,如果只是实现LED翻转、串口输出、液晶显示等操作,那么使用轮询系统将会非常完美。但是,如果加入了按键操作等需要检测外部信号的事件,例如用来模拟紧急报警,那么整个系统的实时响应能力就不会那么好了。假设DoSomething3是按键扫描,当外部按键被按下,相当于一个警报,这个时候,需要立刻响应并做紧急处理,而这个时候程序刚好执行到DoSomething1,致命的是DoSomething1需要执行的时间比较久,久到按键释放之后还没有执行完毕,那么当执行到DoSomething3时就会丢失一次事件。由此可见轮询系统只适合顺序执行的功能代码,当有外部事件驱动时,实时性就会降低。

代码清单3-1 轮询系统伪代码

 1 int main(void)
 2 {
 3     /* 硬件相关初始化 */
 4     HardWareInit();
 5
 6     /* 无限循环 */
 7     for (;;) {
 8         /* 处理事件1 */
 9         DoSomething1();
10
11         /* 处理事件2 */
12         DoSomething2();
13
14         /* 处理事件3 */
15         DoSomething3();
16     }
17 }

3.1.2 前后台系统

相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里称为前台,main()函数中的无限循环称为后台,大概的伪代码参见代码清单3-2。

代码清单3-2 前后台系统伪代码

 1 int flag1 = 0;
 2 int flag2 = 0;
 3 int flag3 = 0;
 4
 5 int main(void)
 6 {
 7     /* 硬件相关初始化 */
 8     HardWareInit();
 9
10     /* 无限循环 */
11     for (;;) {
12         if (flag1) {
13             /* 处理事件1 */
14             DoSomething1();
15         }
16
17         if (flag2) {
18             /* 处理事件2 */
19             DoSomething2();
20         }
21
22         if (flag3) {
23             /* 处理事件3 */
24             DoSomething3();
25         }
26     }
27 }
28
29 void ISR1(void)
30 {
31     /* 置位标志位 */
32     flag1 = 1;
33     /* 如果事件处理时间很短,则在中断里面处理
34     如果事件处理时间比较长,则回到后台处理 */
35     DoSomething1();
36 }
37
38 void ISR2(void)
39 {
40     /* 置位标志位 */
41     flag2 = 1;
42
43     /* 如果事件处理时间很短,则在中断里面处理
44     如果事件处理时间比较长,则回到后台处理 */
45     DoSomething2();
46 }
47
48 void ISR3(void)
49 {
50     /* 置位标志位 */
51     flag3 = 1;
52
53     /* 如果事件处理时间很短,则在中断里面处理
54     如果事件处理时间比较长,则回到后台处理 */
55     DoSomething3();
56 }

在顺序执行后台程序时,如果有中断,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序中标记事件。如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回后台程序处理。虽然事件的响应和处理分开了,但是事件的处理还是在后台顺序执行的,但相比轮询系统,前后台系统确保了事件不会丢失,再加上中断具有可嵌套的功能,这可以大大提高程序的实时响应能力。在大多数中小型项目中,前后台系统运用得好,堪称有操作系统的效果。

3.2 多任务系统

相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务与中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急事件在中断中被标记之后,如果事件对应的任务的优先级足够高,就会立刻得到响应。相比前后台系统,多任务系统的实时性又被提高了。多任务系统大概的伪代码具体参见代码清单3-3。

代码清单3-3 多任务系统伪代码

 1 int flag1 = 0;
 2 int flag2 = 0;
 3 int flag3 = 0;
 4
 5 int main(void)
 6 {
 7     /* 硬件相关初始化 */
 8     HardWareInit();
 9
10     /* RTOS初始化 */
11     RTOSInit();
12
13     /* RTOS启动,开始多任务调度,不再返回 */
14     RTOSStart();
15 }
16
17 void ISR1(void)
18 {
19     /* 置位标志位 */
20     flag1 = 1;
21 }
22
23 void ISR2(void)
24 {
25     /* 置位标志位 */
26     flag2 = 2;
27 }
28
29 void ISR3(void)
30 {
31     /* 置位标志位 */
32     flag3 = 1;
33 }
34
35 void DoSomething1(void)
36 {
37     /* 无限循环,不能返回 */
38     for (;;) {
39         /* 任务实体 */
40         if (flag1) {
41
42         }
43     }
44 }
45
46 void DoSomething2(void)
47 {
48     /* 无限循环,不能返回 */
49     for (;;) {
50         /* 任务实体 */
51         if (flag2) {
52
53         }
54     }
55 }
56
57 void DoSomething3(void)
58 {
59     /* 无限循环,不能返回 */
60     for (;;) {
61         /* 任务实体 */
62         if (flag3) {
63
64         }
65     }
66 }

相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的、无限循环且不能返回的小程序,这个小程序我们称之为任务。每个任务都是独立的、互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程时不需要精心地设计程序的执行流,不用担心每个功能模块之间是否存在干扰,编程反而变得简单了。整个系统的额外开销就是操作系统占据的少量FLASH和RAM。现如今,单片机的FLASH和RAM越来越大,完全足以抵消RTOS的开销。

无论是裸机系统中的轮询系统、前后台系统还是多任务系统,我们不能简单地说孰优孰劣,它们是不同时代的产物,在各自的领域都还有相当大的应用价值,只有合适的才是最好的。有关这三者的软件模型的区别如表3-1所示。

表3-1 轮询系统、前后台系统和多任务系统软件模型的区别