PX4代码解析(6)

news2025/1/15 16:38:37

一、前言

上一节介绍了PX4姿态估计调用函数的流程,这一节分享一下我对PX4姿态解算的解读.首先,要理解PX4姿态解算的程序,要先从传感器的特性入手,这里主要介绍的传感器有加速度计,磁力计,陀螺仪.

二、传感器特性

1.加速度计

pixhawk上使用的为三轴加速度计,主要用于测量x,y,z三轴的加速度值,常用的传感器例如mpu6000与mpu9250,在进行PX4二次开发时,我们并不需要编写加速度计相关驱动程序,其代码已经在PX4_Firmware/src/drivers/imu下进行了实现,感兴趣可以自己查阅.这里我们只需了解加速度计的原理与特性.
在这里插入图片描述

如图所示,该图为加速度计简易模型,由弹簧与质量块构成,当外界有加速度时,质量块位置会发生变化,从而使得电容两端距离改变,流经电容的电流也会发生变化,通过测量电流大小,就可以得到质量块移动距离,从而得到加速度大小。理想状态下,当没有外界作用时,质量块会处于零点位置,但由于工艺,使用时间长短等各种因素,质量块可能会处于非零点位置,即所谓零偏,因此,在飞无人机之前往往需要进行校准。
此外,加速度计校准还涉及到另一个参数,这个参数是标度因数,在这里可以理解为一个比例系数,测量值乘以这个比例系数后得到实际值。
从加速度原理可以看出,在测量加速度时质量块需要不断移动,移动需要时间,因此,在高频情况下加速度计值不一定准确,低频率特性比较好.

2.陀螺仪

pixhawk上另一个比较重要的传感器就是陀螺仪,陀螺仪用于测量x,y,z三轴角速度,其基本的原理是动量守恒。当外部系统发生旋转时,内部转动装置会保持恒定的速度与方向旋转,测量这两个系统的差就可以得到当前系统角速度。为了测量x,y,z三轴角速度,通常采用万向节构成转动装置
在这里插入图片描述
在这里插入图片描述

以其中一个方向为例,当陀螺仪测量装置随着转动轴转动时,在半径方向上会存在力,使得质量块在半径方向进行周期往复运动,从而得到旋转角速度.但由于存在零点漂移,陀螺仪在低频特性较差,高频特性较好.

3.磁力计

磁力计主要用于测量当前磁场强度。为了能够测量地磁方向,通常将地磁分为水平与垂直方向,水平方向可以近似表示地磁方向。但地球的磁轴与地轴有着夹角,一般称为磁偏角,磁偏角在不同地理位置不同,因此在无人机航向计算时,需要gps获得当前经纬度,然后查表得到当前位置磁偏角,对航向进行修正(后续代码中会看到)

三、姿态解算算法

1.为什么采用四元数计算

在介绍姿态解算算法前,先来谈谈姿态的表示方式,常用的姿态表示方法有:四元数,欧拉角,方向余弦这几种方式,并且这几种方式是可以 互相转换,欧拉角虽然计算简单,但是存在退化现象;方向余弦有9个参数,导致其计算量过大,实时性不好;因此,PX4源码中采用四元数来表示姿态。

2.姿态解算的坐标系

在无人机的坐标变换过程中,一般会涉及到以下四个坐标系
1.传感器坐标系
传感器本身具有自己的测量坐标系,在安装过程中会存在安装误差,而传感器读数是在传感器坐标系下测得,为了能让无人机使用,需要将其转换到机体坐标系。但加速度计,磁力计,陀螺仪这些传感器安装时一般与无人机机体中心位置与方向都重合,所以可以粗略认为传感器坐标系与无人机坐标系重合
2.机体坐标系
一般以无人机几何中心为中心,以右手定则建立的三维直角坐标系,x轴位于无人机机体平面,以无人机前进方向为正方向;y轴在机体平面且垂直x轴,z轴垂直机体平面,以向下为正.
3.本地坐标系(local)
为了确定无人机相对于地面的速度与位置,需要引入本地坐标系(仿真中常用的地面坐标系)。一般情况下,本地坐标系是以无人机起点为坐标中心,在水平面上正北方向为x轴,正东为y轴方向,z轴垂直地面向下,这也是所谓的北东地(NED)坐标系
4.全局坐标系(global)
前面提到,在不同经纬度下,地轴与磁轴之间的磁偏角是不一样的,为了修正无人机航向,还需要引入GPS测得的地球经纬度.

