MAVLINK Distance_Sensor message is not processed

I’m trying to connect multiple distance sensors of type GY-US42v2 to a CubeOrange with Ardupilot 4.3.7. Unfortunately, these have a fixed I2C address and Ardupilot does not yet support I2C multiplexers. Therefore, I’d like to connect them via a TCA9548 multiplexer breakout to an Arduino nano, which independently queries the sensors and simply sends MAVLINK Distance Sensor messages to the FC via serial connection.
For starters, I’m trying to get this example code to run with just one sensor. The wiring looks like this:

I can confirm that the sensor properly takes measurements, and that the Arduino, when connected via USB to my computer, outputs something on the console which looks like the packed mavlink messages.
On the FC, I set the following parameters:

  • SERIAL1_TYPE = 2
  • SERIAL1_BAUD = 115

However, when I connect the Arduino to TELEM1 as outlined above, It doesn’t seem to work. I checked it by opening Mission Planner, invoking CTRL-F in the Data view and opening the Radar window via the “Proximity” button. I’d now expect this window to display detected obstacles, which it doesn’t.The Mavlink Inspector doesn’t show any devices except the Autopilot (Vehicle1->Comp1 MAV_COMP_ID_AUTOPILOT1) and the GCS. I’d expect Vehicle to have another entry named MAV_COMP_ID_OBSTACLE_AVOIDANCE.

As already mentioned, the code I’m using comes from this repository:
GitHub - ArtPilot/omniranger: Triggers and Reads I2C Rangefinders and sends data as MAVLink Distance Message via Serial Port.
I’ve made some minor changes to the main module to use one sonar only:

/*
   This Projects Reads out I2C Rangefinders and sends out MAVLink
   Distance Message via Serial Port.
   That are connected via an TCA9548A I2C Multiplexer Board
   We used 8 GY-US42v2 Sonar Rangefinders
   And 8 TOF10120 Time of Flight Optical Rangefinders

   Tested on:
   - Arduino Pro Mini
*/

#include <Arduino.h>
#include <common/mavlink.h>
#include <common/mavlink_msg_distance_sensor.h>
#include <common/mavlink_msg_heartbeat.h>
#include <Wire.h>
#include <tca9548.h>
#include <i2c_range_sensor.h>

#define DEBUG_LOG false

// TODO: Change the Interface to be more expressive
// instead of a digit to configure the sensor type
// a word like I2C_RANGER_GYUS42 would be better,
// OmniRanger::I2CRangeSensor srf_range_sensors[8] = {OmniRanger::I2CRangeSensor(1, 0), OmniRanger::I2CRangeSensor(1, 1), OmniRanger::I2CRangeSensor(1, 2), OmniRanger::I2CRangeSensor(1, 3), OmniRanger::I2CRangeSensor(1, 4), OmniRanger::I2CRangeSensor(1, 5), OmniRanger::I2CRangeSensor(1, 6), OmniRanger::I2CRangeSensor(1, 7)};
OmniRanger::I2CRangeSensor srf_range_sensors[1] = {OmniRanger::I2CRangeSensor(1, 0)};

const char *sensor_positions[8] = {"FC", "FL", "SL",
                     "BL", "BC", "BR", "SR", "FR"
                    };

const int num_of_sensors = sizeof(srf_range_sensors) / sizeof(OmniRanger::I2CRangeSensor);

#define TCA_5948_ADDRESS byte(0x71)
#define SRF_TRIGGER_RESPONSE_WAIT_MS 100

// #define _TASK_TIMECRITICAL      // Enable monitoring scheduling overruns
#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
// #define _TASK_STATUS_REQUEST    // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
// #define _TASK_WDT_IDS           // Compile with support for wdt control points and task ids
// #define _TASK_LTS_POINTER       // Compile with support for local task storage pointer
// #define _TASK_PRIORITY          // Support for layered scheduling priority
// #define _TASK_MICRO_RES         // Support for microsecond resolution
// #define _TASK_STD_FUNCTION      // Support for std::function (ESP8266 and ESP32 ONLY)
// #define _TASK_DEBUG             // Make all methods and variables public for debug purposes
// #define _TASK_INLINE            // Make all methods "inline" - needed to support some multi-tab, multi-file implementations
// #define _TASK_TIMEOUT           // Support for overall task timeout
// #define _TASK_OO_CALLBACKS      // Support for dynamic callback method binding
#include <TaskScheduler.h>

