// ============================================================================= // AR BNO085 Compact Node v1 — main.cpp // ============================================================================= // // Reads heading (ARVR Stabilized Rotation Vector) and yaw rate (Gyroscope) // from the BNO085 via I2C and publishes them on the NMEA 2000 backbone: // PGN 127250 — Vessel Heading (100 Hz) // PGN 127251 — Rate of Turn (100 Hz) // // The main autopilot board (ar_autopilot_v1) receives these PGNs from the // backbone via its nmea2000_consumer and feeds them into the PID outer loop. // ============================================================================= #include #include // --- NMEA 2000 --- #define ESP32_CAN_TX_PIN GPIO_NUM_23 #define ESP32_CAN_RX_PIN GPIO_NUM_4 #include #include // --- BNO085 --- #include // ============================================================================= // Pin definitions // ============================================================================= static constexpr int PIN_I2C_SDA = 21; static constexpr int PIN_I2C_SCL = 22; static constexpr int PIN_BNO_INT = 34; // data-ready, active low static constexpr int PIN_BNO_NRST = 13; // reset, active low // ============================================================================= // BNO085 instance // ============================================================================= static BNO08x imu; // Latest measurements (updated from BNO085 reports) static volatile float g_heading_rad = 0.0f; // true heading, radians static volatile float g_rot_rad_s = 0.0f; // yaw rate, rad/s (+ve = stbd) static volatile bool g_heading_valid = false; static volatile bool g_rot_valid = false; // ============================================================================= // NMEA 2000 setup // ============================================================================= static void nmea2000_init() { NMEA2000.SetProductInformation( "AR-BNO-1", 200, "AR-BNO085-NODE v1", "1.0.0", "AR-BNO085-NODE v1.0" ); NMEA2000.SetDeviceInformation( 2, // Unique number (different from autopilot node = 1) 140, // Device function: Attitude sensor 60, // Device class: Navigation 2046 // Manufacturer code (test) ); NMEA2000.SetMode(tNMEA2000::N2km_NodeOnly, 26); NMEA2000.EnableForward(false); NMEA2000.Open(); } // ============================================================================= // BNO085 initialisation // ============================================================================= static bool bno085_init() { // Hard reset — hold NRST low for 10 ms, then release. pinMode(PIN_BNO_NRST, OUTPUT); digitalWrite(PIN_BNO_NRST, LOW); delay(15); digitalWrite(PIN_BNO_NRST, HIGH); delay(100); // wait for BNO085 startup (~50 ms typical) Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); Wire.setClock(400000); // 400 kHz Fast Mode if (!imu.begin(0x4A, Wire, PIN_BNO_INT)) { Serial.println("[BNO085] begin() failed — check wiring / I2C address"); return false; } // ARVR Stabilized Rotation Vector → heading (tilt-compensated), 100 Hz if (!imu.enableARVRStabilizedRotationVector(10000)) { // 10 ms = 100 Hz Serial.println("[BNO085] enableARVRStabilizedRotationVector() failed"); return false; } // Calibrated Gyroscope → yaw rate, 100 Hz if (!imu.enableGyro(10000)) { Serial.println("[BNO085] enableGyro() failed"); return false; } Serial.println("[BNO085] init OK — heading + yaw rate @ 100 Hz"); return true; } // ============================================================================= // Read BNO085 reports (call frequently) // ============================================================================= static void bno085_read() { if (!imu.dataAvailable()) return; // Rotation Vector → heading (yaw around Z) if (imu.getSensorEventID() == SENSOR_REPORTID_ARVR_STABILIZED_ROTATION_VECTOR) { // SparkFun library: getYaw() returns yaw in radians, -pi..+pi const float yaw = imu.getYaw(); // Convert to 0..2pi (nautical convention, 0 = North, + = clockwise) float hdg = -yaw; // BNO085: +yaw = CCW (math convention); nautical = CW if (hdg < 0.0f) hdg += 2.0f * (float)M_PI; if (hdg >= 2.0f * (float)M_PI) hdg -= 2.0f * (float)M_PI; g_heading_rad = hdg; g_heading_valid = true; } // Gyroscope Z → yaw rate (+ = clockwise = starboard turn) if (imu.getSensorEventID() == SENSOR_REPORTID_GYROSCOPE_CALIBRATED) { // getGyroZ(): rad/s, +Z = looking down = CW from above = starboard g_rot_rad_s = imu.getGyroZ(); g_rot_valid = true; } } // ============================================================================= // Publish NMEA 2000 PGNs // ============================================================================= static void publish_pgns() { if (g_heading_valid) { tN2kMsg msg; SetN2kHeading(msg, 0, // SID (double)g_heading_rad, N2kDoubleNA, // Deviation N2kDoubleNA, // Variation N2khr_true); // Reference: true north (BNO085 is absolute) NMEA2000.SendMsg(msg); } if (g_rot_valid) { tN2kMsg msg; // PGN 127251: rate in rad/s SetN2kRateOfTurn(msg, 0, (double)g_rot_rad_s); NMEA2000.SendMsg(msg); } } // ============================================================================= // Arduino entry points // ============================================================================= void setup() { Serial.begin(115200); Serial.println("[AR-BNO085-NODE] booting..."); nmea2000_init(); if (!bno085_init()) { Serial.println("[AR-BNO085-NODE] FATAL: IMU init failed. Halting."); for (;;) delay(1000); } Serial.println("[AR-BNO085-NODE] ready."); } void loop() { // Read sensor at ~100 Hz (non-blocking, interrupt-driven via INT pin) bno085_read(); // Publish to NMEA 2000 backbone at 10 Hz static uint32_t last_pub = 0; const uint32_t now = millis(); if (now - last_pub >= 100) { last_pub = now; publish_pgns(); } // Keep the NMEA2000 stack alive NMEA2000.ParseMessages(); }