3.Mahony滤波算法

下面来讲解算法,如图为mahony滤波的流程图,取自文献[1]
在这里插入图片描述
先来解释一下上图:
1. a , m a,m a,m ω \omega ω分别是加速度计测得的加速度,磁力计测得的磁场强度以及陀螺仪测得的角速度。其实这里就引出了一个问题,为什么需要加速度计与磁力计,光靠陀螺仪不就可以得到无人机姿态吗?
确实,光靠陀螺仪是可以得到无人机姿态的,通过对得到的角速度积分就得到了姿态,但靠积分得到的姿态会存在积分误差,这个光靠陀螺仪是无法解决的,因此引入了加速度计与磁力计来解决陀螺仪积分误差。
2.四元数微分方程为 Q ˙ = 1 2 ⊗ ω n b b \dot{Q} =\frac{1}{2} \otimes \omega _{nb}^{b} Q˙=21ωnbb,式中 Q Q Q为姿态四元数, ω n b b \omega _{nb}^{b} ωnbb为无人机机体坐标系b相对于北东地(NED)坐标系n的角速度,这个式子是姿态解算的核心
3.由加速度计与磁力计得来的姿态同样存在误差,因此需要PI控制器来对误差进行修正,PI控制器的输入是由加速度计与磁力计算出的姿态与最终通过四元数微分方程计算出的实际姿态的差值,输出角速度修正值,补偿到陀螺仪,抵消陀螺仪误差
4.这个过程在无人机运行过程中循环计算,不断进行姿态解算

四、姿态解算算法代码讲解

在PX4代码解析(5)中我已经介绍了代码运行流程,本节只针对姿态解算算法重点部分进行讲解,我将这部分代码分为以下几个部分:
1.AttitudeEstimatorQ对象建立以及相关数据获取
2.四元数q的初始化
3.姿态解算

先来看看.AttitudeEstimatorQ对象的构造函数

//在对象的构造函数中,主要是对对象成员变量清0
AttitudeEstimatorQ::AttitudeEstimatorQ() :
	ModuleParams(nullptr),
	WorkItem(MODULE_NAME, px4::wq_configurations::att_pos_ctrl)
{
	_vel_prev.zero();
	_pos_acc.zero();
//gyro是陀螺仪数据,是一个1*3或者3*1向量
	_gyro.zero();
//accel表示的是加速度计数据
	_accel.zero();
//mag是磁力计数据
	_mag.zero();
//vision是视觉数据,mocap是动作捕捉的数据,暂时用不到
	_vision_hdg.zero();
	_mocap_hdg.zero();
//四元数q清0
	_q.zero();
	_rates.zero();
	_gyro_bias.zero();
	//将参数文件中参数拷贝到当前进程使用,这些参数就是你在地面站里可以修改的那些,比如加速度计的偏移等数据
    update_parameters(true);
}

在执行完构造函数后,该进程会执行run函数