// Debug and Test options
#define _DEBUG_
//#define _TEST_

#ifdef _DEBUG_
#define _PP(a) Serial.print(a);
#define _PL(a) Serial.println(a);
#else
#define _PP(a)
#define _PL(a)
#endif

// LED_BUILTIN  13
#if defined(ARDUINO_ARCH_ESP32)
#define LED_BUILTIN 23 // esp32 dev2 kit does not have LED
#endif

// Scheduler
Scheduler ts;

#define PERIOD2 50
void updateSonic();
Task tScanUltraSounds(PERIOD2 * TASK_MILLISECOND, TASK_FOREVER, &updateSonic, &ts, true);

#define PERIOD3 20
void sendDistanceSensorMsgs();
Task tPrintData(PERIOD3 * TASK_MILLISECOND, TASK_FOREVER, &sendDistanceSensorMsgs, &ts, true);

#define PERIOD4 250
void sendHearbeatMsg();
Task tSendHeartbeat(PERIOD4 * TASK_MILLISECOND, TASK_FOREVER, &sendHearbeatMsg, &ts, true);


void setup()
{
    // put your setup code here, to run once:
#if defined(_DEBUG_) || defined(_TEST_)
    Serial.begin(115200);
    delay(1*TASK_SECOND);
#endif
    pinMode(LED_BUILTIN, OUTPUT);
    Wire.setClock(400000);

    Wire.begin();
    while (!TCA9548.begin(TCA_5948_ADDRESS))
    {
        Serial.println("No Connection to TCA9548");
        delay(50);
    }
    // Serial.println("Got Connection to TCA9548");
}

void print_srf_ln()
{
  Serial.print("SRF: ");
  for (uint8_t t = 0; t < num_of_sensors; t++)
  {
    Serial.print(srf_range_sensors[t].range);
    if (t < 7)
      Serial.print(", ");
    else
      Serial.println("");
  }
}

unsigned int get_best_distance_for_channel(uint8_t channel)
{
  // if (lrf_range_sensors[channel].range == 2000)
  // {
  return srf_range_sensors[channel].range;
  // }
  // else
  // {
  //   return min(srf_range_sensors[channel].range, lrf_range_sensors[channel].range);
  // }
}

void print_best_distance_for_channel(uint8_t channel)
{
  static char range[7];
  sprintf(range, "%c-%4d", 'S', srf_range_sensors[channel].range);
  Serial.print(range);

}


void print_min_distance() {
    for (uint8_t t = 0; t < num_of_sensors; t++)
    {
        Serial.print(sensor_positions[t]);
        Serial.print(": ");
        print_best_distance_for_channel(t);
        Serial.print("  ");

        if (t < 7)
            Serial.print(", ");
        else
            Serial.println("");
    }
}

void loop()
{
    ts.execute();
}

// === 1 =======================================

void updateSonic()
{
    uint8_t offset = (tScanUltraSounds.getRunCounter() % 2 == 0) ? 1 : 0;
    for (uint8_t t = offset; t < num_of_sensors; t = t + 2)
    {
        if (srf_range_sensors[t].triggered == true) {
            srf_range_sensors[t].read();
        }
    }
    for (uint8_t t = offset; t < num_of_sensors; t = t + 2)
    {
        srf_range_sensors[t].trigger();
    }
}


