FreeRTOS学习笔记 ——应用场景

        去年NXP KW41大赛的时候被迫啃了一口FreeRTOS,我后来打算系统地学习一下它,再尝试应用到自己的DIY项目当中去。FreeRTOS只是众多的 RTOS (字面意思是实时操作系统)中的一种,因为用得广泛有是开源的,作为学习是个不错的选择。我大概地读过了它的文档,现在回头开始梳理,研究下部分的实现细节,一边写这个连载。

什么情况下可以用 FreeRTOS ?

        单片机也要用操作系统?如果以日常用的Windows, Linux, BSD这些,甚至是DOS,来代表操作系统的话,在单片机上运行操作系统时候个很荒诞的想法——因为对大多数单片机,RAM实在太少了。而 FreeRTOS 并不是要提供一个在单片机上运行软件的平台,把软件一个个安装到它上面,供用户选择运行什么,它没有用户界面;它不是一个管家,也不带任何硬件驱动程序,也没有文件系统服务提供。FreeRTOS只是一个操作系统内核,它首先提供了操作系统最重要的特性:任务调度。

        也就是说,有了FreeRTOS,在单片机上实现多任务会容易一些。这里至少有两层含义,一是多任务是否一定要用RTOS才可以实现? 当然不是。对于单片机开发来讲,所有系统资源都是你的,在不同中断服务里面处理不同的任务并不是很困难的事情。第二,是否没有多任务就完全用不着RTOS? 这也得看具体情况,如何界定“任务”的概念了,一件复杂的事情在程序中也有可能划分为几个任务来处理。

       还是举几个例子吧。

1、SD卡MP3音乐播放机

      暂且不管用户操作界面,在播放状态下,从数据流上看播放机是这样工作的(作了一定简化):  

              第一步:I2S接口DMA缓冲区空闲

              第二步:用剩余PCM数据填充缓冲区,请求解码下一块MP3数据

              第三步:请求读取MP3文件下一段内容

              第四步:定位SD卡上要读取扇区位置

              第五步:SDIO控制器读命令发送

              第六步:SDIO控制器接收数据完成中断

              第七步:填充文件数据缓冲区

              第八步:解码MP3数据,写PCM数据缓冲区

              第九步:填满I2S接口的DMA缓冲区

        这个过程涉及到四个关键的软件部分:

                                            FreeRTOS学习笔记 ——应用场景_第1张图片

          若按自顶向下的软件设计思路,I2S设备驱动以固定的节拍被唤醒,进行缓冲区PCM数据填充,因此需要定期去调用MP3解码程序。MP3解码器则根据前面一段解码操作的结果来决定是否要访问文件系统(因为MP3解码一块数据产生的PCM音频数据的量并不能刚好是I2S设备驱动请求的大小),以及需要读取多少字节的MP3文件内容,还有解码出来暂时用不到的数据也要保存起来下次用。到了文件系统这里,请求读文件的位置和长度也未必是正好SD卡上的一个扇区,所以也有缓存,而且还需要跟踪文件在SD卡上的索引。SDIO设备驱动则按照文件系统的请求读取SD卡,等待操作完成以后返回,注意它和前面的模块不同的是,有一段什么事情也不干的硬件IO等待时间。

        一层层的嵌套调用关系如这样:

                                                             FreeRTOS学习笔记 ——应用场景_第2张图片

        注意,每一次的函数调用,都代表一个完整的操作:填充一块缓冲区、解码一段MP3数据、读取一段文件,以及读取SD卡一块数据下层的子程序被调用,完成后返回上一层的程序中继续执行。在没有异常(中断)发生的情况下,程序是不会离开这个调用关系嵌套的。

        又需要注意到,上面几个列举的主要函数,虽然一次调用操作是完整的,但每次操作过后它的内部状态不一定相同。用C语言来说,就是这些函数需要有static型的局部变量,或者是自己用一些全局变量来记住上次调用后状态是怎样了。典型的就是文件系统 read_file() 这样的函数被调用后,它需要记住文件指针的位置,以便下次接着读。

        如上描述实现的音乐播放器实现有一个重要的缺点:在SD卡读操作的等待时间里,CPU的执行只能停留在SD卡访问函数中,不能用来进行MP3解码的运算操作,处理能力被浪费了。在I2S设备的缓冲区填满,到下一次需要再填充这段时间 ,CPU也是处于空闲状态。其实完全可以利用这两段空闲时间,做其它的事情,比如预先解码MP3一部分数据。

        那么问题是,如何在一个函数执行过程中,跳出去执行其它的函数,然后再跳回来?用中断,对,但是把什么放在中断里面呢?

 

2、USB mass storage设备

        就是利用单片机的USB片上设备,模拟一个U盘。做这样的设备,主要内容就是响应来自USB主机的各种请求。当USB主机向设备发出请求的时候,USB硬件会产生一个IRQ,随后USB的中断服务函数(IRQ handler)被执行。通常,USB驱动程序库会提供一些回调(callback)函数接口,就是由用户写一些函数,供USB IRQ handler在需要的时候调用。

        作为USB mass storage设备,需要提供的回调函数必须要有读存储设备、写存储设备等,以产生实际的磁盘数据给USB,以及接收USB要求写的磁盘数据。这些函数什么时候被调用到,是应用程序无法预见的,因为它在USB中断发生之后的响应过程中。这样一来,回调函数处理U盘模拟的事务,主程序里面不用管,可以处理无关的别的工作,也就实现了多任务么!

        然而,在中断环境下执行用户代码,好像不是太好的选择,因为这时候优先级同级和更低的中断就不能响应了

        在实际的USB mass storage设备里面,U盘的数据要从片外存储设备中读取,比如NAND Flash,情况就会糟糕——读取数据需要等待,而这个等待完全发生在USB IRQ handler里面,USB主机的请求暂时也没法再响应了。如果主程序中的任务也需要硬件I/O中断,可能就会受到影响。

        那么,作为只模拟一个U盘,不进行其它任务的应用来说,这样似乎也无关紧要:反正都是等。我亲自做过的USB全速(12Mbps)下U盘设备的表现,传输速度最多到500多kB,比12Mbps差了不少,为什么呢?

                                                                    FreeRTOS学习笔记 ——应用场景_第3张图片FreeRTOS学习笔记 ——应用场景_第4张图片

 

        看上面这个图,USB IRQ中断响应期间,是没有数据传输的,所以12Mbps的有效带宽被浪费了一部分,U盘的吞吐率自然就达不到理论值了。回调函数在I/O等待的时候,USB主机也在等待USB设备的应答。而U盘数据准备好之后,USB硬件发送数据,CPU又无事可干直到下一次请求到来。比较合理的设计是利用USB TX的时间进行实际存储设备的I/O预读取,也就是猜测USB会继续请求读上次读了的后面的数据,那就先把数据读到内存中来,一旦猜对了,就可以缩短下次请求的响应时间,模拟U盘的读取吞吐率就提高了。要这么实现,就不能单纯地在回调函数中处理I/O任务。

https://www.sohu.com/a/224251121_774177

 

       

 

你可能感兴趣的:(FreeRTOS)