PX4代码是基于nuttx操作系统开发的,不是单片机程序,以至于新手在看代码的时候,理不清代码的结构,不知道每个函数对应的功能。后台也有人私信我,问应用的初始化在哪,run函数是在哪调用的。这篇博客,我就整理下PX4的代码结构。
程序的启动分为两种类型,一种是将自身添加到运行队列里,由队列统一管理;另一种是在应用中创建新进程,将程序启动。下面分别介绍两种。
一、在运行队列中运行
在介绍该方式时候,先介绍一下队列管理器。队列管理器管理处于队列中的应用。在文件px4_init.cpp中有一个函数WorkQueueManagerStart();在该函数中调用px4_task_spawn_cmd函数启动一条进程来管理队列中的应用。在队列中,调用wq.Run();来执行应用本身的逻辑。
也就是说,这种方式运行的应用,只要将自身添加到队列中,并实现Run()就可以了。
采用队列方式运行的应用,必须要继承两个类,一个是ModuleBase<T>另一个是WorkItem。
ModuleBase封装了应用常用的功能,包含对start,stop,help等指令的处理。在接收到启动命令后,会调用start_command_base()函数。在该函数中调用T::task_spawn(argc, argv)(也就是说具体的应用要重写task_spawn函数)。在task_spawn(argc, argv)会new一个应用自身的对象,如new MulticopterAttitudeControl()来完成资源分配和变量的初始化。
WorkItem类完成了加入进程队列需要的一些配置,是在初始化时完成的,不要要相关的调用。
前面介绍说在队列中,调用wq.Run();来执行应用本身的逻辑,因此具体的应用要实现Run()这个虚函数。
二、应用自身启动新进程
不同于上一种方式,通过新进程的方式启动的应用,只要继承ModuleBase类就可以了。
在接收到start指令后,调用父类的start_command_base()函数。在该函数中,调用子类重写的task_spawn函数。子类的task_spawn函数通过px4_task_spawn_cmd函数新建一条进程,入口是父类的run_trampoline。在该函数中,统一进行类的初始化及运行run()逻辑处理函数。
相比于上一种启动方式,这里的run函数以小写字母开头,上面一种则是Run()。
三、总结
总结一下,这两种方式是将一些共用的功能通过modleBase进行了提炼,封装成了公共的,减少了代码量。
说一下他们的使用场景。在姿态控制控制、姿态估计这种底层的,必须要的应用时候,一般采用第一种启动方式。对于navigator这种上层的应用,有时候进程挂了,不产生严重影响的应用,则可以采用第二种方式。