#include "twi.h"

const byte MPU_ADDRESS = 0x68;  // I2C address of the MPU-6050. Either 0x68 or 0x69.
const byte ADO_PIN = 12;        // LOW = 0x68 (default); HIGH = 0x69.

const byte SCUPPER_SCL_PIN = 2;
const byte SCUPPER_SDA_PIN = 3;

const byte LOGIC_TRIGGER_PIN = 7;

class Mpu6050
{
  public:

    class AccelTempGyroData
    {
      public:
        const uint32_t reg = 0x3B;
        const uint8_t reg_size = 1;
        volatile byte buffer[14];
        int16_t AccelX() const { return buffer[ 0] << 8 | buffer[ 1]; }
        int16_t AccelY() const { return buffer[ 2] << 8 | buffer[ 3]; }
        int16_t AccelZ() const { return buffer[ 4] << 8 | buffer[ 5]; }
        int16_t Temp()   const { return buffer[ 6] << 8 | buffer[ 7]; }
        int16_t GyroX()  const { return buffer[ 8] << 8 | buffer[ 9]; }
        int16_t GyroY()  const { return buffer[10] << 8 | buffer[11]; }
        int16_t GyroZ()  const { return buffer[12] << 8 | buffer[13]; }
        float   TempC()  const { return Temp() / 340.0 + 36.53;  }     // Equation for temperature in °C from datasheet.
        float   TempF()  const { return TempC() * 9.0 / 5.0 + 32.0; }  // Equation for temperature in °F.
    };

    byte mpuAddress;
    AccelTempGyroData data;

    void begin(byte mpuAddress, byte adoPin)
    {
      this->mpuAddress = mpuAddress;

      Serial.println(mpuAddress);
      Serial.println(this->mpuAddress);

      pinMode(adoPin, OUTPUT);
      digitalWrite(adoPin, mpuAddress & 1);

      //
      // Perform full reset as per MPU-6000/MPU-6050 Register Map and Descriptions, Section 4.28, pages 40 to 41.
      //

      // PWR_MGMT_1 register: DEVICE_RESET = 1
      Serial.println("Resetting MPU...");
      twi.Write(mpuAddress, 0x6B, 1, 0b10000000, TWI::Modes::Wait);
      delay(100);  // Wait for reset to complete.
      Serial.println("Done.");

      // SIGNAL_PATH_RESET register: GYRO_RESET = 1, ACCEL_RESET = 1, TEMP_RESET = 1.
      Serial.println("Resetting MPU gyro, accel and temp...");
      twi.Write(mpuAddress, 0x68, 1, 0b00000111, TWI::Modes::Wait);
      delay(100);  // Wait for reset to complete.
      Serial.println("Done.");

      // Disable SLEEP mode because the reset re-enables it. Section 3, PWR_MGMT_1 register, page 8.
      // PWR_MGMT_1 register: SLEEP = 0
      Serial.println("Resetting MPU sleep mode...");
      twi.Write(mpuAddress, 0x6B, 1, 0b00000000, TWI::Modes::Wait);
      Serial.println("Done.");
    }

    bool Read()
    {
      static bool receiving = false;
      if (!receiving && twi.Read(mpuAddress, data.reg, data.reg_size, data.buffer, sizeof(data.buffer)) == TWI::Results::Started)
      {
        receiving = true;
        Serial.println("\nBurst-read of 14 bytes (using non-blocking ISR).");
      }
      if (receiving && twi.IsReady())
      {
        receiving = false;
        return true;
      }
      return false;
    }

    bool ReadBlocking()
    {
      if (twi.Read(mpuAddress, data.reg, data.reg_size, data.buffer, sizeof(data.buffer), TWI::Modes::Wait) == TWI::Results::Success)
      {
        Serial.println("\nBurst-read of 14 bytes (using blocking while loop).");
        return true;
      }
      return false;
    }

    void PrintResults()
    {
      TWI::Results result = twi.GetResult();
      switch (result)
      {
        case TWI::Results::Success:
          Serial.println("Success");
          Serial.print("AccelX = "); Serial.print(data.AccelX());
          Serial.print(" | AccelY = "); Serial.print(data.AccelY());
          Serial.print(" | AccelZ = "); Serial.print(data.AccelZ());
          Serial.print(" | Temp = "); Serial.print(data.TempC()); Serial.print("°C "); Serial.print(data.TempF()); Serial.print("°F");
          Serial.print(" | GyroX = "); Serial.print(data.GyroX());
          Serial.print(" | GyroY = "); Serial.print(data.GyroY());
          Serial.print(" | GyroZ = "); Serial.println(data.GyroZ());
          Serial.println();
          break;
        case TWI::Results::Timeout:
          Serial.println("Timeout.");
          break;
        default:
          Serial.println("Comms error.");
          break;
      }
    }

};

Mpu6050 mpu;

void setup()
{
  Serial.begin(115200);

  Serial.println("\nsetup()\n");

  pinMode(LED_BUILTIN, OUTPUT);

  twi.Enable();

  if (bitRead(MCUSR, WDRF))
  {
    Serial.println("System was reset due to watchdog timeout.");
    Serial.println(WDTCSR);
    Serial.println();
  }

  pinMode(SCUPPER_SCL_PIN, INPUT_PULLUP);
  pinMode(SCUPPER_SDA_PIN, INPUT_PULLUP);

  pinMode(LOGIC_TRIGGER_PIN, OUTPUT);
  //digitalWrite(LOGIC_TRIGGER_PIN, HIGH);  // Capture data on the Wokwi Logic Analyser.
  digitalWrite(LOGIC_TRIGGER_PIN, LOW);  // Don't capture data on the Wokwi Logic Analyser.

  mpu.begin(MPU_ADDRESS, ADO_PIN);
}

void loop()
{
  //
  // TASK 1: Alternate between non-blocking and blocking burst-reads.
  //

  /**/

  // TASK 1A: Non-blocking burst-read of 14 bytes. (Uses the TWI ISR.)

  if (mpu.Read())
  {
    mpu.PrintResults();
  }

  /**/

  // TASK 1B: Blocking burst-read of 14 bytes. (Uses a while loop by specifying the Wait flag.)

  if (mpu.ReadBlocking())
  {
    mpu.PrintResults();
  }

  /**/

  //
  // TASK 2: Blink the inbuilt LED.
  //

  BlinkLED();
}

void BlinkLED()
{
  uint16_t timestamp = millis();
  const uint16_t LED_BLINK_INTERVAL = 200;
  static uint16_t led_blink_previous_timestamp = timestamp;
  if (timestamp - led_blink_previous_timestamp >= LED_BLINK_INTERVAL)
  {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    led_blink_previous_timestamp += LED_BLINK_INTERVAL;
  }
}
D0D1D2D3D4D5D6D7GNDLOGIC