void sendDistanceSensorMsg(uint8_t orient, uint16_t rngDist) {
  //MAVLINK DISTANCE MESSAGE
  int sysid = 1;                   
  //< The component sending the message.
  int compid = MAV_COMP_ID_OBSTACLE_AVOIDANCE;

  uint32_t time_boot_ms = millis(); /*< Time since system boot*/
  uint16_t min_distance = 1; /*< Minimum distance the sensor can measure in centimeters*/
  uint16_t max_distance = 720; /*< Maximum distance the sensor can measure in centimeters*/
  uint16_t current_distance = rngDist / 10; /*< Current distance reading in cm*/
  uint8_t type = MAV_DISTANCE_SENSOR_UNKNOWN; /*< Type from MAV_DISTANCE_SENSOR enum.*/
  uint8_t id = 1; /*< Onboard ID of the sensor*/
  uint8_t orientation = orient; /*(0=forward, each increment is 45degrees more in clockwise direction), 24 (upwards) or 25 (downwards)*/
  // Consumed within ArduPilot by the proximity class
  uint8_t covariance = 0; /*< Measurement covariance in centimeters, 0 for unknown / invalid readings*/

  // Initialize the required buffers
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];

  // Pack the message
  mavlink_msg_distance_sensor_pack(sysid,compid,&msg,time_boot_ms,min_distance,max_distance,current_distance,type,id,orientation,covariance,0.0,0.0,0);
  // Copy the message to the send buffer
  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  // Send the message (.write sends as bytes) 
  Serial.write(buf, len);
  // Serial.print (orientation);
  // Serial.print (" - ");
  // Serial.println (current_distance);
}

void sendDistanceSensorMsgs() {
  for (uint8_t t = 0; t < num_of_sensors; t = t + 1) {
    sendDistanceSensorMsg(t, get_best_distance_for_channel(t));
  }
}

void sendHearbeatMsg() {
  const int sysid = 1;                            //< ID 1 for this system               
  const int compid = MAV_COMP_ID_OBSTACLE_AVOIDANCE;       //< The component sending the message.
  const uint8_t system_type =MAV_TYPE_GCS;         // Define the system type, in this case ground control station
  const uint8_t autopilot_type = MAV_AUTOPILOT_INVALID;
  const uint8_t system_mode = 0; 
  const uint32_t custom_mode = 0;                
  const uint8_t system_state = 0;
  
    // Initialize the required buffers
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];
  
  // Pack the message
  mavlink_msg_heartbeat_pack(sysid,compid, &msg, system_type, autopilot_type, system_mode, custom_mode, system_state);
  
  // Copy the message to the send buffer
  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  
  // Send the message 
  Serial.write(buf, len);
}

void printData() {
    print_srf_ln();
    // print_min_distance();
}

Does anybody know what I’m missing? Alternatively how can I further debug this issue?

p.s. I know this sensor can be connected via PWM too, but for 8 sensors I’d need a total of 16 ports, and I only have 12 left.

you will need to show the rest of your code before I can give you an answer.

also what flight controller are you using?

look at this for sending multiple mavlink rangefinders

The code is the code from this repository. See my edits for further info.
Indeed, I forgot to mention the flight controller, thanks for noticing. It’s a CubeOrange with a standard carrier board and ArduPilot 4.3.7.

One step further: I can see the MAV_COMP_OBSTACLE_AVOIDANCE component in the Mavlink Inspector.

Stupid error: I’ve been working a lot with PX4 and QGroundControl lately. When configuring parameters via QGroundControl, all changes are automatically written to the FC upon leaving the edit field, no further button presses needed. Now, back in Mission Planner, I forgot to press the “Write Params” button and my changes to SERIAL1_TYPE and SERIAL1_BAUD were never applied.

However, I still don’t see anything in the Proximity view.

According to @geofrancis github link, I’ve also set RNGFND1_TYPE to 10.

This is the data I’m getting:
mavlink

I would try slowing it down, add a 100ms delay and set type =0

I added the delay and set the type to “0”. Unfortunately, I still don’t see anything in the proximity window. Also, type 0 means laser rangefinder, why do I need that?

This is the updated main module (Changes are in lines 185 and 209):

/*
   This Projects Reads out I2C Rangefinders and sends out MAVLink
   Distance Message via Serial Port.
   That are connected via an TCA9548A I2C Multiplexer Board
   We used 8 GY-US42v2 Sonar Rangefinders
   And 8 TOF10120 Time of Flight Optical Rangefinders

   Tested on:
   - Arduino Pro Mini
*/

#include <Arduino.h>
#include <common/mavlink.h>
#include <common/mavlink_msg_distance_sensor.h>
#include <common/mavlink_msg_heartbeat.h>
#include <Wire.h>
#include <tca9548.h>
#include <i2c_range_sensor.h>

#define DEBUG_LOG false