//该函数主要是读取各传感器数据,并分配给相应变量,为后续姿态解算做前置工作
void
AttitudeEstimatorQ::Run()
{
//第一个if主要用于判断传感器那边数据准备好没有
	if (should_exit()) {
		_sensors_sub.unregisterCallback();
		exit_and_cleanup();
		return;
	}
//定义了一个sensor_combine的结构体,用于接收数据
	sensor_combined_s sensors;
//将数据拷贝到sensors
	if (_sensors_sub.update(&sensors)) {

        update_parameters();//默认参数为force
    //检查数据是否为最新陀螺仪
		// Feed validator with recent sensor data
		if (sensors.timestamp > 0) {
			_gyro(0) = sensors.gyro_rad[0];
			_gyro(1) = sensors.gyro_rad[1];
			_gyro(2) = sensors.gyro_rad[2];
		}
//更新加速度计数据
		if (sensors.accelerometer_timestamp_relative != sensor_combined_s::RELATIVE_TIMESTAMP_INVALID) {
			_accel(0) = sensors.accelerometer_m_s2[0];
			_accel(1) = sensors.accelerometer_m_s2[1];
			_accel(2) = sensors.accelerometer_m_s2[2];

			if (_accel.length() < 0.01f) {
				PX4_ERR("degenerate accel!");
				return;
			}
		}

        // U更新磁力计数据
		if (_magnetometer_sub.updated()) {
			vehicle_magnetometer_s magnetometer;

			if (_magnetometer_sub.copy(&magnetometer)) {
				_mag(0) = magnetometer.magnetometer_ga[0];
				_mag(1) = magnetometer.magnetometer_ga[1];
				_mag(2) = magnetometer.magnetometer_ga[2];
//如果磁力计数据太小,则返回报错
				if (_mag.length() < 0.01f) {
					PX4_ERR("degenerate mag!");
					return;
				}
			}

		}
//数据更新完成标志位
		_data_good = true;

		// Update vision and motion capture heading
        //是否有视觉或者动作捕捉,false不使用
		_ext_hdg_good = false;

		if (_vision_odom_sub.updated()) {
			vehicle_odometry_s vision;

			if (_vision_odom_sub.copy(&vision)) {
				// validation check for vision attitude data
				bool vision_att_valid = PX4_ISFINITE(vision.q[0])
							&& (PX4_ISFINITE(vision.pose_covariance[vision.COVARIANCE_MATRIX_ROLL_VARIANCE]) ? sqrtf(fmaxf(
									vision.pose_covariance[vision.COVARIANCE_MATRIX_ROLL_VARIANCE],
									fmaxf(vision.pose_covariance[vision.COVARIANCE_MATRIX_PITCH_VARIANCE],
											vision.pose_covariance[vision.COVARIANCE_MATRIX_YAW_VARIANCE]))) <= _eo_max_std_dev : true);

				if (vision_att_valid) {
					Dcmf Rvis = Quatf(vision.q);
					Vector3f v(1.0f, 0.0f, 0.4f);

					// Rvis is Rwr (robot respect to world) while v is respect to world.
					// Hence Rvis must be transposed having (Rwr)' * Vw
					// Rrw * Vw = vn. This way we have consistency
					_vision_hdg = Rvis.transpose() * v;

					// vision external heading usage (ATT_EXT_HDG_M 1)
					if (_param_att_ext_hdg_m.get() == 1) {
						// Check for timeouts on data
						_ext_hdg_good = vision.timestamp > 0 && (hrt_elapsed_time(&vision.timestamp) < 500000);
					}
				}
			}
		}
        //动捕
		if (_mocap_odom_sub.updated()) {
			vehicle_odometry_s mocap;

			if (_mocap_odom_sub.copy(&mocap)) {
				// validation check for mocap attitude data
				bool mocap_att_valid = PX4_ISFINITE(mocap.q[0])
						       && (PX4_ISFINITE(mocap.pose_covariance[mocap.COVARIANCE_MATRIX_ROLL_VARIANCE]) ? sqrtf(fmaxf(
								       mocap.pose_covariance[mocap.COVARIANCE_MATRIX_ROLL_VARIANCE],
								       fmaxf(mocap.pose_covariance[mocap.COVARIANCE_MATRIX_PITCH_VARIANCE],
										       mocap.pose_covariance[mocap.COVARIANCE_MATRIX_YAW_VARIANCE]))) <= _eo_max_std_dev : true);

				if (mocap_att_valid) {
					Dcmf Rmoc = Quatf(mocap.q);
					Vector3f v(1.0f, 0.0f, 0.4f);

					// Rmoc is Rwr (robot respect to world) while v is respect to world.
					// Hence Rmoc must be transposed having (Rwr)' * Vw
					// Rrw * Vw = vn. This way we have consistency
					_mocap_hdg = Rmoc.transpose() * v;

					// Motion Capture external heading usage (ATT_EXT_HDG_M 2)
					if (_param_att_ext_hdg_m.get() == 2) {
						// Check for timeouts on data
						_ext_hdg_good = mocap.timestamp > 0 && (hrt_elapsed_time(&mocap.timestamp) < 500000);
					}
				}
			}
		}
        //如果gps数据更新
		if (_gps_sub.updated()) {
            vehicle_gps_position_s gps;//定义结构体,该结构体在msg文件夹中

			if (_gps_sub.copy(&gps)) {
				if (_param_att_mag_decl_a.get() && (gps.eph < 20.0f)) {
//通过gps得到的经纬度查表,得到磁偏角,去修正磁力计数据
					update_mag_declination(math::radians(get_mag_declination(gps.lat, gps.lon)));
				}
			}
		}
//定义在惯性系坐标系下的位置?
		if (_local_position_sub.updated()) {
			vehicle_local_position_s lpos;

			if (_local_position_sub.copy(&lpos)) {
//是否进行运动加速度补偿,加速度计数据比较好
				if (_param_att_acc_comp.get() && (hrt_elapsed_time(&lpos.timestamp) < 20_ms)
				    && lpos.v_xy_valid && lpos.v_z_valid && (lpos.eph < 5.0f) && _inited) {

					/* position data is actual */
					const Vector3f vel(lpos.vx, lpos.vy, lpos.vz);
//更新速度
					/* velocity updated */
					if (_vel_prev_t != 0 && lpos.timestamp != _vel_prev_t) {
						float vel_dt = (lpos.timestamp - _vel_prev_t) / 1e6f;
                        /* calculate acceleration in body frame *///补偿加速度计
						_pos_acc = _q.conjugate_inversed((vel - _vel_prev) / vel_dt);
					}

					_vel_prev_t = lpos.timestamp;
					_vel_prev = vel;

                } else {//数据过期,重置加速度
					/* position data is outdated, reset acceleration */
					_pos_acc.zero();
					_vel_prev.zero();
					_vel_prev_t = 0;
				}
			}
		}
//以上是获取数据,对数据简单处理
//上一次迭代时间
		/* time from previous iteration */
        hrt_abstime now = hrt_absolute_time();//高精度定时器,得到当前时间
		const float dt = math::constrain((now - _last_time) / 1e6f, _dt_min, _dt_max);
		_last_time = now;

        if (update(dt)) {//姿态解算
            vehicle_attitude_s att = {};//
			att.timestamp = sensors.timestamp;
			_q.copyTo(att.q);

			/* the instance count is not used here */
            _att_pub.publish(att);//姿态发布

#if defined(ENABLE_LOCKSTEP_SCHEDULER)

			if (_param_est_group.get() == 3) {
				// In this case the estimator_q is running without LPE and needs
				// to publish ekf2_timestamps because SITL lockstep requires it.
				ekf2_timestamps_s ekf2_timestamps;
				ekf2_timestamps.timestamp = now;
				ekf2_timestamps.airspeed_timestamp_rel = 0;
				ekf2_timestamps.distance_sensor_timestamp_rel = 0;
				ekf2_timestamps.optical_flow_timestamp_rel = 0;
				ekf2_timestamps.vehicle_air_data_timestamp_rel = 0;
				ekf2_timestamps.vehicle_magnetometer_timestamp_rel = 0;
				ekf2_timestamps.visual_odometry_timestamp_rel = 0;
				_ekf2_timestamps_pub.publish(ekf2_timestamps);
			}

#endif
		}
	}
}

