前面几周跟着野火的教程从0到1实现了RT-Thread的内核,对RT-Thread的调度机制和线程、定时器的底层实现有了总体的了解。后面还需进一步对齐实现细节进行探索,但大致先了解其框架,后面再进行细致的了解。在学习新知识时,最重要的是思维模式的转变。先了解其大致框架,再深入去了解细节。有些人(就是我,后来太痛苦终于悟了)学一个新知识,刚上来就从头到尾一个字一个字仔细看,生怕错过什么重要的内容,搞不懂关键点就立马停下来去查,一查发现哇!解释内容这么多,看解释的时候又发现有不懂得地方,又停下来去查,如此一环套一环,何时是个头。而且这样学习的结果就是学一个新知识很快就会筋疲力尽,觉得自己什么都不懂,自信心受到打击,从而陷入对自己的深深怀疑之中,最后很可能放弃学习(这种学习方式就像C语言中函数的递归调用,一层套一层,层层套娃,很容易导致栈空间不够用,最后造成程序崩溃。在陈正冲老师的《C语言深度剖析》中,就建议尽量不要使用递归,如果要使用一定要注意递归调用的深度和对应使用的栈空间)!其实你想想看,这些知识是前人付出多少心血才总结出来的,哪可能会让你一上来就搞得门儿清?除非你天赋异禀异于常人。因此在学习新知识的时候,先了解其大概,不求甚解,遇到了问题先保持疑问,带着疑问继续学下去。动手做起来再说,做的好不好,完不完美那是另一回事,先做,在做的过程中逐渐完善。有些人总是思前想后觉得要考虑的周到,完美再去做,这也没错,但是你考虑的就会和实际情况一模一样吗?在做中学习,诸葛亮舌战群儒说笔下虽有千言,而心中实无一策。我们也要做到不能心中虽有千言,而手上丝毫不动。很多问题其实在你学习后面的知识的过程中就自然而然的解决了。还记得去年学习Linux,对Linux的驱动开发、应用开发学的是一知半解,云里雾里。那时还在学习RT-Thread,学了一段时间Linux之后回过头再看RT-Thread发现好多在学习RT-Thread的疑问自然懂了。这就是当你接触了一个高等级的知识后,再回过头去看低等级的知识时发现就豁然开朗了。
比如在深入学习STM32时,你就会发现main函数并不是上电就开始运行的第一个函数,在main函数之前,在startup_xxx.S文件中(基于执行效率的考虑,使用汇编语言编写),系统做了好多事情,如完成堆和栈的内存分配等,最后在跳转到用户main函数运行。假使这个时候你发现汇编语言好像你什么都不会,对ARM的架构也不是很了解,然后买了本汇编语言和ARM-Cortex内核权威指南吭哧吭哧的开始读,希望能先解决掉这两个不懂得内容,你就会发现你看的时候实际上会碰到更多不懂得知识。假使你真的坚持住了,学完了这两本书,肯定你的知识层次会提高很多,但同时和你一块的人早都跑远了。因此,在实际学习新知识时,先了解个大概,后面再去进行细节的完善,当你有一个整个框架的时候再将细节逐渐补齐就会发现是水到渠成。
学习嵌入式的知识更要学会嵌入式的知识过于庞杂,硬件、协议、软件,这每一个都是大部头,如果心里没有规划,遇到什么都想去搞明白、研究透彻,那学来学去就忘记自己究竟想学什么了。确定自己学习的主线,先将该主线补齐,再去深入研究该主线上的分支。
当设计一个产品时,需要使用单片机还是Linux取决于硬件成本和软件成本,而不是一味的根据个人喜好去选择高性能的或套件更完善的,嵌入式中没有最好的,只有最合适的(此处可以参照清华大学曾鸣老师的《嵌入式ARM微控制器》,该课程逻辑清晰,值得反复刷)。因此,既要学会使用MCU跑裸机或RTOS用于一些低端的设备,也要学会Linux用于要求较高的场合。
在上层软件对底层硬件的操作上,单片机和Linux的实现没有太大的区别:
- 根据原理图确定是哪个引脚,需要配置该引脚为输入?输出?或交由外设控制?
- 查看芯片手册中实现具体功能需要使用到的寄存器
- 软件实现操作逻辑,实现既定功能(最终会落实到对寄存器的操作)
在单片机程序中没有程序分层的概念,应用程序和驱动程序混着写,至少没有明确的界限,很可能一个人就包揽了从硬件设计、模块调试(针对各模块进行驱动程序开发,如开发SPI、I2C器件的通讯程序,对外留出调用接口)、功能实现(应用程序开发,即使用接口对外围器件进行操作,程序实现各个功能模块的相互配合,从而实现既定功能)的全部内容,一般小公司都这么干,讲究全能型,什么活都能干,不需要有规范的标准,反正能搞出来,能以最短的时间实现既定的功能需求就是最好的。
Linux程序中驱动程序和应用程序进行了分层,驱动程序访问寄存器,应用程序只能通过驱动程序实现对寄存器的访问,而不能直接去操作寄存器。为什么要设计成应用分层有以下原因?
- 保证程序运行的安全性
Linux系统比较复杂,庞大,因此其中应用程序很多。如果在软件程序中开发对底层寄存器的访问,那软件的设计不良可能会导致程序运行崩溃。因此在Linux系统中,将对底层寄存器访问的重要工作交给驱动程序去完成(系统认为驱动程序比较靠谱,只相信驱动程序的话,一般驱动程序开发需要即懂硬件又懂软件,从而才能设计出可靠的驱动程序,一般该工作由驱动工程师完成)
- 保证应用程序的可移植性
驱动程序是和底层硬件直接打交道的,因此驱动程序和硬件之间是强相关的。在A款芯片上开发的驱动程序想在B款芯片上面使用就要修改,因此只要硬件有改动,就需要检查驱动程序是否也需要针对硬件的改动作出修改。而软件程序是和驱动程序打交道的,具体点来说是和驱动程序的调用接口打交道的。只要驱动程序提供的调用接口不变,底层硬件如何改变,应用程序是不会care的,也是不需要作任何修改的。这样做的好处就是,在需要将A款芯片上的程序移植到B款芯片上的时候,只需要根据硬件的变化修改驱动程序,应用程序不需要修改。
- 程序分层易于团队协作
不像单片机程序,Linux项目一般规模比较大,一个人很难全套都搞定,因此就需要分工协作。将程序分为驱动程序和应用程序有利于团队协作,具体点就是做应用程序的只需关心如何实现业务逻辑,做驱动程序的只需要关心如何能提供对硬件操作的接口。举例假如要做一个人脸识别项目,有擅长做图像处理的,只要能给到他图像,他就能进行图像分析,具体你这个图像是用什么拍的,他是不关心的。而不同摄像头的硬件操作可能是存在差异的,因此使用哪款硬件,驱动工程师就需要针对硬件进行驱动程序的更改。
前面对RT-Thread的内核通过从0到1的实现已经有了大概的认识,接下来学习RT-Thread的设备驱动框架,和Linux的驱动树进行类比,其实RT-Thread的很多设计思想和具体实现都有借鉴Linux的设计,好的东西当然大家都要借鉴!因此,学习RT-Thread设备驱动框架又会促进对Linux的设备树的理解。白岩松说过,学习就是这样一个相互作用的过程,当你还不知道你学的知识有什么用的时候,坚持学就完事了。你不知道有什么用是因为你读的书还太少,当你读的书多了,学的知识多了,这些点才会被连起来,到时候就不是零零散散,而会成势。
RT-Thread的设备管理框架如上图所示,可以将以上分为应用层、驱动层、硬件层。RT-Thread的设计思想借鉴了Linux的应用层和驱动层设计理念,因此其设备框架和Linux非常相似,也许以后RT-Thread会引入设备树?
【应用层】应用层只需关心业务逻辑,而不需要关心底层硬件
【驱动层】对应用层提供接口使其可以间接的使用硬件,从而搭建起了应用层和硬件层之间的桥梁,驱动层未实现的硬件驱动应用层无法调用。
【硬件层】具体的硬件,可配置其内部的寄存器实现硬件功能的定制
可以这样去理解,【硬件层】是一堆原材料,【驱动层】是一个工匠,他可以将这些原材料打造成各种各样的工具,【应用层】是人,他只能使用工匠打造好的工具而不能直接去使用原材料。
应用层通过驱动层实现对硬件层设备的访问,驱动层程序的升级更改并不会影响应用层大程序。以此可以实现驱动层程序和应用层程序的低耦合,可独立进行开发。
要使用一个设备,要首先将其注册到I/O设备管理器中,让系统知道其的存在。设备被创建之后需要实现其对硬件的操作方法。根据设备功能的不同,可以选择实现部分操作方法或全部操作方法。
当设备被注册到I/O设备管理器之后,用户应用程序通过I/O设备管理器的接口来访问硬件设备。具体映射关系如下:
应用程序需要对硬件进行操作时可以按照如下步骤:
- 查找设备rt_device_t rt_device_find(const char* name);。根据设备名称(字符串)在I/O设备管理器中查找该设备,找到之后返回该设备的句柄(指针)。因此注册设备时需注意不同的设备名称不能重复
- 初始化设备rt_err_t rt_device_init(rt_device_t dev);。若该设备已经在之前被初始化成功,调用该接口将不会重复再对设备进行初始化
- 打开设备rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);。打开设备时,系统会检查该设备是否已经被初始化,未被初始化的系统会自动调用初始化接口对设备进行初始化
- 读设备rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
或写设备rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);或控制设备rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);。
5、关闭设备rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);。打开设备和关闭设备一般需成对使用,因为打开设备会对打开设备次数加1,关闭设备会对打开的设备次数减1。如果不成对使用,则很可能设备并不会被完全关闭。例如调用了2次打开设备后调用1次关闭设备,实际上此时设备并未被关闭,仍是处于打开状态,只有当设备打开次数减为0时才会真正关闭设备。
需要注意的是,只有官方提供的BSP包才可以使用ENV工具进行配置。RT-Thread官方已经对主流芯片和开发板进行BSP包支持,因此在实际的项目使用中,我们可以先找自己项目使用的芯片对应的BSP包,再使用ENV工具对内核和功能组件进行配置,使用ENV下载软件包,生成MDK项目工程,在此基础之上进行项目开发。
ENV工具是针对全功能版本的RT-Thread源码进行配置的工具,因此不适用于使用RT-Thread Nano的版本。ENV工具常用的命令也没有几条,用几遍就记住了,记不住搜一下就出来了。
当使用ENV工具在配置工程后重新创建工程时,会发现调试选项会恢复成默认的。针对此问题,在《嵌入式实时操作系统 RT-Thread设计与实现》P193有说明,可以双击打开BSP中的template.uvprojx工程,直接修改其中的调试选项,如此使用Scons命令生成的新工程也会包含对模板文件中的修改。
RTT学习--制作BSP2https://blog.csdn.net/weixin_42381351/article/details/91127709
参考如上这篇文章将RT-Thread完整版移植到STM32F105芯片上。