/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Support for MicroStrain GQ7 serially connected AHRS Systems Usage in SITL with hardware for debugging: $ sim_vehicle.py -v Plane -A "--serial3=uart:/dev/3dm-gq7" --console --map -DG $ ./Tools/autotest/sim_vehicle.py -v Plane -A "--serial3=uart:/dev/3dm-gq7" -DG param set AHRS_EKF_TYPE 11 param set EAHRS_TYPE 7 param set GPS1_TYPE 21 param set GPS2_TYPE 21 param set SERIAL3_BAUD 115 param set SERIAL3_PROTOCOL 36 UDEV rules for repeatable USB connection: $ cat /etc/udev/rules.d/99-usb-serial.rules SUBSYSTEM=="tty", ATTRS{manufacturer}=="Lord Microstrain", SYMLINK+="3dm-gq7" Usage with simulated MicroStrain7: ./Tools/autotest/sim_vehicle.py -v Plane -A "--serial3=sim:MicroStrain7" --console --map -DG */ #define ALLOW_DOUBLE_MATH_FUNCTIONS #include "AP_ExternalAHRS_config.h" #if AP_EXTERNAL_AHRS_MICROSTRAIN7_ENABLED #include "AP_ExternalAHRS_MicroStrain7.h" #include "AP_Compass/AP_Compass_config.h" #include #include #include #include #include #include #include #include #include #include // For memcpy static const char* LOG_FMT = "%s ExternalAHRS: %s"; extern const AP_HAL::HAL &hal; // AP_ExternalAHRS_MicroStrain7::AP_ExternalAHRS_MicroStrain7(AP_ExternalAHRS *_frontend, // AP_ExternalAHRS::state_t &_state): AP_ExternalAHRS_backend(_frontend, _state) // { // auto &sm = AP::serialmanager(); // uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0); // baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0); // port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0); // if (!uart) { // GCS_SEND_TEXT(MAV_SEVERITY_ERROR, LOG_FMT, get_name(), "no UART"); // return; // } // if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_MicroStrain7::update_thread, void), "AHRS", 2048, AP_HAL::Scheduler::PRIORITY_SPI, 0)) { // AP_BoardConfig::allocation_error("MicroStrain7 ExternalAHRS failed to allocate ExternalAHRS update thread"); // } // // don't offer IMU by default, at 100Hz it is too slow for many aircraft // set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) | // uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) | // uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS)); // hal.scheduler->delay(5000); // if (!initialised()) { // GCS_SEND_TEXT(MAV_SEVERITY_WARNING, LOG_FMT, get_name(), "failed to initialise."); // } // } AP_ExternalAHRS_MicroStrain7::AP_ExternalAHRS_MicroStrain7(AP_ExternalAHRS *_frontend, AP_ExternalAHRS::state_t &_state): AP_ExternalAHRS_backend(_frontend, _state) { auto &sm = AP::serialmanager(); uart = sm.find_serial(AP_SerialManager::SerialProtocol_AHRS, 0); baudrate = sm.find_baudrate(AP_SerialManager::SerialProtocol_AHRS, 0); port_num = sm.find_portnum(AP_SerialManager::SerialProtocol_AHRS, 0); if (!uart) { GCS_SEND_TEXT(MAV_SEVERITY_ERROR, LOG_FMT, get_name(), "no UART"); return; } if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_ExternalAHRS_MicroStrain7::update_thread, void), "AHRS", 2048, AP_HAL::Scheduler::PRIORITY_SPI, 0)) { AP_BoardConfig::allocation_error("MicroStrain7 ExternalAHRS failed to allocate ExternalAHRS update thread"); } // Send the "Get Device Info" command uint8_t command[2] = {2, 0x03}; // Field Length = 2, Descriptor = 0x03 uart->write(command, sizeof(command)); // Wait for the response uint8_t response[256]; uint16_t response_len = uart->read(response, sizeof(response)); // Validate the response length if (response_len < 2 || response[1] != 0x81) { // Response Descriptor = 0x81 GCS_SEND_TEXT(MAV_SEVERITY_WARNING, LOG_FMT, get_name(), "Device detection failed: Invalid response"); return; } // Parse the device information uint8_t device_info[response[0]]; // Response Length at response[0] memcpy(device_info, &response[2], response[0]); // Copy device info starting from byte 2 // Example: Check for CV7 or GQ7 if (device_info[0] == static_cast(DeviceInfo::CV7_IDENTIFIER)) { // Example identifier is_cv7 = true; GCS_SEND_TEXT(MAV_SEVERITY_INFO, LOG_FMT, get_name(), "Detected device: 3DM-CV7"); } else if (device_info[0] == static_cast(DeviceInfo::GQ7_IDENTIFIER)) { // Example identifier is_cv7 = false; GCS_SEND_TEXT(MAV_SEVERITY_INFO, LOG_FMT, get_name(), "Detected device: 3DM-GQ7"); } else { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, LOG_FMT, get_name(), "Device detection failed: Unknown model"); return; } // Ensure required packets are available if ((is_cv7 && last_imu_pkt == 0) || (!is_cv7 && (last_imu_pkt == 0 || last_gps_pkt == 0 || last_filter_pkt == 0))) { GCS_SEND_TEXT(MAV_SEVERITY_WARNING, LOG_FMT, get_name(), "failed to initialise: Missing required packets."); return; } // don't offer IMU by default, at 100Hz it is too slow for many aircraft set_default_sensors(uint16_t(AP_ExternalAHRS::AvailableSensor::GPS) | uint16_t(AP_ExternalAHRS::AvailableSensor::BARO) | uint16_t(AP_ExternalAHRS::AvailableSensor::COMPASS)); GCS_SEND_TEXT(MAV_SEVERITY_INFO, LOG_FMT, get_name(), "initialised."); } bool AP_ExternalAHRS_MicroStrain7::pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const //No changes { if (!initialised()) { hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "not initialised"); return false; } if (!times_healthy()) { hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "data is stale"); return false; } if (!filter_healthy()) { hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "filter is unhealthy"); return false; } if (!healthy()) { hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "unhealthy"); return false; } static_assert(NUM_GNSS_INSTANCES == 2, "This check only works if there are two GPS types."); if (gnss_data[0].fix_type < GPS_FIX_TYPE_3D_FIX && gnss_data[1].fix_type < GPS_FIX_TYPE_3D_FIX) { hal.util->snprintf(failure_msg, failure_msg_len, LOG_FMT, get_name(), "missing 3D GPS fix on either GPS"); return false; } return true; } void AP_ExternalAHRS_MicroStrain7::update_thread(void) //No changes { if (!port_open) { port_open = true; uart->begin(baudrate); } while (true) { build_packet(); hal.scheduler->delay_microseconds(100); check_initialise_state(); } } void AP_ExternalAHRS_MicroStrain7::check_initialise_state(void) //No changes { const auto new_init_state = initialised(); // Only send the message after fully booted up, otherwise it gets dropped. if (!last_init_state && new_init_state && AP_HAL::millis() > 5000) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, LOG_FMT, get_name(), "initialised."); last_init_state = new_init_state; } } // bool AP_ExternalAHRS_MicroStrain7::initialised(void) const // { // // Send the "Get Device Info" command // uint8_t command[2] = {0x01, 0x03}; // Example command for "Get Device Info" // uart->write(command, sizeof(command)); // // Wait for the response // uint8_t response[256]; // uint16_t response_len = uart->read(response, sizeof(response)); // // Validate the response length // if (response_len < static_cast(DeviceInfo::MIN_RESPONSE_LENGTH)) { // hal.console->printf("Device initialization failed: Insufficient response\n"); // return false; // } // // Extract the model number from the response // uint16_t model_number = 0; // memcpy(&model_number, &response[static_cast(DeviceInfo::MODEL_NUMBER_OFFSET)], sizeof(model_number)); // // Check if the model number matches CV7 or GQ7 // if (model_number == static_cast(DeviceInfo::CV7_MODEL_NUMBER)) { // hal.console->printf("Detected device: 3DM-CV7\n"); // is_cv7 = true; // return last_imu_pkt != 0; // CV7 requires only IMU packets // } else if (model_number == static_cast(DeviceInfo::GQ7_MODEL_NUMBER)) { // hal.console->printf("Detected device: 3DM-GQ7\n"); // is_cv7 = false; // return last_imu_pkt != 0 && last_gps_pkt != 0 && last_filter_pkt != 0; // GQ7 requires all packets // } else { // hal.console->printf("Device initialization failed: Unknown model number (%u)\n", model_number); // return false; // } // } bool AP_ExternalAHRS_MicroStrain7::initialised(void) const { // Check packets based on device type if (is_cv7) { // CV7 requires only IMU packets return last_imu_pkt != 0; } else { // GQ7 requires IMU, GPS, and Filter packets const bool got_packets = (last_imu_pkt != 0) && (last_gps_pkt != 0) && (last_filter_pkt != 0); return got_packets; } } // bool AP_ExternalAHRS_MicroStrain7::detect_device(void) const// new method to distinguish between cv7 and gq7 // { // // Send the "Get Device Info" command // uint8_t command[2] = {0x01, 0x03}; // Command for "Get Device Info" // uart->write(command, sizeof(command)); // // Wait for response // uint8_t response[256]; // uint16_t response_len = uart->read(response, sizeof(response)); // // Validate the response length // if (response_len < static_cast(DeviceInfo::MIN_RESPONSE_LENGTH)) { // hal.console->printf("Device detection failed: Insufficient response\n"); // return false; // } // // Extract the model number from the response (assuming it is a 16-character string) // char model_number[17] = {0}; // 16 characters + null terminator // memcpy(model_number, &response[static_cast(DeviceInfo::MODEL_NUMBER_OFFSET)], 16); // // Compare the model number to known values // if (strcmp(model_number, "6286") == 0) { // CV7 model number as string // is_cv7 = true; // hal.console->printf("Detected device: 3DM-CV7\n"); // } else if (strcmp(model_number, "6284") == 0) { // GQ7 model number as string // is_cv7 = false; // hal.console->printf("Detected device: 3DM-GQ7\n"); // } else { // hal.console->printf("Device detection failed: Unknown model number\n"); // return false; // } // return true; // } // Builds packets by looking at each individual byte, once a full packet has been read in it checks the checksum then handles the packet. void AP_ExternalAHRS_MicroStrain7::build_packet() { if (uart == nullptr) { return; } WITH_SEMAPHORE(sem); uint32_t nbytes = MIN(uart->available(), 2048u); while (nbytes--> 0) { uint8_t b; if (!uart->read(b)) { break; } DescriptorSet descriptor; if (handle_byte(b, descriptor)) { switch (descriptor) { case DescriptorSet::IMUData: post_imu(); break; case DescriptorSet::GNSSData: case DescriptorSet::GNSSRecv1: case DescriptorSet::GNSSRecv2: break; case DescriptorSet::FilterData: post_filter(); break; case DescriptorSet::BaseCommand: case DescriptorSet::DMCommand: case DescriptorSet::SystemCommand: break; } } } } // Posts data from an imu packet to `state` and `handle_external` methods void AP_ExternalAHRS_MicroStrain7::post_imu() const // updated method for 3dm-cv7 imu (sheteshreyash) { { WITH_SEMAPHORE(state.sem); state.accel = imu_data.accel; state.gyro = imu_data.gyro; } { // *INDENT-OFF* AP_ExternalAHRS::ins_data_message_t ins { accel: imu_data.accel, gyro: imu_data.gyro, temperature: -300 // Placeholder for temperature }; // *INDENT-ON* AP::ins().handle_external(ins); } #if AP_COMPASS_EXTERNALAHRS_ENABLED { // Check if magnetometer data is valid and handle it for both CV7 and GQ7 AP_ExternalAHRS::mag_data_message_t mag { field: imu_data.mag }; AP::compass().handle_external(mag); } #endif #if AP_BARO_EXTERNALAHRS_ENABLED if (!is_cv7) { // Only handle barometer data for GQ7 // *INDENT-OFF* const AP_ExternalAHRS::baro_data_message_t baro { instance: 0, pressure_pa: imu_data.pressure, temperature: 25, // Default temperature for barometer }; // *INDENT-ON* AP::baro().handle_external(baro); } #endif } // void AP_ExternalAHRS_MicroStrain7::post_filter() const //sheteshreyash // { // { // WITH_SEMAPHORE(state.sem); // // Update quaternion from filter data // state.quat = filter_data.attitude_quat; // state.have_quaternion = true; // // Update velocity from filter data // state.velocity = Vector3f{ // filter_data.ned_velocity_north, // filter_data.ned_velocity_east, // filter_data.ned_velocity_down // }; // state.have_velocity = true; // // Update location from filter data (if lat/lon/alt are valid) // if (filter_data.lat != 0 && filter_data.lon != 0) { // state.location = Location{ // filter_data.lat, // filter_data.lon, // filter_data.hae_altitude, // Using HAE altitude for height above ellipsoid // Location::AltFrame::ABSOLUTE // }; // state.have_location = true; // } else { // state.location = Location{}; // Set to invalid if lat/lon are zero // state.have_location = false; // } // } // // AHRS-specific functionality // // Process AHRS specific data (e.g., attitude, velocity, location, etc.) // { // // If the system has a valid origin, set the location to the origin when the first valid GNSS fix is received // if (state.have_origin) { // WITH_SEMAPHORE(state.sem); // // Check if the GNSS fix is 3D (good enough for origin) // if (!state.have_origin) { // state.origin = Location{ // int32_t(filter_data.lat), // int32_t(filter_data.lon), // int32_t(filter_data.hae_altitude), // HAE altitude used // Location::AltFrame::ABSOLUTE // }; // state.have_origin = true; // } // } // // Example AHRS specific handling (attitude and velocity data) // // Assuming some functions or data structures are already defined to handle the IMU data: // // Process the AHRS attitude and velocity for output // Vector3f ahrs_velocity{filter_data.ned_velocity_north, filter_data.ned_velocity_east, filter_data.ned_velocity_down}; // Vector3f ahrs_orientation{filter_data.attitude_quat.q1, filter_data.attitude_quat.q2, filter_data.attitude_quat.q3}; // // Update other AHRS-specific data if necessary // // For example, if there are specific flags or calculations, you can do that here. // // Some custom logic specific to 3DM CV7 can go here (if needed). // } // // Handle GPS and GNSS data if available // for (int instance = 0; instance < NUM_GNSS_INSTANCES; instance++) { // // Process GNSS data as needed (assuming gnss_data and filter_data are properly updated) // AP_ExternalAHRS::gps_data_message_t gps{ // gps_week: filter_data.week, // ms_tow: filter_data.tow_ms, // fix_type: AP_GPS_FixType(gnss_data[instance].fix_type), // satellites_in_view: gnss_data[instance].satellites, // horizontal_pos_accuracy: gnss_data[instance].horizontal_position_accuracy, // vertical_pos_accuracy: gnss_data[instance].vertical_position_accuracy, // horizontal_vel_accuracy: gnss_data[instance].speed_accuracy, // hdop: gnss_data[instance].hdop, // vdop: gnss_data[instance].vdop, // longitude: gnss_data[instance].lon, // latitude: gnss_data[instance].lat, // msl_altitude: gnss_data[instance].msl_altitude, // ned_vel_north: gnss_data[instance].ned_velocity_north, // ned_vel_east: gnss_data[instance].ned_velocity_east, // ned_vel_down: gnss_data[instance].ned_velocity_down, // }; // // Handle external GPS data in Ardupilot // if (gps.fix_type >= AP_GPS_FixType::FIX_3D && !state.have_origin) { // WITH_SEMAPHORE(state.sem); // state.origin = Location{int32_t(gnss_data[instance].lat), // int32_t(gnss_data[instance].lon), // int32_t(gnss_data[instance].msl_altitude), // Location::AltFrame::ABSOLUTE}; // state.have_origin = true; // } // AP::gps().handle_external(gps, instance); // } // } void AP_ExternalAHRS_MicroStrain7::post_filter() const //sheteshreyash { { WITH_SEMAPHORE(state.sem); // Publish filter velocity and quaternion data state.velocity = Vector3f{filter_data.ned_velocity_north, filter_data.ned_velocity_east, filter_data.ned_velocity_down}; state.have_velocity = true; state.quat = filter_data.attitude_quat; state.have_quaternion = true; // Handle location data if (!is_cv7) { // For GQ7, use GNSS data for location state.location = Location{filter_data.lat, filter_data.lon, gnss_data[0].msl_altitude, Location::AltFrame::ABSOLUTE}; state.have_location = true; } else { // For CV7, use filter data or set location as invalid state.location = Location{}; state.have_location = false; } } // Publish GNSS data only for GQ7 if (!is_cv7) { for (int instance = 0; instance < NUM_GNSS_INSTANCES; instance++) { // Prepare GNSS message AP_ExternalAHRS::gps_data_message_t gps{ gps_week: filter_data.week, ms_tow: filter_data.tow_ms, fix_type: AP_GPS_FixType(gnss_data[instance].fix_type), satellites_in_view: gnss_data[instance].satellites, horizontal_pos_accuracy: gnss_data[instance].horizontal_position_accuracy, vertical_pos_accuracy: gnss_data[instance].vertical_position_accuracy, horizontal_vel_accuracy: gnss_data[instance].speed_accuracy, hdop: gnss_data[instance].hdop, vdop: gnss_data[instance].vdop, longitude: gnss_data[instance].lon, latitude: gnss_data[instance].lat, msl_altitude: gnss_data[instance].msl_altitude, ned_vel_north: gnss_data[instance].ned_velocity_north, ned_vel_east: gnss_data[instance].ned_velocity_east, ned_vel_down: gnss_data[instance].ned_velocity_down, }; // Update origin if not set and fix type is 3D or better if (gps.fix_type >= AP_GPS_FixType::FIX_3D && !state.have_origin) { WITH_SEMAPHORE(state.sem); state.origin = Location{int32_t(gnss_data[instance].lat), int32_t(gnss_data[instance].lon), int32_t(gnss_data[instance].msl_altitude), Location::AltFrame::ABSOLUTE}; state.have_origin = true; } // Send GPS data to the AP framework AP::gps().handle_external(gps, instance); } } } int8_t AP_ExternalAHRS_MicroStrain7::get_port(void) const //No changes { if (!uart) { return -1; } return port_num; }; // Get model/type name const char* AP_ExternalAHRS_MicroStrain7::get_name() const //No changes { return "MicroStrain7"; } bool AP_ExternalAHRS_MicroStrain7::healthy(void) const //No changes { return times_healthy() && filter_healthy(); } void AP_ExternalAHRS_MicroStrain7::get_filter_status(nav_filter_status &status) const //No changes { memset(&status, 0, sizeof(status)); if (last_imu_pkt != 0 && last_gps_pkt != 0) { status.flags.initalized = true; } if (healthy() && last_imu_pkt != 0) { status.flags.attitude = true; status.flags.vert_vel = true; status.flags.vert_pos = true; const auto filter_state = static_cast(filter_status.state); if (filter_state_healthy(filter_state)) { status.flags.horiz_vel = true; status.flags.horiz_pos_rel = true; status.flags.horiz_pos_abs = true; status.flags.pred_horiz_pos_rel = true; status.flags.pred_horiz_pos_abs = true; status.flags.using_gps = true; } } } // get variances bool AP_ExternalAHRS_MicroStrain7::get_variances(float &velVar, float &posVar, float &hgtVar, Vector3f &magVar, float &tasVar) const //No changes { velVar = filter_data.ned_velocity_uncertainty.length() * vel_gate_scale; posVar = filter_data.ned_position_uncertainty.xy().length() * pos_gate_scale; hgtVar = filter_data.ned_position_uncertainty.z * hgt_gate_scale; tasVar = 0; return true; } bool AP_ExternalAHRS_MicroStrain7::times_healthy() const //No changes { uint32_t now = AP_HAL::millis(); // Expect the following rates: // * Navigation Filter: 25Hz = 40mS // * GPS: 2Hz = 500mS // * IMU: 25Hz = 40mS // Allow for some slight variance of 10% constexpr float RateFoS = 1.1; constexpr uint32_t expected_filter_time_delta_ms = 40; constexpr uint32_t expected_gps_time_delta_ms = 500; constexpr uint32_t expected_imu_time_delta_ms = 40; const bool times_healthy = (now - last_imu_pkt < expected_imu_time_delta_ms * RateFoS && \ now - last_gps_pkt < expected_gps_time_delta_ms * RateFoS && \ now - last_filter_pkt < expected_filter_time_delta_ms * RateFoS); return times_healthy; } bool AP_ExternalAHRS_MicroStrain7::filter_healthy() const //No changes { const auto filter_state = static_cast(filter_status.state); const bool filter_healthy = filter_state_healthy(filter_state); return filter_healthy; } bool AP_ExternalAHRS_MicroStrain7::filter_state_healthy(FilterState state) //No changes { switch (state) { case FilterState::GQ7_FULL_NAV: case FilterState::GQ7_AHRS: return true; default: return false; } } #endif // AP_EXTERNAL_AHRS_MICROSTRAIN7_ENABLED