代码中,我对其进行了注释,下面有几个需要强调的点

五、参考文献及博客

[1]储开斌, 赵爽, 冯成涛. 基于Mahony-EKF的无人机姿态解算算法[J]. 电子测量与仪器学报, 2020, 32(12):7.
[2]Valenti, Roberto G , Dryanovski, et al. Sensors, Vol. 15, Pages 19302-19330: Keeping a Good Attitude: A Quaternion-Based Orientation Filter for IMUs and MARGs. 2015.
[3]米刚, 田增山, 金悦,等. 基于MIMU和磁力计的姿态更新算法研究[J]. 传感技术学报, 2015, 28(1):6.
[4]姿态估计(互补滤波)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GeoServer服务迁移出现 EncryptionOperationNotPossibleException 错误的解决方案

目录1.前言2.GeoServer服务迁移一般流程3.遇到问题4.原因分析5.解决办法6.根本原因分析7.总结1.前言 这几天我在迁移 GeoServer 服务的时候发现&#xff0c;报了一个错&#xff0c;EncryptionOperationNotPossibleException &#xff0c;这个错误的大概意思是加密操作不可用异常…

Jenkins - 打造强大的前端自动化工作流

什么是 Jenkins&#xff1f; Jenkins 是一款业界流行的开源持续集成工具&#xff0c;广泛用于项目开发&#xff0c;具有自动化构建、测试和部署等功能。我们可以利用 Jenkins 来对项目的持续性集成进行管控处理。 Jenkins 任务的创建 这里就简单不再累赘说明 Jenkins 的安装启动…

