#define APP_DATE "2026-01-22"

/*====================================================================
 I2C Chip Scanner - scan I2C bus and try to identify chips

Example output:

===========================
I2C Chip Scanner 2026-01-22
===========================
Step 1: Scanning Adressses ... 0x29(41)  - Found 1 devices

Step 2: Identifying Devices ...
try: VL53L3CX time-of-flight distance --> read adr:0x29 reg:10F --> received:EAAA --> MATCH

Step 3: Summary
address 0x29 (dec  41) VL53L3CX time-of-flight distance

====================================================================*/

#include <Arduino.h>
#include "Wire.h"

TwoWire *i2c = &Wire;

#define ESP32_SDA_PIN 18 //23
#define ESP32_SCL_PIN 19 //22

#define RP2040_SDA_PIN 32 //RP2040/RP2350A Wire: 0, 4(default), 8, 12, 16, 20   Wire1: 2, 6, 10, 14, 18, 26(default)
#define RP2040_SCL_PIN 33 //RP2040/RP2350A Wire: 1, 5(default), 9, 13, 17, 21   Wire1: 3, 7, 11, 15, 19, 27(default)

#define STM32_SDA_PIN PB9
#define STM32_SCL_PIN PB8

#define I2C_FRQ 100000




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

  //start WIRE
  #if defined ARDUINO_ARCH_ESP32
    i2c->begin(ESP32_SDA_PIN, ESP32_SCL_PIN, I2C_FRQ);
  #elif defined ARDUINO_ARCH_RP2040
    i2c->setSDA(RP2040_SDA_PIN);
    i2c->setSCL(RP2040_SCL_PIN);
    i2c->setClock(I2C_FRQ);
    i2c->setTimeout(25, true); //timeout, reset_with_timeout
    i2c->begin();
  #elif defined ARDUINO_ARCH_STM32
    i2c->setSDA(STM32_SDA_PIN);
    i2c->setSCL(STM32_SCL_PIN);
    i2c->setClock(I2C_FRQ);
    i2c->begin();    
  #else 
    #warning "Using default I2C pins"
    i2c->begin();
  #endif
}

void loop() {
  uint8_t num_adr_found = 0;
  uint8_t adr_found[128];
  String adr_msg[128];

  Serial.printf("\n");
  Serial.printf("===========================\n");
  Serial.printf("I2C Chip Scanner " APP_DATE "\n");
  Serial.printf("===========================\n");

  //scan addresses to find devices
  Serial.printf("Step 1: Scanning Adressses ... ");
  for (uint8_t adr = 1; adr < 128; adr++) {
    if (i2c_TestAddress(adr)) {
      Serial.printf("0x%02X(%d) ",adr,adr);
      adr_found[num_adr_found] = adr;
      num_adr_found++;
    }
  }
  Serial.printf(" - Found %d devices\n", num_adr_found);

  //try to identify the devices
  Serial.printf("\nStep 2: Identifying Devices ...\n");
  for (uint8_t i = 0; i < num_adr_found; i++) {
    i2c_identify_chip(adr_found[i], adr_msg[i]);
  }

  Serial.printf("\nStep 3: Summary\n");
  for (uint8_t i = 0; i < num_adr_found; i++) {
    Serial.printf("address 0x%02X (dec %3d) %s\n", adr_found[i], adr_found[i], adr_msg[i].c_str());
  }

  delay(1000);
}

struct test_struct{
    uint8_t adr1;
    uint8_t adr2;
    uint32_t reg;
    uint32_t expected;
    String descr;
};

