一、理查德巴利与FreeRTOS

1.RTOS和FreeRTOS的概念

RTOS是操作系统的一种类型。它是Real-Time Operating System的缩写,也就是实时操作系统。这种操作系统强调实时性,使用抢占式调度。打个比方,比如你在公园散步的时候来了电话,你就得停下脚步接电话。高优先级的事情一旦发生,它就会打断低优先级的事情,从而立刻获得执行权。我们所说的实时操作系统(RTOS)内部就是按照这个逻辑进行调度的。

和RTOS相对的是TSOS,它是另外一种类型的操作系统。TSOS是Time-sharing Operating System的缩写,也就是“分时操作系统”。分时操作系统一般使用时间片轮转调度,强调共享和公平。比如你可以使用电脑边听音乐、边浏览网页,同时还能通过打印机打印文档。很多个不同的任务共享CPU的执行权,从而给用户一种所有事情在同时进行的感受。

TSOS一般用在电脑、手机等这些强调用户体验的大型设备上,而RTOS则应用在对实时性要求较高的嵌入式设备当中,比如以单片机、DSP等作为主控的硬件电路。

FreeRTOS是RTOS的一种。比如,我们在手机或者电脑上使用的TSOS操作系统有很多种类,像是Windows、MacOS、Linux、Andriod等等。对于RTOS来说也是如此,市面上有很多种RTOS操作系统供开发者选择,比如FreeRTOS、ThreadX、VxWorks、QNX等。

2.FreeRTOS能做什么

在日常生活里,我们习惯上说的操作系统一般指的是运行在电脑上的操作系统,比如Windows。这种操作系统兼具很多种不同的职责,它就像一个大管家,负责管理电脑的各种硬件资源,让它们协同起来为人类服务。比如,它可以管理和分配电脑里的内存资源,这种职能叫做内存管理;它可以访问电脑的硬盘,并以文件的形式呈现给用户,这种职能叫做文件管理;它能够创建多个进程,不同的进程做不同的事、互不干扰,这种职能叫做进程管理;另外它还能利用网卡把电脑接入到网络当中,从而访问网络当中的资源,这种职能叫做网络接口。诸如此类的职能还有很多。

与电脑上运行的操作系统相比,FreeRTOS的职能就比较单一了。FreeRTOS既不能管理内存,也不能管理文件,事实上它做的事情就只有一个:“管理任务”。这和刚才所说的进程管理比较相似,都是让代码并行执行的手段。通俗地讲,FreeRTOS能够做到让单片机在同一时间处理多件事,这就是FreeRTOS的核心作用。

例1:我们使用一个游戏机的例子来理解什么是“管理任务”。现在想象一下,你要手搓一款像下边这张图里的游戏机,主控选择STM32F103单片机。可以看到,要让这么一台游戏机运转起来所需要做的事情还是蛮多的。

比如,要时刻监测用户按下按钮的动作、要计算游戏的内部逻辑、要播放背景音乐和音效还要在LCD屏幕上显示游戏的图像。要做这么多件事,但是我们的单片机只有一颗,它怎么能忙得过来呢?使用传统的裸机编程方法很难满足这个项目的要求。

如果我们引入FreeRTOS的话,这个问题就能迎刃而解了。我们只要把要做的这些事情切分成一个一个的任务,然后把每个任务的代码写出来就可以了,其它事情交给FreeRTOS来做。

比如对于这个游戏机来说,我们可以把它划分成4个独立的任务:任务1 - 按键检测、任务2 - 游戏裸机、任务3 - 音乐和音效、任务4 - LCD显示。划分好任务之后,需要把这些任务对应的代码写出来。在编写任务代码的时候我们认为每个任务的运行是完全独立的,可以独占整个单片机。因此,我们就像记流水账一样把需要的功能写出来就可以了,剩下的工作交给FreeRTOS的调度器。FreeRTOS的调度器负责在多有任务之间切换,它会选择合适的时机执行任务代码。比如,如上图它先执行任务1,执行任务1的一部分,然后切换到任务4,再切换到任务3等等。

尽管很多任务共享单片机,但是有了FreeRTOS的存在,它们就能有条不紊地工作下去。这就是FreeRTOS的强大能力。

3.FreeRTOS的历史

在过去32位单片机还没有普及的时候,由于8位或16位单片机的能力不足,所以没办法在上边跑操作系统。21世纪初,也就是两千零几年,那个时候32位单片机开始普及,单片机的处理能力也随之得到极大的提升。所以在单片机上跑操作系统这件事慢慢变成了可能。一开始的时候,大家只能使用一些传统的商业操作系统,比如VxWorks或者μC/OS。这些操作系统价格昂贵(授权费高达数万到数十万美元),且操作复杂,一般小公司或者个人根本负担不起。后来有一位叫理查德·巴利(Richard Barry)的工程师想要改变这种局面,于是他动手编写了一个开源嵌入式实时操作系统,这个操作系统就是我们要学习的FreeRTOS。FreeRTOS以其开源免费、代码易读、使用简单等优势迅速占领了市场,拿下了大量的市场份额。到2012年,FreeRTOS已经被应用在了超过1亿台设备上,这是一个巨大的成功。当时理查德·巴利经营着一家名为“实时工程师有限公司”(Real-Time Engineers Ltd)的企业,这个公司开展的是嵌入式技术咨询相关的业务。理查德·巴利持续经营着这家公司,直到2016年被WITTENSTEIN收购。又过了一年,也就是2017年,为了布局云服务业务,亚马逊又从WITTENSTEIN手中再次收购了它。因此理查德·巴利就带着他的FreeRTOS项目进入亚马逊工作了。现在理查德巴利仍然就职于亚马逊,担任AWS物联网部门的高级首席工程师。截至目前他仍然领导FreeRTOS的开发与演进。所以你可以看到,我们正在学习的FreeRTOS其实是由理查德·巴利从无到有创造出来的,他是FreeRTOS之父

