PX4_ECL_EKF代码分析1

2023-05-16

写在前面

源码版本:1.6.0rc1
源码位置1:Firmware-1.6.0rc1\src\modules\ekf2_main.cpp
源码位置2:Firmware-1.6.0rc1\src\lib\ecl\EKF\

整体框架:
在这里插入图片描述
上图PX4的EKF代码框架,PX4的代码由两部分组成,一部分是在modules下的ekf2,另一部分是ecl代码库中EKF部分。

第一部分实现数据的订阅(subscribe)、整理、储存、经过处理的数据发布(publish)。
第二部分实现数据的处理。

第一部分:

int ekf2_main(int argc, char *argv[])
{
	if (argc < 2) {
		PX4_WARN("usage: ekf2 {start|stop|status}");
		return 1;
	}

	if (!strcmp(argv[1], "start")) {

		if (ekf2::instance != nullptr) {
			PX4_WARN("already running");
			return 1;
		}

		ekf2::instance = new Ekf2();

		if (ekf2::instance == nullptr) {
			PX4_WARN("alloc failed");
			return 1;
		}

		if (argc >= 3) {
			if (!strcmp(argv[2], "--replay")) {
				ekf2::instance->set_replay_mode(true);
			}
		}

		if (OK != ekf2::instance->start()) {
			delete ekf2::instance;
			ekf2::instance = nullptr;
			PX4_WARN("start failed");
			return 1;
		}
		return 0;
	}
	if (!strcmp(argv[1], "stop")) {
		if (ekf2::instance == nullptr) {
			PX4_WARN("not running");
			return 1;
		}
		ekf2::instance->exit();
		// wait for the destruction of the instance
		while (ekf2::instance != nullptr) {
			usleep(50000);
		}
		return 0;
	}

	if (!strcmp(argv[1], "print")) {
		if (ekf2::instance != nullptr) {

			return 0;
		}
		return 1;
	}

	if (!strcmp(argv[1], "status")) {
		if (ekf2::instance) {
			PX4_WARN("running");
			ekf2::instance->print_status();
			return 0;

		} else {
			PX4_WARN("not running");
			return 1;
		}
	}
	PX4_WARN("unrecognized command");
	return 1;
}

EKF2的功能代码查询,上位机在输入{start|stop|status}三个字符串使得EKF2代码运行、停止、查询状态,如:输入start,若EKF2已经在运行则打印“already running”,若没有运行就实例化一个对象 (其中包含了一些私有变量的初始化)

创建一个新的进程 new Ekf2(),并使之ekf2::instance->start()。跟进这个start()。

int Ekf2::start()
{
	ASSERT(_control_task == -1);
	/* start the task */
	_control_task = px4_task_spawn_cmd("ekf2",
					   SCHED_DEFAULT,
					   SCHED_PRIORITY_MAX - 5,
					   5800,
					   (px4_main_t)&Ekf2::task_main_trampoline,
					   nullptr);

	if (_control_task < 0) {
		PX4_WARN("task start failed");
		return -errno;
	}
	return OK;
}

函数中px4_task_spawn_cmd函数的作用是创建一个新的进程,在nuttx系统中作为一个独立的进程运行,和其他模块之间通过uORB进程间相互通讯,参数变量表示分别为:1、进程的入口函数 2、进程默认调度 3、进程优先级 4、进程栈大小 5、进程入口函数(下一步会跟进这个函数) 6、nullptr。

跟进第五个变量,进程入口函数:

void Ekf2::task_main_trampoline(int argc, char *argv[])
{
	ekf2::instance->task_main();
}

跟进task_main() 因函数体积较大故将其拆开分析

