PX4原生固件及其地面站网址
https://github.com/PX4
http://px4.io/
原生固件代码:C++,构架清晰,容易维护修改,多进程
目前来讲功能方面和细节上不如APM固件
AutoPoilt 固件及其地面站网址
http://ardupilot.org/
https://github.com/ArduPilot/ardupilot
https://github.com/ArduPilot/MissionPlanner
APM固件代码:C++,稳定成熟,功能丰富,发起时间比较早
系统构架不如原生固件那么清晰,相对来讲不容易维护和修改
PIXHAWK控制器硬件
https://github.com/PX4/Hardware
经常有人将 Pixhawk、PX4、APM 还有 ArduPilot 弄混。这里首先还是简要 说明一下: Pixhawk 是飞控硬件平台,PX4 和 ArduPilot 都是开源的可以烧写到 Pixhawk 飞控中的自驾仪软件,PX4 称为原生固件,专为 Pixhawk 打造。APM(Ardupilot Mega)早期也是一款自驾仪硬件,到 APM3.0 版本,这款基于 Arduino Mega 的 自驾仪已经走到了它的终点。ArduPilot 早期是 APM 自驾仪的固件,Pixhawk 作 为 APM 的升级版,也兼容 ArduPilot 固件,APM 自驾仪卒了之后,ArduPilot 现 在全面支持 Pixhawk,现在大家亲切的称 ArduPilot 固件为 APM。
pixhack硬件介绍:https://docs.px4.io/master/en/flight_controller/pixhack_v3.html#pixhack-v3
11
51单片机,只有一个main函数,main函数中有一个主循环while。
全部代码被编译成为一个可执行文件
初始化硬件、初始化OS等代码被编译成一个可以行程序,上电后执行。
如:姿态估计代码、姿态控制算法代码分别被编译成两个独立的可执行程序。这些独立的可执行程序(如windows中的**.exe)由OS以进程的形式执行,OS为这些可执行程序创建进程时,可配置执行频率。
负责姿态估计的代码被编译为可执行程序,每一个之可执行程序都有一个入口main函数。
如该可执行程序中有一个循环,则对应的进程按照频率执行,若没有循环,执行完这段可执行程序后(遇到return),对应的进程即结束。
1
源码:即代码,还未编译,可以是多个.c文件(或其他语言的源文件);或是一个工程文件project
固件(Firmware):已经编译,并把各个已经编译好的文件打包成一个整体,以供烧录进单片机中。
PX4源码最后编译成以下固件:
Firmware/ROMFS/px4fmu_common/init.d 文件夹下的 rcS 启动脚本(startup script)。 这个脚本位于被编译到固件中的 ROM 文件系统中。这个脚本检测可用的硬件,加载硬件驱动,并且根据你的设置启动系统正常运行所需的有 app(任务软件 , 包括位置和姿态估计,控制遥测等)。所有属于自启动程序的脚本文件可以在 init.d 文件夹中找到。
1
作用:只是初始化?(配置寄存器)
对应的进程:只执行一次?无while死循环?
LED驱动的入口函数:
class AttitudeEstimatorQ;
namespace attitude_estimator_q
{
AttitudeEstimatorQ *instance;
}
class AttitudeEstimatorQ
{
public:
…
}
class AttitudeEstimatorQ;
namespace attitude_estimator_q
{
AttitudeEstimatorQ *instance;
}
class AttitudeEstimatorQ
{
public:
…
}
先声明有这个类,再定义一个类的对象的指针instance,将这个变量(对象指针)放在一个名为attitude_estimator_q的命名空间中。
_***的变量,以下划线开头的变量或者函数表示类的室友成员。
NUTTX启动(也就是上面的固件代码,汇编语句就是跳转到这个位置来执行stm32_start.c):
代码位置:Firmware/build_px4fmu-v2_default/px4fmu-v2/Nuttx/nuttx/arch/arm/src/stm32/stm32_start.c
注意:build_px4fmu-v2_default文件夹要编译整个工程之后才会生成。
stm32_start.c中处理器执行的第一条指令(px4使用的是stm32,入口在stm32_start.c中)
stm32_clockconfig() #初始化时钟
rcc_reset() #复位rcc
stm32_stdclockconfig() #初始化标准时钟
rcc_enableperipherals()#使能外设时钟
stm32_fpuconfig() #配置fpu
stm32_lowsetup() #基本初始化串口,之后可以使用up_lowputc()
stm32_gpioinit() #初始化gpio,只是调用stm32_gpioremap()设置重映射
up_earlyserialinit() #初始化串口,之后可以使用up_putc()
stm32_boardinitialize()
stm32_spiinitialize() #初始化spi,只是调用stm32_configgpio()设置gpio
stm32_usbinitialize() #初始化usb,只是调用stm32_configgpio()设置gpio
up_ledinit(); #初始化led,只是调用stm32_configgpio()设置gpio
在stm32_start.c文件中我们会看到这么一句话:
/* Then start NuttX */
showprogress(’\r’);
showprogress(’\n’);
os_start(); #系统开始启动
以下是 os_start()内容:
dq_init() #初始化各种状态的任务列表(置为null)
g_pidhash[i]= #初始化唯一可以确定的元素–进程ID
g_pidhash[PIDHASH(0)]= #分配空闲任务的进程ID为0
g_idletcb= #初始化空闲任务的任务控制块
sem_initialize()-- #初始化信号量
dq_init() #将信号量队列置为null
sem_initholders() #初始化持有者结构以支持优先级继承
up_allocate_heap() #分配用户模式的堆(设置堆的起点和大小)
kumm_initialize() #初始化用户模式的堆
up_allocate_kheap() #分配内核模式的堆
kmm_initialize() #初始化内核模式的堆
task_initialize() #初始化任务数据结构
irq_initialize() #将所有中断向量都指向同一个异常中断处理程序
wd_initialize() #初始化看门狗数据结构
clock_initialize() #初始化rtc
timer_initialize() #配置POSIX定时器
sig_initialize() #初始化信号
mq_initialize() #初始化命名消息队列
pthread_initialize() #初始化线程特定的数据,空函数
fs_initialize()— #初始化文件系统
sem_init() #初始化节点信号量为1
files_initialize() #初始化文件数组,空函数
net_initialize()-- #初始化网络
uip_initialize() #初始化uIP层
net_initroute() #初始化路由表
netdev_seminit() #初始化网络设备信号量
arptimer_init() #初始化ARP定时器
up_initialize()— #处理器特定的初始化
up_calibratedelay() #校准定时器
up_addregion() #增加额外的内存段
up_irqinitialize() #设置中断优先级,关联硬件异常处理函数
up_pminitialize() #初始化电源管理
up_dmainitialize() #初始化DMA
up_timerinit() #初始化定时器中断
devnull_register() #注册/dev/null
devzero_register() #注册/dev/zero
up_serialinit() #注册串口控制台/dev/console和串口/dev/ttyS0
up_rnginitialize() #初始化并注册随机数生成器
up_netinitialize() #初始化网络,是arch/arm/src/chip/stm32_eth.c中的
up_usbinitialize() #初始化usb驱动
board_led_on() #打开中断使能led,但很快会被其它地方的led操作改变状态
lib_initialize() #初始化c库,空函数
group_allocate() #分配空闲组
group_setupidlefiles() #在空闲任务上创建stdout、stderr、stdin
group_initialize() #完全初始化空闲组
os_bringup()------ #创建初始任务
KEKERNEL_THREAD() #启动内核工作者线程
board_initialize() #最后一刻的板级初始化
TASK_CREATE() #启动默认应用程序
forup_idle() #空闲任务循环
for( ; ; ) #不应该到达这里(程序不该执行到此)
Pixhawk 整体逻辑大致为:
77
函数调用结构示意:
官方解析:
后台任务
px4_task_spawn_cmd() 用于启动与父任务独立运行的新任务(NuttX)或者新线程(POSIX - Linux/macOS):
independent_task = px4_task_spawn_cmd(
"commander", // 进程名称
SCHED_DEFAULT, // 调度类型(RR 或 FIFO)
SCHED_PRIORITY_DEFAULT + 40, // 调度优先级
3600, // 新任务或线程的堆栈大小
commander_thread_main, // 任务(或线程的主函数)
(char * const *)&argv[0] // Void 指针传递到新任务
// (这里是命令行参数)
);
操作系统相关的信息
NuttX
NuttX 是在飞控板上运行 PX4 的首选 RTOS 。 它是一个开源软件(BSD 许可), 非常轻量化,运行高效且稳定。
各模块以任务(Task)模式运行:他们有各自的文件描述列表,但共用一个地址空间。 单个任务可以使用同一个文件描述列表启动单个或者多个线程。
每一个任务/线程都有一个固定大小的栈堆,并且有一个周期性的任务会定期检查所有栈堆都有足够的可用空间(基于 stack coloring)。
Linux/MacOS
在 Linux 或者 macOS 系统上, PX4 在一个单独的进程中运行,各个模块在各自线程中运行(在 NuttX 中任务和线程没有任何区别)。
task_main函数中:
请参考PX4开发者手册
git clone --recursive -j8 https://github.com/PX4/Firmware.git
源码大小638.2 Mb,1.11版本。
运行make 后悔多出一个build目录,运行 make distclean 会清除build目录以及编译过程中的所有中间文件。
先进入Firemare目录,再执行make命令。
运行编译之前,先运行
make distclean
The following list shows the build commands for common boards:
make px4_fmu-v4_default upload
A successful run will end with this output:
Erase : [====================] 100.0%
Program: [====================] 100.0%
Verify : [====================] 100.0%
Rebooting.
[100%] Built target upload
This section shows how make options are constructed and how to find the available choices.
make命令语法:
make [ VENDOR_MODEL_VARIANT ] [ VIEWER_MODEL_DEBUGGER_WORLD ]
例如:
make px4_fmu-v4_default
make px4_sitl jmavsim
如:
General Build Errors
Many build problems are caused by either mismatching submodules or an incompletely cleaned-up build environment. Updating the submodules and doing a distclean can fix these kinds of errors:
git submodule update --recursive
make distclean
更多请参考PX4开发者手册 / troubleshooting
33
44
task_main_trampoline 中的 trampoline :蹦床,跳床,弹床
px4_task_spawn_cmd 中的 spawn :引发;引起;导致;造成
void AttitudeEstimatorQ::task_main_trampoline(int argc, char *argv[])
{
attitude_estimator_q::instance->task_main();
}
00
PX4 由两个主要部分组成:一是 飞行控制栈(flight stack) ,该部分主要包括状态估计和飞行控制系统;另一个是 中间件 ,该部分是一个通用的机器人应用层,可支持任意类型的自主机器人,主要负责机器人的内部/外部通讯和硬件整合。
所有的 PX4 支持的 无人机机型 (包括其他诸如无人船、无人车、无人水下航行器等平台)均共用同一个代码库。 整个系统采用了 响应式(reactive) 设计,这意味着:
下面的架构图对 PX4 的各个积木模块以及各模块之间的联系进行了一个详细的概述。 图的上半部分包括了中间件模块,而下半部分展示的则是飞行控制栈的组件。
hello sky
The uORB is an asynchronous publish() / subscribe() messaging API used for inter-thread/inter-process communication.
uORB是用于线程间/进程间通信的异步publish()/subscribe()消息传递API。
数据被封装成一个UORB消息,C++编程编程中,以结构体为消息载体,进行发布、订阅。
规则:一个发布者、多个订阅者。
//读:
int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_combined));
struct sensor_combined_s raw;
while(true)
{
int poll_ret = px4_poll(fds, 1, 1000);
orb_copy(ORB_ID(sensor_combined), sensor_sub_fd, &raw);
}
//写:
struct vehicle_attitude_s att;
orb_advert_t att_pub_fd = orb_advertise(ORB_ID(vehicle_attitude), &att);
while(true)
{
att = 传感器硬件接口函数(或驱动);
orb_publish(ORB_ID(vehicle_attitude), att_pub_fd, &att);
}
解析:
总结:
读:
orb_subscribe( )
orb_copy( )
写:
orb_advertise( )
orb_publish( )
各应用之间的消息通道被称为 topics ;
消息包种类、通用的MAVLink消息(包)的定义和用途介绍可以查阅网站https://mavlink.io/en/messages/common.html#messages了解。
消息包结构封装的信息用于发送消息、接收消息、识别消息种类,而负载信息则用于描述消息所要传达的具体内容,可以理解为信封和信纸的关系
通过MavLink协议实现通信需要地面站软件和飞行控制软件的协作。地面站软件与飞行控制软件在发送、接收MavLink消息时需要依照预先设定的流程,本文以无人机与地面站连接时通信握手、参数列表请求、参数设定、状态消息包循环收发为例,在第四章介绍。
uint8_t magic; ///< protocol magic marker
uint8_t len; ///< Length of payload
uint8_t incompat_flags; ///< flags that must be understood
uint8_t compat_flags; ///< flags that can be ignored if not understood
uint8_t seq; ///< Sequence of packet
uint8_t sysid; ///< ID of message sender system/aircraft
uint8_t compid; ///< ID of the message sender component
uint8_t msgid 0:7; ///< first 8 bits of the ID of the message
uint8_t msgid 8:15; ///< middle 8 bits of the ID of the message
uint8_t msgid 16:23; ///< last 8 bits of the ID of the message
uint8_t payload[max 255]; ///< A maximum of 255 payload bytes
uint16_t checksum; ///< X.25 CRC
uint8_t signature[13]; ///< Signature which allows ensuring that the link is tamper-proof (optional)
两个或多个MAVLink系统之间通信的方式:相互收发 Packet 。
Packet可以分为两类:
如果满足以下条件,请考虑发送command:
否则,可以自由使用任何一种方法。
Use commands for actions in missions or if you need acknowledgment or retry logic from a request. Otherwise use messages.
区分 information nessages data message-data command enum 的含义即联系。
message type
message set 消息集
message 消息
消息在XML文件中定义
大多数地面控制站和自动驾驶仪实现的参考消息集在common.xml中定义
MAVLink方言(Dialects)是XML文件,用于定义协议和特定于供应商的消息,枚举和命令。
枚举用于定义命名的值,这些值可以用作消息中的选项-例如,定义错误,状态或模式。还有一个特殊的枚举MAV_CMD,用于定义MAVLink命令。
MAVLink XML文件的大致结构:
<mavlink>
<include>common.xmlinclude>
<include>other_dialect.xmlinclude>
<dialect>8dialect>
<enums>
enums>
<messages>
messages>
mavlink>
MAVLink的enums、 messages,、commands 和 other elements在XML文件中定义,然后使用代码生成器转换为支持的编程语言的库。
本教程假定你在 msg/ca_trajectory.msg 文件中定义了一个名为 ca_trajectory 的 自定义 uORB 消息,以及在 mavlink/include/mavlink/v2.0/custom_messages/mavlink_msg_ca_trajectory.h 文件中定义了一个名为 ca_trajectory的 自定义 MAVLink 消息。
此章节旨在说明:如何使用一条自定义uORB消息,并将其作为一条MAVLink消息发送出去。