LCOV - code coverage report
Current view: top level - AP_ExternalAHRS - AP_ExternalAHRS_MicroStrain7.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 150 171 87.7 %
Date: 2026-09-09 11:02:08 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             :    This program is distributed in the hope that it will be useful,
       7             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       8             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       9             :    GNU General Public License for more details.
      10             :    You should have received a copy of the GNU General Public License
      11             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      12             :  */
      13             : /*
      14             :   Support for MicroStrain GQ7 serially connected AHRS Systems
      15             :   Usage in SITL with hardware for debugging:
      16             :     $ sim_vehicle.py -v Plane -A "--serial3=uart:/dev/3dm-gq7" --console --map -DG
      17             :     $ ./Tools/autotest/sim_vehicle.py -v Plane -A "--serial3=uart:/dev/3dm-gq7" -DG
      18             :     param set AHRS_EKF_TYPE 11
      19             :     param set EAHRS_TYPE 7
      20             :     param set GPS1_TYPE 21
      21             :     param set GPS2_TYPE 21
      22             :     param set SERIAL3_BAUD 115
      23             :     param set SERIAL3_PROTOCOL 36
      24             :   UDEV rules for repeatable USB connection:
      25             :     $ cat /etc/udev/rules.d/99-usb-serial.rules
      26             :     SUBSYSTEM=="tty", ATTRS{manufacturer}=="Lord Microstrain", SYMLINK+="3dm-gq7"
      27             :   Usage with simulated MicroStrain7:
      28             :     ./Tools/autotest/sim_vehicle.py -v Plane -A "--serial3=sim:MicroStrain7" --console --map -DG
      29             :  */
      30             : 
      31             : #define AP_MATH_ALLOW_DOUBLE_FUNCTIONS 1
      32             : 
      33             : #include "AP_ExternalAHRS_config.h"
      34             : 
      35             : #if AP_EXTERNAL_AHRS_MICROSTRAIN7_ENABLED
      36             : 
      37             : #include "AP_ExternalAHRS_MicroStrain7.h"
      38             : #include "AP_Compass/AP_Compass_config.h"
      39             : #include <AP_Baro/AP_Baro.h>
      40             : #include <AP_Compass/AP_Compass.h>
      41             : #include <AP_GPS/AP_GPS.h>
      42             : #include <AP_HAL/utility/sparse-endian.h>
      43             : #include <AP_InertialSensor/AP_InertialSensor.h>
      44             : #include <GCS_MAVLink/GCS.h>
      45             : #include <AP_Logger/AP_Logger.h>
      46             : #include <AP_BoardConfig/AP_BoardConfig.h>
      47             : #include <AP_SerialManager/AP_SerialManager.h>
      48             : 
      49             : static const char* LOG_FMT = "%s ExternalAHRS: %s";
      50             : 
      51             : extern const AP_HAL::HAL &hal;
      52             : 
      53          14 : AP_ExternalAHRS_MicroStrain7::AP_ExternalAHRS_MicroStrain7(AP_ExternalAHRS *_frontend,
      54          14 :         AP_ExternalAHRS::state_t &_state): AP_ExternalAHRS_backend(_frontend, _state)
      55             : {
      56          14 :     auto &sm = AP::serialmanager();
      57          14 :     uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0);
      58             : 
      59          14 :     baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0);
      60          14 :     port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0);
      61             : 
      62          14 :     if (!uart) {
      63           0 :         GCS_SEND_TEXT(MAV_SEVERITY_ERROR, LOG_FMT, get_name(), "no UART");
      64           0 :         return;
      65             :     }
      66             : 
      67          14 :     if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_MicroStrain7::update_thread, void), "AHRS", 2048, AP_HAL::Scheduler::PRIORITY_SPI, 0)) {
      68           0 :         AP_BoardConfig::allocation_error("MicroStrain7 ExternalAHRS failed to allocate ExternalAHRS update thread");
      69             :     }
      70             : 
      71             :     // don't offer IMU by default, at 100Hz it is too slow for many aircraft
      72          14 :     set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) |
      73             :                         uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) |
      74             :                         uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS));
      75             : 
      76          14 :     hal.scheduler->delay(5000);
      77          14 :     if (!initialised()) {
      78           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, LOG_FMT, get_name(), "failed to initialise.");
      79             :     }
      80             : }
      81             : 
      82          14 : void AP_ExternalAHRS_MicroStrain7::update_thread(void)
      83             : {
      84          14 :     if (!port_open) {
      85          14 :         port_open = true;
      86          14 :         uart->begin(baudrate);
      87             :     }
      88             : 
      89             :     while (true) {
      90       68933 :         build_packet();
      91       68933 :         hal.scheduler->delay_microseconds(100);
      92       68919 :         check_initialise_state();
      93             :     }
      94             : }
      95             : 
      96       68919 : void AP_ExternalAHRS_MicroStrain7::check_initialise_state(void)
      97             : {
      98       68919 :     const auto new_init_state = initialised();
      99             :     // Only send the message after fully booted up, otherwise it gets dropped.
     100       68919 :     if (!last_init_state && new_init_state && AP_HAL::millis() > 5000) {
     101          14 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, LOG_FMT, get_name(), "initialised.");
     102          14 :         last_init_state = new_init_state;
     103             :     }
     104       68919 : }
     105             : 
     106             : 
     107             : // Builds packets by looking at each individual byte, once a full packet has been read in it checks the checksum then handles the packet.
     108       85724 : void AP_ExternalAHRS_MicroStrain7::build_packet()
     109             : {
     110       85724 :     if (uart == nullptr) {
     111           0 :         return;
     112             :     }
     113             : 
     114       85724 :     WITH_SEMAPHORE(sem);
     115       85724 :     uint32_t nbytes = MIN(uart->available(), 2048u);
     116     1573412 :     while (nbytes--> 0) {
     117             :         uint8_t b;
     118     1487688 :         if (!uart->read(b)) {
     119           0 :             break;
     120             :         }
     121             :         DescriptorSet descriptor;
     122     1487688 :         if (handle_byte(b, descriptor)) {
     123       17240 :             switch (descriptor) {
     124        7982 :             case DescriptorSet::IMUData:
     125        7982 :                 post_imu();
     126        7982 :                 break;
     127        1276 :             case DescriptorSet::GNSSData:
     128             :             case DescriptorSet::GNSSRecv1:
     129             :             case DescriptorSet::GNSSRecv2:
     130        1276 :                 break;
     131        7982 :             case DescriptorSet::FilterData:
     132        7982 :                 post_filter();
     133        7982 :                 break;
     134           0 :             case DescriptorSet::BaseCommand:
     135             :             case DescriptorSet::DMCommand:
     136             :             case DescriptorSet::SystemCommand:
     137           0 :                 break;
     138             :             }
     139             :         }
     140             :     }
     141       85724 : }
     142             : 
     143             : 
     144             : 
     145             : // Posts data from an imu packet to `state` and `handle_external` methods
     146        7982 : void AP_ExternalAHRS_MicroStrain7::post_imu() const
     147             : {
     148             :     {
     149        7982 :         WITH_SEMAPHORE(state.sem);
     150        7982 :         state.accel = imu_data.accel;
     151        7982 :         state.gyro = imu_data.gyro;
     152        7982 :     }
     153             : 
     154             :     {
     155             :         // *INDENT-OFF*
     156        7982 :         AP_ExternalAHRS::ins_data_message_t ins {
     157             :             accel: imu_data.accel,
     158             :             gyro: imu_data.gyro,
     159             :             temperature: -300
     160        7982 :         };
     161             :         // *INDENT-ON*
     162        7982 :         AP::ins().handle_external(ins);
     163             :     }
     164             : 
     165             : #if AP_COMPASS_EXTERNALAHRS_ENABLED
     166             :     {
     167             :         // *INDENT-OFF*
     168             :         AP_ExternalAHRS::mag_data_message_t mag {
     169             :             field: imu_data.mag
     170        7982 :         };
     171             :         // *INDENT-ON*
     172        7982 :         AP::compass().handle_external(mag);
     173             :     }
     174             : #endif
     175             : 
     176             : #if AP_BARO_EXTERNALAHRS_ENABLED
     177             :     {
     178             :         // *INDENT-OFF*
     179        7982 :         const AP_ExternalAHRS::baro_data_message_t baro {
     180             :             instance: 0,
     181        7982 :             pressure_pa: imu_data.pressure,
     182             :             // setting temp to 25 effectively disables barometer temperature calibrations - these are already performed by MicroStrain
     183             :             temperature: 25,
     184        7982 :         };
     185             :         // *INDENT-ON*
     186        7982 :         AP::baro().handle_external(baro);
     187             :     }
     188             : #endif
     189        7982 : }
     190             : 
     191        7982 : void AP_ExternalAHRS_MicroStrain7::post_filter() const
     192             : {
     193             :     {
     194        7982 :         WITH_SEMAPHORE(state.sem);
     195        7982 :         state.velocity = Vector3f{filter_data.ned_velocity_north, filter_data.ned_velocity_east, filter_data.ned_velocity_down};
     196        7982 :         state.have_velocity = true;
     197             : 
     198             :         // TODO the filter does not supply MSL altitude.
     199             :         // The GNSS system has both MSL and WGS-84 ellipsoid height.
     200             :         // Use GNSS 0 even though it may be bad.
     201        7982 :         state.location = Location{filter_data.lat, filter_data.lon, gnss_data[0].msl_altitude, Location::AltFrame::ABSOLUTE};
     202        7982 :         state.have_location = true;
     203        7982 :         state.last_location_update_us = AP_HAL::micros();
     204             : 
     205        7982 :         state.quat = filter_data.attitude_quat;
     206        7982 :         state.have_quaternion = true;
     207        7982 :     }
     208             : 
     209       23946 :     for (int instance = 0; instance < NUM_GNSS_INSTANCES; instance++) {
     210             :         // *INDENT-OFF*
     211             :         AP_ExternalAHRS::gps_data_message_t gps {
     212       15964 :             gps_week: filter_data.week,
     213       15964 :             ms_tow: filter_data.tow_ms,
     214       15964 :             fix_type: AP_GPS_FixType(gnss_data[instance].fix_type),
     215       15964 :             satellites_in_view: gnss_data[instance].satellites,
     216             : 
     217       15964 :             horizontal_pos_accuracy: gnss_data[instance].horizontal_position_accuracy,
     218       15964 :             vertical_pos_accuracy: gnss_data[instance].vertical_position_accuracy,
     219       15964 :             horizontal_vel_accuracy: gnss_data[instance].speed_accuracy,
     220             : 
     221       15964 :             hdop: gnss_data[instance].hdop,
     222       15964 :             vdop: gnss_data[instance].vdop,
     223             : 
     224       15964 :             longitude: gnss_data[instance].lon,
     225       15964 :             latitude: gnss_data[instance].lat,
     226       15964 :             msl_altitude: gnss_data[instance].msl_altitude,
     227             : 
     228       15964 :             ned_vel_north: gnss_data[instance].ned_velocity_north,
     229       15964 :             ned_vel_east: gnss_data[instance].ned_velocity_east,
     230       15964 :             ned_vel_down: gnss_data[instance].ned_velocity_down,
     231       15964 :         };
     232             :         // *INDENT-ON*
     233             : 
     234       15964 :         if (gps.fix_type >= AP_GPS_FixType::FIX_3D && !state.have_origin) {
     235          14 :             WITH_SEMAPHORE(state.sem);
     236          28 :             state.origin = Location{int32_t(gnss_data[instance].lat),
     237          14 :                                     int32_t(gnss_data[instance].lon),
     238          14 :                                     int32_t(gnss_data[instance].msl_altitude),
     239             :                                     Location::AltFrame::ABSOLUTE};
     240          14 :             state.have_origin = true;
     241          14 :         }
     242       15964 :         AP::gps().handle_external(gps, instance);
     243             :     }
     244        7982 : }
     245             : 
     246          41 : int8_t AP_ExternalAHRS_MicroStrain7::get_port(void) const
     247             : {
     248          41 :     if (!uart) {
     249           0 :         return -1;
     250             :     }
     251          41 :     return port_num;
     252             : };
     253             : 
     254             : // Get model/type name
     255          14 : const char* AP_ExternalAHRS_MicroStrain7::get_name() const
     256             : {
     257          14 :     return "MicroStrain7";
     258             : }
     259             : 
     260       35608 : bool AP_ExternalAHRS_MicroStrain7::healthy(void) const
     261             : {
     262       35608 :     return times_healthy() && filter_healthy();
     263             : }
     264             : 
     265       85802 : bool AP_ExternalAHRS_MicroStrain7::initialised(void) const
     266             : {
     267       85802 :     const bool got_packets = last_imu_pkt != 0 && last_gps_pkt != 0 && last_filter_pkt != 0;
     268       85802 :     return got_packets;
     269             : }
     270             : 
     271          79 : bool AP_ExternalAHRS_MicroStrain7::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const
     272             : {
     273          79 :     if (!initialised()) {
     274           0 :         hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "not initialised");
     275           0 :         return false;
     276             :     }
     277          79 :     if (!times_healthy()) {
     278           0 :         hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "data is stale");
     279           0 :         return false;
     280             :     }
     281          79 :     if (!filter_healthy()) {
     282           0 :         hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "filter is unhealthy");
     283           0 :         return false;
     284             :     }
     285          79 :     if (!healthy()) {
     286           0 :         hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "unhealthy");
     287           0 :         return false;
     288             :     }
     289             :     static_assert(NUM_GNSS_INSTANCES == 2, "This check only works if there are two GPS types.");
     290          79 :     if (gnss_data[0].fix_type < AP_GPS_FixType::FIX_3D && gnss_data[1].fix_type < AP_GPS_FixType::FIX_3D) {
     291           0 :         hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "missing 3D GPS fix on either GPS");
     292           0 :         return false;
     293             :     }
     294             : 
     295          79 :     return true;
     296             : }
     297             : 
     298       18738 : void AP_ExternalAHRS_MicroStrain7::get_filter_status(nav_filter_status &status) const
     299             : {
     300       18738 :     memset(&status, 0, sizeof(status));
     301       18738 :     if (last_imu_pkt != 0 && last_gps_pkt != 0) {
     302       18738 :         status.flags.initalized = true;
     303             :     }
     304       18738 :     if (healthy() && last_imu_pkt != 0) {
     305       18738 :         status.flags.attitude = true;
     306       18738 :         status.flags.vert_vel = true;
     307       18738 :         status.flags.vert_pos = true;
     308             : 
     309       18738 :         const auto filter_state = static_cast<FilterState>(filter_status.state);
     310       18738 :         if (filter_state_healthy(filter_state)) {
     311       18738 :             status.flags.horiz_vel = true;
     312       18738 :             status.flags.horiz_pos_rel = true;
     313       18738 :             status.flags.horiz_pos_abs = true;
     314       18738 :             status.flags.pred_horiz_pos_rel = true;
     315       18738 :             status.flags.pred_horiz_pos_abs = true;
     316       18738 :             status.flags.using_gps = true;
     317             :         }
     318             :     }
     319       18738 : }
     320             : 
     321             : // get variances
     322       18738 : bool AP_ExternalAHRS_MicroStrain7::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const
     323             : {
     324       18738 :     velVar = filter_data.ned_velocity_uncertainty.length() * vel_gate_scale;
     325       18738 :     posVar = filter_data.ned_position_uncertainty.xy().length() * pos_gate_scale;
     326       18738 :     hgtVar = filter_data.ned_position_uncertainty.z * hgt_gate_scale;
     327       18738 :     tasVar = 0;
     328       18738 :     return true;
     329             : }
     330             : 
     331       35687 : bool AP_ExternalAHRS_MicroStrain7::times_healthy() const
     332             : {
     333       35687 :     uint32_t now = AP_HAL::millis();
     334             : 
     335             :     // Expect the following rates:
     336             :     // * Navigation Filter: 25Hz = 40mS
     337             :     // * GPS: 2Hz = 500mS
     338             :     // * IMU: 25Hz = 40mS
     339             : 
     340             :     // Allow for some slight variance of 10%
     341       35687 :     constexpr float RateFoS = 1.1;
     342             : 
     343       35687 :     constexpr uint32_t expected_filter_time_delta_ms = 40;
     344       35687 :     constexpr uint32_t expected_gps_time_delta_ms = 500;
     345       35687 :     constexpr uint32_t expected_imu_time_delta_ms = 40;
     346             : 
     347      107061 :     const bool times_healthy = (now - last_imu_pkt < expected_imu_time_delta_ms * RateFoS && \
     348       71374 :                                 now - last_gps_pkt < expected_gps_time_delta_ms * RateFoS && \
     349       35687 :                                 now - last_filter_pkt < expected_filter_time_delta_ms * RateFoS);
     350             : 
     351       35687 :     return times_healthy;
     352             : }
     353             : 
     354       35687 : bool AP_ExternalAHRS_MicroStrain7::filter_healthy() const
     355             : {
     356       35687 :     const auto filter_state = static_cast<FilterState>(filter_status.state);
     357       35687 :     const bool filter_healthy = filter_state_healthy(filter_state);
     358       35687 :     return filter_healthy;
     359             : }
     360             : 
     361       54425 : bool AP_ExternalAHRS_MicroStrain7::filter_state_healthy(FilterState state)
     362             : {
     363       54425 :     switch (state) {
     364       54425 :     case FilterState::GQ7_FULL_NAV:
     365             :     case FilterState::GQ7_AHRS:
     366       54425 :         return true;
     367           0 :     default:
     368           0 :         return false;
     369             :     }
     370             : }
     371             : 
     372             : #endif // AP_EXTERNAL_AHRS_MICROSTRAIN7_ENABLED

Generated by: LCOV version 1.14