// TODO: Change the Interface to be more expressive
// instead of a digit to configure the sensor type
// a word like I2C_RANGER_GYUS42 would be better,
// OmniRanger::I2CRangeSensor srf_range_sensors[8] = {OmniRanger::I2CRangeSensor(1, 0), OmniRanger::I2CRangeSensor(1, 1), OmniRanger::I2CRangeSensor(1, 2), OmniRanger::I2CRangeSensor(1, 3), OmniRanger::I2CRangeSensor(1, 4), OmniRanger::I2CRangeSensor(1, 5), OmniRanger::I2CRangeSensor(1, 6), OmniRanger::I2CRangeSensor(1, 7)};
OmniRanger::I2CRangeSensor srf_range_sensors[1] = {OmniRanger::I2CRangeSensor(1, 0)};

const char *sensor_positions[8] = {"FC", "FL", "SL",
                     "BL", "BC", "BR", "SR", "FR"
                    };

const int num_of_sensors = sizeof(srf_range_sensors) / sizeof(OmniRanger::I2CRangeSensor);

#define TCA_5948_ADDRESS byte(0x71)
#define SRF_TRIGGER_RESPONSE_WAIT_MS 100

// #define _TASK_TIMECRITICAL      // Enable monitoring scheduling overruns
#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
// #define _TASK_STATUS_REQUEST    // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
// #define _TASK_WDT_IDS           // Compile with support for wdt control points and task ids
// #define _TASK_LTS_POINTER       // Compile with support for local task storage pointer
// #define _TASK_PRIORITY          // Support for layered scheduling priority
// #define _TASK_MICRO_RES         // Support for microsecond resolution
// #define _TASK_STD_FUNCTION      // Support for std::function (ESP8266 and ESP32 ONLY)
// #define _TASK_DEBUG             // Make all methods and variables public for debug purposes
// #define _TASK_INLINE            // Make all methods "inline" - needed to support some multi-tab, multi-file implementations
// #define _TASK_TIMEOUT           // Support for overall task timeout
// #define _TASK_OO_CALLBACKS      // Support for dynamic callback method binding
#include <TaskScheduler.h>

// Debug and Test options
#define _DEBUG_
//#define _TEST_

#ifdef _DEBUG_
#define _PP(a) Serial.print(a);
#define _PL(a) Serial.println(a);
#else
#define _PP(a)
#define _PL(a)
#endif

// LED_BUILTIN  13
#if defined(ARDUINO_ARCH_ESP32)
#define LED_BUILTIN 23 // esp32 dev2 kit does not have LED
#endif

// Scheduler
Scheduler ts;

#define PERIOD2 50
void updateSonic();
Task tScanUltraSounds(PERIOD2 * TASK_MILLISECOND, TASK_FOREVER, &updateSonic, &ts, true);

#define PERIOD3 20
void sendDistanceSensorMsgs();
Task tPrintData(PERIOD3 * TASK_MILLISECOND, TASK_FOREVER, &sendDistanceSensorMsgs, &ts, true);

#define PERIOD4 250
void sendHearbeatMsg();
Task tSendHeartbeat(PERIOD4 * TASK_MILLISECOND, TASK_FOREVER, &sendHearbeatMsg, &ts, true);


void setup()
{
    // put your setup code here, to run once:
#if defined(_DEBUG_) || defined(_TEST_)
    Serial.begin(115200);
    delay(1*TASK_SECOND);
#endif
    pinMode(LED_BUILTIN, OUTPUT);
    Wire.setClock(400000);

    Wire.begin();
    while (!TCA9548.begin(TCA_5948_ADDRESS))
    {
        Serial.println("No Connection to TCA9548");
        delay(50);
    }
    // Serial.println("Got Connection to TCA9548");
}

void print_srf_ln()
{
  Serial.print("SRF: ");
  for (uint8_t t = 0; t < num_of_sensors; t++)
  {
    Serial.print(srf_range_sensors[t].range);
    if (t < 7)
      Serial.print(", ");
    else
      Serial.println("");
  }
}

unsigned int get_best_distance_for_channel(uint8_t channel)
{
  // if (lrf_range_sensors[channel].range == 2000)
  // {
  return srf_range_sensors[channel].range;
  // }
  // else
  // {
  //   return min(srf_range_sensors[channel].range, lrf_range_sensors[channel].range);
  // }
}

