目录
-
Nuttx 代码获取编译
-
Nuttx 启动流程
-
Nuttx BootLoader 开发之源码分析
-
gpio 驱动分析
-
I2c驱动分析
-
PX4 框架分析
-
UORB 进程间通讯分析
-
PX4应用层驱动分析并实现例程
-
串口驱动GPS 驱动分析
-
mavlink 飞行控制协议分析
1.Nuttx 代码获取编译
可以参考官网安装过程
Installing — NuttX latest documentation
1.1 ubuntu 下载依赖
#sudo apt install \
bison flex gettext texinfo libncurses5-dev libncursesw5-dev \
gperf automake libtool pkg-config build-essential gperf genromfs \
libgmp-dev libmpc-dev libmpfr-dev libisl-dev binutils-dev libelf-dev \
libexpat-dev gcc-multilib g++-multilib picocom u-boot-tools util-linux
bison flex gettext texinfo libncurses5-dev libncursesw5-dev \
gperf automake libtool pkg-config build-essential gperf genromfs \
libgmp-dev libmpc-dev libmpfr-dev libisl-dev binutils-dev libelf-dev \
libexpat-dev gcc-multilib g++-multilib picocom u-boot-tools util-linux
1.2 KConfig frontend
#sudo apt install kconfig-frontends
1.3 Toolchain
#sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi
1.4 Download NuttX
#mkdir nuttxspace
#cd nuttxspace
#git clone https://github.com/apache/incubator-nuttx.git nuttx
#git clone https://github.com/apache/incubator-nuttx-apps apps
#cd nuttxspace
#git clone https://github.com/apache/incubator-nuttx.git nuttx
#git clone https://github.com/apache/incubator-nuttx-apps apps
1.5 查看nuttx支持的所有硬件平台的配置,可以先查看stm32 的配置
#./tools/configure.sh -L | less | grep stm32
<img data-cke-saved-src="file:///tmp/lu50995x3qlr8.tmp/lu50995x3qq99_tmp_3f6d84a23f302f52.png" src="file:///tmp/lu50995x3qlr8.tmp/lu50995x3qq99_tmp_3f6d84a23f302f52.png" data-cke-saved-name="图像1" name="图像1" width="643" height="361" />
1.6 选择一个stm32的配置,你可以选择其他型号的配置
#cd nuttx
#./tools/configure.sh -l stm32f4discovery:nsh
#./tools/configure.sh -l stm32f4discovery:nsh
1.7 然后你可以通过使用基于菜单的配置系统来定制这种配置
#cd nuttx
#make menuconfig
#make menuconfig
1.8 编译nuttx
#cd nuttx
#make -j4
1.10 使用openocd
烧写固件
#sudo apt install openocd
连接好你的USB 线进行烧写
#cd nuttx/
#openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'init' \
-c 'program nuttx/nuttx.bin verify reset' -c 'shutdown'
1.11 使用nuttshell 进行交互
#picocom -b 115200 /dev/ttyUSB0
你可能必须把自己加入到Linux上的dialout组,才能有权限访问串口。
#gpasswd -a <user> dialout
1.12 NuttX构建。
#cd nuttx
#./tools/configure.sh -l sim:nsh
Copy files
Select CONFIG_HOST_LINUX=y
Refreshing...
1.13 Build & run
#make clean; make
#./nuttx
login:
登录用户名称和密码
用户名: admin
密码: Administrator
1.14 从另一个终端窗口,杀死模拟器。
#sudo pkill nuttx
#./nuttx
login:
登录用户名称和密码
用户名: admin
密码: Administrator
1.14 从另一个终端窗口,杀死模拟器。
#sudo pkill nuttx
2. Nuttx 启动流程
2.1 nuttx 的启动流程一共有五个阶段
|
|
|
|
|
|
2.2下面根据源码进行分析
vi ./include/nuttx/init.h +53
| |
2.3 MCU 执行到 nx_start
系统起来之后即为完成 OSINIT_BOOT
阶段。
2.4在 OSINIT_TASKLISTS
阶段会去初始化任务列表。
2.5在 OSINIT_MEMORY
阶段需要率先初始化信号量(因为接下来很多系统组件需要使用信号量),然后初始化内存管理。
2.6在 OSINIT_HARDWARE
阶段需要先初始化文件系统(因为后面的设备驱动需要使用到文件系统,这涉及到了 nuttx 的驱动管理方式,后面我会再讲。),然后配置 中断向量表、看门狗、时钟、系统 tick 、系统信号、系统消息队列、网络、线程栈内容初始化、注册标准的设备(如:/dev/null
,/dev/zero
,/dev/loop
./dev/random
)。
2.7在 OSINIT_OSREADY
阶段需要完成多系统相关的初始化(如启动 IDLE 线程)。
2.8下面是 启动过程的分析 vi ./sched/init/nx_start.c +260
2.9除了上面的分析,网上还有其他博主对启动流程分析图,这个更为详细。如下所示:
按照stm32 启动例程来说
vi arch/arm/src/stm32/stm32_start.c +185
vi boards/arm/stm32/axoloti/src/stm32_boot.c +53
对上面的流程加以分析
| |
| |
3.Nuttx BootLoader 开发之源码分析
3.1下面主要是以STM32为例子,首先分析一下stm32 的3种启动方式启动方式
Cortex M3的内核有三种启动方式,其分别是:
3.1.1.通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;
3.1.2.通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;
3.1.3.通过boot引脚设置可以将中断向量表定位于内置Bootloader区,M3单片机复位后,从0x00000000取栈指针(SP),从0x00000004取复位向量(PC),有了栈指针和复位向量后,单片机就按照正常流程运行了,单片机启动默认先运行BootLoader,所以默认的中断向量表位置是BootLoader的中断向量表。
3.1.4 Cortex-M3单片机中断向量表的重要性
Cortex-M3单片机有一个管理中断向量表的寄存器,叫做向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。
STM32的BootLoader程序一般是在0x08000000,不是在0x00000000是因为STM32的重映射技术(不符合Cortex-M3),所以BootLoader的中断向量表在0x08000000那里。如果我们新下载的程序在0x08060000,并且新程序的中断向量表在起始地址,那么下载完程序,为了新程序能正确运行,我们需要把中断向量表重定位到0x08060000那里,再跳转到新程序运行。
Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。Cortex-M3内核是固定了中断向量表的位置而起始地址是可变化的.
3.2 Bootloader源码:CPU启动过程
3.2.1上电启动:EXTERN (vector_table)
1)初始化堆栈指针 .initial_sp_value = &_stack,
2)硬件错误为阻塞 .hard_fault = hard_fault_handler,
3)中断控制器 .irq = { IRQ_HANDLERS }
4)系统的复位入口函数 .reset = reset_handler,
3.2.2入口函数:ENTRY(reset_handler)
1)定义数据段 .data和.bss
2)pre_main()(开启协处理器)
3)main() //分为main_f1.c和main_f4.c
3.2.3.main函数:main(void)
1)board_init()(开发板的初始化)
2)bootloader()(nuttx系统的设置)
3)jump_to_app()(测试引导nuttx系统)
3.2.4.bootloader引导进入.vectors向量表:stm32_vectors.S
1)定义堆栈的大小
2)定义STM32的中断向量表
3)入口函数是ENTRY(__start)
3.2.5.入口函数是ENTRY(__start)
1)stm32的配置和初始化
2)nuttx系统的入口函数os_start() //\Firmware\NuttX\nuttx\sched\os_start.c启动Nuttx系统
3.2.6.系统入口函数os_start()
1)nuttx系统的初始化
2)nuttx系统的启动进程os_bringup()
3.2.7.系统的启动进程os_bringup()
1)创建内核进程
2)创建用户进程
3.2.8创建init进程(main_t)CONFIG_USER_ENTRYPOINT
IO板 : CONFIG_USER_ENTRYPOINT =user_start
Fmu板:CONFIG_USER_ENTRYPOINT = nsh_main
3.2.9.下面以 stm32 F1 为例子,从main_f1.c 启动初始化中断向量表,在bl.c中 启动nuttx,剩下的可以主要看一下串口虚拟代码路径kinetis 和os烧写拷贝实现过程。
├── bl.c
├── bl.h
├── board_types.txt
├── Bootloader.sublime-project
├── cdcacm.h
├── crypto_hal
│ ├── crypto.h
│ ├── image_toc.c
│ ├── image_toc.h
│ ├── monocypher
│ │ ├── crypto.c
│ │ ├── Makefile.include
│ │ └── public_key.h
│ ├── README.txt
│ └── test_key
│ └── key0.pub
├── hw_config.h
├── Jenkinsfile
├── jig_px4fmu.cfg
├── kinetis //这个路径里面是USB CDC 串口虚拟代码
│ ├── cdcacm.c
│ ├── usart.c
│ ├── usb_device_descriptor.c
│ ├── usb_device_descriptor.h
│ └── virtual_com.h
├── kinetis.c
├── kinetis.h
├── kinetisk66.ld
├── lib
│ └── kinetis
│ └── NXP_Kinetis_Bootloader_2_0_0
├── libopencm3
├── LICENSE.md
├── main_f1.c
├── main_f3.c
├── main_f4.c
├── main_f7.c
├── main_k66.c
├── Makefile
├── Makefile.f1
├── Makefile.f3
├── Makefile.f4
├── Makefile.f7
├── Makefile.k66
├── monocypher
├── px_mkfw.py
├── px_uploader.py
├── README.md
├── rules.mk
├── stm32
│ ├── cdcacm.c
│ └── usart.c
├── stm32f102.cfg
├── stm32f1.ld
├── stm32f1x.cfg
├── stm32f3.ld
├── stm32f3x.cfg
├── stm32f4.ld
├── stm32f4x.cfg
├── stm32f7.ld
├── Tools
│ ├── astylerc
│ ├── check_code_style_all.sh
│ ├── check_code_style.sh
│ ├── check_submodules.sh
│ ├── docker_run.sh
│ ├── files_to_check_code_style.sh
│ ├── fix_code_style.sh
│ └── pre-commit
└── uart.h
4.gpio 驱动分析
gpio分析以drivers/ioexpander/gpio.c为例子
4.1首先要实现文件读写的结构体,实现基本的 read open close 接口函数
static const struct file_operations g_gpio_drvrops =
{
NULL, /* open */
NULL, /* close */
gpio_read, /* read */
gpio_write, /* write */
gpio_seek, /* seek */
gpio_ioctl, /* ioctl */
NULL /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, NULL /* unlink */
#endif
};
4.2.下面的结构体是实现是硬件寄存器直接操作,是给上面 g_gpio_drvrops 使用
struct gpio_operations_s
{
/* Interface methods */
CODE int (*go_read)(FAR struct gpio_dev_s *dev, FAR bool *value);
CODE int (*go_write)(FAR struct gpio_dev_s *dev, bool value);
CODE int (*go_attach)(FAR struct gpio_dev_s *dev,
pin_interrupt_t callback);
CODE int (*go_enable)(FAR struct gpio_dev_s *dev, bool enable);
CODE int (*go_setpintype)(FAR struct gpio_dev_s *dev, enum gpio_pintype_e pintype);
};
static const struct gpio_operations_s gpin_ops =
{
.go_read = gpin_read,
.go_write = NULL,
.go_attach = NULL,
.go_enable = NULL,
};
static const struct gpio_operations_s gpout_ops =
{
.go_read = gpout_read,
.go_write = gpout_write,
.go_attach = NULL,
.go_enable = NULL,
};
static const struct gpio_operations_s gpint_ops =
{
.go_read = gpint_read,
.go_write = NULL,
.go_attach = gpint_attach,
.go_enable = gpint_enable,
};
4.3 这是驱动的入口函数,用来获取
int gpio_pin_register(FAR struct gpio_dev_s *dev, int minor)
4.4.通过register_driver函数申请设备描述符,”/dev/gpiox”,方便应用程序的调用
register_driver(devname, &g_gpio_drvrops, 0666, dev);
→inode_reserve(path, mode, &node);
5.I2c驱动分析
gpio分析以drivers/i2c/i2c_driver.c为例子,主要实现以下接口
1.入口函数
i2c_register(FAR struct i2c_master_s *i2c, int bus)
2.实现 file_operations的基本接口函数
static const struct file_operations i2cdrvr_fops
3.申请设备描述符
register_driver(devname, &i2cdrvr_fops, 0666, priv);
4.跟硬件直接交互的接口函数
I2C_TRANSFER(priv->i2c, transfer->msgv, transfer->msgc);
6.PX4 框架分析
6.1 PX4自动驾驶仪软件可分为三大部分:实时操作系统、中间件和飞行控制栈。
6.2 NuttX实时操作系统,上文对nuttx 已经做了深析,它提供POSIX-style的用户操作环境(如printf(), pthreads,/dev/ttyS1,open(),write(),poll(),ioctl()),进行底层的任务调度。
6.3 PX4中间件,PX4中间件运行于操作系统之上,提供设备驱动和一个微对象请求代理(micro object request broker,uORB)用于驾驶仪上运行的单个任务之间的异步通信。Px4被3DR开源后,整个代码结构被⼤改,原先的系统被摒弃,进而采用Nuttx,但是核心思想没变-为了简化开发而采用牺牲部分效率的消息传递机制,这是Px4 与ArduPilot 最本质的差别。
6.4 PX4飞行控制栈。飞行控制栈可以使用PX4的控制软件栈,也可以使用其他的控制软APM:Plane、APM:Copter,但必须运行于PX4中间件之上。
6.4.1决策导航部分:根据飞行器自身安全状态和接收到的命令,决定工作于什么模式,下一步应该怎么做。
6.4.2位置姿态估计部分:根据传感器得到自身的位置和姿态信息,此部分算法含金量最高,算法也相当多。
6.4.3位置姿态控制部分:根据期望位置和姿态设计控制结构,尽可能快、稳的达到期望位置和姿态。
7.UORB 进程间通讯分析
7.1 uORB(Micro Object Request Broker,微对象请求代理器)是PX4/Pixhawk系统中非常重要且关键的一个模块,它肩负了整个系统的数据传输任务,所有的传感器数据、GPS、PPM信号等都要从芯片获取后通过uORB进行传输到各个模块进行计算处理。实际上uORB是一套跨「进程」 的IPC通讯模块。在Pixhawk中, 所有的功能被独立以进程模块为单位进行实现并工作。而进程间的数据交互就由为重要,必须要能够符合实时、有序的特点。
7.2 Pixhawk使用的是NuttX实时ARM系统,uORB实际上是多个进程打开同一个设备文件,进程间通过此文件节点进行数据交互和共享。进程通过命名的「总线」交换的消息称之为「主题」(topic),在Pixhawk 中,一个主题仅包含一种消息类型,通俗点就是数据类型。每个进程可以「订阅」或者「发布」主题,可以存在多个发布者,或者一个进程可以订阅多个主题,但是一条总线上始终只有一条消息。
7.3 应用层中操作基础飞行的应用之间都是隔离的,这样提供了一种安保模式,以确保基础操作独立的高级别系统状态的稳定性。而沟通它们的就是uORB。
topics : 系统通用接口定义的标准主题,比如电池电量转态、GPS的位置参数等
module.mk : uORB模块makefile文件
objects_common.cpp: 通用接口标准主题定义集合,如添加新主题在这里定义
ORBMap.hpp : 对象请求器节点链表管理(驱动节点)
ORBSet.hpp : 对象请求器节点管理(非驱动节点)
Publication.cpp : 在不同的发布中遍历使用
Publication.hpp : 在不同的发布中遍历使用
Subscription.cpp : 在不同的订阅中遍历使用
Subscription.hpp : 在不同的订阅中遍历使用
uORB.cpp : uORB的实现
uORB.h : uORB头文件
uORBCommon.hpp : uORB公共部分变量定义实现
uORBCommunicator.hpp : 远程订阅的接口实现,实现了对不同的通信通道管理,如添加/移除订阅者,可以基于TCP/IP或fastRPC;传递给通信链路的实现,以提供在信道上接收消息的回调。
uORBDevices.hpp :
uORBDevices_nuttx.cpp : 节点操作,close,open,read,write
uORBDevices_nuttx.hpp :
uORBDevices_posix.cpp :
uORBDevices_posix.hpp :
uORBMain.cpp : uORB入口
uORBManager.hpp : uORB功能函数实现头文件
uORBManager_nuttx.cpp : uORB功能函数实现(Nuttx)
uORBManager_posix.cpp : uORB功能函数实现(Posix)
uORBTest_UnitTest.cpp : uORB测试
uORBTest_UnitTest.hpp : uORB测试头文件,包括主题定义和声明等
8.PX4应用层驱动分析并实现例程
8.1下面例子是一个发布订阅的例子,可供参考和使用
sensor_combined
主题是官方提供的通用接口标准主题。vehicle_attitude
主题是官方提供的通用接口标准主题。
8.2 程序流程图如下:
/**
* @file px4_simple_app.c
* Minimal application example for PX4 autopilot
*/
#include <nuttx/config.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <uORB/uORB.h>
#include <uORB/topics/sensor_combined.h>
#include <uORB/topics/vehicle_attitude.h>
__EXPORT int px4_simple_app_main(int argc, char *argv[]);
int px4_simple_app_main(int argc, char *argv[])
{
printf("Hello Sky!\n");
/*
订阅
sensor_combined
主题
*/
int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_combined));
orb_set_interval(sensor_sub_fd, 1000);
/*
公告
attitude
主题
*/
struct vehicle_attitude_s att;
memset(&att, 0, sizeof(att));
int att_pub_fd = orb_advertise(ORB_ID(vehicle_attitude), &att);
/*
一个应用可以等待多个主题,在这里只等待一个主题
*/
struct pollfd fds[] = {
{ .fd = sensor_sub_fd, .events = POLLIN },
/* there could be more file descriptors here, in the form like:
* { .fd = other_sub_fd, .events = POLLIN },
*/
};
int error_counter = 0;
while (true) {
/* wait for sensor update of 1 file descriptor for 1000 ms (1 second) */
int poll_ret = poll(fds, 1, 1000);
/* handle the poll result */
if (poll_ret == 0) {
/* this means none of our providers is giving us data */
printf("[px4_simple_app] Got no data within a second\n");
} else if (poll_ret < 0) {
/* this is seriously bad - should be an emergency */
if (error_counter < 10 || error_counter % 50 == 0) {
/* use a counter to prevent flooding (and slowing us down) */
printf("[px4_simple_app] ERROR return value from poll(): %d\n"
, poll_ret);
}
error_counter++;
} else {
if (fds[0].revents & POLLIN) {
/* obtained data for the first file descriptor */
struct sensor_combined_s raw;
/* copy sensors raw data into local buffer */
orb_copy(ORB_ID(sensor_combined), sensor_sub_fd, &raw);
printf("[px4_simple_app] Accelerometer:\t%8.4f\t%8.4f\t%8.4f\n",
(double)raw.accelerometer_m_s2[0],
(double)raw.accelerometer_m_s2[1],
(double)raw.accelerometer_m_s2[2]);
/*
赋值
att
并且发布这些数据给其他的应用
*/
att.roll = raw.accelerometer_m_s2[0];
att.pitch = raw.accelerometer_m_s2[1];
att.yaw = raw.accelerometer_m_s2[2];
orb_publish(ORB_ID(vehicle_attitude), att_pub_fd, &att);
}
/* there could be more file descriptors here, in the form like:
* if (fds[1..n].revents & POLLIN) {}
*/
}
}
return 0;
}
8.3
sensor_combined
主题是官方提供的通用接口标准主题。
/**
* @file px4_simple_app.c
* Minimal application example for PX4 autopilot
*/
#include <nuttx/config.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <uORB/uORB.h>
#include <uORB/topics/sensor_combined.h>
__EXPORT int px4_simple_app_main(int argc, char *argv[]);
int px4_simple_app_main(int argc, char *argv[])
{
printf("Hello Sky!\n");
/*订阅sensor_combined 主题*/
int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_combined));
/*一个应用可以等待多个主题,在这里只等待一个主题*/
struct pollfd fds[] = {
{ .fd = sensor_sub_fd, .events = POLLIN },
/* 这里可以添加更多的文件描述符;
* { .fd = other_sub_fd, .events = POLLIN },
*/
};
int error_counter = 0;
while (true) {
/*poll函数调用阻塞的时间为1s*/
int poll_ret = poll(fds, 1, 1000);
/*处理poll返回的结果 */
if (poll_ret == 0) {
/* 这表示时间溢出了,在1s内没有获取到发布者的数据 */
printf("[px4_simple_app] Got no data within a second\n");
} else if (poll_ret < 0) {
/* 出现问题 */
if (error_counter < 10 || error_counter % 50 == 0) {
/* use a counter to prevent flooding (and slowing us down) */
printf("[px4_simple_app] ERROR return value from poll(): %d\n"
, poll_ret);
}
error_counter++;
} else {
if (fds[0].revents & POLLIN) {
/*从文件描述符中获取订阅的数据*/
struct sensor_combined_s raw;
/* copy sensors raw data into local buffer */
orb_copy(ORB_ID(sensor_combined), sensor_sub_fd, &raw);
printf("[px4_simple_app] Accelerometer:\t%8.4f\t%8.4f\t%8.4f\n",
(double)raw.accelerometer_m_s2[0],
(double)raw.accelerometer_m_s2[1],
(double)raw.accelerometer_m_s2[2]);
}
/* 如果有更多的文件描述符,可以这样:
* if (fds[1..n].revents & POLLIN) {}
*/
}
}
return 0;
}
9.串口驱动GPS 驱动分析
9.1 GPS 通过串口获取数据,可以选择不同数据协议,我这边选用NEMA协议,不断轮训获取新数据,并且通过UORB将读到的数据放到队列中,被其他进程获取到。
10.mavlink 飞行控制协议分析
Mavlink是目前最常见的无人机飞控协议之一。PX4对Mavlink协议提供了良好的原生支持。该协议既可以用于地面站(GCS)对无人机(UAV)的控制,也可用于UAV对GCS的信息反馈。其飞控场景一般是这样的:
a) 手工飞控:GCS -> (MavLink) -> UAV
b) 信息采集:GCS <- (Mavlink) <- UAV
c) 自治飞控:User App -> (MavLink) -> UAV
也就是说,如果你想实现地面站控制飞行,那么由你的地面站使用Mavlink协议,通过射频信道(或 wifi etc.)给无人机发送控制指令就可以了。如果你想实现无人机自主飞行,那么就由你自己写的应用(运行在无人机系统上)使用Mavlink协议给无人机发送本地的控制指令就可以了。
然而,为实现飞控架构的灵活性,避免对底层实现细节的依赖,在PX4中,并不鼓励开发者在自定义飞控程序中直接使用Mavlink,而是鼓励开发者使用一种名为uORB((Micro Object Request Broker,微对象请求代理)的消息机制。其实uORB在概念上等同于posix里面的命名管道(named pipe),它本质上是一种进程间通信机制。由于PX4实际使用的是NuttX实时ARM系统,因此uORB实际上相当于是多个进程(驱动级模块)打开同一个设备文件,多个进程(驱动级模块)通过此文件节点进行数据交互和共享。
在uORB机制中,交换的消息被称之为topic,一个topic仅包含一种message类型(即数据结构)。每个进程(或驱动模块)均可“订阅”或“发布”多个topic,一个topic可以存在多个发布者,而且一个订阅者可也订阅多个topic。而正因为有了uORB机制的存在,上述飞控场景变成了:
a) 手工飞控:GCS -> (MavLink) -> (uORB topic) -> UAV
b) 信息采集:GCS <- (Mavlink) <- (uORB topic) <- UAV
c) 自治飞控:User App -> (uORB topic) -> (MavLink) -> UAV
有了以上背景基础,便可以自写飞控逻辑了,仅需在PX4源码中,添加一个自定义module,然后使用uORB订阅相关信息(如传感器消息等),并发布相关控制信息(如飞行模式控制消息等)即可。具体的uORB API、uORB消息定义可参考PX4文档与源码,所有控制命令都在firmware代码的msg里面,不再敷述。
最后值得一提的是,在PX4系统中,还提供了一个名为mavlink的专用module,源码在firmware的src/modules/mavlink中,这货与linux的控制台命令工具集相当相似,其既可以作为ntt控制台下的命令使用,又可作为系统模块加载后台运行。其所实现的功能包括:1)uORB消息解析,将uORB消息实际翻译为具体的Mavlink底层指令,或反之。2)通过serial/射频通信接口获取或发送Mavlink消息,既考虑到了用户自写程序的开发模式,也适用于类似linux的脚本工具链开发模式,使用起来很灵活,有兴趣的可以看看。
→GPSDriverNMEA::receive//此时传递超时时间去解析下面的数据,超时时间200ms 5HZ
->read(buf, sizeof(buf), timeout);//此处传参给回调
->_callback(GPSCallbackType::readDeviceData, buf, buf_length, _callback_user); //回调接收
→pollOrRead //读取数据
→parseChar(buf[i]); //检查协议逗号
→handleMessage(l); //开始解析