CF思维训练,2020长春CCPC(A,D)

D. Knowledge Cards(华容道GAME) 题意&#xff1a;给定n*m的矩阵&#xff0c;初始在左上角有一摞牌&#xff0c;从上到下每张的数字构成了一个长度为k的排列&#xff0c;请问在保证1.不能使得牌在移动过程中重叠2.不能让牌反复出现在左上和右下角。这两个条件下&#xff0c;能…

是不是Jenkins大神,看这几个技巧就够

01 Performance插件兼容性问题 自由风格项目中&#xff0c;有使用 Performance 插件收集构建产物&#xff0c;但是截至到目前最新版本&#xff08;Jenkins v2.298&#xff0c;Performance&#xff1a;v3.19&#xff09;&#xff0c;此插件和Jenkins都存在有兼容性问题&#xf…

运筹说 第75期 | 数学家欧拉也玩跨界

莱昂哈德欧拉&#xff08;Leonhard Euler&#xff09;的一生&#xff0c;是为数学发展而奋斗的一生&#xff0c;他不但为数学界作出贡献&#xff0c;更把整个数学推至物理的领域。欧拉杰出的智慧&#xff0c;顽强的毅力&#xff0c;孜孜不倦的奋斗精神和高尚的科学道德&#xf…

用uniapp开发打包多端应用完整指南

一、uni-app项目介绍 用uni-app开发多端项目&#xff0c;一套代码可同时打包出各端小程序、h5和app&#xff0c;uni-app支持通过 HBuilderX可视化界面 和 vue-cli命令行 两种方式创建项目&#xff0c;下面示例项目采用 HBuilderX可视化界面 的方式创建&#xff0c;cli项目可参…

opencv c++ 图像梯度、边缘、锐化

图像梯度的目的&#xff1a; 获取图像上沿着某一方向或多个方向上&#xff0c;像素值的突变图像。 即&#xff1a; 对满足之间相互独立的函数&#xff0c; 求&#xff0c; 1、预备知识 1.1、常见的梯度计算算子 1.2、梯度计算方法 L2法&#xff1a; L1法&#xff1a; 1.3…

Packet Tracer - 比较 2960 和 3560

目标 第 1 部分&#xff1a;比较第 2 层和第 3 层交换机 第 2 部分&#xff1a;比较第 3 层交换机和路由器 拓扑图 背景信息 在本练习中&#xff0c;您将使用各种命令检查三种不同的交换拓扑&#xff0c;并且比较 2960 和 3560 交换机之间的异同。 您还将比较 1941 路由器和…

章鱼应用链|UniqueOne 构建一体化的 NFT 和元宇宙的体验

全长1325字&#xff0c;预计阅读 6 分钟 作者&#xff1a;MiX 章鱼网络生态候选应用链 UniqueOne 已经通过投票&#xff0c;近期将启动主网。 UniqueOne 正在构建「NFT市场-元宇宙-DeFi」结构的多层价值生态&#xff0c;UoNo NFT Marketplace 将无缝连接元宇宙 UniqueOne.Wo…

七个研究生必备高效科研网站

文章目录一、Papers With Code二、Connected papers三、Semantic Scholar四、Researcher五、Academic Phrasebank六、EndNote七、DeepL翻译一、Papers With Code Reddit用户rstoj做了一个网站&#xff0c;将ArXiv上的最新机器学习论文与GitHub上的代码&#xff08;TensorFlow/…