void print_best_distance_for_channel(uint8_t channel)
{
  static char range[7];
  sprintf(range, "%c-%4d", 'S', srf_range_sensors[channel].range);
  Serial.print(range);

}


void print_min_distance() {
    for (uint8_t t = 0; t < num_of_sensors; t++)
    {
        Serial.print(sensor_positions[t]);
        Serial.print(": ");
        print_best_distance_for_channel(t);
        Serial.print("  ");

        if (t < 7)
            Serial.print(", ");
        else
            Serial.println("");
    }
}

void loop()
{
    ts.execute();
}

// === 1 =======================================

void updateSonic()
{
    uint8_t offset = (tScanUltraSounds.getRunCounter() % 2 == 0) ? 1 : 0;
    for (uint8_t t = offset; t < num_of_sensors; t = t + 2)
    {
        if (srf_range_sensors[t].triggered == true) {
            srf_range_sensors[t].read();
        }
    }
    for (uint8_t t = offset; t < num_of_sensors; t = t + 2)
    {
        srf_range_sensors[t].trigger();
    }
}


void sendDistanceSensorMsg(uint8_t orient, uint16_t rngDist) {
  //MAVLINK DISTANCE MESSAGE
  int sysid = 1;                   
  //< The component sending the message.
  int compid = MAV_COMP_ID_OBSTACLE_AVOIDANCE;

  uint32_t time_boot_ms = millis(); /*< Time since system boot*/
  uint16_t min_distance = 1; /*< Minimum distance the sensor can measure in centimeters*/
  uint16_t max_distance = 720; /*< Maximum distance the sensor can measure in centimeters*/
  uint16_t current_distance = rngDist / 10; /*< Current distance reading in cm*/
  uint8_t type = MAV_DISTANCE_SENSOR_LASER; /*< Type from MAV_DISTANCE_SENSOR enum.*/
  uint8_t id = 1; /*< Onboard ID of the sensor*/
  uint8_t orientation = orient; /*(0=forward, each increment is 45degrees more in clockwise direction), 24 (upwards) or 25 (downwards)*/
  // Consumed within ArduPilot by the proximity class
  uint8_t covariance = 0; /*< Measurement covariance in centimeters, 0 for unknown / invalid readings*/

  // Initialize the required buffers
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];

  // Pack the message
  mavlink_msg_distance_sensor_pack(sysid,compid,&msg,time_boot_ms,min_distance,max_distance,current_distance,type,id,orientation,covariance,0.0,0.0,0);
  // Copy the message to the send buffer
  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  // Send the message (.write sends as bytes) 
  Serial.write(buf, len);
  // Serial.print (orientation);
  // Serial.print (" - ");
  // Serial.println (current_distance);
}

void sendDistanceSensorMsgs() {
  for (uint8_t t = 0; t < num_of_sensors; t = t + 1) {
    sendDistanceSensorMsg(t, get_best_distance_for_channel(t));
    delay(100);
  }
}

void sendHearbeatMsg() {
  const int sysid = 1;                            //< ID 1 for this system               
  const int compid = MAV_COMP_ID_OBSTACLE_AVOIDANCE;       //< The component sending the message.
  const uint8_t system_type =MAV_TYPE_GCS;         // Define the system type, in this case ground control station
  const uint8_t autopilot_type = MAV_AUTOPILOT_INVALID;
  const uint8_t system_mode = 0; 
  const uint32_t custom_mode = 0;                
  const uint8_t system_state = 0;
  
    // Initialize the required buffers
  mavlink_message_t msg;
  uint8_t buf[MAVLINK_MAX_PACKET_LEN];
  
  // Pack the message
  mavlink_msg_heartbeat_pack(sysid,compid, &msg, system_type, autopilot_type, system_mode, custom_mode, system_state);
  
  // Copy the message to the send buffer
  uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
  
  // Send the message 
  Serial.write(buf, len);
}

void printData() {
    print_srf_ln();
    // print_min_distance();
}

And this is the Mavlink Inspector’s output:
mavlink

I’m now getting a distance reading after setting PRX1_TYPE to “2”.

I’m quite confused by the inner workings of collision avoidance right now, but the hardware part seems to be solved and this thread can be closed.

For the other configuration questions, I will open a new thread in the appropriate forum if need be.

Thanks to @geofrancis for the tips.

1 Like