1、PX4目录结构
(1)build
build是编译目标目录,其中包括了程序源代码编译之后所生成的编译选项、中间文件、目标文件等,通常按照不同的编译选项分为多个不同的目录。
(2)cmake(存放整个飞控程序的所有编译选项)
1)common
全局公共编译配置项,px4_base.cmake中存放整个px4程序中的全局配置选项和功能函数。例如编译源代码所使用的编码标准和警告与错误选项。
2)config
独立编译配置项,允许用户可以根据自己的需要编译成不同的目标文件
3)toolchains
工具链,其中存放了以不同编译选项编译源代码时所使用的编译工具。
(3)mavlink
轻量级外部通讯协议,与外部通讯即是与计算机上的地面站程序来进行通讯。我们可以使用协议生成工具来定制我们自己需要的协议内容,并可以通过需要生成不同编程语言所使用的源文件,从而可以进行多平台同时使用。
(4)msg
msg文件夹中存放的就是uORB所需要的所有Message,即uORB在通讯时需要定义的数据格式,以.msg结尾,其内容为特定的uORB数据类型。
1)message
msg目录直属的.msg文件,都是uORB的message文件,即通讯协议内容。
2)templates
用于将.msg文件编译成.h头文件和.cpp源文件。
3)tools
存放的是将.msg生成.h和.cpp源代码的工具文件。
(5)platforms
运行平台,px4架构支持多种运行平台。在不同的运行平台上,编译源代码的内容与链接源代码均不尽相同,所以就需要在不同的编译选项下,编译不同的平台源代码。platforms中存放了不同平台下的源代码内容。
(6)ROMFS
ROMFS中存放了不同编译选项中的ROM文件系统,即存放了飞控程序启动之后首先加载的运行脚本rcS(在Nuttx配置文件defconfig中配置)和混控文件mixer。
(7)src
1)drivers:驱动目录,其中存放所有的驱动模块,包括了GPS、MPU6000、HM5883等等。
2) include:包含头文件目录。
3)lib:常用的库函数,例如全局坐标与本地坐标的转换函数等等。
4)modules:系统主要模块commander、mc_pos_control、mc_att_control等等。
5)systemcmds:系统命令param、pwm、reboot等。
(8)Tools
px4编译与烧写固件的工具包,其中有很多采用Python编写的工具类或是使用shell脚本编译的工具程序。例如:固件烧写工具px_uploader.py;飞控参数生成工具srcparser.py;半物理仿真工具jmavsim等等。
2、uORB原理与使用
原理:
uORB在在数据发布与接收过程中并不保证发送者的所有数据都可以被接收者收到,而只保证接收者在想要接收时能收到最新的数据,他们之间是多对多的关系。
使用:
(1)发布者发布oORB
1)公告/多重公告uORB;
2)发布uORB。
(2)接收者接收uORB
1)订阅uORB
2)判断uORB数据是否更行
3)复制uORB数据内容到本地内存
相关函数:
//公告
orb_advert_t orb_advertise(const struct orb_metadata *meta, const void *data, unsigned int queue_size = 1);
//多重公告
orb_advert_t orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance, int priority, unsigned int queue_size = 1);
//取消公告
int orb_unadvertise(orb_advert_t handle);
//发布
int orb_publish(const struct orb_metadata *meta, orb_advert_t handle, const void *data);
//订阅
int orb_subscribe(const struct orb_metadata *meta);
//多重订阅
int orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance);
//取消订阅
int orb_unsubscribe(int handle);
//检查是否有数据更新
int orb_check(int handle, bool *updated);
//接收,复制数据到本地内存
int orb_copy(const struct orb_metadata *meta, int handle, void *buffer);
//检查uORB是否存在
int orb_exists(const struct orb_metadata *meta, int instance);
//取得优先级
int orb_priority(int handle, int32_t *priority);
3、PX4驱动程序
src/drivers/存放的都是飞控中的驱动程序,驱动程序从级别上划分大致分为系统级驱动程序和应用级驱动程序。两类驱动程序的定义如下所示:
(1)系统级驱动程序
在操作系统中注册设备节点/dev/xxx,并为应用级驱动程序提供标准的调用方法(open、close、read、write、seek、ioctl等)。
驱动程序的实现步骤:
1)配置编译选项
2)创建驱动程序
3)配置文件操作函数
4)实现驱动函数
5)实现入口函数
6)测试驱动程序
(2)应用级驱动程序
通过操作现有的驱动程序设备节点,配置、读取、写入相关数据与设备节点交互,并通过uORB机制与上层应用进行交互,即将设备节点与上层应用建立通讯链路。
应用级驱动只需要对已存在的设备节点进行操作,并与上层应用程序做交互即可,即应用级驱动的作用是将已存在的设备与上层应用建立起一个链接。PX4里采用的方式是uORB机制,例如:我们在/dev下有一个串口设备结节/dev/ttyS2,我们将一个GPS设备接入到这个串口节点上,然后就可以通过标准的驱动程序调用(open、close、read、write、seek、ioctl等)来对这个设备节点/dev/ttyS2来操作了。
驱动程序的实现步骤:
1)配置编译选项
2)创建驱动程序
3)编写入口函数
4)创建后台运行进程
5)驱动核心函数
6)发布uORB
4、工作队列
每一个进程和线程被创建时,都要消耗掉一些额外的内存空间。这对于内存较大的服务器和个人计算机来说,并不是什么大事,而对于嵌入式的操作系统来说,内存资源非常有限,所以为了节省内存的消耗,操作系统通常都会为用户提供一种叫做工作队列的机制——workqueue。工作队列在操作系统中也是一个普通的进程,也是操作系统的一个调度单元,但其中可以为用户保存多个工作任务。用户可以向工作队列中加入等待执行的功能函数。操作系统在执行工作队列时就会调用这些预先保存的功能函数。
操作系统在执行工作队列时,会判断当前时版中的队列节点中是否有函数需要执行,如果有则执行,并将执行后的函数移出队列,之后将后续的每一个节点的时间片减一。需要说明的是,在同一个时间片当中(同一个TICK当中),可能存在多个需要执行的函数内容,它们同样也以队列的方式存储在内存当中,此时则需要执行完毕当前时间片中的所有函数之后,才可以进行下述的相关操作。
5、整数转罗马数字(算法题)
题目:
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。
思路:
题述所要实现的是将阿拉伯数字转变为罗马数字,首先我们需要搞清楚的是两种数字的转换规则。通过题目可以理解转换规则为优先转换较大的罗马数字,继而考虑较小的罗马数字。类比于我们所熟悉的阿拉伯体系,比如说1101则首先考虑为1000+100+1.但是除此之外,罗马数字还具有900、400、90、40、9、4等特殊进制数字,所以需要全部考虑。
接下来需要考虑的就是如何转换,其实通过上面的逻辑描述已经清楚的知道了转换的过程,通过不断地拼凑来拼凑出一个罗马数字。
此题最关键的点是如何将罗马数字与阿拉伯数字一一对应,在这里应用到的是pair方法,pair方法将2个数据组合成一组数据。
代码:
const pair<int, string> valueSymbols[] = {
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
};
class Solution {
public:
string intToRoman(int num) {
string roman;
for(const auto &[value, symbol] : valueSymbols){
while(num >= value){
num -= value;
roman += symbol;
}
if(num == 0){
break;
}
}
return roman;
}
};
6、pair方法
(1)pair的应用主要如下两方面:
1)2个数据组合成一组数据,类似于map。
2)当需要返回数据时,可以使用pair,可以直接使用pair的成员变量。
(2)pair的创建和初始化:
在创建pair对象时,必须提供两个类型名,两个对应的类型名的类型不必相同。 同时可以在定义时就进行初始化。如果需要定义多个pair对象类型时,可以使用typedef关键字或者直接定义数组来存储多个数据。
pair<string, string> anon; // 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count; // 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line; // 创建一个空对象line,两个元素类型分别是string和vector类型
(3)pair对象的操作
访问两个元素操作可以通过first和sencond访问,类似于结构体。
(4)pair对象的初始化
利用make_pair创建新的pair对象
pair<int, double> p1;
p1 = make_pair(1, 1.2);
cout << p1.first << p1.second << endl;
(5)pair对象的接收
当函数以pair对象作为返回值时,可以直接通过std::tie进行接收。
std::pair<std::string, int> getPreson() {
return std::make_pair("Sven", 25);
}
int main(int argc, char **argv) {
std::string name;
int ages;
std::tie(name, ages) = getPreson();
std::cout << "name: " << name << ", ages: " << ages << std::endl;
return 0;
}