Connecting ESP32 to PixHawk 6c via I2C

This question seems to have asked before but, without a resolution. I have been attempting to connect an ESP32 module via I2C to a PixHawk 6C. So far I’ve been unsuccessful. The goal is to connect an Yuneec Typhoon H Sonar Sensor, which appears to be similar to a HC SR04 sensor. I’ve have the sensor working on the ESP32. What is correct method to get the ESP32 communicating with the PixHawk 6C? I’ve tried using the pin pullup on the ESP32.

Welcome to the forum.

Should the ESP32 be in I2C Slave mode and act as a I2C device that the PixHawk knows ?
The I2C bus is not like a Serial/UART bus.

1 Like

Start by posting an annotated schematic showing how you have connected the hardware items. A frizzy is not very useful, it is a wiring diagram not a schematic. We are trying to debug not build. Be sure to show all power sources, grounds, external components etc. Post links to technical information on the hardware items you have.

My assumption is that the PixMark 6C FC would be the master, since multiple I2C can be connected to it. I'll put together a diagram and upload it. The sonic sensor is working. I can see it's output on Serial output. I'm using the default SDA and SLC pins for Wire...

Here is my diagram. Does this scenario require external pull-up resistors?

I grabbed code from here, with a couple of minor tweaks, in case I was simply overthinking:

master reader and slave sender with distance sensor

ESP32 Specs

arduino-esp32 includes examples of Wire/I2C in peripheral mode. I would get the examples working then print out what the pixhawk is sending. I assume you must get info like what I2C bus address and data format pixhawk is expecting from the pixhawk docs. Or connect up a logic analyzer that can sniff I2C bus traffic.

WiFi is power hungry so ESP32 is not the best choice unless WiFi is required.

This is just a test setup... I can always disable the wifi radio in production... I'm just trying to get it working...

I've tried the slave mode... nada... Maybe I should hook up two esp32 via I2C, just to make sure I can get the boards working... BTW, the firmware on the PixHawk 6C is PX4...

Can you write code in the PixHawk board ?

What if you forget about the I2C bus, and then do your project ?
The ESP32 has a Serial2 port that is free to use.

I've used the existing UART ports for over devices... Hence, attempts to use the I2C port... Ideally, there should be a driver that allow using the GPIO pins on the FC.

The I2C bus is not a bus where you can send data and assume that it shows up on the other end. That is not how the I2C bus works.
After reading this topic once more, I think that I can conclude that it is not possible to make it work.

There is a driver on the FC side for a similar sensor that uses I2C. The initial problem is just getting the FC to detect that there is an I2C device on the bus. So, in theory this should work, but I need basically enable I2C on the ESP32.

Here is the code that I'm hoping to get working properly. It is loosely based on the rangefinder drivers in PX4 firmware:

/********************************************************************
* ClassName (Sensor_V2) is declared/created using class keyword     *
* speed of sound in the air at 20ºC (68ºF) = 343m/s                 *
* distance to an object = ((speed of sound in the air)*time)/2      *
* Based on:                                                         *
* https://randomnerdtutorials.com/esp32-hc-sr04-ultrasonic-arduino/ *
********************************************************************/

#include "Wire.h"

uint32_t _count = 0;


class Sensor_V2 {

public:
  // Constructor
  explicit Sensor_V2(TwoWire *_Wire, uint8_t trigger, uint8_t echo, uint8_t i2caddr, uint8_t slc, uint8_t sda);
  //member functions
  void     Begin();
  void     Ping();
  double   Pong();
  uint8_t  Alert();

  void setMinRange(uint16_t val) {
    _min_range = val;
  }
  void setMaxRange(uint16_t val) {
    _max_range = val;
  }
  void setTriggerPulse(uint8_t val) {
    _TriggerPulse = val;
  }
  void setUseCM(bool val) {
    _useCM = val;
  }
  inline uint16_t getMinRange() const {
    return _min_range;
  }
  inline uint16_t getMaxRange() const {
    return _max_range;
  }
  inline uint16_t getTriggerPulse() const {
    return _TriggerPulse;
  }
  inline bool getUseCM() const {
    return _useCM;
  }

