Skip to content

Quickstart

Read oxygen from your MCU: wire four pins, write a small port class, call three driver functions.

1. Wire it

MCU FDO2-G2 connector Notes
UART TX pin 3, RXD Sensor input, 3.0 V levels, 3.3 V maximum
UART RX pin 2, TXD Sensor output, 3.0 V levels
GND pin 1, GND Common ground
3.3 V to 5.0 V pin 4, VCC 40 mA peak during a measurement

Set the UART to 19200 baud, 8 data bits, no parity, 1 stop bit, no flow control. A 5 V MCU needs a level shifter on its TX line. The Hardware page has the connector, the timing and the electrical limits.

2. Add the driver

Add the repository as a submodule and link the hf::fdo2 CMake target (or add inc/ to your include path and build with C++17). Installation covers CMake and ESP-IDF.

git submodule add https://github.com/hardfoc/hf-fdo2-driver.git external/hf-fdo2-driver

3. Write the port

The port is the only platform code. It writes bytes, returns one complete reply line and flushes the receive buffer. Replace the five platform_* calls with your SDK's.

my_uart.hpp
class MyUart : public fdo2::UartInterface<MyUart> {
public:
  // Send one command line (the driver adds the CR).
  void write(const uint8_t* data, std::size_t length) noexcept { platform_uart_write(data, length); }

  // Return one complete reply line, CR included, or 0 if none arrived within timeout_ms.
  // timeout_ms == 0 means "only if a whole line is already here".
  std::size_t read(uint8_t* out, std::size_t max, uint32_t timeout_ms) noexcept {
    const uint32_t start = platform_millis();
    for (;;) {
      uint8_t byte = 0;
      while (platform_uart_read_byte(byte)) {
        if (len_ < sizeof(line_)) {
          line_[len_++] = byte;
        }
        if (byte == '\r') {                          // the module ends every reply with one CR
          const std::size_t n = (len_ < max) ? len_ : max;
          std::memcpy(out, line_, n);
          len_ = 0;
          return n;
        }
      }
      if (platform_millis() - start >= timeout_ms) {
        return 0;                                    // keep a partial line for the next call
      }
      platform_delay_ms(1);
    }
  }

  void flush_rx() noexcept {
    platform_uart_flush();
    len_ = 0;
  }

  // Optional: lets the driver and your code wait through UartInterface::delay_ms().
  void delay_ms_impl(uint32_t ms) noexcept { platform_delay_ms(ms); }

private:
  uint8_t line_[192]{};                              // longest reply: #MRAW, under 120 characters
  std::size_t len_ = 0;
};

read() must return a whole line ending in CR, or nothing. Porting explains why and lists the rules for each method.

4. Read oxygen

main.cpp
#include "fdo2.hpp"
#include <cstdio>

int read_oxygen(MyUart& uart) {
  fdo2::Driver<MyUart> sensor(uart);
  uart.delay_ms(fdo2::kFdo2G2PowerUpSettleMs);          // 1. the module needs about 1 s after power-up

  const auto version = sensor.ReadVersion();            // 2. identify: D = 8 for an FDO2-G2
  if (!version || version.value.device_id != 8) {
    return -1;                                          // wiring, baud rate or supply
  }

  const auto m = sensor.MeasureMoxy();                  // 3. one measurement, up to 150 ms
  if (!m || !fdo2::MoxyStatusOkForOxygen(m.value.status_u32)) {
    return -2;                                          // timeout, #ERRO or a fault bit in S
  }
  std::printf("pO2 %.3f hPa at %.3f C\n", m.value.p_o2_hpa, m.value.temp_c);  // 4. use it
  return 0;
}

What each step does:

  1. The module answers commands about 1 s after power-up. kFdo2G2PowerUpSettleMs is 1100 ms.
  2. ReadVersion() sends #VERS and checks that a module answers. An FDO2-G2 reports device id 8.
  3. MeasureMoxy() sends #MOXY, waits for the reply and scales it. MoxyStatusOkForOxygen() accepts the status values 0 and 1 only: any other value means the oxygen or temperature reading can be wrong.
  4. p_o2_hpa is the oxygen partial pressure in hPa and temp_c the sensor temperature in °C.

Allow 3 minutes of warm-up for full accuracy. This example is built by the host CI, so it compiles against the current headers.

If it does not work

Symptom First thing to check
ReadVersion() returns Timeout TX and RX crossed (MCU TX to sensor RXD), 19200 baud, supply, 1 s boot delay
ProtocolError on every call Baud rate, or broadcast mode left on by another tool (Troubleshooting)
DeviceError Read LastDeviceErrorCode(); the codes are in UART protocol
Status not 0 or 1 Ambient light, condensation on the membrane, a sensor fault (Troubleshooting)

Next

  • API guide: raw data, volume percent, non-blocking measurement, errors.
  • Examples: two complete ESP32-S3 applications.
  • UART protocol: every command and field the driver uses.