1859 lines
58 KiB
C++
Executable File

/****************************************************************************
*
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <lib/drivers/device/Device.hpp>
#include "MicroStrain.hpp"
device::Serial device_uart{};
MicroStrain::MicroStrain(const char *uart_port) :
ModuleParams(nullptr),
ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::INS3),
_vehicle_global_position_pub((_param_ms_mode.get() == 0) ? ORB_ID(external_ins_global_position) : ORB_ID(
vehicle_global_position)),
_vehicle_attitude_pub((_param_ms_mode.get() == 0) ? ORB_ID(external_ins_attitude) : ORB_ID(vehicle_attitude)),
_vehicle_local_position_pub((_param_ms_mode.get() == 0) ? ORB_ID(external_ins_local_position) : ORB_ID(
vehicle_local_position))
{
// Store port name
memset(_port, '\0', sizeof(_port));
const size_t max_len = math::min(sizeof(_port), strnlen(uart_port, sizeof(_port)));
strncpy(_port, uart_port, max_len);
// Enforce null termination
_port[max_len] = '\0';
// The scheduling rate (in microseconds) is set higher than the highest data rate param (in Hz)
const int max_param_rate = math::max(math::max(_param_ms_imu_rate_hz.get(), _param_ms_mag_rate_hz.get(),
_param_ms_baro_rate_hz.get()), _param_ms_filter_rate_hz.get());
// We always want the schedule rate faster than the highest sensor data rate
_ms_schedule_rate_us = (1e6) / (2 * max_param_rate);
PX4_DEBUG("Schedule rate: %i", _ms_schedule_rate_us);
device::Device::DeviceId device_id{};
device_id.devid_s.devtype = DRV_INS_DEVTYPE_MICROSTRAIN;
device_id.devid_s.bus_type = device::Device::DeviceBusType_SERIAL;
device_id.devid_s.bus = 2;
_dev_id = device_id.devid;
_px4_accel.set_device_id(_dev_id);
_px4_gyro.set_device_id(_dev_id);
_px4_mag.set_device_id(_dev_id);
_sensor_baro.device_id = _dev_id;
_sensor_baro.pressure = 0;
_sensor_baro.temperature = 0;
_sensor_baro.error_count = 0;
gnss_antenna_offset1[0] = _param_ms_gnss_offset1_x.get();
gnss_antenna_offset1[1] = _param_ms_gnss_offset1_y.get();
gnss_antenna_offset1[2] = _param_ms_gnss_offset1_z.get();
PX4_DEBUG("GNSS antenna offset 1: %f/%f/%f", (double)_param_ms_gnss_offset1_x.get(),
(double)_param_ms_gnss_offset1_y.get(),
(double)_param_ms_gnss_offset1_z.get());
gnss_antenna_offset2[0] = _param_ms_gnss_offset2_x.get();
gnss_antenna_offset2[1] = _param_ms_gnss_offset2_y.get();
gnss_antenna_offset2[2] = _param_ms_gnss_offset2_z.get();
PX4_DEBUG("GNSS antenna offset 2: %f/%f/%f", (double)_param_ms_gnss_offset2_x.get(),
(double)_param_ms_gnss_offset2_y.get(),
(double)_param_ms_gnss_offset2_z.get());
rotation.euler[0] = _param_ms_sensor_roll.get();
rotation.euler[1] = _param_ms_sensor_pitch.get();
rotation.euler[2] = _param_ms_sensor_yaw.get();
PX4_DEBUG("Device Roll/Pitch/Yaw: %f/%f/%f", (double)_param_ms_sensor_roll.get(),
(double)_param_ms_sensor_pitch.get(),
(double)_param_ms_sensor_yaw.get());
_sensor_baro_pub.advertise();
_sensor_selection_pub.advertise();
_vehicle_local_position_pub.advertise();
_vehicle_angular_velocity_pub.advertise();
_vehicle_attitude_pub.advertise();
_vehicle_global_position_pub.advertise();
_vehicle_odometry_pub.advertise();
_estimator_status_pub.advertise();
_sensor_gps_pub[0].advertise();
_sensor_gps_pub[1].advertise();
}
MicroStrain::~MicroStrain()
{
if (device_uart.isOpen()) {
device_uart.close();
}
PX4_DEBUG("Destructor");
_sensor_baro_pub.unadvertise();
_sensor_selection_pub.unadvertise();
_vehicle_local_position_pub.unadvertise();
_vehicle_angular_velocity_pub.unadvertise();
_vehicle_attitude_pub.unadvertise();
_vehicle_global_position_pub.unadvertise();
_vehicle_odometry_pub.unadvertise();
_estimator_status_pub.unadvertise();
perf_free(_loop_perf);
perf_free(_loop_interval_perf);
}
bool mipInterfaceUserRecvFromDevice(mip_interface *device, uint8_t *buffer, size_t max_length,
timeout_type wait_time,
size_t *out_length, timestamp_type *timestamp_out)
{
(void)device;
*timestamp_out = hrt_absolute_time();
int res = device_uart.read(buffer, max_length);
if (res == -1 && errno != EAGAIN) {
PX4_ERR("MicroStrain driver failed to read(%d): %s", errno, strerror(errno));
*out_length = 0;
return false;
}
if (res >= 0) {
*out_length = res;
}
return true;
}
bool mipInterfaceUserSendToDevice(mip_interface *device, const uint8_t *data, size_t length)
{
int res = device_uart.write(const_cast<uint8_t *>(data), length);
if (res >= 0) {
return true;
}
PX4_ERR("MicroStrain driver failed to write(%d): %s", errno, strerror(errno));
return false;
}
mip_cmd_result MicroStrain::forceIdle()
{
// Setting to idle may fail the first couple times, so call it a few times in case the device is streaming too much data
PX4_DEBUG("Setting device to idle");
mip_cmd_result res = MIP_PX4_ERROR;
uint8_t set_to_idle_tries = 0;
while (set_to_idle_tries++ < 3) {
if (mip_cmd_result_is_ack((res = mip_base_set_idle(&_device)))) {
break;
} else {
usleep(1_s);
}
}
return res;
}
int MicroStrain::connectAtBaud(int32_t baud)
{
if (device_uart.isOpen()) {
if (device_uart.setBaudrate(baud) == false) {
PX4_ERR("Failed to set UART %lu baud", baud);
}
} else {
if (device_uart.setPort(_port) == false) {
PX4_ERR("Could not set device port!");
return PX4_ERROR;
}
if (device_uart.setBaudrate(baud) == false) {
PX4_ERR("Failed to set UART %lu baud", baud);
return PX4_ERROR;
}
if (device_uart.open() == false) {
PX4_ERR("Could not open device port!");
return PX4_ERROR;
}
}
PX4_INFO("Serial Port %s with baud of %lu baud", (device_uart.isOpen() ? "CONNECTED" : "NOT CONNECTED"), baud);
// Re-init the interface with the correct timeouts
mip_interface_init(&_device, _parse_buffer, sizeof(_parse_buffer), mip_timeout_from_baudrate(baud) * 1_ms, 250_ms,
&mipInterfaceUserSendToDevice, &mipInterfaceUserRecvFromDevice, &mip_interface_default_update, NULL);
if (!mip_cmd_result_is_ack(forceIdle())) {
PX4_ERR("Could not set device to idle");
return PX4_ERROR;
}
PX4_INFO("Successfully opened and pinged");
return PX4_OK;
}
mip_cmd_result MicroStrain::getSupportedDescriptors()
{
const size_t descriptors_max_size = sizeof(_supported_descriptors) / sizeof(_supported_descriptors[0]);
uint8_t descriptors_count, extended_descriptors_count;
// Pull the descriptors and the extended descriptors from the device
mip_cmd_result res = mip_base_get_device_descriptors(&_device, _supported_descriptors, descriptors_max_size,
&descriptors_count);
mip_cmd_result res_extended = mip_base_get_extended_descriptors(&_device, &(_supported_descriptors[descriptors_count]),
descriptors_max_size - descriptors_count, &extended_descriptors_count);
if (!mip_cmd_result_is_ack(res)) {
return res;
}
if (mip_cmd_result_is_ack(res_extended)) {
PX4_DEBUG("Device does not support the extended descriptors command.");
}
_supported_desc_len = descriptors_count + extended_descriptors_count;
// Get the supported descriptor sets from the obtained descriptors
for (uint16_t i = 0; i < _supported_desc_len; i++) {
auto descriptor_set = static_cast<uint8_t>((_supported_descriptors[i] & 0xFF00) >> 8);
bool unique = true;
uint16_t j;
for (j = 0; j < _supported_desc_set_len; j++) {
if (_supported_descriptor_sets[j] == descriptor_set) {
unique = false;
break;
} else if (_supported_descriptor_sets[j] == 0) {
break;
}
}
if (unique) {_supported_descriptor_sets[j] = descriptor_set; _supported_desc_set_len++;}
}
return res;
}
bool MicroStrain::supportsDescriptorSet(uint8_t descriptor_set)
{
for (uint16_t i = 0; i < _supported_desc_set_len; i++) {
if (_supported_descriptor_sets[i] == descriptor_set) {
return true;
}
}
return false;
}
bool MicroStrain::supportsDescriptor(uint8_t descriptor_set, uint8_t field_descriptor)
{
// Check if the descriptor set is supported
if (!supportsDescriptorSet(descriptor_set)) {return false;}
// Check if the field descriptor is supported
const uint16_t full_descriptor = (descriptor_set << 8) | field_descriptor;
for (uint16_t i = 0; i < _supported_desc_len; i++) {
if (_supported_descriptors[i] == full_descriptor) {
return true;
}
}
return false;
}
mip_cmd_result MicroStrain::writeBaudRate(uint32_t baudrate, uint8_t port)
{
mip_cmd_result res;
// Writes the baudrate using whichever command the device supports
if (supportsDescriptor(MIP_BASE_CMD_DESC_SET, MIP_CMD_DESC_BASE_COMM_SPEED)) {
res = mip_base_write_comm_speed(&_device, port, baudrate);
}
else if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_UART_BAUDRATE)) {
res = mip_3dm_write_uart_baudrate(&_device, baudrate);
}
else {
PX4_ERR("Does not support write baudrate commands");
res = MIP_PX4_ERROR;
}
return res;
}
mip_cmd_result MicroStrain::configureImuRange()
{
mip_cmd_result res = MIP_ACK_OK;
// Checks if the accel range is to be configured
if (_param_ms_accel_range_setting.get() != -1) {
if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_SENSOR_RANGE)) {
res = mip_3dm_write_sensor_range(&_device, MIP_SENSOR_RANGE_TYPE_ACCEL, _param_ms_accel_range_setting.get());
} else {
PX4_ERR("Accel sensor range write command is not supported");
res = MIP_PX4_ERROR;
}
}
if (!mip_cmd_result_is_ack(res)) {
return res;
}
// Checks if the gyro range is to be configured
if (_param_ms_gyro_range_setting.get() != -1) {
if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_SENSOR_RANGE)) {
res = mip_3dm_write_sensor_range(&_device, MIP_SENSOR_RANGE_TYPE_GYRO, _param_ms_gyro_range_setting.get());
} else {
PX4_ERR("Gyro sensor range write command is not supported");
res = MIP_PX4_ERROR;
}
}
return res;
}
mip_cmd_result MicroStrain::getBaseRate(uint8_t descriptor_set, uint16_t *base_rate)
{
mip_cmd_result res;
// Gets the base rate using whichever command the device supports
if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_GET_BASE_RATE)) {
res = mip_3dm_get_base_rate(&_device, descriptor_set, base_rate);
} else {
switch (descriptor_set) {
case MIP_SENSOR_DATA_DESC_SET: {
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_CMD_DESC_3DM_GET_IMU_BASE_RATE)) {
res = mip_3dm_imu_get_base_rate(&_device, base_rate);
} else {
PX4_ERR("IMU base rate command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
case MIP_GNSS1_DATA_DESC_SET:
case MIP_GNSS2_DATA_DESC_SET:
case MIP_GNSS_DATA_DESC_SET: {
if (supportsDescriptor(descriptor_set, MIP_CMD_DESC_3DM_GET_GNSS_BASE_RATE)) {
res = mip_3dm_gps_get_base_rate(&_device, base_rate);
} else {
PX4_ERR("GNSS base rate command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
case MIP_FILTER_DATA_DESC_SET: {
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_CMD_DESC_3DM_GET_FILTER_BASE_RATE)) {
res = mip_3dm_filter_get_base_rate(&_device, base_rate);
} else {
PX4_ERR("Filter base rate command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
default:
PX4_ERR("Descriptor set for base rate is not supported");
res = MIP_PX4_ERROR;
break;
}
}
return res;
}
mip_cmd_result MicroStrain::writeMessageFormat(uint8_t descriptor_set, uint8_t num_descriptors,
const mip::DescriptorRate *descriptors)
{
mip_cmd_result res;
// Writes the message format using whichever command the device supports
if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_MESSAGE_FORMAT)) {
return mip_3dm_write_message_format(&_device, descriptor_set, num_descriptors,
descriptors);
} else {
switch (descriptor_set) {
case MIP_SENSOR_DATA_DESC_SET: {
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_CMD_DESC_3DM_IMU_MESSAGE_FORMAT)) {
res = mip_3dm_write_imu_message_format(&_device, num_descriptors, descriptors);
} else {
PX4_ERR("IMU message format command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
case MIP_GNSS1_DATA_DESC_SET:
case MIP_GNSS2_DATA_DESC_SET:
case MIP_GNSS_DATA_DESC_SET: {
if (supportsDescriptor(descriptor_set, MIP_CMD_DESC_3DM_GNSS_MESSAGE_FORMAT)) {
res = mip_3dm_write_gps_message_format(&_device, num_descriptors, descriptors);
} else {
PX4_ERR("GNSS messaage format command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
case MIP_FILTER_DATA_DESC_SET: {
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_CMD_DESC_3DM_FILTER_MESSAGE_FORMAT)) {
res = mip_3dm_write_filter_message_format(&_device, num_descriptors, descriptors);
} else {
PX4_ERR("Filter message format command is not supported");
res = MIP_PX4_ERROR;
}
break;
}
default:
PX4_ERR("Descriptor set for writing message format is not supported");
res = MIP_PX4_ERROR;
break;
}
}
return res;
}
mip_cmd_result MicroStrain::configureImuMessageFormat()
{
PX4_DEBUG("Configuring IMU Message Format");
uint8_t num_imu_descriptors = 0;
mip_descriptor_rate imu_descriptors[5];
// Get the base rate
uint16_t base_rate;
mip_cmd_result res = getBaseRate(MIP_SENSOR_DATA_DESC_SET, &base_rate);
PX4_DEBUG("The IMU base rate is %d", base_rate);
if (!mip_cmd_result_is_ack(res)) {
PX4_ERR("Could not get the IMU base rate");
return res;
}
// Configure the Message Format depending on if the device supports the descriptor
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_DATA_DESC_SENSOR_ACCEL_SCALED)
&& _param_ms_imu_rate_hz.get() > 0) {
uint16_t imu_decimation = base_rate / (uint16_t)_param_ms_imu_rate_hz.get();
imu_descriptors[num_imu_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SENSOR_ACCEL_SCALED, imu_decimation};
PX4_DEBUG("IMU: Scaled accel enabled with decimation %i", imu_decimation);
}
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_DATA_DESC_SENSOR_GYRO_SCALED)
&& _param_ms_imu_rate_hz.get() > 0) {
uint16_t imu_decimation = base_rate / (uint16_t)_param_ms_imu_rate_hz.get();
imu_descriptors[num_imu_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SENSOR_GYRO_SCALED, imu_decimation};
PX4_DEBUG("IMU: Scaled gyro enabled with decimation %i", imu_decimation);
}
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_DATA_DESC_SENSOR_MAG_SCALED)
&& _param_ms_mag_rate_hz.get() > 0) {
uint16_t mag_decimation = base_rate / (uint16_t)_param_ms_mag_rate_hz.get();
imu_descriptors[num_imu_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SENSOR_MAG_SCALED, mag_decimation};
PX4_DEBUG("IMU: Scaled mag enabled with decimation %i", mag_decimation);
}
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_DATA_DESC_SENSOR_PRESSURE_SCALED)
&& _param_ms_baro_rate_hz.get() > 0) {
uint16_t baro_decimation = base_rate / (uint16_t)_param_ms_baro_rate_hz.get();
imu_descriptors[num_imu_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SENSOR_PRESSURE_SCALED, baro_decimation};
PX4_DEBUG("IMU: Scaled pressure enabled with decimation %i", baro_decimation);
}
if (supportsDescriptor(MIP_SENSOR_DATA_DESC_SET, MIP_DATA_DESC_SHARED_GPS_TIME)
&& _param_ms_filter_rate_hz.get() > 0) {
const int max_param_rate = math::max(_param_ms_imu_rate_hz.get(), _param_ms_mag_rate_hz.get(),
_param_ms_baro_rate_hz.get());
uint16_t gps_time_decimation = base_rate / max_param_rate;
imu_descriptors[num_imu_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SHARED_GPS_TIME, gps_time_decimation};
PX4_DEBUG("IMU: GPS Time enabled");
}
// Write the settings
res = writeMessageFormat(MIP_SENSOR_DATA_DESC_SET, num_imu_descriptors,
imu_descriptors);
return res;
}
mip_cmd_result MicroStrain::configureFilterMessageFormat()
{
PX4_DEBUG("Configuring Filter Message Format");
uint8_t num_filter_descriptors = 0;
mip_descriptor_rate filter_descriptors[11];
// Get the base rate
uint16_t base_rate;
mip_cmd_result res = getBaseRate(MIP_FILTER_DATA_DESC_SET, &base_rate);
PX4_DEBUG("The filter base rate is %d", base_rate);
if (!mip_cmd_result_is_ack(res)) {
PX4_ERR("Could not get the filter base rate");
return res;
}
// Configure the Message Format depending on if the device supports the descriptor
uint16_t filter_decimation = 250;
if (_param_ms_filter_rate_hz.get() != 0) {
filter_decimation = base_rate / (uint16_t)_param_ms_filter_rate_hz.get();
PX4_DEBUG("Filter decimation: %i", filter_decimation);
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_POS_LLH)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_POS_LLH, filter_decimation};
PX4_DEBUG("Filter: LLH pos enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_ATT_QUATERNION)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_ATT_QUATERNION, filter_decimation};
PX4_DEBUG("Filter: Attitude quaternion enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_VEL_NED)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_VEL_NED, filter_decimation};
PX4_DEBUG("Filter: Velocity NED enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_LINEAR_ACCELERATION)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_LINEAR_ACCELERATION, filter_decimation};
PX4_DEBUG("Filter: Linear accel FRD enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_POS_UNCERTAINTY)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_POS_UNCERTAINTY, filter_decimation};
PX4_DEBUG("Filter: Position uncertainty enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_VEL_UNCERTAINTY)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_VEL_UNCERTAINTY, filter_decimation};
PX4_DEBUG("Filter: Velocity uncertainty enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_ATT_UNCERTAINTY_EULER)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_ATT_UNCERTAINTY_EULER, filter_decimation};
PX4_DEBUG("Filter: Attitude euler uncertainty enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_COMPENSATED_ANGULAR_RATE)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_COMPENSATED_ANGULAR_RATE, filter_decimation};
PX4_DEBUG("Filter: Compensated angular rate enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_FILTER_STATUS)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_FILTER_STATUS, filter_decimation};
PX4_DEBUG("Filter: Status enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_FILTER_GNSS_DUAL_ANTENNA_STATUS)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_FILTER_GNSS_DUAL_ANTENNA_STATUS, filter_decimation};
PX4_DEBUG("Filter: GNSS Dual antenna status enabled");
}
if (supportsDescriptor(MIP_FILTER_DATA_DESC_SET, MIP_DATA_DESC_SHARED_GPS_TIME)
&& _param_ms_filter_rate_hz.get() > 0) {
filter_descriptors[num_filter_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SHARED_GPS_TIME, filter_decimation};
PX4_DEBUG("Filter: GPS Time enabled");
}
// Write the settings
res = writeMessageFormat(MIP_FILTER_DATA_DESC_SET, num_filter_descriptors,
filter_descriptors);
return res;
}
mip_cmd_result MicroStrain::configureGnssMessageFormat(uint8_t descriptor_set)
{
PX4_DEBUG("Configuring GNSS Message Format");
uint8_t num_gnss_descriptors = 0;
mip_descriptor_rate gnss_descriptors[6];
// Get the base rate
uint16_t base_rate;
mip_cmd_result res = getBaseRate(descriptor_set, &base_rate);
PX4_DEBUG("The GNSS base rate is %d", base_rate);
if (!mip_cmd_result_is_ack(res)) {
PX4_ERR("Could not get the GNSS base rate");
return res;
}
uint16_t gnss_decimation = 5;
if (_param_ms_gnss_rate_hz.get() != 0) {
gnss_decimation = base_rate / (uint16_t)_param_ms_gnss_rate_hz.get();
PX4_DEBUG("GNSS decimation: %i", gnss_decimation);
}
// Configure the Message Format depending on if the device supports the descriptor
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_GNSS_POSITION_LLH)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_GNSS_POSITION_LLH, gnss_decimation};
PX4_DEBUG("GNSS: LLH Pos enabled");
}
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_GNSS_DOP)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_GNSS_DOP, gnss_decimation};
PX4_DEBUG("GNSS: DOP enabled");
}
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_GNSS_VELOCITY_NED)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_GNSS_VELOCITY_NED, gnss_decimation};
PX4_DEBUG("GNSS: Velocity NED enabled");
}
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_SHARED_GPS_TIME)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_SHARED_GPS_TIME, gnss_decimation};
PX4_DEBUG("GNSS: GPS Time enabled");
}
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_GNSS_GPS_LEAP_SECONDS)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_GNSS_GPS_LEAP_SECONDS, gnss_decimation};
PX4_DEBUG("GNSS: GPS Leap seconds enabled");
}
if (supportsDescriptor(descriptor_set, MIP_DATA_DESC_GNSS_FIX_INFO)
&& _param_ms_gnss_rate_hz.get() > 0) {
gnss_descriptors[num_gnss_descriptors++] = mip_descriptor_rate { MIP_DATA_DESC_GNSS_FIX_INFO, gnss_decimation};
PX4_DEBUG("GNSS: Fix info enabled");
}
// Write the settings
res = writeMessageFormat(descriptor_set, num_gnss_descriptors,
gnss_descriptors);
return res;
}
mip_cmd_result MicroStrain::configureAidingMeasurement(uint16_t aiding_source, bool enable)
{
mip_cmd_result res;
// Configures the aiding measurement if the device support it
if (supportsDescriptor(MIP_FILTER_CMD_DESC_SET, MIP_CMD_DESC_FILTER_AIDING_MEASUREMENT_ENABLE)) {
res = mip_filter_write_aiding_measurement_enable(&_device, aiding_source, enable);
// If the device doesnt support the aiding source but the command is to disable it, we consider it a success
if (res == MIP_NACK_INVALID_PARAM && !enable) {
res = MIP_ACK_OK;
}
} else {
PX4_WARN("Aiding measurements are not supported");
// If the command was to disable, we consider it a success
if (!enable) {
res = MIP_ACK_OK;
} else {
res = MIP_PX4_ERROR;
}
}
return res;
}
mip_cmd_result MicroStrain::configureAidingSources()
{
PX4_DEBUG("Configuring aiding sources");
mip_cmd_result res;
// Selectively turn on the magnetometer aiding source
if (!mip_cmd_result_is_ack(res = configureAidingMeasurement(
MIP_FILTER_AIDING_MEASUREMENT_ENABLE_COMMAND_AIDING_SOURCE_MAGNETOMETER,
_param_ms_int_mag_en.get()))) {
PX4_ERR("Could not configure magnetometer aiding");
return res;
}
// Enables GNSS Position & Velocity as an aiding measurement
res = configureAidingMeasurement(MIP_FILTER_AIDING_MEASUREMENT_ENABLE_COMMAND_AIDING_SOURCE_GNSS_POS_VEL,
true);
if (!mip_cmd_result_is_ack(res)) {
if (res != MIP_NACK_INVALID_PARAM && res != MIP_PX4_ERROR) {
PX4_ERR("Error enabling GNSS Position & Velocity aiding");
return res;
} else {
PX4_WARN("Could not enable GNSS Position & Velocity aiding");
}
} else {
// Check to see if sending GNSS position and velocity as an aiding measurement is supported
bool pos_aiding = supportsDescriptor(MIP_AIDING_CMD_DESC_SET, MIP_CMD_DESC_AIDING_POS_LLH);
bool vel_aiding = supportsDescriptor(MIP_AIDING_CMD_DESC_SET, MIP_CMD_DESC_AIDING_VEL_NED);
_ext_pos_vel_aiding = pos_aiding && vel_aiding;
if (!_ext_pos_vel_aiding) {
PX4_ERR("Sending GNSS pos/vel aiding messages is not supported");
return MIP_PX4_ERROR;
}
}
// Selectively turn on external heading as an aiding measurement
if (!mip_cmd_result_is_ack(res = configureAidingMeasurement(
MIP_FILTER_AIDING_MEASUREMENT_ENABLE_COMMAND_AIDING_SOURCE_GNSS_HEADING,
_param_ms_ext_heading_en.get()))) {
PX4_ERR("Could not configure external heading aiding");
return res;
} else {
// Check to see if sending external heading as an aiding measurement is supported
_ext_heading_aiding = supportsDescriptor(MIP_AIDING_CMD_DESC_SET, MIP_CMD_DESC_AIDING_HEADING_TRUE)
&& _param_ms_ext_heading_en.get();
if (!_ext_heading_aiding && _param_ms_ext_heading_en.get()) {
PX4_ERR("Sending external heading aiding messages is not supported");
return MIP_PX4_ERROR;
}
}
// Prioritizing setting up multi antenna offsets if it is supported
if (supportsDescriptor(MIP_FILTER_CMD_DESC_SET, MIP_CMD_DESC_FILTER_MULTI_ANTENNA_OFFSET)) {
mip_cmd_result res1 = mip_filter_write_multi_antenna_offset(&_device, 1, gnss_antenna_offset1);
mip_cmd_result res2 = mip_filter_write_multi_antenna_offset(&_device, 2, gnss_antenna_offset2);
if (!mip_cmd_result_is_ack(res1)) {
PX4_ERR("Could not write multi antenna offsets");
return res1;
}
else if (!mip_cmd_result_is_ack(res2)) {
PX4_ERR("Could not write multi antenna offsets");
return res2;
}
if (supportsDescriptor(MIP_FILTER_CMD_DESC_SET, MIP_CMD_DESC_FILTER_GNSS_SOURCE_CONTROL)) {
if (!mip_cmd_result_is_ack(res = mip_filter_write_gnss_source(&_device, (uint8_t)_param_ms_gnss_aid_src_ctrl.get()))) {
PX4_ERR("Could not write the gnss aiding source");
return res;
}
_ext_pos_vel_aiding = (_param_ms_gnss_aid_src_ctrl.get() == MIP_FILTER_GNSS_SOURCE_COMMAND_SOURCE_EXT);
} else {
PX4_ERR("Does not support GNSS source control");
return MIP_PX4_ERROR;
}
// Selectively enables dual antenna heading as an aiding measurement
if (!mip_cmd_result_is_ack(res = configureAidingMeasurement(
MIP_FILTER_AIDING_MEASUREMENT_ENABLE_COMMAND_AIDING_SOURCE_GNSS_HEADING,
_param_ms_int_heading_en.get()))) {
PX4_ERR("Could not configure dual antenna heading aiding");
return res;
}
}
// Otherwise sets up the aiding frame
else if (supportsDescriptor(MIP_AIDING_CMD_DESC_SET, MIP_CMD_DESC_AIDING_FRAME_CONFIG)) {
if (!mip_cmd_result_is_ack(res = mip_aiding_write_frame_config(&_device, 1,
MIP_AIDING_FRAME_CONFIG_COMMAND_FORMAT_EULER, false,
gnss_antenna_offset1, &rotation))) {
PX4_ERR("Could not write aiding frame config");
return res;
}
}
else {
PX4_WARN("Aiding frames are not supported");
}
_ext_aiding = (_ext_pos_vel_aiding || _ext_heading_aiding);
return res;
}
mip_cmd_result MicroStrain::writeFilterInitConfig()
{
PX4_DEBUG("Initializing filter");
mip_cmd_result res;
float filter_init_pos[3] = {0};
float filter_init_vel[3] = {0};
const uint8_t initial_alignment = _param_ms_alignment.get();
// Filter initialization configuration
if (supportsDescriptor(MIP_FILTER_CMD_DESC_SET, MIP_CMD_DESC_FILTER_INITIALIZATION_CONFIGURATION)) {
res = mip_filter_write_initialization_configuration(&_device, 0,
MIP_FILTER_INITIALIZATION_CONFIGURATION_COMMAND_INITIAL_CONDITION_SOURCE_AUTO_POS_VEL_ATT,
initial_alignment,
0.0, 0.0, 0.0, filter_init_pos, filter_init_vel, MIP_FILTER_REFERENCE_FRAME_LLH);
}
else {
PX4_WARN("Filter initialization is not supported");
res = MIP_ACK_OK;
}
return res;
}
bool MicroStrain::initializeIns()
{
mip_cmd_result res;
const uint32_t DESIRED_BAUDRATE = 921600;
static constexpr uint32_t BAUDRATES[] {115200, 921600, 460800, 230400, 128000, 38400, 19200, 57600, 9600};
bool is_connected = false;
for (auto &baudrate : BAUDRATES) {
if (connectAtBaud(baudrate) == PX4_OK) {
PX4_INFO("found baudrate %lu", baudrate);
is_connected = true;
break;
}
}
if (!is_connected) {
PX4_ERR("Could not connect to the device, exiting");
return false;
}
// Get the supported descriptors for the device in use
if (!mip_cmd_result_is_ack(getSupportedDescriptors())) {
PX4_ERR("Could not get descriptors");
return false;
}
// Setting the device baudrate to the desired value
PX4_INFO("Setting the baud to desired baud rate");
if (!mip_cmd_result_is_ack(res = writeBaudRate(DESIRED_BAUDRATE, 1))) {
PX4_ERR("Could not set the baudrate!");
return false;
}
device_uart.flush();
// Connecting using the desired baudrate
if (connectAtBaud(DESIRED_BAUDRATE) != PX4_OK) {
PX4_ERR("Could not Connect at %lu", DESIRED_BAUDRATE);
return false;
}
// Configure IMU ranges
if (!mip_cmd_result_is_ack(res = configureImuRange())) {
MS_PX4_ERROR(res, "Could not configure IMU range");
return false;
}
// Configure the IMU message formt based on what descriptors are supported
if (!mip_cmd_result_is_ack(res = configureImuMessageFormat())) {
MS_PX4_ERROR(res, "Could not write IMU message format");
return false;
}
// Register data callbacks
mip_interface_register_packet_callback(&_device, &_sensor_data_handler, MIP_SENSOR_DATA_DESC_SET, false,
&sensorCallback,
this);
// Configure the Filter message format based on what descriptors are supported
if (!mip_cmd_result_is_ack(res = configureFilterMessageFormat())) {
MS_PX4_ERROR(res, "Could not write filter message format");
return false;
}
// Register data callbacks
mip_interface_register_packet_callback(&_device, &_filter_data_handler, MIP_FILTER_DATA_DESC_SET, false,
&filterCallback,
this);
// Configure the GNSS1 message format based on what descriptors are supported
if (!mip_cmd_result_is_ack(res = configureGnssMessageFormat(MIP_GNSS1_DATA_DESC_SET))) {
MS_PX4_ERROR(res, "Could not write GNSS1 message format");
}
// Register data callbacks
mip_interface_register_packet_callback(&_device, &_gnss_data_handler[0], MIP_GNSS1_DATA_DESC_SET, false,
&gnssCallback,
this);
// Configure the GNSS2 message format based on what descriptors are supported
if (!mip_cmd_result_is_ack(res = configureGnssMessageFormat(MIP_GNSS2_DATA_DESC_SET))) {
MS_PX4_ERROR(res, "Could not write GNSS2 message format");
}
// Register data callbacks
mip_interface_register_packet_callback(&_device, &_gnss_data_handler[1], MIP_GNSS2_DATA_DESC_SET, false,
&gnssCallback,
this);
// Configure the aiding sources based on what the sensor supports
if (!mip_cmd_result_is_ack(res = configureAidingSources())) {
MS_PX4_ERROR(res, "Could not configure aiding frames!");
return false;
}
// Initialize the filter
if (!mip_cmd_result_is_ack(res = writeFilterInitConfig())) {
MS_PX4_ERROR(res, "Could not configure filter initialization!");
return false;
}
// Setup the rotation based on PX4 standard rotation sets
if (_param_ms_svt_en.get() && supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_SENSOR2VEHICLE_TRANSFORM_EUL)) {
PX4_DEBUG("Writing SVT");
if (!mip_cmd_result_is_ack(res = mip_3dm_write_sensor_2_vehicle_transform_euler(&_device,
math::radians<float>(rotation.euler[0]),
math::radians<float>(rotation.euler[1]), math::radians<float>(rotation.euler[2])))) {
MS_PX4_ERROR(res, "Could not set sensor-to-vehicle transformation!");
return false;
}
}
// Reset the filter
if (supportsDescriptor(MIP_FILTER_CMD_DESC_SET, MIP_CMD_DESC_FILTER_RESET_FILTER)) {
PX4_DEBUG("Reseting filter");
if (!mip_cmd_result_is_ack(res = mip_filter_reset(&_device))) {
MS_PX4_ERROR(res, "Could not reset the filter!");
return false;
}
}
if (supportsDescriptor(MIP_3DM_CMD_DESC_SET, MIP_CMD_DESC_3DM_CONTROL_DATA_STREAM)) {
PX4_DEBUG("Writing datastream control");
if (!mip_cmd_result_is_ack(res = mip_3dm_write_datastream_control(&_device,
MIP_3DM_DATASTREAM_CONTROL_COMMAND_ALL_STREAMS, true))) {
MS_PX4_ERROR(res, "Could not enable the data stream");
return false;
}
}
// Resume the device
if (supportsDescriptor(MIP_BASE_CMD_DESC_SET, MIP_CMD_DESC_BASE_RESUME)) {
PX4_DEBUG("Resuming device");
if (!mip_cmd_result_is_ack(res = mip_base_resume(&_device))) {
MS_PX4_ERROR(res, "Could not resume the device!");
return false;
}
}
return true;
}
void MicroStrain::sensorCallback(void *user, const mip_packet *packet, mip::Timestamp timestamp)
{
MicroStrain *ref = static_cast<MicroStrain *>(user);
assert(mip_packet_descriptor_set(packet) == MIP_SENSOR_DATA_DESC_SET);
SensorSample<mip_sensor_scaled_accel_data> accel;
SensorSample<mip_sensor_scaled_gyro_data> gyro;
SensorSample<mip_sensor_scaled_mag_data> mag;
SensorSample<mip_sensor_scaled_pressure_data> baro;
// Iterate through the packet and extract based on the descriptor present
auto t = hrt_absolute_time();
for (mip_field field = mip_field_first_from_packet(packet); mip_field_is_valid(&field); mip_field_next(&field)) {
switch (mip_field_field_descriptor(&field)) {
case MIP_DATA_DESC_SENSOR_ACCEL_SCALED:
extract_mip_sensor_scaled_accel_data_from_field(&field, &accel.sample);
accel.updated = true;
break;
case MIP_DATA_DESC_SENSOR_GYRO_SCALED:
extract_mip_sensor_scaled_gyro_data_from_field(&field, &gyro.sample);
gyro.updated = true;
break;
case MIP_DATA_DESC_SENSOR_MAG_SCALED:
extract_mip_sensor_scaled_mag_data_from_field(&field, &mag.sample);
mag.updated = true;
break;
case MIP_DATA_DESC_SENSOR_PRESSURE_SCALED:
extract_mip_sensor_scaled_pressure_data_from_field(&field, &baro.sample);
baro.updated = true;
break;
default:
break;
}
}
// Publish only if the corresponding data was extracted from the packet
if (accel.updated) {
ref->_px4_accel.update(t, accel.sample.scaled_accel[0]*CONSTANTS_ONE_G,
accel.sample.scaled_accel[1]*CONSTANTS_ONE_G,
accel.sample.scaled_accel[2]*CONSTANTS_ONE_G);
}
if (gyro.updated) {
ref->_px4_gyro.update(t, gyro.sample.scaled_gyro[0],
gyro.sample.scaled_gyro[1],
gyro.sample.scaled_gyro[2]);
}
if (mag.updated) {
ref->_px4_mag.update(t, mag.sample.scaled_mag[0],
mag.sample.scaled_mag[1],
mag.sample.scaled_mag[2]);
}
if (baro.updated) {
ref->_sensor_baro.timestamp_sample = t;
ref->_sensor_baro.pressure = baro.sample.scaled_pressure * 100.f; // convert [Pa] to [mBar]
ref->_sensor_baro.timestamp = hrt_absolute_time();
ref->_sensor_baro_pub.publish(ref->_sensor_baro);
}
}
void MicroStrain::filterCallback(void *user, const mip_packet *packet, mip::Timestamp timestamp)
{
MicroStrain *ref = static_cast<MicroStrain *>(user);
assert(mip_packet_descriptor_set(packet) == MIP_FILTER_DATA_DESC_SET);
SensorSample<mip_filter_position_llh_data> pos_llh;
SensorSample<mip_filter_attitude_quaternion_data> att_quat;
SensorSample<mip_filter_comp_angular_rate_data> ang_rate;
SensorSample<mip_filter_velocity_ned_data> vel_ned;
SensorSample<mip_filter_status_data> stat;
SensorSample<mip_filter_position_llh_uncertainty_data> llh_uncert;
SensorSample<mip_filter_velocity_ned_uncertainty_data> vel_uncert;
SensorSample<mip_filter_euler_angles_uncertainty_data> att_euler_uncert;
SensorSample<mip_filter_linear_accel_data> lin_accel;
// Iterate through the packet and extract based on the descriptor present
auto t = hrt_absolute_time();
for (mip_field field = mip_field_first_from_packet(packet); mip_field_is_valid(&field); mip_field_next(&field)) {
switch (mip_field_field_descriptor(&field)) {
case MIP_DATA_DESC_FILTER_POS_LLH:
extract_mip_filter_position_llh_data_from_field(&field, &pos_llh.sample);
pos_llh.updated = true;
break;
case MIP_DATA_DESC_FILTER_ATT_QUATERNION:
extract_mip_filter_attitude_quaternion_data_from_field(&field, &att_quat.sample);
att_quat.updated = true;
break;
case MIP_DATA_DESC_FILTER_VEL_NED:
extract_mip_filter_velocity_ned_data_from_field(&field, &vel_ned.sample);
vel_ned.updated = true ;
break;
case MIP_DATA_DESC_FILTER_LINEAR_ACCELERATION:
extract_mip_filter_linear_accel_data_from_field(&field, &lin_accel.sample);
lin_accel.updated = true;
break;
case MIP_DATA_DESC_FILTER_POS_UNCERTAINTY:
extract_mip_filter_position_llh_uncertainty_data_from_field(&field, &llh_uncert.sample);
llh_uncert.updated = true;
break;
case MIP_DATA_DESC_FILTER_VEL_UNCERTAINTY:
extract_mip_filter_velocity_ned_uncertainty_data_from_field(&field, &vel_uncert.sample);
vel_uncert.updated = true;
break;
case MIP_DATA_DESC_FILTER_ATT_UNCERTAINTY_EULER:
extract_mip_filter_euler_angles_uncertainty_data_from_field(&field, &att_euler_uncert.sample);
att_euler_uncert.updated = true;
break;
case MIP_DATA_DESC_FILTER_COMPENSATED_ANGULAR_RATE:
extract_mip_filter_comp_angular_rate_data_from_field(&field, &ang_rate.sample);
ang_rate.updated = true;
break;
case MIP_DATA_DESC_FILTER_FILTER_STATUS:
extract_mip_filter_status_data_from_field(&field, &stat.sample);
stat.updated = true;
break;
case MIP_DATA_DESC_FILTER_GNSS_DUAL_ANTENNA_STATUS:
extract_mip_filter_gnss_dual_antenna_status_data_from_field(&field, &ref->dual_ant_stat);
break;
default:
break;
}
}
// Publish only if the required data was extracted from the packet
bool vehicle_global_position_valid = pos_llh.updated && llh_uncert.updated && stat.updated;
bool vehicle_attitude_valid = att_quat.updated;
bool vehicle_local_position_valid = pos_llh.updated && att_quat.updated && vel_ned.updated && lin_accel.updated
&& llh_uncert.updated && vel_uncert.updated && att_euler_uncert.updated && stat.updated;
bool vehicle_odometry_valid = pos_llh.updated && att_quat.updated && vel_ned.updated && llh_uncert.updated
&& vel_uncert.updated && att_euler_uncert.updated && ang_rate.updated;
bool estimator_status_valid = stat.updated && llh_uncert.updated;
if (vehicle_global_position_valid) {
vehicle_global_position_s gp{0};
gp.timestamp_sample = t;
bool is_fullnav = (stat.sample.filter_state == MIP_FILTER_MODE_FULL_NAV);
gp.lat = pos_llh.sample.latitude;
gp.lon = pos_llh.sample.longitude;
gp.lat_lon_valid = is_fullnav && pos_llh.sample.valid_flags;
// gp.alt is supposed to be in MSL, since the filter only estimates ellipsoid, we publish that instead.
gp.alt_ellipsoid = pos_llh.sample.ellipsoid_height;
gp.alt = pos_llh.sample.ellipsoid_height - (double)ref->_geoid_height_lpf.getState();
gp.alt_valid = is_fullnav && pos_llh.sample.valid_flags;
gp.eph = sqrtf(sq(llh_uncert.sample.north) + sq(llh_uncert.sample.east) + sq(ref->_gps_origin_ep[0]));
gp.epv = sqrtf(sq(llh_uncert.sample.down) + sq(ref->_gps_origin_ep[1]));
// ------- Fields we cannot obtain -------
gp.delta_alt = 0;
gp.delta_terrain = 0;
gp.lat_lon_reset_counter = 0;
gp.alt_reset_counter = 0;
gp.terrain_reset_counter = 0;
gp.dead_reckoning = false;
gp.terrain_alt_valid = false;
gp.terrain_alt = 0;
// ---------------------------------------
gp.timestamp = hrt_absolute_time();
ref->_vehicle_global_position_pub.publish(gp);
}
if (vehicle_attitude_valid) {
vehicle_attitude_s att_data{0};
att_data.timestamp_sample = t;
att_data.q[0] = att_quat.sample.q[0];
att_data.q[1] = att_quat.sample.q[1];
att_data.q[2] = att_quat.sample.q[2];
att_data.q[3] = att_quat.sample.q[3];
// ------- Fields we cannot obtain -------
att_data.delta_q_reset[0] = 0;
att_data.delta_q_reset[0] = 0;
att_data.delta_q_reset[0] = 0;
att_data.delta_q_reset[0] = 0;
att_data.quat_reset_counter = 0;
// ---------------------------------------
att_data.timestamp = hrt_absolute_time();
ref->_vehicle_attitude_pub.publish(att_data);
}
if (vehicle_local_position_valid) {
vehicle_local_position_s vp{0};
vp.timestamp_sample = t;
bool is_fullNav = (stat.sample.filter_state == MIP_FILTER_MODE_FULL_NAV);
const Vector2f pos_ned = ref->_pos_ref.project(pos_llh.sample.latitude, pos_llh.sample.longitude);
vp.x = pos_ned(0);
vp.y = pos_ned(1);
vp.z = -((pos_llh.sample.ellipsoid_height - (double)ref->_geoid_height_lpf.getState()) - ref->_ref_alt);
vp.xy_valid = is_fullNav && ref->_pos_ref.isInitialized();
vp.z_valid = is_fullNav && ref->_pos_ref.isInitialized() && pos_llh.sample.valid_flags;
vp.vx = vel_ned.sample.north;
vp.vy = vel_ned.sample.east;
vp.vz = vel_ned.sample.down;
vp.z_deriv = vp.vz;
vp.v_xy_valid = is_fullNav && vel_ned.sample.valid_flags;
vp.v_z_valid = is_fullNav && vel_ned.sample.valid_flags;
// Conversion of linear acceleration from body frame to NED frame
matrix::Quatf quat{att_quat.sample.q[0], att_quat.sample.q[1], att_quat.sample.q[2], att_quat.sample.q[3]};
matrix::Vector3f acc_body{lin_accel.sample.accel[0], lin_accel.sample.accel[1], lin_accel.sample.accel[2]};
matrix::Vector3f acc_ned = quat.rotateVectorInverse(acc_body);
vp.ax = acc_ned(0);
vp.ay = acc_ned(1);
vp.az = acc_ned(2);
// Get yaw from attitude for heading
matrix::Eulerf euler_attitude(matrix::Quatf(att_quat.sample.q[0], att_quat.sample.q[1], att_quat.sample.q[2],
att_quat.sample.q[3]));
float yaw = euler_attitude.psi();
vp.heading_good_for_control = (!std::isnan(yaw));
vp.heading = yaw;
vp.heading_var = att_euler_uncert.sample.yaw;
// vp.ref_alt is supposed to be in MSL, since the filter only estimates ellipsoid, we publish that instead.
vp.xy_global = (ref->_pos_ref.isInitialized() == true);
vp.z_global = (ref->_pos_ref.isInitialized() == true);
vp.ref_timestamp = ref->_pos_ref.getProjectionReferenceTimestamp();
vp.ref_lat = ref->_pos_ref.getProjectionReferenceLat();
vp.ref_lon = ref->_pos_ref.getProjectionReferenceLon();
vp.ref_alt = ref->_ref_alt;
vp.eph = sqrtf(sq(llh_uncert.sample.north) + sq(llh_uncert.sample.east));
vp.epv = llh_uncert.sample.down;
vp.evh = sqrtf(sq(vel_uncert.sample.north) + sq(vel_uncert.sample.east));
vp.evv = vel_uncert.sample.down;
vp.vxy_max = INFINITY;
vp.vz_max = INFINITY;
vp.hagl_min = INFINITY;
vp.hagl_max_xy = INFINITY;
vp.hagl_max_z = INFINITY;
// ------- Fields we cannot obtain -------
vp.delta_xy[0] = 0;
vp.delta_xy[1] = 0;
vp.xy_reset_counter = 0;
vp.delta_z = 0;
vp.z_reset_counter = 0;
vp.delta_vxy[0] = 0;
vp.delta_vxy[1] = 0;
vp.vxy_reset_counter = 0;
vp.delta_vz = 0;
vp.vz_reset_counter = 0;
vp.unaided_heading = NAN;
vp.delta_heading = 0;
vp.heading_reset_counter = 0;
vp.tilt_var = 0;
vp.dist_bottom_valid = 0;
vp.dist_bottom = 0;
vp.dist_bottom_var = 0;
vp.delta_dist_bottom = 0;
vp.dist_bottom_reset_counter = 0;
vp.dist_bottom_sensor_bitfield = 0;
vp.dead_reckoning = false;
// ---------------------------------------
vp.timestamp = hrt_absolute_time();
ref->_vehicle_local_position_pub.publish(vp);
}
if (vehicle_odometry_valid && ref->_param_ms_mode.get()) {
vehicle_odometry_s vo{0};
vo.timestamp_sample = t;
const Vector2f pos_ned = ref->_pos_ref.project(pos_llh.sample.latitude, pos_llh.sample.longitude);
vo.pose_frame = 1;
vo.position[0] = pos_ned(0);
vo.position[1] = pos_ned(1);
vo.position[2] = -((pos_llh.sample.ellipsoid_height - (double)ref->_geoid_height_lpf.getState()) - ref->_ref_alt);
vo.q[0] = att_quat.sample.q[0];
vo.q[1] = att_quat.sample.q[1];
vo.q[2] = att_quat.sample.q[2];
vo.q[3] = att_quat.sample.q[3];
vo.velocity_frame = 1;
vo.velocity[0] = vel_ned.sample.north;
vo.velocity[1] = vel_ned.sample.east;
vo.velocity[2] = vel_ned.sample.down;
vo.angular_velocity[0] = ang_rate.sample.gyro[0];
vo.angular_velocity[1] = ang_rate.sample.gyro[1];
vo.angular_velocity[2] = ang_rate.sample.gyro[2];
vo.position_variance[0] = sq(llh_uncert.sample.north);
vo.position_variance[1] = sq(llh_uncert.sample.east);
vo.position_variance[2] = sq(llh_uncert.sample.down);
vo.velocity_variance[0] = sq(vel_uncert.sample.north);
vo.velocity_variance[1] = sq(vel_uncert.sample.east);
vo.velocity_variance[2] = sq(vel_uncert.sample.down);
vo.orientation_variance[0] = sq(att_euler_uncert.sample.roll);
vo.orientation_variance[1] = sq(att_euler_uncert.sample.pitch);
vo.orientation_variance[2] = sq(att_euler_uncert.sample.yaw);
// ------- Fields we cannot obtain -------
vo.reset_counter = 0;
vo.quality = 0;
// ---------------------------------------
vo.timestamp = hrt_absolute_time();
ref->_vehicle_odometry_pub.publish(vo);
}
if (estimator_status_valid && ref->_param_ms_mode.get()) {
estimator_status_s status{0};
status.timestamp_sample = t;
status.output_tracking_error[0] = 0;
status.output_tracking_error[1] = 0;
status.output_tracking_error[2] = 0;
status.gps_check_fail_flags = 0;
// Minimal mapping of error flags from device to the PX4 health flags
status.control_mode_flags = stat.sample.filter_state == 4 ? (0x1ULL << estimator_status_s::CS_GNSS_POS) |
(0x1ULL << estimator_status_s::CS_GNSS_VEL) |
(0x1ULL << estimator_status_s::CS_GPS_HGT) : 0x00;
status.filter_fault_flags = stat.sample.status_flags;
status.pos_horiz_accuracy = sqrtf(sq(llh_uncert.sample.north) + sq(llh_uncert.sample.east));
status.pos_vert_accuracy = llh_uncert.sample.down;
status.hdg_test_ratio = 0.1f;
status.vel_test_ratio = 0.1f;
status.pos_test_ratio = 0.1f;
status.hgt_test_ratio = 0.1f;
status.tas_test_ratio = 0.1f;
status.hagl_test_ratio = 0.1f;
status.beta_test_ratio = 0.1f;
status.solution_status_flags = 0;
status.reset_count_vel_ne = 0;
status.reset_count_vel_d = 0;
status.reset_count_pos_ne = 0;
status.reset_count_pod_d = 0;
status.reset_count_quat = 0;
status.time_slip = 0;
status.pre_flt_fail_innov_heading = false;
status.pre_flt_fail_innov_height = false;
status.pre_flt_fail_innov_pos_horiz = false;
status.pre_flt_fail_innov_vel_horiz = false;
status.pre_flt_fail_innov_vel_vert = false;
status.pre_flt_fail_mag_field_disturbed = false;
status.accel_device_id = ref->_dev_id;
status.gyro_device_id = ref->_dev_id;
status.mag_device_id = ref->_dev_id;
status.baro_device_id = ref->_dev_id;
status.health_flags = 0;
status.timeout_flags = 0;
status.mag_inclination_deg = 0;
status.mag_inclination_ref_deg = 0;
status.mag_strength_gs = 0;
status.mag_strength_ref_gs = 0;
status.timestamp = hrt_absolute_time();
ref->_estimator_status_pub.publish(status);
sensor_selection_s sensor_selection{};
sensor_selection.accel_device_id = ref->_dev_id;
sensor_selection.gyro_device_id = ref->_dev_id;
sensor_selection.timestamp = hrt_absolute_time();
ref->_sensor_selection_pub.publish(sensor_selection);
}
}
void MicroStrain::gnssCallback(void *user, const mip_packet *packet, mip::Timestamp timestamp)
{
MicroStrain *ref = static_cast<MicroStrain *>(user);
int instance = 0;
assert((mip_packet_descriptor_set(packet) == MIP_GNSS1_DATA_DESC_SET)
|| (mip_packet_descriptor_set(packet) == MIP_GNSS2_DATA_DESC_SET));
if (mip_packet_descriptor_set(packet) == MIP_GNSS2_DATA_DESC_SET) {
instance = 1;
}
SensorSample<mip_gnss_pos_llh_data> pos_llh;
SensorSample<mip_gnss_dop_data> dop;
SensorSample<mip_gnss_vel_ned_data> vel_ned;
SensorSample<mip_shared_gps_timestamp_data> gps_time;
SensorSample<mip_gnss_gps_leap_seconds_data> gps_leap_sec;
SensorSample<mip_gnss_satellite_status_data> sat;
SensorSample<mip_gnss_fix_info_data> fix_info;
// Iterate through the packet and extract based on the descriptor present
auto t = hrt_absolute_time();
for (mip_field field = mip_field_first_from_packet(packet); mip_field_is_valid(&field); mip_field_next(&field)) {
switch (mip_field_field_descriptor(&field)) {
case MIP_DATA_DESC_GNSS_POSITION_LLH:
extract_mip_gnss_pos_llh_data_from_field(&field, &pos_llh.sample);
pos_llh.updated = true;
break;
case MIP_DATA_DESC_GNSS_DOP:
extract_mip_gnss_dop_data_from_field(&field, &dop.sample);
dop.updated = true;
break;
case MIP_DATA_DESC_GNSS_VELOCITY_NED:
extract_mip_gnss_vel_ned_data_from_field(&field, &vel_ned.sample);
vel_ned.updated = true;
break;
case MIP_DATA_DESC_SHARED_GPS_TIME:
extract_mip_shared_gps_timestamp_data_from_field(&field, &gps_time.sample);
gps_time.updated = true;
break;
case MIP_DATA_DESC_GNSS_GPS_LEAP_SECONDS:
extract_mip_gnss_gps_leap_seconds_data_from_field(&field, &gps_leap_sec.sample);
gps_leap_sec.updated = true;
break;
case MIP_DATA_DESC_GNSS_FIX_INFO:
extract_mip_gnss_fix_info_data_from_field(&field, &fix_info.sample);
fix_info.updated = true;
break;
default:
break;
}
}
bool gnss_valid = pos_llh.updated && dop.updated && vel_ned.updated && gps_leap_sec.updated && fix_info.updated;
// Publish only if the corresponding data was extracted from the packet
if (gnss_valid) {
sensor_gps_s gps{0};
gps.timestamp_sample = t;
gps.device_id = ref->_dev_id;
gps.latitude_deg = pos_llh.sample.latitude;
gps.longitude_deg = pos_llh.sample.longitude;
gps.altitude_msl_m = pos_llh.sample.msl_height;
gps.altitude_ellipsoid_m = pos_llh.sample.ellipsoid_height;
const float _geoid_height = pos_llh.sample.ellipsoid_height - pos_llh.sample.msl_height;
gps.s_variance_m_s = vel_ned.sample.speed_accuracy;
gps.c_variance_rad = 0;
switch (fix_info.sample.fix_type) {
case 0:
gps.fix_type = sensor_gps_s::FIX_TYPE_3D;
break;
case 1:
gps.fix_type = sensor_gps_s::FIX_TYPE_2D;
break;
case 5:
gps.fix_type = sensor_gps_s::FIX_TYPE_RTK_FLOAT;
break;
case 6:
gps.fix_type = sensor_gps_s::FIX_TYPE_RTK_FIXED;
break;
case 7:
gps.fix_type = sensor_gps_s::FIX_TYPE_RTCM_CODE_DIFFERENTIAL;
break;
default:
gps.fix_type = sensor_gps_s::FIX_TYPE_NONE;
}
gps.eph = pos_llh.sample.horizontal_accuracy;
gps.epv = pos_llh.sample.vertical_accuracy;
gps.hdop = dop.sample.hdop;
gps.vdop = dop.sample.vdop;
gps.noise_per_ms = 0;
gps.automatic_gain_control = 0;
gps.jamming_state = 0;
gps.jamming_indicator = 0;
gps.spoofing_state = 0;
gps.vel_m_s = vel_ned.sample.speed;
gps.vel_n_m_s = vel_ned.sample.v[0];
gps.vel_e_m_s = vel_ned.sample.v[1];
gps.vel_d_m_s = vel_ned.sample.v[2];
gps.cog_rad = 0;
gps.vel_ned_valid = (vel_ned.sample.valid_flags >> 1) & 1;
gps.timestamp_time_relative = 0; //
gps.time_utc_usec = ((gps_time.sample.week_number * 604800) + gps_time.sample.tow + 315964800 -
gps_leap_sec.sample.leap_seconds) * 1000000;
gps.satellites_used = fix_info.sample.num_sv;
gps.heading = ref->dual_ant_stat.heading;
gps.heading_offset = 0;
gps.heading_accuracy = 0;
gps.rtcm_injection_rate = 0;
gps.selected_rtcm_instance = 0;
gps.rtcm_crc_failed = 0;
gps.rtcm_msg_used = 0;
gps.timestamp = hrt_absolute_time();
if (instance == 0) {ref->updateGeoidHeight(_geoid_height, gps.timestamp);}
ref->_sensor_gps_pub[instance].publish(gps);
}
}
void MicroStrain::initializeRefPos()
{
sensor_gps_s gps{0};
_vehicle_gps_position_sub.update(&gps);
// Fix isn't 3D or RTK or RTCM
if ((gps.fix_type < 3) || (gps.fix_type > 6)) {
return;
}
// If the timestamp has not been set, then don't send any data into the filter
if (gps.time_utc_usec == 0) {
return;
}
const hrt_abstime t = hrt_absolute_time();
_pos_ref.initReference(gps.latitude_deg, gps.longitude_deg, t);
_ref_alt = gps.altitude_msl_m;
_gps_origin_ep[0] = gps.eph;
_gps_origin_ep[1] = gps.epv;
PX4_DEBUG("Reference position initialized");
}
void MicroStrain::updateGeoidHeight(float geoid_height, float t)
{
// Updates the low pass filter for geoid height
if (_last_geoid_height_update_us == 0) {
_geoid_height_lpf.reset(geoid_height);
_last_geoid_height_update_us = t;
} else if (t > _last_geoid_height_update_us) {
const float dt = 1e-6f * (t - _last_geoid_height_update_us);
_geoid_height_lpf.setParameters(dt, kGeoidHeightLpfTimeConstant);
_geoid_height_lpf.update(geoid_height);
_last_geoid_height_update_us = t;
}
}
void MicroStrain::sendAidingMeasurements()
{
sensor_gps_s gps{0};
// No new data
if (!_vehicle_gps_position_sub.update(&gps)) {
return;
}
// Fix isn't 3D or RTK or RTCM
if ((gps.fix_type < 3) || (gps.fix_type > 6)) {
return;
}
// If the timestamp has not been set, then don't send any data into the filter
if (gps.time_utc_usec == 0) {
return;
}
mip_time t;
t.timebase = MIP_TIME_TIMEBASE_TIME_OF_ARRIVAL;
t.reserved = 0x01;
t.nanoseconds = 0;
// Sends GNSS position and velocity aiding data if they are both supported
if (_ext_pos_vel_aiding) {
float llh_uncertainty[3] = {gps.eph, gps.eph, gps.epv};
mip_aiding_llh_pos(&_device, &t, MIP_FILTER_REFERENCE_FRAME_LLH, gps.latitude_deg,
gps.longitude_deg,
gps.altitude_ellipsoid_m, llh_uncertainty, MIP_AIDING_LLH_POS_COMMAND_VALID_FLAGS_ALL);
// Calculate the geoid height and update the low pass filter
const float _geoid_height = gps.altitude_ellipsoid_m - gps.altitude_msl_m;
updateGeoidHeight(_geoid_height, gps.timestamp);
if (gps.vel_ned_valid) {
float ned_v[3] = {gps.vel_n_m_s, gps.vel_e_m_s, gps.vel_d_m_s};
float ned_velocity_uncertainty[3] = {sqrtf(gps.s_variance_m_s), sqrtf(gps.s_variance_m_s), sqrtf(gps.s_variance_m_s)};
mip_aiding_ned_vel(&_device, &t, MIP_FILTER_REFERENCE_FRAME_LLH, ned_v, ned_velocity_uncertainty,
MIP_AIDING_NED_VEL_COMMAND_VALID_FLAGS_ALL);
}
}
// Sends external heading aiding data if they are both supported
if (_ext_heading_aiding && PX4_ISFINITE(gps.heading)) {
float heading = gps.heading + gps.heading_offset;
mip_aiding_true_heading(&_device, &t, MIP_FILTER_REFERENCE_FRAME_LLH, heading, gps.heading_accuracy, 0xff);
}
}
bool MicroStrain::init()
{
// Run on fixed interval
ScheduleOnInterval(_ms_schedule_rate_us);
return true;
}
void MicroStrain::Run()
{
if (should_exit()) {
ScheduleClear();
exit_and_cleanup();
return;
}
perf_begin(_loop_perf);
perf_count(_loop_interval_perf);
if (!_is_initialized) {
_is_initialized = initializeIns();
PX4_INFO("Initialization complete");
}
// Initialization failed, stop the module
if (!_is_initialized) {
request_stop();
perf_end(_loop_perf);
return;
}
// Check if parameters have changed
if (_parameter_update_sub.updated()) {
// clear update
parameter_update_s param_update;
_parameter_update_sub.copy(&param_update);
updateParams(); // update module parameters (in DEFINE_PARAMETERS)
}
mip_interface_update(&_device, false);
//Initializes reference position if there is gps data
if (_vehicle_gps_position_sub.updated() && !_pos_ref.isInitialized()) {initializeRefPos();}
// Sends aiding data only if external aiding was set up
if (_ext_aiding) {sendAidingMeasurements();}
perf_end(_loop_perf);
}
int MicroStrain::task_spawn(int argc, char *argv[])
{
int ch;
int myoptind = 1;
const char *myoptarg = nullptr;
const char *dev = "/dev/ttyS4";
while ((ch = px4_getopt(argc, argv, "d:", &myoptind, &myoptarg)) != EOF) {
switch (ch) {
case 'd':
dev = myoptarg;
break;
default:
PX4_WARN("Unrecognized option, Using defaults");
break;
}
}
if (dev == nullptr || strlen(dev) == 0) {
print_usage("no device specified");
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
PX4_INFO("Opening device port %s", dev);
MicroStrain *instance = new MicroStrain(dev);
if (instance) {
_object.store(instance);
_task_id = task_id_is_work_queue;
if (instance->init()) {
return PX4_OK;
}
} else {
PX4_ERR("alloc failed");
}
delete instance;
_object.store(nullptr);
_task_id = -1;
return PX4_ERROR;
}
int MicroStrain::print_status()
{
perf_print_counter(_loop_perf);
perf_print_counter(_loop_interval_perf);
return 0;
}
int MicroStrain::custom_command(int argc, char *argv[])
{
return print_usage("unknown command");
}
int MicroStrain::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
MicroStrain by HBK Inertial Sensor Driver.
Currently supports the following sensors:
-[CV7-AR](https://www.hbkworld.com/en/products/transducers/inertial-sensors/vertical-reference-units--vru-/3dm-cv7-ar)
-[CV7-AHRS](https://www.hbkworld.com/en/products/transducers/inertial-sensors/attitude-and-heading-reference-systems--ahrs-/3dm-cv7-ahrs)
-[CV7-INS](https://www.hbkworld.com/en/products/transducers/inertial-sensors/inertial-navigation-systems--ins-/3dm-cv7-ins)
-[CV7-GNSS/INS](https://www.hbkworld.com/en/products/transducers/inertial-sensors/inertial-navigation-systems--ins-/3dm-cv7-gnss-ins)
This driver is not included in the firmware by default.
Include the module in firmware by setting the
[kconfig](../hardware/porting_guide_config.md#px4-board-configuration-kconfig) variables:
`CONFIG_DRIVERS_INS_MICROSTRAIN` or `CONFIG_COMMON_INS`.
### Examples
Attempt to start driver on a specified serial device.
The driver will choose /dev/ttyS4 by default if no port is specified
$ microstrain start -d /dev/ttyS1
Stop driver
$ microstrain stop
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("MicroStrain", "driver");
PRINT_MODULE_USAGE_SUBCATEGORY("ins");
PRINT_MODULE_USAGE_COMMAND_DESCR("start", "Start driver");
PRINT_MODULE_USAGE_PARAM_STRING('d', "/dev/ttyS4", "<file:dev>", "Port", true);
PRINT_MODULE_USAGE_COMMAND_DESCR("stop", "Stop driver");
PRINT_MODULE_USAGE_COMMAND_DESCR("status", "Driver status");
return PX4_OK;
}
extern "C" __EXPORT int microstrain_main(int argc, char *argv[])
{
return MicroStrain::main(argc, argv);
}