RK3399 Android10 移除应用权限(包含USB)申请弹框

Android板外接了USB设备&#xff0c;每次开机后第一次启动我们的APP&#xff0c;都会弹出申请USB的弹框&#xff0c;客户使用起来很不方便&#xff0c;翻了一些文章和代码后通过修改UsbPermissionActivity.java实现。 我们看到此Activity是一个AlertActivity&#xff0c;也就是…

Zookeeper 实现分布式锁 -- 基于Curator

Zookeeper的四种节点类型 1、持久化节点 &#xff1a;所谓持久节点&#xff0c;是指在节点创建后&#xff0c;就一直存在&#xff0c;直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。 2、持久化顺序节点&#xff1a;这类节点的基本特性和上…

postgres源码解析38 表创建执行全流程梳理--3

本文结合实例讲解表创建执行流程 [CREATE TABLE wp_shy(id int primary key, name carchar(20))],相关知识回顾见&#xff1a; postgres源码解析38 表创建执行全流程梳理–1 postgres源码解析38 表创建执行全流程梳理–2 执行流程图 transformCreateStmt函数是表创建真正的入口…

第十四届蓝桥杯模拟赛(第二场)题解·2022年·C/C++

前言 本场比赛是校内赛&#xff0c;总体感觉是DP规划比赛和简单数据结构比赛&#xff0c;但是要细心一点就可以了。 因为不知道答案&#xff0c;所以本题解只有一点参考意义&#xff0c;欢迎评论区和小熊同学讨论。 **不保证答案一定就是对的&#xff01;&#xff01;&#x…

退役了,总结的ACM近年区域赛的所有题型

之前写了个退役文章记录想记录下&#xff0c;但是没有内容&#xff0c;还是给删了&#xff0c;所以前面的是退役小记&#xff0c;后面是我个人写近2年所有区域赛场次记录的题型 目录 退役小记&#xff08;没兴趣可以不看&#xff09; 这里简单记下我的acm生涯 省赛和三场区…

Miniconda:在pycharm的terminal中无法使用Conda命令

在pycharm的terminal中无法使用Conda命令 问题&#xff1a; 在本地下载好conda系列后&#xff0c;在pycharm的terminal中无法使用conda命令 问题分析&#xff1a; 说的很清楚了&#xff0c;是因为当前支持的shells没有初始化conda 所以我们只需要把Shell 路径改成激活cond…

腾讯云架构师整理总结的MySQL性能优化和高可用架构实践文档

前言 有人调侃我们说&#xff1a; 程序员不如送外卖。送外卖是搬运食物&#xff0c;自己是搬运代码&#xff0c;都不产出新的东西……透支体力&#xff0c;又消耗健康&#xff0c;可替代性极强&#xff0c;30岁之后就要面临被优化的危险……想跳槽&#xff0c;但是更高的平台…

wordpress改成https网址方法

我们建议搭建wordpress使用宝塔更为方便&#xff0c;另外也可以选择WDCP或者phpstudy也不错。本次教程适用于宝塔。 老样子买一台云服务器&#xff0c;建议找一些主流云服务器提供商&#xff0c;然后买一台Linux系统的然后安装好宝塔控制面板&#xff0c;创建一个站点&#xf…

【T+】畅捷通T+服务管理中,异步任务服务(TPlusPopAsyncTaskService1700)无法启动

【问题描述】 畅捷通T产品&#xff0c; 服务管理中的【异步任务服务&#xff08;TPlusPopAsyncTaskService1700&#xff09;】一直处于停止状态&#xff0c;且点击启动没有任何反应。 【解决方法】 【排查过程】 首先&#xff1a;检查数据库配置&#xff0c;以及网站端口配置…

可以写进简历的软件测试电商项目,不进来get一下?

前言 说实话&#xff0c;在找项目的过程中&#xff0c;我下载过&#xff08;甚至付费下载过&#xff09;N多个项目、联系过很多项目的作者&#xff0c;但是绝大部分项目&#xff0c;在我看来&#xff0c;并不适合你拿来练习&#xff0c;它们或多或少都存在着“问题”&#xff…