  // I2C Functions
  void onRequest();

  void onReceive(int len);

  void wirePrint(int val) {
    _wire->print(val);
  }
  void wirePrintln(int val) {
    _wire->println(val);
  }
  int wireRead() {
    return _wire->read();
  }
  void wireWrite(int val) {
    _wire->write(val);
  }


private:
  //define sound speed in cm/uS
  static constexpr double SOUND_SPEED = 0.034;
  static constexpr double CM_TO_INCH = 0.3937014;

  // Define I2C Address
  static constexpr uint8_t I2C_DEV_ADDR = 0x55;

  //variables can only be accessed by the functions under public: specifier
  uint8_t _AlertState;
  uint8_t _TriggerPin;
  uint8_t _EchoPin;
  uint8_t _TriggerPulse = 10;  // 10uS TTL pulse
  uint8_t _I2CAddr;
  uint8_t _SLCPin;
  uint8_t _SDAPin;

  uint16_t _min_range = 45;
  uint16_t _max_range = 400;

  long     _duration_us;
  double   _distanceCm, _distanceIn;
  double   _currPos, _prevPos;

  bool     _useCM = true;

  double   convert();

  TwoWire  *_wire;
};

//constructor to initilize Class
Sensor_V2::Sensor_V2(TwoWire *_Wire, uint8_t trigger = 18, uint8_t echo = 19, uint8_t i2caddr = 0x55, uint8_t slc = 22, uint8_t sda = 21) {
  _TriggerPin = trigger;
  _EchoPin = echo;
  _I2CAddr = i2caddr;
  _SLCPin = slc;
  _SDAPin = sda;
  _wire = _Wire;
}

//member function definition; :: (double colon) is called scope resolution operator
void Sensor_V2::Begin() {
  pinMode(_TriggerPin, OUTPUT);
  pinMode(_EchoPin, INPUT);

  // Setup I2C
  //_wire->onReceive(onReceive);
  //_wire->onRequest(onRequest);
  _wire->setPins(_SDAPin, _SLCPin);
  _wire->begin((uint8_t)_I2CAddr);

#if CONFIG_IDF_TARGET_ESP32
  //char message[64];
  //snprintf(message, 64, "%lu Packets.", i++);
  //Wire.slaveWrite((uint8_t *)message, strlen(message));
#endif
}

void Sensor_V2::Ping() {
  // Sets the TriggerPin on HIGH state for 10 micro seconds
  digitalWrite(_TriggerPin, HIGH);
  delayMicroseconds(_TriggerPulse);
  digitalWrite(_TriggerPin, LOW);
}

double Sensor_V2::Pong() {
  // Reads the echoPin, returns the sound wave travel time in microseconds
  _duration_us = pulseIn(_EchoPin, HIGH);

  // Calculate the distance
  _distanceCm = ((_duration_us * SOUND_SPEED) / 2);

  return convert();
}

uint8_t Sensor_V2::Alert() {
  uint16_t _rtn = 0;
  uint16_t _min_max_diff = (_max_range - _min_range);
  uint16_t _alertRange1 = (_min_max_diff * .10);
  uint16_t _alertRange2 = (_min_max_diff * .15);
  uint16_t _alertRange3 = (_min_max_diff * .20);
  uint16_t _alertRange4 = (_min_max_diff * .25);
  //uint16_t _alertRange5 = (_min_max_diff * .30);

  if (_distanceCm <= _min_range) {
    _rtn = 9;
  } else if ((_distanceCm > _min_range) && (_distanceCm <= _alertRange1)) {
    _rtn = 4;
  } else if ((_distanceCm > _alertRange1) && (_distanceCm <= _alertRange2)) {
    _rtn = 3;
  } else if ((_distanceCm > _alertRange2) && (_distanceCm <= _alertRange3)) {
    _rtn = 2;
  } else if ((_distanceCm > _alertRange3) && (_distanceCm <= _alertRange4)) {
    _rtn = 1;
  } else {
    _rtn = 0;
  }
  return _rtn;
}