void Ekf2::task_main()
{
	// subscribe to relevant topics
	int sensors_sub = orb_subscribe(ORB_ID(sensor_combined));
	int gps_sub = orb_subscribe(ORB_ID(vehicle_gps_position));
	int airspeed_sub = orb_subscribe(ORB_ID(airspeed));
	int params_sub = orb_subscribe(ORB_ID(parameter_update));
	int optical_flow_sub = orb_subscribe(ORB_ID(optical_flow));
	int range_finder_sub = orb_subscribe(ORB_ID(distance_sensor));
	int ev_pos_sub = orb_subscribe(ORB_ID(vehicle_vision_position));
	int ev_att_sub = orb_subscribe(ORB_ID(vehicle_vision_attitude));
	int vehicle_land_detected_sub = orb_subscribe(ORB_ID(vehicle_land_detected));
	int status_sub = orb_subscribe(ORB_ID(vehicle_status));
	int sensor_selection_sub = orb_subscribe(ORB_ID(sensor_selection));

	// initialise parameter cache
	updateParams();
	// initialize data structures outside of loop
	// because they will else not always be
	// properly populated
	sensor_combined_s sensors = {};
	vehicle_gps_position_s gps = {};
	airspeed_s airspeed = {};
	optical_flow_s optical_flow = {};
	distance_sensor_s range_finder = {};
	vehicle_land_detected_s vehicle_land_detected = {};
	vehicle_local_position_s ev_pos = {};
	vehicle_attitude_s ev_att = {};
	vehicle_status_s vehicle_status = {};
	sensor_selection_s sensor_selection = {};

函数开头,订阅了一堆数据用于后面计算使用,更新参数,定义结构体。订阅的数据根据消息ID信息(如:sensor_combined),可以到:Firmware-1.6.0rc1\msg\ sensor_combined.msg 查看msg文件夹下是PX4用到的所有结构体。

	px4_pollfd_struct_t fds[2] = {};
	fds[0].fd = sensors_sub;
	fds[0].events = POLLIN;
	fds[1].fd = params_sub;
	fds[1].events = POLLIN;
	
	while (!_task_should_exit) {
		int ret = px4_poll(fds, sizeof(fds) / sizeof(fds[0]), 1000);

		if (ret < 0) {
			// Poll error, sleep and try again
			usleep(10000);
			continue;

		} else if (ret == 0) {
			// Poll timeout or no new data, do nothing
			continue;
		}
		if (fds[1].revents & POLLIN) {
			// read from param to clear updated flag
			struct parameter_update_s update;
			orb_copy(ORB_ID(parameter_update), params_sub, &update);
			updateParams();
			// fetch sensor data in next loop
			continue;
			
		} else if (!(fds[0].revents & POLLIN)) {
			// no new data
			continue;
		}

这里定义的两组topic作为阻塞等待获取(fds[0].fd=阻塞等待的句柄,fds[0].events=阻塞等待方式),而其他订阅的数据作为检查更新,言外之意阻塞等待的数据是比较重要的数据,是进行数据处理的关键步骤,而检查更新的数据是次要的有更新才会获取。

以sensor_combined阻塞等待为例:以1000毫秒阻塞等待句柄为_sensors_sub的数据,前面可以看到其消息ID为sensor_combined,如果返回值<0,说明出现错误,休息一会(usleep(10000);)继续(continue)阻塞等待这个这个数据;如果返回值=0,说明在等待1000毫秒之后依然没有拿到数据,继续(continue)阻塞等待,直到拿到数据为止。

这里两个topic分别是sensors_sub、params_sub,在往上看是由sensor_combined、parameter_update为句柄定义的topic,分别看一下两个结构体里面都有啥:
在这里插入图片描述
sensor_combined.msg里面有陀螺、加计、磁力计、气压计等一系列数据融合所必须的数据,所以对sensor_combined为句柄的topic需要阻塞等待
在这里插入图片描述
parameter_update.msg 里面只有一个变量,但这个此消息用于通知系统一个或多个参数更改,固其作用也很重要。

	bool gps_updated = false;
	bool airspeed_updated = false;
	bool optical_flow_updated = false;
	bool range_finder_updated = false;
	bool vehicle_land_detected_updated = false;
	bool vision_position_updated = false;
	bool vision_attitude_updated = false;
	bool vehicle_status_updated = false;

定义一些标志位。

		orb_copy(ORB_ID(sensor_combined), sensors_sub, &sensors);
		// update all other topics if they have new data

		orb_check(status_sub, &vehicle_status_updated);

		if (vehicle_status_updated) {
			orb_copy(ORB_ID(vehicle_status), status_sub, &vehicle_status);
		}

		orb_check(gps_sub, &gps_updated);

		if (gps_updated) {
			orb_copy(ORB_ID(vehicle_gps_position), gps_sub, &gps);
		}

		orb_check(airspeed_sub, &airspeed_updated);

		if (airspeed_updated) {
			orb_copy(ORB_ID(airspeed), airspeed_sub, &airspeed);
		}

		orb_check(optical_flow_sub, &optical_flow_updated);

		if (optical_flow_updated) {
			orb_copy(ORB_ID(optical_flow), optical_flow_sub, &optical_flow);
		}

		orb_check(range_finder_sub, &range_finder_updated);

		if (range_finder_updated) {
			orb_copy(ORB_ID(distance_sensor), range_finder_sub, &range_finder);

			if (range_finder.min_distance > range_finder.current_distance
			    || range_finder.max_distance < range_finder.current_distance) {
				range_finder_updated = false;
			}
		}

		orb_check(ev_pos_sub, &vision_position_updated);

		if (vision_position_updated) {
			orb_copy(ORB_ID(vehicle_vision_position), ev_pos_sub, &ev_pos);
		}

		orb_check(ev_att_sub, &vision_attitude_updated);

		if (vision_attitude_updated) {
			orb_copy(ORB_ID(vehicle_vision_attitude), ev_att_sub, &ev_att);
		}

上面就是检查更新的部分了,例如:检查(check)以vehicle_status为消息ID的数据是否有更新,如果有更新则把数据复制出来(copy),后面的同理。

// in replay mode we are getting the actual timestamp from the sensor topic
		hrt_abstime now = 0;
		if (_replay_mode) {
			now = sensors.timestamp;
		} else {
			now = hrt_absolute_time();
		}

这里获取绝对时间,用于回放时,各种传感器的实际时间戳

// push imu data into estimator
		float gyro_integral[3];
		gyro_integral[0] = sensors.gyro_rad[0] * sensors.gyro_integral_dt;
		gyro_integral[1] = sensors.gyro_rad[1] * sensors.gyro_integral_dt;
		gyro_integral[2] = sensors.gyro_rad[2] * sensors.gyro_integral_dt;
		float accel_integral[3];
		accel_integral[0] = sensors.accelerometer_m_s2[0] * sensors.accelerometer_integral_dt;
		accel_integral[1] = sensors.accelerometer_m_s2[1] * sensors.accelerometer_integral_dt;
		accel_integral[2] = sensors.accelerometer_m_s2[2] * sensors.accelerometer_integral_dt;

将陀螺、加计的数据进行积分,并压栈储存。

 _ekf.setIMUData(now, sensors.gyro_integral_dt * 1.e6f, sensors.accelerometer_integral_dt * 1.e6f,
				gyro_integral, accel_integral);

凡是带有 _ekf. 前缀的函数都在ECL库中。进入这个setIMUData()看一下都干啥了:

void EstimatorInterface::setIMUData(uint64_t time_usec, uint64_t delta_ang_dt, uint64_t delta_vel_dt,
				    float (&delta_ang)[3], float (&delta_vel)[3])
{
	if (!_initialised) {
		init(time_usec);
		_initialised = true;
	}
	float dt = (float)(time_usec - _time_last_imu) / 1000 / 1000;
	dt = math::max(dt, 1.0e-4f);
	dt = math::min(dt, 0.02f);

	_time_last_imu = time_usec;

	if (_time_last_imu > 0) {
		_dt_imu_avg = 0.8f * _dt_imu_avg + 0.2f * dt;
	}
	// copy data
	imuSample imu_sample_new = {};
	imu_sample_new.delta_ang = Vector3f(delta_ang);
	imu_sample_new.delta_vel = Vector3f(delta_vel);

	// convert time from us to secs   把时间转换成秒
	imu_sample_new.delta_ang_dt = delta_ang_dt / 1e6f;
	imu_sample_new.delta_vel_dt = delta_vel_dt / 1e6f;
	imu_sample_new.time_us = time_usec;
	_imu_ticks++;

	// calculate a metric which indicates the amount of coning vibration
	//计算一个表示圆锥振动量的度量
	Vector3f temp = cross_product(imu_sample_new.delta_ang, _delta_ang_prev);//做一个向量叉乘
	_vibe_metrics[0] = 0.99f * _vibe_metrics[0] + 0.01f * temp.norm();

	// calculate a metric which indiates the amount of high frequency gyro vibration
	//计算一个表示高频陀螺振动量的度量
	temp = imu_sample_new.delta_ang - _delta_ang_prev;
	_delta_ang_prev = imu_sample_new.delta_ang;
	_vibe_metrics[1] = 0.99f * _vibe_metrics[1] + 0.01f * temp.norm();

	// calculate a metric which indicates the amount of high fequency accelerometer vibration
	//计算一个表示高频加速度计振动量的度量值
	temp = imu_sample_new.delta_vel - _delta_vel_prev;
	_delta_vel_prev = imu_sample_new.delta_vel;
	_vibe_metrics[2] = 0.99f * _vibe_metrics[2] + 0.01f * temp.norm();

	// accumulate and down-sample imu data and push to the buffer when new downsampled data becomes available
	//累积和下采样imu数据,并在新的下采样数据可用时推入缓冲区
	if (collect_imu(imu_sample_new)) {//判断采样是否允许
		_imu_buffer.push(imu_sample_new);//imu采样数据压栈储存
		_imu_ticks = 0;
		_imu_updated = true;

		// down-sample the drag specific force data by accumulating and calculating the mean when
		// sufficient samples have been collected
		//在收集到足够的样本后,通过累积和计算平均值对阻力比力数据进行取样
		if (_params.fusion_mode & MASK_USE_DRAG) {
			_drag_sample_count ++;
			// note acceleration is accumulated as a delta velocity
			_drag_down_sampled.accelXY(0) += imu_sample_new.delta_vel(0);
			_drag_down_sampled.accelXY(1) += imu_sample_new.delta_vel(1);
			_drag_down_sampled.time_us += imu_sample_new.time_us;
			_drag_sample_time_dt += imu_sample_new.delta_vel_dt;

			// calculate the downsample ratio for drag specific force data
			//为阻力比力数据计算下样本比
			uint8_t min_sample_ratio = (uint8_t) ceilf((float)_imu_buffer_length / _obs_buffer_length);

			if (min_sample_ratio < 5) {
				min_sample_ratio = 5;
			}
			// calculate and store means from accumulated values
			if (_drag_sample_count >= min_sample_ratio) {
				// note conversion from accumulated delta velocity to acceleration
				_drag_down_sampled.accelXY(0) /= _drag_sample_time_dt;
				_drag_down_sampled.accelXY(1) /= _drag_sample_time_dt;
				_drag_down_sampled.time_us /= _drag_sample_count;

				// write to buffer
				_drag_buffer.push(_drag_down_sampled);//最终的目的就是push  将下采样数据压栈保存

				// reset accumulators
				_drag_sample_count = 0;
				_drag_down_sampled.accelXY.zero();
				_drag_down_sampled.time_us = 0;
				_drag_sample_time_dt = 0.0f;
			}
		}
		// get the oldest data from the buffer
		_imu_sample_delayed = _imu_buffer.get_oldest();

		// calculate the minimum interval between observations required to guarantee no loss of data
		// this will occur if data is overwritten before its time stamp falls behind the fusion time horizon
		//计算观测所需的最小间隔,以确保数据不会丢失。
		//如果数据在时间戳落后于融合时间范围之前被覆盖,就会发生这种情况
		_min_obs_interval_us = (_imu_sample_new.time_us - _imu_sample_delayed.time_us) / (_obs_buffer_length - 1);

	} else {
		_imu_updated = false;
	}
		// read mag data读取磁力计
        if (sensors.magnetometer_timestamp_relative == sensor_combined_s::RELATIVE_TIMESTAMP_INVALID) {// 如果无效
			// set a zero timestamp to let the ekf replay program know that this data is not valid
			_timestamp_mag_us = 0;

        } else {//有效
			if ((sensors.timestamp + sensors.magnetometer_timestamp_relative) != _timestamp_mag_us) {
				_timestamp_mag_us = sensors.timestamp + sensors.magnetometer_timestamp_relative;

				// Reset learned bias parameters if there has been a persistant change in magnetometer ID
				// Do not reset parmameters when armed to prevent potential time slips casued by parameter set
				// and notification events
				// Check if there has been a persistant change in magnetometer ID

                //如果磁力计ID有持续变化,则重置学习偏置参数
                //不重置参数时,以防止潜在的时间滑脱造成的参数设置和通知事件
                //检查磁力计ID是否持续变化
				orb_copy(ORB_ID(sensor_selection), sensor_selection_sub, &sensor_selection);
				if (sensor_selection.mag_device_id != 0 && sensor_selection.mag_device_id != _mag_bias_id.get()) {
					if (_invalid_mag_id_count < 200) {
						_invalid_mag_id_count++;
					}
				} else {
					if (_invalid_mag_id_count > 0) {
						_invalid_mag_id_count--;
					}
				}
				if ((vehicle_status.arming_state != vehicle_status_s::ARMING_STATE_ARMED) && (_invalid_mag_id_count > 100)) {
					// the sensor ID used for the last saved mag bias is not confirmed to be the same as the current sensor ID
					// this means we need to reset the learned bias values to zero
                    //上次保存的mag偏置使用的传感器ID没有被确认为与当前传感器ID相同,这意味着需要将学习偏置值重置为零
					_mag_bias_x.set(0.f);
					_mag_bias_x.commit_no_notification();
					_mag_bias_y.set(0.f);
					_mag_bias_y.commit_no_notification();
					_mag_bias_z.set(0.f);
					_mag_bias_z.commit_no_notification();
					_mag_bias_id.set(sensor_selection.mag_device_id);
                    _mag_bias_id.commit();
					_invalid_mag_id_count = 0;

					PX4_INFO("Mag sensor ID changed to %i", _mag_bias_id.get());
				}
				// If the time last used by the EKF is less than specified, then accumulate the
                // data and push the average when the specified interval is reached.
                //如果EKF最后使用的时间小于指定的时间,则累积数据并在达到指定的时间间隔时推入平均值。
				_mag_time_sum_ms += _timestamp_mag_us / 1000;
				_mag_sample_count++;
				_mag_data_sum[0] += sensors.magnetometer_ga[0];
				_mag_data_sum[1] += sensors.magnetometer_ga[1];
				_mag_data_sum[2] += sensors.magnetometer_ga[2];
				uint32_t mag_time_ms = _mag_time_sum_ms / _mag_sample_count;
				
				if (mag_time_ms - _mag_time_ms_last_used > _params->sensor_interval_min_ms) {
					float mag_sample_count_inv = 1.0f / (float)_mag_sample_count;
                    // calculate mean of measurements and correct for learned bias offsets
                    //计算测量值的平均值,并纠正学习偏置
					float mag_data_avg_ga[3] = {_mag_data_sum[0] *mag_sample_count_inv - _mag_bias_x.get(),
								    _mag_data_sum[1] *mag_sample_count_inv - _mag_bias_y.get(),
								    _mag_data_sum[2] *mag_sample_count_inv - _mag_bias_z.get()
								   };
					_ekf.setMagData(1000 * (uint64_t)mag_time_ms, mag_data_avg_ga);
					_mag_time_ms_last_used = mag_time_ms;
					_mag_time_sum_ms = 0;
					_mag_sample_count = 0;
					_mag_data_sum[0] = 0.0f;
					_mag_data_sum[1] = 0.0f;
					_mag_data_sum[2] = 0.0f;
				}
			}
		}

同样最后将磁力计数据压栈储存_ekf.setMagData(1000 * (uint64_t)mag_time_ms, mag_data_avg_ga);这里就不将setMagData()函数列出来了,同上面一样都在Firmware-1.6.0rc1\src\lib\ecl\EKF\estimator_interface.cpp文件中。

		// read baro data
		if (sensors.baro_timestamp_relative == sensor_combined_s::RELATIVE_TIMESTAMP_INVALID) {
			// set a zero timestamp to let the ekf replay program know that this data is not valid
			_timestamp_balt_us = 0;

		} else {
			if ((sensors.timestamp + sensors.baro_timestamp_relative) != _timestamp_balt_us) {
				_timestamp_balt_us = sensors.timestamp + sensors.baro_timestamp_relative;

				// If the time last used by the EKF is less than specified, then accumulate the
				// data and push the average when the specified interval is reached.
				_balt_time_sum_ms += _timestamp_balt_us / 1000;
				_balt_sample_count++;
				_balt_data_sum += sensors.baro_alt_meter;
				uint32_t balt_time_ms = _balt_time_sum_ms / _balt_sample_count;

				if (balt_time_ms - _balt_time_ms_last_used > (uint32_t)_params->sensor_interval_min_ms) {
					float balt_data_avg = _balt_data_sum / (float)_balt_sample_count;
					_ekf.setBaroData(1000 * (uint64_t)balt_time_ms, balt_data_avg);//气压计数据压栈储存
					_balt_time_ms_last_used = balt_time_ms;
					_balt_time_sum_ms = 0;
					_balt_sample_count = 0;
					_balt_data_sum = 0.0f;

				}
			}
		}

读取气压计并储存。

		// read gps data if available
		if (gps_updated) {
			struct gps_message gps_msg = {};
			gps_msg.time_usec = gps.timestamp;
			gps_msg.lat = gps.lat;
			gps_msg.lon = gps.lon;
			gps_msg.alt = gps.alt;
			gps_msg.fix_type = gps.fix_type;
			gps_msg.eph = gps.eph;
			gps_msg.epv = gps.epv;
			gps_msg.sacc = gps.s_variance_m_s;
			gps_msg.vel_m_s = gps.vel_m_s;
			gps_msg.vel_ned[0] = gps.vel_n_m_s;
			gps_msg.vel_ned[1] = gps.vel_e_m_s;
			gps_msg.vel_ned[2] = gps.vel_d_m_s;
			gps_msg.vel_ned_valid = gps.vel_ned_valid;
			gps_msg.nsats = gps.satellites_used;
			//TODO add gdop to gps topic
			gps_msg.gdop = 0.0f;
			_ekf.setGpsData(gps.timestamp, &gps_msg);
		}

更新GPS数据,在后面是空速计、光流、视觉这里就不再一一粘贴。

// run the EKF update and output
	if (_ekf.update())

别看着一句话很短 _ekf.update() ,但计算量很大,这个是EKF代码的核心,这个代码在ECL库中,这里先不展开,先把整体框架写完。

	{	//这个括号是对应上面的if (_ekf.update())的
		// integrate time to monitor time slippage  集成时间来监控时间滑动
		if (start_time_us == 0) {
			start_time_us = now;
		
		} else if (start_time_us > 0) {
			integrated_time_us += (uint64_t)((double)sensors.gyro_integral_dt * 1.0e6);//换算成微秒
		}
		matrix::Quaternion<float> q;
		_ekf.copy_quaternion(q.data());
		
		float velocity[3];
		_ekf.get_velocity(velocity);

这里定义个时间,用于回放日志时间戳对齐的作用,定义一个四元数、速度用来ekf中计算出来的q和v,这里的作用是为了发布不同的topic填充数据的作用。

			float gyro_rad[3];
			// generate control state data
			control_state_s ctrl_state = {};
			float gyro_bias[3] = {};
			_ekf.get_gyro_bias(gyro_bias);//复制陀螺仪的偏差
			ctrl_state.timestamp = now;//时间戳
			gyro_rad[0] = sensors.gyro_rad[0] - gyro_bias[0];
			gyro_rad[1] = sensors.gyro_rad[1] - gyro_bias[1];
			gyro_rad[2] = sensors.gyro_rad[2] - gyro_bias[2];
			ctrl_state.roll_rate = _lp_roll_rate.apply(gyro_rad[0]);//数据填充
			ctrl_state.pitch_rate = _lp_pitch_rate.apply(gyro_rad[1]);
			ctrl_state.yaw_rate = _lp_yaw_rate.apply(gyro_rad[2]);
			ctrl_state.roll_rate_bias = gyro_bias[0];
			ctrl_state.pitch_rate_bias = gyro_bias[1];
			ctrl_state.yaw_rate_bias = gyro_bias[2];

			// Velocity in body frame
			Vector3f v_n(velocity);
			matrix::Dcm<float> R_to_body(q.inversed());
			Vector3f v_b = R_to_body * v_n;
			ctrl_state.x_vel = v_b(0);
			ctrl_state.y_vel = v_b(1);
			ctrl_state.z_vel = v_b(2);


			// Local Position NED
			float position[3];
			_ekf.get_position(position);
			ctrl_state.x_pos = position[0];//数据填充
			ctrl_state.y_pos = position[1];
			ctrl_state.z_pos = position[2];

			// Attitude quaternion
			q.copyTo(ctrl_state.q);

			_ekf.get_quat_reset(&ctrl_state.delta_q_reset[0], &ctrl_state.quat_reset_counter);

			// Acceleration data
			matrix::Vector<float, 3> acceleration(sensors.accelerometer_m_s2);

			float accel_bias[3];
			_ekf.get_accel_bias(accel_bias);//复制加速度误差
			ctrl_state.x_acc = acceleration(0) - accel_bias[0];//数据填充
			ctrl_state.y_acc = acceleration(1) - accel_bias[1];
			ctrl_state.z_acc = acceleration(2) - accel_bias[2];

			// compute lowpass filtered horizontal acceleration
			acceleration = R_to_body.transpose() * acceleration;
			_acc_hor_filt = 0.95f * _acc_hor_filt + 0.05f * sqrtf(acceleration(0) * acceleration(0) +
					acceleration(1) * acceleration(1));
			ctrl_state.horz_acc_mag = _acc_hor_filt;

			ctrl_state.airspeed_valid = false;

			// use estimated velocity for airspeed estimate//用估计的速度估计空速
			if (_airspeed_mode.get() == control_state_s::AIRSPD_MODE_MEAS) {
				// use measured airspeed
				if (PX4_ISFINITE(airspeed.indicated_airspeed_m_s) && now - airspeed.timestamp < 1e6
				    && airspeed.timestamp > 0) {
					ctrl_state.airspeed = airspeed.indicated_airspeed_m_s;
					ctrl_state.airspeed_valid = true;
				}

			} else if (_airspeed_mode.get() == control_state_s::AIRSPD_MODE_EST) {
				if (_ekf.local_position_is_valid()) {
					ctrl_state.airspeed = sqrtf(velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2]);
					ctrl_state.airspeed_valid = true;
				}

			} else if (_airspeed_mode.get() == control_state_s::AIRSPD_MODE_DISABLED) {
				// do nothing, airspeed has been declared as non-valid above, controllers
				// will handle this assuming always trim airspeed
			}

			// publish control state data
			if (_control_state_pub == nullptr) {
				_control_state_pub = orb_advertise(ORB_ID(control_state), &ctrl_state);

			} else {
				orb_publish(ORB_ID(control_state), _control_state_pub, &ctrl_state);
			}

经过一系列的数据填充,最后以消息ID为control_state的topic发布数据,这里我们进去看一下control_state这个结构体都有啥,
Firmware-1.6.0rc1\msg\ control_state.msg
在这里插入图片描述
正式上面所填充的有关数据,这里没有截图完这个内容比较多。


			// generate vehicle attitude quaternion data
			struct vehicle_attitude_s att = {};
			att.timestamp = now;

			q.copyTo(att.q);
			att.rollspeed = gyro_rad[0];
			att.pitchspeed = gyro_rad[1];
			att.yawspeed = gyro_rad[2];

			// publish vehicle attitude data
			if (_att_pub == nullptr) {
				_att_pub = orb_advertise(ORB_ID(vehicle_attitude), &att);

			} else {
                   int j = 0;
                   mhrt_abstime[timei] = hrt_absolute_time();
                    PX4_INFO("hrt_absolute_time = %d",hrt_absolute_time());
                   if(timei == 9)
                   {
                       for(j=0;j<9;j++)
                       {
                          //  PX4_WARN("mhrt_abstime = %d", mhrt_abstime[j]);
                            temp_time = mhrt_abstime[j+1] - mhrt_abstime[j];
                           //PX4_WARN("mhrt_abstime = %d",temp_time/1000);
                       }
                        timei = 0;
                   }
                    timei++;
				orb_publish(ORB_ID(vehicle_attitude), _att_pub, &att);
			}

这里可以发现,每个topic发布前面都会有一个时间戳,这个是为了回放日志的时候时间对其的一个作用,这段代码也很明确,将消息ID为vehicle_attitude的topic发布。
vehicle_attitude,从字面意思来看是载具的姿态,同样看一下这个topic里面都包含什么:
在这里插入图片描述
这里正是上面所填充的数据,进行发布。

后面就不再粘贴了,都是数据基本处理,填充数据,以不同的ID发布topic。vehicle_global_position、estimator_status、wind_estimate等等。

这里就把ekf2的框架写完了,但是PX4的EKF最核心的东西还没开始,其最核心的在ECL代码库中的EKF融合算法。

总结:

  1. 数据订阅
  2. 数据处理(这里核心的还没写)
  3. 数据打包填充,按照不同的topic发布出去。

以上就是我对PX4 ekf2_main.cpp的理解,还有很多不理解的地方还在学习中,如有理解错误的地方请批评指正,欢迎一起学习填坑PX4,QQ:1103706199

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PX4_ECL_EKF代码分析1 的相关文章

  • px4 avoidance笔记

    最近在用px4官方的avoidance代码跑硬件避障 xff0c 官方介绍了只要生成符合sensor msgs PointCloud2点云信息就能使用 xff0c 因此为了应用长基线双目 xff0c 没有使用realsense的相机 xff
  • PX4进入系统控制台以及运行程序

    这里提供进入控制台两种办法 1 运行 Tools mavlink shell py dev ttyACM0 是我进入Px4系统控制台的命令 xff0c 进入之后应该是这样 Pixhawk src Firmware Tools mavlink
  • PX4 -- EKF2

    文章目录 EKF2参数高度估计Range Finder滤波 单变量更新单变量更新对多变量的影响 EKF2 参数 EKF2 中有一类 GATE 参数 当测量值在 VAR GATE 范围内才会更新值 高度估计 四种高度控制方法 xff1a 气压
  • PX4模块设计之十一:Built-In框架

    PX4模块设计之十一 xff1a Built In框架 1 Nuttx Built In框架2 PX4 Built In框架2 1 NSH Built In关联文件2 2 NSH Built In关联文件生成2 3 NSH Built In
  • PX4模块设计之十六:Hardfault模块

    PX4模块设计之十六 xff1a Hardfault模块 1 Hardfault模块初始化2 Hardfault模块主程序3 Hardfault命令3 1 hardfault check status3 2 hardfault rearm3
  • PX4模块设计之十七:ModuleBase模块

    PX4模块设计之十七 xff1a ModuleBase模块 1 ModuleBase模块介绍2 ModuleBase类介绍3 ModuleBase类功能介绍3 1 模块入口3 2 模块启动3 3 模块停止3 4 状态查询3 5 任务回调3
  • PX4模块设计之二十一:uORB消息管理模块

    PX4模块设计之二十一 xff1a uORB消息管理模块 1 uORB模块构建模式2 uORB消息管理函数2 1 状态查询2 2 资源利用2 3 模块启动2 4 模块停止3 uORB消息接口3 1 消息主题注册3 2 消息主题去注册3 3
  • PX4模块设计之二十六:BatteryStatus模块

    PX4模块设计之二十六 xff1a BatteryStatus模块 1 BatteryStatus模块简介2 模块入口函数2 1 主入口battery status main2 2 自定义子命令custom command 3 Batter
  • PX4模块设计之三十三:Sensors模块

    PX4模块设计之三十三 xff1a Sensors模块 1 Sensors模块简介2 模块入口函数2 1 主入口sensors main2 2 自定义子命令custom command2 3 模块状态print status 重载 3 Se
  • PX4模块设计之四十五:param模块

    PX4模块设计之四十五 xff1a param模块 1 param模块简介2 模块入口函数param main3 重要函数列表4 总结5 参考资料 1 param模块简介 Description Command to access and
  • mavros连接px4失败的usb-ttl原因

    问题描述 xff1a 最近在搞mavros xff0c 以方便协处理器和pixhawk通讯 xff0c 在按照官网教程安装mavros xff0c 设置px4 xff0c 连接硬件之后发现mavros卡在中间下不去 xff1a MAVROS
  • 【px4】运行mavsdk中的offboard example

    运行MAVSDK中的offboard例子时无人机不执行 想控制无人机前后左右移动 xff0c 在按照官方教程实现offboard 插件的时候 发现用action插件能正常起飞和降落 但是一旦执行到offboard的插件代码的时候就会自动降落
  • YOLOv2代码分析_读取labels[by zhangzexuan]

    YOLOv2代码分析 读取labels by zhangzexuan YOLOv2代码分析 读取labelsby zhangzexuan YOLOv2的输入代码阅读 嗯 现在参与的项目要求在人脸检测步骤直接连同人脸特征点一起预测出来 xff
  • px4下载指定版本的固件、git用法

    https hub fastgit org PX4 PX4 Autopilot git describe tag 查看当前版本号 git tag l 查看所有版本 xff0c 也就是打个tag git checkout v1 9 1 跳转到
  • robot_pose_ekf 使用说明

    协方差参数的设置 主要确定mpu6050和odom编码器协方差参数的设置 参考 xff1a turtlebot node协方差的设置 mpu605参数的设置 参考 xff1a https github com Arkapravo turtl
  • 【PX4 飞控剖析】06 树莓派加载安装ROS,Mavros以及PX4固件

    PX4 飞控剖析 06 树莓派加载安装Mavros以及PX4固件 1 树莓派 刷镜像1 1 用Win32DiskImager刷入ubuntu mate 16 04 2 desktop armhf raspberry pi的镜像 1 2 开机
  • PX4通过参数脚本给飞控导入参数

    PX4通过参数脚本给飞控导入参数 先找一架正常能飞的无人机连接地面站 在参数页面右上角点击工具 gt 保存到文件 保存的时候文件名注明参数的相关信息 然后将需要加载参数的无人机连接至地面站 xff0c 注意需要加载参数的无人机必须和保存的参
  • 大神浅谈无人机飞控软件设计 系统性总结

    写在前面 深感自己对飞控软件 算法的知识点过于杂乱 很久没有进行系统的总结了 因此决定写几篇文章记录一些飞控开发过程的知识点 主要是针对一些软件 算法部分进行讨论 如内容有错误 欢迎指出 1 飞控软件的基本模块 无人机能够飞行主要是依靠传感
  • doom3 源代码评测 1

    原文地址 http fabiensanglard net doom3 2012年6月8日 DOOM3源代码评测 简介 第1部分 共6部分 gt gt 2011年11月23日 id软件保持传统 并发布了其以前引擎的源代码 这一次是轮到了idT
  • u-boot的norflash驱动分析,以及一些调试信息

    Flash 存储器接口还有两个标准 CFI和JEDEC CFI为公共Flash接口 Common Flash Interface 用来帮助程序从Flash芯片中获取操作方式信息 而不用在程序中硬编码Flash的ID JEDEC用来帮助程序读

随机推荐

  • git 删除远程文件目录

    git rm r cached 文件夹名称 git add git commit m 34 aa 34 git push origin master
  • git删除远程分支

    branch 1 列出分支 xff0c a参数是列出所有分支 xff0c 包括远程分支 git branch a 2 创建一个本地分支 git branch branchname 3 创建一个分支 xff0c 并切换到该分支 git che
  • maven命令上传第三方包

    mvn deploy deploy file Dmaven test skip 61 true DgroupId 61 sdk的groupId DartifactId 61 包的名称 Dversion 61 版本号 如 xff1a 0 0
  • 解决图片上传权限问题

    linux默认umask为022 xff0c 对应权限为755 xff0c 其它用户可读可执行 可以vim etc profile xff0c 搜索umusk关键字查看 if UID gt 199 amp amp 34 96 usr bin
  • 微信小程序 解决 wx.request同步问题 方便开发 Promise方式

  • Python经典书籍有哪些?这份书单送给你_黑马程序员

    文章目录 一 Python 基础 01 Python编程 xff1a 从入门到实践 xff08 第2版 xff09 02 Python编程快速上手 xff08 第2版 xff09 03 Python编程初学者指南 04 笨方法 学Pytho
  • 记忆的方法

    1 第一招 xff0c 在背书的时候 xff0c 用双手捂住你的耳朵 xff0c 并且大声的朗读 研究表明 xff0c 用手捂着耳朵来朗读的话 xff0c 声音是直接通过骨骼来传输到内耳 xff0c 对大脑刺激会更加强烈 xff0c 记忆也
  • ssh登录服务器缓慢问题

    问题描述 问题刚开始是由pod启动失败 xff0c 报错unable to ensure pod container exists failed to create container for kubepods burstable pod8
  • UCOSIII学习-任务管理

    UCOSIII学习 任务管理 1 UCOSIII 任务组成2 UCOSIII 默认系统任务3 UCOSIII 任务状态4 任务堆栈1 任务堆栈的创建2 任务堆栈初始化 5 任务控制块1 任务控制块创建2 任务控制块初始化 6 任务就绪表1
  • ubuntu(Linux)配置允许远程登陆

    安装ubuntu后默认不可以以root方式登录系统 xff0c 需要做以下配置 1 使用sudo i 命令可以让用户切换到root用户 xff0c guo用户是安装ubuntu时配置的用户 xff0c 因人而异 xff1b 2 配置root
  • python带下划线的变量和函数

    参考文献 xff1a https blog csdn net AI S YE article details 44685139
  • ADD,COPY,ENTRYPOINT和cmd

    Dockerfile中有关信息 xff1a ADD与COPY区别 add 1 对压缩包进行解压2 可以在后面直接跟文件地址 copy xff1a 把本地的文件copy到容器里面 ENTRYPOINT与CMD区别 1 第一种解释 xff08
  • docker实例操作

    很多东西都是借鉴各位大神的 xff0c 也不知道具体是谁或是哪个链接 xff0c 很抱歉 两者同为目前版本中个人和小团队常用的服务级操作系统 xff0c 在线提供的软件库中可以很方便的安装到很多开源的软件及库 两者都使用bash作为基础sh
  • 三、FreeRTOS任务管理--常用函数

    任务的基本概念 FreeRTOS 的任务可认为是一系列独立任务的集合 每个任务在自己的环境中运行 在任何时刻 xff0c 只有一个任务得到运行 xff0c FreeRTOS 调度器决定运行哪个任务 调度器会不断的启动 停止每一个任务 xff
  • 七、FreeRTOS事件和常用函数接口

    基本概念 事件是一种实现任务间通信的机制 xff0c 主要用于实现多任务间的同步 xff0c 但事件通信只能是事件类型的通信 xff0c 无数据传输 与信号量不同的是 xff0c 它可以实现一对多 xff0c 多对多的同步 即一个任务可以等
  • PX4姿态估计源码分析

    写在前面 今天入坑PX4开始学习PX4代码 xff0c pixhawk硬件是可以支持PX4 ardupilot两套固件 我用的是1 6 0rc1版本代码 代码位置 xff1a Firmware1 6 0rc1 src modules att
  • PX4位置估计源码分析

    写在前面 源码版本 xff1a 1 6 0rc1 源码位置 xff1a Firmware 1 6 0rc1 src modules position estimator inav position estimator inav main c
  • ROS入门笔记(二):ROS安装与环境配置及卸载(重点)

    ROS入门笔记 xff08 二 xff09 xff1a ROS安装与环境配置及卸载 xff08 重点 xff09 文章目录 1 ROS安装步骤1 1 ROS版本1 2 确定Ubuntu版本号1 3 安装ROS1 3 1 Ubuntu初始环境
  • windows安装Java环境及Flightplot分析PX4飞行日志

    写在前面 用Flightplot分析PX4飞行日志 xff0c 不管是windows还是Ubuntu都需要安装对应的Java环境 xff08 我用的是win10家庭版 xff09 1 下载安装java环境 xff1a a 下载 点击地址进行
  • PX4_ECL_EKF代码分析1

    写在前面 源码版本 xff1a 1 6 0rc1 源码位置1 xff1a Firmware 1 6 0rc1 src modules ekf2 main cpp 源码位置2 xff1a Firmware 1 6 0rc1 src lib e