//table of addresses, 8/16bit register and 8/16/24bit expected reply (use 0x10000 for 16 bit register 0)
const test_struct tests[] = {
// adr1  adr2  reg      expected  descr
  {0x10, 0x13, 0x40,    0x32,     "BMM150 magnetometer"},
  {0x1E, 0x1E, 0x0A,    0x483433, "HMC5883L magnetometer"}, // ID-Reg-A 'H', ID-Reg-B '4', ID-Reg-C '3'
  {0x30, 0x0D, 0x39,    0x10,     "MMC5603 magnetometer"},
  {0x0D, 0x0D, 0x0D,    0xFF,     "QMC5883L magnetometer"},
  {0x2C, 0x2C, 0x00,    0x80,     "QMC5883P magnetometer"},
  {0x7C, 0x7C, 0x00,    0x90,     "QMC6309 magnetometer"},

  {0x42, 0x42, 0x00,    0x9B,     "GC0308 camera"},
  {0x30, 0x30, 0x00,    0x01B0,   "HM01B0 camera"},
  {0x24, 0x25, 0x10000, 0x0360,   "HM0360 camera"},
  {0x34, 0x35, 0x10000, 0x0360,   "HM0360 camera"},
  {0x30, 0x30, 0x0A,    0x26,     "OV2460 camera"},
  {0x3C, 0x3C, 0x300A,  0x3660,   "OV3660 camera"},
  {0x3C, 0x3C, 0x300A,  0x5640,   "OV5640 camera"},
  {0x42, 0x42, 0x0A,    0x76,     "OV7670 camera"},
  {0x42, 0x42, 0x0A,    0x77,     "OV7725 camera"},
  {0x30, 0x30, 0x0A,    0x9711,   "OV9712 / OV9211 camera"},

  {0x40, 0x4F, 0x00,    0x399F,   "INA219 current sensor"},
  {0x40, 0x4F, 0x00,    0x4127,   "INA226 current sensor"},
  {0x40, 0x4F, 0x3F,    0x2281,   "INA228 current sensor"},

  {0x48, 0x4F, 0x01,    0x8583,   "ADS1113 / ADS1114 / ADS1115 ADC"},

  {0x51, 0x51, 0x03,    0x3B,     "DTS6012M lidar"},
  {0x29, 0x29, 0x010F,  0xEAAA,   "VL53L3CX time-of-flight distance"},

  {0x68, 0x69, 0x75,    0x12,     "ICM20602 6DOF motion"},
  {0x68, 0x69, 0x75,    0x98,     "ICM20689 6DOF motion"},
  {0x68, 0x69, 0x00,    0xEA,     "ICM20948 9DOF motion"},
  {0x68, 0x69, 0x75,    0x47,     "ICM42688P 6DOF motion"},
  {0x68, 0x69, 0x00,    0x68,     "MPU3050 motion"},
  {0x68, 0x69, 0x00,    0x69,     "MPU3050 motion"},
  {0x68, 0x69, 0x75,    0x68,     "MPU6000 / MPU6050 / MPU9150 6/9DOF motion"},
  {0x68, 0x69, 0x75,    0x70,     "MPU6500 6DOF motion"},
  {0x68, 0x69, 0x75,    0x19,     "MPU6886 6DOF motion"},
  {0x68, 0x69, 0x75,    0x74,     "MPU9515 motion"},
  {0x68, 0x69, 0x75,    0x71,     "MPU9250 9DOF motion"},
  {0x68, 0x69, 0x75,    0x73,     "MPU9255 9DOF motion"},

  {0x68, 0x69, 0x75,    0x69,     "FAKE MPU6050, got WHO_AM_I=0x69, real chip returns 0x68"},
  {0x68, 0x69, 0x75,    0x72,     "FAKE MPU6050, got WHO_AM_I=0x72, real chip returns 0x68"},
  {0x68, 0x69, 0x75,    0x75,     "FAKE MPU9250, got WHO_AM_I=0x75, real chip returns 0x71"},
  {0x68, 0x69, 0x75,    0x78,     "FAKE MPU9250, got WHO_AM_I=0x78, real chip returns 0x71"},

  {0x76, 0x77, 0xD0,    0x61,     "BME680 barometer, temperature, humidity, gas"},
  {0x76, 0x77, 0xD0,    0x55,     "BMP180 barometer"},
  {0x76, 0x77, 0xD0,    0x56,     "BMP280 barometer"},
  {0x76, 0x77, 0xD0,    0x57,     "BMP280 barometer"},
  {0x76, 0x77, 0xD0,    0x58,     "BMP280 barometer"},
  {0x76, 0x77, 0xD0,    0x60,     "BME280 barometer, temperature, humidity"},
  {0x76, 0x77, 0x00,    0x50,     "BMP388 barometer"},
  {0x76, 0x77, 0x00,    0x60,     "BMP390 barometer"},
  {0x46, 0x47, 0x01,    0x50,     "BMP580 / BMP581 barometer"},
  {0x46, 0x47, 0x01,    0x51,     "BMP585 barometer"},
  {0x76, 0x77, 0x0D,    0x10,     "DPS310 / HP303B / SPL06 barometer"},
  {0x76, 0x77, 0x8F,    0x80,     "HP203B barometer"},

  {0,0,0,0,""} //end
};

struct adrtest_struct{
    uint8_t adr1;
    uint8_t adr2;
    char descr[60];
};