double Sensor_V2::convert() {
  if (_useCM) {
    return _distanceCm;
  } else {
    // Convert to inches
    _distanceIn = _distanceCm * CM_TO_INCH;
    return _distanceIn;
  }
}

void Sensor_V2::onRequest() {
  _wire->print(_count++);
  _wire->print(" Packets.");
  Serial.println("onRequest");
}

void Sensor_V2::onReceive(int len) {
  Serial.printf("onReceive[%d]: ", len);
  while (_wire->available()) {
    Serial.write(_wire->read());
  }
  Serial.println();
}

/*********************************************************************
* Example usage                                                      *
*********************************************************************/

double _distance;
  //uint32_t i = 0;

  // Initialize sensor
  Sensor_V2 sensor(&Wire, 18, 19);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);  // Starts the serial communication

  // Setup sensor
  sensor.Begin();
}

void loop() {
  // put your main code here, to run repeatedly:

  // Send pulse
  sensor.Ping();

  // Read echo
  _distance = sensor.Pong();

  // Prints the distance in the Serial Monitor
  if (sensor.getUseCM()) {
    Serial.print("Distance (cm): ");
    Serial.println(_distance);
  } else {
    Serial.print("Distance (inch): ");
    Serial.println(_distance);
  }
  delay(100);
}

I agree using a UART is simpler than I2C but requires adding a driver to the pixhawk firmware. The pixhawk firmware is open source so with lots of effort it should be possible to add a custom driver. That is way out of scope for this forum.

The ESP32 I2C master and slave examples work. I used two ESP32-S2 dev boards just because they were handy on my desk. I did not even use external pull up resistors because the serial console output indicated the code is working. But it probably best to use pull up resistors. There are 3 wires connected between the boards: GND, SDA, and SCL.

It should be possible to examine the pixhawk source code to find out what I2C address pixhawk expects the rangefinder to use. Then configure the ESP32 use the same address. Then find out the expected data format by studying more source code.

@customcontroller Thank you for the extra information, I still did not know if the code for the PixHawk was available and could be changed. But I assume that it does not run Arduino code.

I think that the ESP32 was originally not designed to work as a I2C Slave. It kind of works now. The newer variants, such as the ESP32-S2 can work as a I2C Slave.

@1midniterider I see that you are a skilled code writer. However, a I2C Slave with Arduino requires short code for the onRequest and onReceive handlers, depending on the used board. There is still a long way to go, and it is uncertain if it will work in the end.

Not every I2C Master can deal with every I2C Slave when the I2C Slave is a processor. The I2C bus is a "standard", but there are many differences. For example the I2C Master must be able to deal with clock pulse stretching. Sometimes it is just a timing problem that makes them incompatible.
When the I2C Master runs Arduino software and the I2C Slave runs Arduino software, then there is a bigger chance that it will work.

This is the I2C bus we are talking about and you want to use the I2C bus outside its comfort zone with a ESP32 that is not really a I2C Slave.
You are not the first one who thinks that it should be possible. I even wrote some notes about person-in-the-middle here: Arduino in Target mode · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub

If you can use Serial/UART, that would be much easier and it will work.

Thank you @customcontroller and @Koepel for the information. I do have later models of the esp32, but I may have finally sorted out getting the PX4-Autopilot driver to at least compile.

The issue with using the UART is that I've used the available ones for other add-ons...

I've also have an alternate method, switch to ArduPilot, but it is more of a last resort.

Thank you again...

1 Like

Here is an example I2C driver (master) for an srf02 distance sensor. The I2C commands to get distance quite simple.

SRF02 - Mbed

Here is another half solution (slave)...

HC-SR04 Python

How would the onRequest function be called from a Class configuration? Ideas?