#include "ScanI2CTwoWire.h" #include "configuration.h" #include "detect/ScanI2C.h" #if !MESHTASTIC_EXCLUDE_I2C #include "concurrency/LockGuard.h" #if defined(ARCH_PORTDUINO) #include "linux/LinuxHardwareI2C.h" #endif #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32) #include "meshUtils.h" // vformat #endif #if defined(HAS_QMA6100P) && (defined(ARCH_NRF52) || defined(NRF52_SERIES) || defined(NRF52)) #include "platform/nrf52/Nrf52Twim.h" #include namespace { bool probeQMA6100P(uint8_t address) { uint8_t chipID = 0; Nrf52Twim::restoreBus(); const bool readOk = Nrf52Twim::readRegister(address, SFE_QMA6100P_CHIP_ID, chipID); const bool found = readOk && chipID == QMA6100P_CHIP_ID; Nrf52Twim::restoreBus(); return found; } } // namespace #endif bool in_array(uint8_t *array, int size, uint8_t lookfor) { int i; for (i = 0; i < size; i++) if (lookfor == array[i]) return true; return false; } ScanI2C::FoundDevice ScanI2CTwoWire::find(ScanI2C::DeviceType type) const { concurrency::LockGuard guard((concurrency::Lock *)&lock); return exists(type) ? ScanI2C::FoundDevice(type, deviceAddresses.at(type)) : DEVICE_NONE; } bool ScanI2CTwoWire::exists(ScanI2C::DeviceType type) const { return deviceAddresses.find(type) != deviceAddresses.end(); } ScanI2C::FoundDevice ScanI2CTwoWire::firstOfOrNONE(size_t count, DeviceType types[]) const { concurrency::LockGuard guard((concurrency::Lock *)&lock); for (size_t k = 0; k < count; k++) { ScanI2C::DeviceType current = types[k]; if (exists(current)) { return ScanI2C::FoundDevice(current, deviceAddresses.at(current)); } } return DEVICE_NONE; } ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const { TwoWire *i2cBus = fetchI2CBus(addr); uint8_t r = 0; uint8_t r_prev = 0; uint8_t c = 0; ScanI2C::DeviceType o_probe = ScanI2C::DeviceType::SCREEN_UNKNOWN; do { r_prev = r; i2cBus->beginTransmission(addr.address); i2cBus->write((uint8_t)0x00); i2cBus->endTransmission(); i2cBus->requestFrom((int)addr.address, 1); if (i2cBus->available()) { r = i2cBus->read(); } if (r == 0x80) { LOG_INFO("QMC6310N found at address 0x%02X", addr.address); return ScanI2C::DeviceType::QMC6310N; } r &= 0x0f; if (r == 0x08 || r == 0x00) { logFoundDevice("SH1106", (uint8_t)addr.address); o_probe = SCREEN_SH1106; // SH1106 } else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07 || r == 0x05) { logFoundDevice("SSD1306", (uint8_t)addr.address); o_probe = SCREEN_SSD1306; // SSD1306 } c++; } while ((r != r_prev) && (c < 4)); LOG_DEBUG("0x%x subtype probed in %i tries ", r, c); return o_probe; } uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation ®isterLocation, ScanI2CTwoWire::ResponseWidth responseWidth, bool zeropad = false) const { uint16_t value = 0x00; TwoWire *i2cBus = fetchI2CBus(registerLocation.i2cAddress); i2cBus->beginTransmission(registerLocation.i2cAddress.address); i2cBus->write(registerLocation.registerAddress); if (zeropad) { // Lark Commands need the argument list length in 2 bytes. i2cBus->write((int)0); i2cBus->write((int)0); } i2cBus->endTransmission(); delay(20); i2cBus->requestFrom(registerLocation.i2cAddress.address, responseWidth); if (i2cBus->available() > 1) { // Read MSB, then LSB value = (uint16_t)i2cBus->read() << 8; value |= i2cBus->read(); } else if (i2cBus->available()) { value = i2cBus->read(); } // Drain excess bytes for (uint8_t i = 0; i < responseWidth - 1; i++) { if (i2cBus->available()) i2cBus->read(); } LOG_DEBUG("Register value from 0x%x: 0x%x", registerLocation.i2cAddress.address, value); return value; } bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint16_t command, uint8_t expectedLength) const { TwoWire *i2cBus = fetchI2CBus(addr); i2cBus->beginTransmission(addr.address); if (command > 0xFF) { i2cBus->write((uint8_t)(command >> 8)); } i2cBus->write((uint8_t)(command & 0xFF)); if (i2cBus->endTransmission() != 0) { return false; } delay(20); uint8_t received = i2cBus->requestFrom(addr.address, expectedLength); bool match = (received == expectedLength); while (i2cBus->available()) i2cBus->read(); return match; } #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR #include "../modules/Telemetry/Sensor/SEN5XSensor.h" bool probeSEN5X(TwoWire *i2cBus, uint8_t address, ScanI2C::I2CPort port) { SEN5XSensor sen5xsensor; return sen5xsensor.probe(i2cBus, address, port); } #endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR static uint8_t crcSHT2X(const uint8_t *data, uint8_t len) { uint8_t crc = 0; for (uint8_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t bit = 0; bit < 8; bit++) { crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : crc << 1; } } return crc; } bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address) { uint8_t serialA[8] = {0}; uint8_t serialB[6] = {0}; i2cBus->beginTransmission(address); i2cBus->write(0xFA); i2cBus->write(0x0F); if (i2cBus->endTransmission() != 0) return false; if (i2cBus->requestFrom(address, (uint8_t)sizeof(serialA)) != sizeof(serialA)) return false; for (uint8_t i = 0; i < sizeof(serialA); i++) { if (!i2cBus->available()) return false; serialA[i] = i2cBus->read(); } i2cBus->beginTransmission(address); i2cBus->write(0xFC); i2cBus->write(0xC9); if (i2cBus->endTransmission() != 0) return false; if (i2cBus->requestFrom(address, (uint8_t)sizeof(serialB)) != sizeof(serialB)) return false; for (uint8_t i = 0; i < sizeof(serialB); i++) { if (!i2cBus->available()) return false; serialB[i] = i2cBus->read(); } return crcSHT2X(&serialA[0], 1) == serialA[1] && crcSHT2X(&serialA[2], 1) == serialA[3] && crcSHT2X(&serialA[4], 1) == serialA[5] && crcSHT2X(&serialA[6], 1) == serialA[7] && crcSHT2X(&serialB[0], 2) == serialB[2] && crcSHT2X(&serialB[3], 2) == serialB[5]; } #endif // Everest Semiconductor ES7210 4-channel audio ADC, as found on the T-Deck. Its AD1/AD0 straps // select 0x40..0x43, so it lands squarely on the INA/SHT2X addresses. Chip ID registers and their // reset values, per the ES7210 datasheet rev 2.0. static constexpr uint8_t ES7210_CHIP_ID1_REG = 0x3D; static constexpr uint8_t ES7210_CHIP_ID1 = 0x72; static constexpr uint8_t ES7210_CHIP_ID0_REG = 0x3E; static constexpr uint8_t ES7210_CHIP_ID0 = 0x10; static bool readByteRegister(TwoWire *i2cBus, uint8_t address, uint8_t reg, uint8_t &value) { i2cBus->beginTransmission(address); i2cBus->write(reg); if (i2cBus->endTransmission() != 0) return false; if (i2cBus->requestFrom(address, (uint8_t)1) != 1 || !i2cBus->available()) return false; value = i2cBus->read(); return true; } // The INA219 has no manufacturer or die ID register to check, so it is inferred from "something // answered here and it wasn't anything else we know". That makes positively identifying the other // occupants of this address the only way to keep them out of the fallback. static bool detectES7210(TwoWire *i2cBus, uint8_t address) { uint8_t id = 0; // Read the high ID byte first so anything that clearly isn't an ES7210 costs a single // transaction, then confirm with the low byte. if (!readByteRegister(i2cBus, address, ES7210_CHIP_ID1_REG, id) || id != ES7210_CHIP_ID1) return false; return readByteRegister(i2cBus, address, ES7210_CHIP_ID0_REG, id) && id == ES7210_CHIP_ID0; } #define SCAN_SIMPLE_CASE(ADDR, T, ...) \ case ADDR: \ logFoundDevice(__VA_ARGS__); \ type = T; \ break; void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) { concurrency::LockGuard guard((concurrency::Lock *)&lock); LOG_DEBUG("Scan for I2C devices on port %d", port); uint8_t err; DeviceAddress addr(port, 0x00); uint16_t registerValue = 0x00; ScanI2C::DeviceType type; TwoWire *i2cBus; #ifdef RV3028_RTC Melopero_RV3028 rtc; #endif #if WIRE_INTERFACES_COUNT == 2 if (port == I2CPort::WIRE1) { i2cBus = &Wire1; } else { #endif i2cBus = &Wire; #if WIRE_INTERFACES_COUNT == 2 } #endif // We only need to scan 112 addresses, the rest is reserved for special purposes // 0x00 General Call // 0x01 CBUS addresses // 0x02 Reserved for different bus formats // 0x03 Reserved for future purposes // 0x04-0x07 High Speed Master Code // 0x78-0x7B 10-bit slave addressing // 0x7C-0x7F Reserved for future purposes for (addr.address = 8; addr.address < 120; addr.address++) { #if defined(HAS_QMA6100P) && (defined(ARCH_NRF52) || defined(NRF52_SERIES) || defined(NRF52)) bool nrf52QmaFound = false; #endif if (asize != 0) { if (!in_array(address, asize, (uint8_t)addr.address)) continue; LOG_DEBUG("Scan address 0x%x", (uint8_t)addr.address); } // For QMA6100P candidates on nRF52, use bounded I2C probing; otherwise use normal Wire #if defined(HAS_QMA6100P) && (defined(ARCH_NRF52) || defined(NRF52_SERIES) || defined(NRF52)) if (addr.address == QMA6100P_ADDRESS_LOW || addr.address == QMA6100P_ADDRESS_HIGH) { nrf52QmaFound = probeQMA6100P(addr.address); err = nrf52QmaFound ? 0 : 2; } else { i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO err = 2; if ((addr.address >= 0x30 && addr.address <= 0x37) || (addr.address >= 0x50 && addr.address <= 0x5F)) { if (i2cBus->read() != -1) err = 0; } else { err = i2cBus->writeQuick((uint8_t)0); } if (err != 0) err = 2; #else err = i2cBus->endTransmission(); #endif } #else i2cBus->beginTransmission(addr.address); #ifdef ARCH_PORTDUINO err = 2; if ((addr.address >= 0x30 && addr.address <= 0x37) || (addr.address >= 0x50 && addr.address <= 0x5F)) { if (i2cBus->read() != -1) err = 0; } else { err = i2cBus->writeQuick((uint8_t)0); } if (err != 0) err = 2; #else err = i2cBus->endTransmission(); #endif #endif type = NONE; if (err == 0) { switch (addr.address) { case SSD1306_ADDRESS_H: case SSD1306_ADDRESS_L: type = probeOLED(addr); break; #ifdef RV3028_RTC case RV3028_RTC: // foundDevices[addr] = RTC_RV3028; type = RTC_RV3028; logFoundDevice("RV3028", (uint8_t)addr.address); rtc.initI2C(*i2cBus); // Update RTC EEPROM settings, if necessary if (rtc.readEEPROMRegister(0x35) != 0x07) { rtc.writeEEPROMRegister(0x35, 0x07); // no Clkout } if (rtc.readEEPROMRegister(0x37) != 0xB4) { rtc.writeEEPROMRegister(0x37, 0xB4); } break; #endif #ifdef PCF8563_RTC SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address) #endif #ifdef RX8130CE_RTC SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address) #endif #ifdef PCF85063_RTC SCAN_SIMPLE_CASE(PCF85063_RTC, RTC_PCF85063, "PCF85063", (uint8_t)addr.address) #endif case CARDKB_ADDR: // Do we have the RAK14006 instead? registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1); if (registerValue == 0x02) { // KEYPAD_VERSION logFoundDevice("RAK14004", (uint8_t)addr.address); type = RAK14004; } else { logFoundDevice("M5 cardKB", (uint8_t)addr.address); type = CARDKB; } break; case TDECK_KB_ADDR: // Do we have the T-Deck keyboard or the T-Deck Pro battery sensor? registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1); if (registerValue != 0) { logFoundDevice("BQ27220", (uint8_t)addr.address); type = BQ27220; } else { logFoundDevice("TDECKKB", (uint8_t)addr.address); type = TDECKKB; } break; SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10", (uint8_t)addr.address); SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address); #ifdef HAS_NCP5623 SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address); #endif case XPOWERS_AXP192_AXP2101_ADDRESS: // Do we have the axp2101/192 or the TCA8418 registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x90), 1); if (registerValue == 0x0) { logFoundDevice("TCA8418", (uint8_t)addr.address); type = TCA8418KB; } else { logFoundDevice("AXP192/AXP2101", (uint8_t)addr.address); type = PMU_AXP192_AXP2101; } break; case BME_ADDR: case BME_ADDR_ALTERNATE: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD0), 1); // GET_ID switch (registerValue) { case 0x61: logFoundDevice("BME680", (uint8_t)addr.address); type = BME_680; break; case 0x60: logFoundDevice("BME280", (uint8_t)addr.address); type = BME_280; break; case 0x55: logFoundDevice("BMP085/BMP180", (uint8_t)addr.address); type = BMP_085; break; case 0x00: // do we have a DPS310 instead? registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0D), 1); switch (registerValue) { case 0x10: logFoundDevice("DPS310", (uint8_t)addr.address); type = DPS310; break; case 0x11: logFoundDevice("SPA06-003", (uint8_t)addr.address); type = SPA06; break; } if (type == DPS310 || type == SPA06) { break; } default: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID switch (registerValue) { case 0x50: // BMP-388 should be 0x50 logFoundDevice("BMP-388", (uint8_t)addr.address); type = BMP_3XX; break; case 0x60: // BMP-390 should be 0x60 logFoundDevice("BMP-390", (uint8_t)addr.address); type = BMP_3XX; break; case 0x58: // BMP-280 should be 0x58 default: logFoundDevice("BMP-280", (uint8_t)addr.address); type = BMP_280; break; } break; } break; #ifndef HAS_NCP5623 case AHT10_ADDR: logFoundDevice("AHT10", (uint8_t)addr.address); type = AHT10; break; #endif #if !defined(M5STACK_UNITC6L) case INA_ADDR: // Same as SHT2X case INA_ADDR_ALTERNATE: case INA_ADDR_WAVESHARE_UPS: { uint16_t mfg = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2); LOG_DEBUG("Register MFG_UID: 0x%x", mfg); // Only read DIE_UID for vendors we recognize as INA-compatible to avoid // an extra I2C transaction + delay on other devices sharing this address. if (mfg == 0x5449 || mfg == 0x190F) { uint16_t die = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 2); LOG_DEBUG("Register DIE_UID: 0x%x", die); // TI INA226 or fully compatible clones (e.g. TPA626) if (mfg == 0x5449 && die == 0x2260) { logFoundDevice("INA226", (uint8_t)addr.address); type = INA226; } // Silergy SQ52201 (INA226-compatible with different IDs) else if (mfg == 0x190F && die == 0x0000) { logFoundDevice("INA226 (SQ52201)", (uint8_t)addr.address); type = INA226; } // TI INA260 else if (mfg == 0x5449) { logFoundDevice("INA260", (uint8_t)addr.address); type = INA260; } } // The ES7210 audio ADC shares this address on some boards (T-Deck). It answers // none of the checks above, so identify it here and leave the type unset - driving // an audio codec as if it were a power monitor crashes the device. See #11115. if (type == NONE && detectES7210(i2cBus, (uint8_t)addr.address)) { LOG_INFO("ES7210 audio codec at 0x%x, not a power sensor", (uint8_t)addr.address); break; } #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR if (type == NONE && detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) { logFoundDevice("SHTXX (SHT2X)", (uint8_t)addr.address); type = SHTXX; } #endif // Guarded on the type rather than chained to the SHT2X check above, so that a // positively identified INA226/INA260 isn't overwritten right after being found. if (type == NONE) { // Assume INA219 if none of the above ones are found logFoundDevice("INA219", (uint8_t)addr.address); type = INA219; } break; } case INA3221_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2); LOG_DEBUG("Register MFG_UID FE: 0x%x", registerValue); if (registerValue == 0x5449) { logFoundDevice("INA3221", (uint8_t)addr.address); type = INA3221; } else { /* check the first 2 bytes of the 6 byte response register LARK FW 1.0 should return: RESPONSE_STATUS STATUS_SUCCESS (0x53) RESPONSE_CMD CMD_GET_VERSION (0x05) RESPONSE_LEN_L 0x02 RESPONSE_LEN_H 0x00 RESPONSE_PAYLOAD 0x01 RESPONSE_PAYLOAD+1 0x00 */ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x05), 6, true); LOG_DEBUG("Register MFG_UID 05: 0x%x", registerValue); if (registerValue == 0x5305) { logFoundDevice("DFRobot Lark", (uint8_t)addr.address); type = DFROBOT_LARK; } // else: probably a RAK12500/UBLOX GPS on I2C } break; #endif case MCP9808_ADDR: // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips. { #ifdef HAS_STK8XXX // Check register 0x00 for 0x8700 response to ID STK8BA53 chip. registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 2); if (registerValue == 0x8700) { type = STK8BAXX; logFoundDevice("STK8BAXX", (uint8_t)addr.address); break; } #endif // Check register 0x07 for 0x0400 response to ID MCP9808 chip. registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2); if (registerValue == 0x0400) { type = MCP9808; logFoundDevice("MCP9808", (uint8_t)addr.address); break; } // Check register 0x0F for 0x3300 response to ID LIS3DH chip. registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2); if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333 type = LIS3DH; logFoundDevice("LIS3DH", (uint8_t)addr.address); } break; } case SHTXX_ADDR: // same as OPT3001_ADDR_ALT case SHTXX_ADDR_ALT: // same as OPT3001_ADDR if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { type = OPT3001; logFoundDevice("OPT3001", (uint8_t)addr.address); } else { // SHTXX type = SHTXX; logFoundDevice("SHTXX", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTXX, "SHTXX", (uint8_t)addr.address) case RCWL9620_ADDR: // get MAX30102 PARTID registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1); if (registerValue == 0x15) { type = MAX30102; logFoundDevice("MAX30102", (uint8_t)addr.address); break; } else { type = RCWL9620; logFoundDevice("RCWL9620", (uint8_t)addr.address); } break; case LPS22HB_ADDR_ALT: // SFA30 detection: send 2-byte command 0xD060 (Get Device Marking) and check for 48-byte response if (i2cCommandResponseLength(addr, 0xD060, 48)) { type = SFA30; logFoundDevice("SFA30", (uint8_t)addr.address); break; } // Fallback: LPS22HB detection at alternate address using WHO_AM_I register (0x0F == 0xB1) registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); if (registerValue == 0xB1) { type = LPS22HB; logFoundDevice("LPS22HB", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address) SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address) case QMI8658_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID if (registerValue == 0xC0) { type = BQ24295; logFoundDevice("BQ24295", (uint8_t)addr.address); break; } registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x14), 1); // get ID if ((registerValue & 0b00000011) == 0b00000010) { type = BQ25896; logFoundDevice("BQ25896", (uint8_t)addr.address); break; } registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID if (registerValue == 0x6A) { type = LSM6DS3; logFoundDevice("LSM6DS3", (uint8_t)addr.address); } else if (registerValue == 0x6B) { type = ISM330DHCX; logFoundDevice("ISM330DHCX", (uint8_t)addr.address); } else { type = QMI8658; logFoundDevice("QMI8658", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L", (uint8_t)addr.address) case HMC5883L_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x4FU), 1); // get ID if (registerValue == 0x40) { type = IIS2MDCTR; logFoundDevice("IIS2MDCTR", (uint8_t)addr.address); break; } else { type = HMC5883L; logFoundDevice("HMC5883L", (uint8_t)addr.address); break; } #ifdef HAS_QMA6100P SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address) #else SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address) #endif case MMC5983MA_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x2F), 1); if (registerValue == 0x30) { type = MMC5983MA; logFoundDevice("MMC5983MA", (uint8_t)addr.address); #ifdef HAS_LP5562 } else { type = LP5562; logFoundDevice("LP5562", (uint8_t)addr.address); #endif } break; case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2); if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333 type = LIS3DH; logFoundDevice("LIS3DH", (uint8_t)addr.address); } else if ((registerValue & 0xFF00) == 0x1100) { // Silan SC7A20: LIS3DH register map, but answers 0x11 here. type = SC7A20; logFoundDevice("SC7A20", (uint8_t)addr.address); } else { type = BMA423; logFoundDevice("BMA423", (uint8_t)addr.address); } break; case TCA9535_ADDR: case RAK120352_ADDR: case RAK120353_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x02), 1); if (registerValue == addr.address) { // RAK12035 returns its I2C address at 0x02 (eg 0x20) type = RAK12035; logFoundDevice("RAK12035", (uint8_t)addr.address); } else { type = TCA9535; logFoundDevice("TCA9535", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address); case TCA9555_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1); if (registerValue == 0x13) { registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); if (registerValue == 0x81) { type = DA217; logFoundDevice("DA217", (uint8_t)addr.address); } else { type = TCA9555; logFoundDevice("TCA9555", (uint8_t)addr.address); } } else { type = TCA9555; logFoundDevice("TCA9555", (uint8_t)addr.address); } break; case TSL25911_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1); if (registerValue == 0x50) { type = TSL2591; logFoundDevice("TSL25911", (uint8_t)addr.address); } else { type = TSL2561; logFoundDevice("TSL2561", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address); case CST328_ADDR: // Do we have the CST328 or the CST226SE,CST3530 { // T-Deck pro V1.1 new touch panel use CST3530 int retry = 5; while (retry--) { uint8_t buffer[7]; uint8_t r_cmd[] = {0x0d0, 0x03, 0x00, 0x00}; i2cBus->beginTransmission(addr.address); i2cBus->write(r_cmd, sizeof(r_cmd)); if (i2cBus->endTransmission() == 0) { i2cBus->requestFrom((int)addr.address, 7); i2cBus->readBytes(buffer, 7); if (buffer[2] == 0xCA && buffer[3] == 0xCA) { logFoundDevice("CST3530", (uint8_t)addr.address); type = CST3530; break; } } uint8_t cmd1[] = {0xD0, 0x00, 0x04, 0x00}; i2cBus->beginTransmission(addr.address); i2cBus->write(cmd1, sizeof(cmd1)); i2cBus->endTransmission(); delay(50); } } registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1); if (registerValue == 0xA9) { type = CST226SE; logFoundDevice("CST226SE", (uint8_t)addr.address); } else { type = CST328; logFoundDevice("CST328", (uint8_t)addr.address); } break; SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address); case LTR553ALS_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register if (registerValue == 0x92) { // LTR553ALS Part ID type = LTR553ALS; logFoundDevice("LTR553ALS", (uint8_t)addr.address); } else { // Test BH1750 - send power on command i2cBus->beginTransmission(addr.address); i2cBus->write(0x01); // Power On command uint8_t bh1750_error = i2cBus->endTransmission(); if (bh1750_error == 0) { type = BH1750; logFoundDevice("BH1750", (uint8_t)addr.address); } else { LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address); } } break; SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address); case SCD4X_ADDR: { registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x8), 1); if (registerValue == 0x18) { logFoundDevice("CW2015", (uint8_t)addr.address); type = CW2015; } else { logFoundDevice("SCD4X", (uint8_t)addr.address); type = SCD4X; } break; } case BMM150_ADDR: #if defined(HAS_QMA6100P) && (defined(ARCH_NRF52) || defined(NRF52_SERIES) || defined(NRF52)) if (nrf52QmaFound) { logFoundDevice("QMA6100P", (uint8_t)addr.address); type = QMA6100P; break; } #endif logFoundDevice("BMM150", (uint8_t)addr.address); type = BMM150; break; #ifdef HAS_TPS65233 SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address); #endif case MLX90614_ADDR_DEF: // Do we have the MLX90614 or the MPR121KB or the CST226SE registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x06), 1); if (registerValue == 0xAB) { type = CST226SE; logFoundDevice("CST226SE", (uint8_t)addr.address); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1) == 0x5a) { type = MLX90614; logFoundDevice("MLX90614", (uint8_t)addr.address); } else { registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS if (registerValue == 0xe0) { type = DRV2605; logFoundDevice("DRV2605", (uint8_t)addr.address); } else { type = MPR121KB; logFoundDevice("MPR121KB", (uint8_t)addr.address); } } break; case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, ICM42607P_ADDR_ALT, and SEN5X_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR, and ICM42607P_ADDR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); #ifdef HAS_ICM20948 type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); break; #endif if (registerValue == 0xEA) { type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); break; } else if (registerValue == 0x24) { type = BMI270; logFoundDevice("BMI270", (uint8_t)addr.address); break; } else if (registerValue == 0xD8) { // BMX160 chip ID at register 0x00 type = BMX160; logFoundDevice("BMX160", (uint8_t)addr.address); break; } else { registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x75), 1); // WHO_AM_I if (registerValue == 0x60) { type = ICM42607P; logFoundDevice("ICM-42607-P", (uint8_t)addr.address); break; } else if (registerValue == 0x68) { // WHO_AM_I from datasheet type = MPU6050; logFoundDevice("MPU6050", (uint8_t)addr.address); break; } #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR if (probeSEN5X(i2cBus, addr.address, port)) { type = SEN5X; logFoundDevice("SEN5X", addr.address); break; } #endif } break; case CGRADSENS_ADDR: // Register 0x00 of the RadSens sensor contains is product identifier 0x7D // Undocumented, but some devices return a product identifier of 0x7A registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); if (registerValue == 0x7D || registerValue == 0x7A) { type = CGRADSENS; logFoundDevice("ClimateGuard RadSens", (uint8_t)addr.address); break; } else { LOG_DEBUG("Unexpected Device ID for RadSense: addr=0x%x id=0x%x", CGRADSENS_ADDR, registerValue); } break; case 0x48: { // T=1oI2C soft reset; an SE050 answers A5 E0 00 3F 19. requestFrom() is // required: readBytes() only drains the RX buffer requestFrom() fills. const uint8_t getInfo[] = {0x5A, 0xC0, 0x00, 0xFF, 0xFC}; const uint8_t expectedInfo[] = {0xA5, 0xE0, 0x00, 0x3F, 0x19}; uint8_t info[sizeof(expectedInfo)] = {0}; size_t len = 0; bool isSE050 = false; i2cBus->beginTransmission(addr.address); i2cBus->write(getInfo, sizeof(getInfo)); if (i2cBus->endTransmission() == 0) { delay(2); // guard time before the answer can be read back len = i2cBus->requestFrom((uint8_t)addr.address, (uint8_t)sizeof(info)); if (len == sizeof(info)) { for (size_t i = 0; i < sizeof(info); i++) info[i] = i2cBus->read(); isSE050 = (memcmp(expectedInfo, info, sizeof(info)) == 0); } } if (isSE050) { LOG_INFO("NXP SE050 crypto chip found"); type = NXP_SE050; break; } registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); if (registerValue == 0x8583 || registerValue == 0x8580) { type = ADS1115; logFoundDevice("ADS1115 ADC", (uint8_t)addr.address); break; } LOG_INFO("FT6336U touchscreen found"); type = FT6336U; break; } default: LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address); } } else if (err == 4) { LOG_ERROR("Unknown error at address 0x%x", (uint8_t)addr.address); } // Check if a type was found for the enumerated device - save, if so if (type != NONE) { deviceAddresses[type] = addr; foundDevices[addr] = type; } } } void ScanI2CTwoWire::scanPort(I2CPort port) { scanPort(port, nullptr, 0); } TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) { if (address.port == ScanI2C::I2CPort::WIRE) { return &Wire; } else { #if WIRE_INTERFACES_COUNT == 2 return &Wire1; #else return &Wire; #endif } } size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); } void ScanI2CTwoWire::logFoundDevice(const char *device, uint8_t address) { LOG_INFO("%s found at address 0x%x", device, address); } #endif