//address only tests
const adrtest_struct adrtests[] = {
// adr1  adr2  descr
  {0x29, 0x29, "VL53L7CX"}, //no public register map available
  {0x77, 0x77, "MS5611"}, //no ID register
  {0,0,""} //end
};  

void i2c_identify_chip(uint8_t adr, String &msg) {
  //try adr,reg,result match
  int i = 0;
  while(tests[i].adr1) {
    uint8_t adr1 = tests[i].adr1;
    uint8_t adr2 = tests[i].adr2;
    uint32_t reg = tests[i].reg;
    uint32_t expected = tests[i].expected;
    uint32_t received = 0;
    uint8_t rlen = (expected >= 0x10000 ? 3 : (expected >= 0x100 ? 2 : 1));
    if((adr1 <= adr) && (adr <= adr2)) {
      uint8_t data[4];
      Serial.printf("try: %s --> read adr:0x%02X reg:%02X --> received:", tests[i].descr.c_str(), (int)adr, (int)reg);
      int reveivedlen = i2c_ReadRegs(adr, reg, data, rlen, true);
      for(int i = 0; i < reveivedlen; i++) {
        received = (received << 8) + data[i];
        Serial.printf("%02X", (int)data[i]);
      }
      if(received == expected) {
        msg = tests[i].descr;
        Serial.printf(" --> MATCH\n");
        return;
      } 
      Serial.printf("\n");
    }
    i++;
  }
  
  //try address only match
  i = 0;  
  while(adrtests[i].adr1) {
    int adr1 = adrtests[i].adr1;
    int adr2 = adrtests[i].adr2;    
    if((adr1 <= adr) && (adr <= adr2)) {
      msg = adrtests[i].descr + String(" - ADDRESS MATCH ONLY");
      return;
    }
    i++;
  }

  msg = "UNKNOWN";
}

bool i2c_TestAddress(uint8_t adr) {
  i2c->beginTransmission(adr);       // Begin I2C transmission Address (i)
  return (i2c->endTransmission() == 0); // Receive 0 = success (ACK response) 
}

void i2c_WriteByte( uint8_t adr, uint8_t reg ) {
  i2c->beginTransmission(adr); 
  i2c->write(reg);
  i2c->endTransmission();
}

//returns -1 on fail
int i2c_ReadByte( uint8_t adr ) {
  int rv = -1;
  uint8_t data;
  uint8_t bytesReceived = i2c->requestFrom(adr, 1);
  if(bytesReceived == 1) {
    i2c->readBytes(&data, 1);
    rv = data;
  }
  //i2c->endTransmission(); -- don't do this
  return rv;
}

void i2c_WriteReg( uint8_t adr, uint8_t reg, uint8_t data ) {
  i2c->beginTransmission(adr); 
  i2c->write(reg);
  i2c->write(data);
  i2c->endTransmission();
}

int i2c_ReadRegs( uint8_t adr, uint32_t reg, uint8_t *data, uint8_t n, bool stop ) {
  i2c->beginTransmission(adr);
  if(reg > 0xff) i2c->write((reg >> 8) & 0xff); //write MSB (if any)
  i2c->write(reg & 0xff); //write LSB
  i2c->endTransmission(stop); //false = repeated start, true = stop + start
  int bytesReceived = i2c->requestFrom(adr, n);
  if(bytesReceived > 0) {
    i2c->readBytes(data, bytesReceived);
  }
  //i2c->endTransmission(); -- don't do this
  return bytesReceived;
}

int i2c_ReadRegs( uint8_t adr, uint8_t reg, uint8_t *data, uint8_t n) {
  return i2c_ReadRegs( adr, reg, data, n, false);
}

//returns -1 on fail
int i2c_ReadReg( uint8_t adr, uint8_t reg, bool stop ) {
    uint8_t data = 0;
    int bytesReceived = i2c_ReadRegs(adr, reg, &data, 1);
    if(bytesReceived == 1) {
      return data;
    }
    return -1;
}

int i2c_ReadReg( uint8_t adr, uint8_t reg) {
  return i2c_ReadReg( adr, reg, false );
}

void i2c_scan() {
  Serial.printf("I2C: Scanning ...\n");
  byte count = 0;
  i2c->begin();
  for (byte i = 8; i < 120; i++) {
    i2c->beginTransmission(i);          // Begin I2C transmission Address (i)
    if (i2c->endTransmission() == 0) { // Receive 0 = success (ACK response) 
      Serial.printf("I2C: Found address: 0x%02X (%d)\n",i,i);
      count++;
    }
  }
  Serial.printf("I2C: Found %d device(s)\n", count);      
}