二、初次使用Freertos

1.基本的裸机编程

例1:在我们的开发板上有两颗LED,分别是LED1(连接单片机的PC3引脚)和LED3(连接单片机的PC1引脚),我们可以通过编程的方式控制这两颗LED闪烁。现在请你编写程序,让LED1每次点亮100ms、熄灭100ms,同时让LED3每次点亮50ms、熄灭150ms。

解:根据题目要求,我们首先画出两颗LED闪烁的时序图。

从时序图当中可以看出,虽然LED1和LED3闪灯的参数不同,但它们每次闪烁的周期都是200ms。根据这个特点我们可以把LED1和LED3的代码写在同一个while循环里边,其实也就是写在main()方法的while循环里。

我们只要重复这3个步骤即可:1. 先同时点亮LED1和LED3,并延迟50ms; 2. 熄灭LED3并再次延迟50ms;3. 熄灭LED1并延迟100ms。

2.复杂的裸机编程

例2:这次还是请大家用编程的方式控制LED1和LED3闪烁。LED1仍然点亮100ms、熄灭100ms,但LED3的情况有所变化,这次它亮灯30ms、灭灯150ms。

解:与例1相比,虽然我们只是让LED3的亮灯时间从50ms缩短到30ms,但这一点点改动却会产生巨大的问题。为了方便说明,我们还是把它们的时序图画出来:

从图中我们可以看出,因为修改了参数,所以LED1和LED3的闪烁变得“不同步”了。具体表现为它们的周期不再相等,LED1闪烁一次消耗200ms,LED3闪烁1次消耗180ms,它俩并不相等。这意味着,我们不能再像例1一样,把所有的代码写在同一个while循环里。事情逐渐变得棘手了。如果不借助FreeRTOS的话,我们需要使用一种极为复杂的编程方式:“状态机”。

如上图,我们需要为LED1和LED3分别创建一个状态机,每个状态机有两种状态:点亮和熄灭。当满足条件后,状态机会在两种状态之间切换。

static void LED1_Proc(){
	if(led1_state == 0 && HAL_GetTick() - led1_last_toggle_time >= 100){
		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
		led1_state = 1;
		led1_last_toggle_time = HAL_GetTick();
	}
	else if(led1_state == 1 && HAL_GetTick() - led1_last_toggle_time >= 100){
		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
		led1_state = 0;
		led1_last_toggle_time = HAL_GetTick();
	}
}

static void LED3_Proc(){
	if(led3_state == 0 && HAL_GetTick() - led3_last_toggle_time >= 30){
		HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET);
		led3_state = 1;
		led3_last_toggle_time = HAL_GetTick();
	}
	else if(led3_state == 1 && HAL_GetTick() - led3_last_toggle_time >= 150){
		HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET);
		led3_state = 0;
		led3_last_toggle_time = HAL_GetTick();
	}
}

从上边的例子可以看出,我们确实可以在不使用RTOS操作系统的条件下,使用状态机实现多任务的并行。但这个过程会极其繁琐。打个比方,如下图:这是一条生产线,它可以加工A、B、C三种货物,这些货物要根据需求被送上生产线。我们可以把这3种货物想象成要并行处理的3段代码。使用状态机就像是在生产线上搭建一个复杂的上料系统,这种方式虽然可行但会消耗大量的精力来建造它。而且随着项目越来越复杂,状态机的设计也会变得极其繁琐,以至于在一些稍大的项目当中我们根本不可能设计出这种状态机。

相比状态机编程,使用FreeRTOS就方便多了。你向工程里引入FreeRTOS,就仿佛为生产线专门聘请了一个熟练的搬运工。我们再也不需要设计复杂的上料系统,这个搬运工在合适的时机帮你把合适的原料搬到生产线上。甚至,他比上料系统更灵活、更方便、更知道轻重缓急。

FreeRTOS能够帮助编程者自动实现任务调度,是对人力的解放,这就是FreeRTOS最核心的作用。

3.使用Freertos

  1. 像写单独的main()方法一样,分别编写了LED1任务和LED3任务的任务函数。

注意,函数内部使用的是vTaskDelay()进行延迟,而非HAL_Delay(),具体原因我们后边会介绍。

  1. 在main()方法中调用xTaskCreate()分别创建LED1任务和LED3任务。

其实就是把刚刚写好的vLED1Task()和vLED3Task()交给调度器。

  1. 最后调用vTaskStartScheduler()方法启动调度器。

一般情况下,启动调度器的方法永远不会退出,除非因为创建空闲任务或者定时器任务出错。因此只要程序执行正常,那么就不会执行到main()方法里的while循环处。