Skip to content

UART-RVC mode

Heading, pitch, roll and acceleration at 100 Hz over one UART line, with no protocol stack and no commands.

UART-RVC is a simplified output mode CEVA designed for ground robots such as robot vacuum cleaners. The hub sends a fixed 19-byte frame every 10 ms and listens to nothing. You cannot choose reports, change the rate or update firmware in this mode; for any of that, use SH-2 over I2C, SPI or UART.

Select it

Strap PS1 low and PS0 high, then reset (datasheet figure 1-5). The UART runs at 115200 bit/s, 8 data bits, no parity, 1 stop bit, and needs the external 32.768 kHz crystal or clock: the internal oscillator is not accurate enough. Only the chip's TX pin (pin 20) is used; connect it to your MCU's RX.

Use it

The port reports BNO085Interface::UARTRVC and returns received bytes from Read() without blocking:

my_rvc.hpp
class MyRvcUart : public bno08x::CommInterface<MyRvcUart> {
public:
  static constexpr int kRstnGpio = 16; // NRST, active low (optional)

  BNO085Interface GetInterfaceType() noexcept {
    return BNO085Interface::UARTRVC;
  }

  bool Open() noexcept {
    return platform_uart_open(115200);
  }
  void Close() noexcept {
    platform_uart_close();
  }

  // RVC is transmit-only on the sensor side: the driver never writes.
  int Write(const uint8_t* /*data*/, uint32_t /*length*/) noexcept {
    return 0;
  }

  // Non-blocking: whatever the UART has received, up to length bytes. ServiceRvc() calls it until
  // it returns 0.
  int Read(uint8_t* data, uint32_t length) noexcept {
    return platform_uart_read(data, length);
  }

  // No interrupt line in RVC mode.
  bool DataAvailable() noexcept {
    return true;
  }

  void Delay(uint32_t ms) noexcept {
    platform_delay_ms(ms);
  }
  uint32_t GetTimeUs() noexcept {
    return platform_micros();
  }

  bool GpioSet(bno08x::CtrlPin pin, bno08x::GpioSignal signal) noexcept {
    if (pin == bno08x::CtrlPin::RSTN) {
      return platform_gpio_write(kRstnGpio, signal != bno08x::GpioSignal::ACTIVE);
    }
    return true; // only NRST is wired
  }
};
int stream_heading(MyRvcUart& uart) {
  BNO085<MyRvcUart> imu(uart);
  imu.SetRvcCallback([](const RvcSensorValue& v) {
    std::printf("#%u yaw %.2f pitch %.2f roll %.2f deg  a %.3f %.3f %.3f g\n",
                static_cast<unsigned>(v.index), v.yaw_deg, v.pitch_deg, v.roll_deg, v.acc_x_g,
                v.acc_y_g, v.acc_z_g);
  });
  if (!imu.BeginRvc()) { // opens the UART; fails unless the port reports UARTRVC
    return imu.GetLastError();
  }
  for (;;) {
    imu.ServiceRvc(); // parse what has arrived; one frame every 10 ms
    uart.Delay(5);
  }
}

BeginRvc() opens the port and starts the parser; it fails with SH2_ERR_BAD_PARAM on any other interface type. ServiceRvc() reads every byte that has arrived and calls your callback once per valid frame. CloseRvc() (or Close()) closes the port. SH-2 calls on an RVC object fail the same way, so one object cannot mix the modes.

Frame format

Offset Bytes Field Unit RvcSensorValue
0 2 Header 0xAA 0xAA
2 1 Index, counts 0 to 255 index
3 2 Yaw, little-endian int16, ±180° 0.01° yaw_deg
5 2 Pitch, ±90° 0.01° pitch_deg
7 2 Roll, ±180° 0.01° roll_deg
9 2 X acceleration mg acc_x_g
11 2 Y acceleration mg acc_y_g
13 2 Z acceleration mg acc_z_g
15 1 Motion intent (BNO086; reserved otherwise) mi
16 1 Motion request (BNO086; reserved otherwise) mr
17 1 Reserved, zero
18 1 Checksum: 8-bit sum of bytes 2 to 17

Yaw is measured from the orientation at reset. Apply the rotations in the order yaw, pitch, roll. The driver converts to degrees and g (RvcSensorValue) and stamps each frame with GetTimeUs().

The datasheet's example frame, AA AA DE 01 00 92 FF 25 08 8D FE EC FF D1 03 00 00 00 E7, decodes to index 222, yaw 0.01°, pitch −1.10°, roll 20.85° and acceleration (−0.371, −0.020, 0.977) g. The host tests feed exactly this frame to the parser (Testing and CI).

How the parser finds frames

The parser keeps a 19-byte sliding window. Each byte shifts the window; when it starts with 0xAA 0xAA and the checksum matches, the frame is decoded, the callback runs and the window empties. A frame with a wrong checksum is skipped and the window keeps sliding, so the parser recovers by itself after noise or a partial frame.

Next

Porting has the port's contract; the rvc_mode ESP32 example runs it on hardware.