mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-14 07:21:30 +10:00
* Add AS3935 lightning sensor support Implements meshtastic/firmware#10774: an AS3935Sensor (TelemetrySensor subclass) that reports lightning_strike_count_1h and lightning_distance_km on the normal environment telemetry interval, like a rain gauge - strikes are counted over a fixed rolling ~1h window and read non-destructively, so replying to a peer's telemetry request in between broadcasts can't silently drop counted strikes. The AS3935's IRQ pin (opt-in per board via AS3935_IRQ) is polled with a plain digitalRead() in runOnce(), deliberately not attachInterrupt(): the IRQ line is a level that stays asserted until its interrupt register is read, so polling can't miss an event regardless of timing, matching the SparkFun library's own reference examples. An interrupt would also buy nothing here even setting that aside - classification requires an I2C read (readInterruptReg(), which itself calls delay(2) per the datasheet's settle-time requirement), and blocking I2C/delay() calls aren't safe from ISR context on any of this codebase's target platforms, so the ISR could only ever set a flag for later draining - no less work than just polling the pin directly on the next tick. A genuine lightning classification also requests an immediate out-of-cycle send via a new EnvironmentTelemetryModule:: requestImmediateSend() hook. There's no fixed debounce on the request itself - EnvironmentTelemetryModule's existing airtime/duty-cycle gate already paces every send, so it sends as often as airtime allows rather than an arbitrary fixed rate. The request does expire after 5 minutes unfulfilled, so it can't fire an arbitrarily stale broadcast if airtime was blocked for a long stretch. The AS3935's I2C addresses (0x01-0x03) fall inside the range this codebase's I2C scanner otherwise skips as reserved, so detection is a small dedicated probe gated behind AS3935_IRQ and respecting the caller's address filter, rather than a change to the general scan loop. Presence is confirmed via a register write/readback round-trip rather than a fixed expected value, since the AS3935 has no WHOAMI register and a power-on-reset-only check can't survive a warm reboot that doesn't power-cycle the sensor (initDevice() permanently rewrites that register on first configuration). Generated files under src/mesh/generated/ are intentionally excluded from this commit - they're regenerated from the protobufs submodule by update_protobufs.yml, and hand edits get overwritten and conflict once the companion protobufs PR merges and the submodule pointer updates. Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Andrew Yong <me@ndoo.sg> * fix(as3935): calibration and telemetry logging initDevice() never called the library's calibrateOsc(). The AS3935's internal oscillators are calibrated against the antenna's resonance, which the AFE/watchdog/spike-rejection thresholds depend on; without it, only a directly-driven IRQ pin (bypassing detection entirely) reacted during testing. The sensor could already have a historical detection event latching the IRQ pin high before our initialization. Added an explicit drain read after the IRQ pin is configured, so the sensor doesn't start out stuck asserting IRQ. EnvironmentTelemetryModule::sendTelemetry() logs every other environment metric category on send but was missing lightning; added a matching log line. Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Andrew Yong <me@ndoo.sg> * Support AS3935 without an IRQ line, make the antenna trim configurable Detection no longer requires AS3935_IRQ. The probe is gated like the other environmental sensors, so an I2C-only breakout is found on any board. Where AS3935_IRQ is defined the pin still gates the I2C read, otherwise runOnce() polls the interrupt register, which latches until read. Antenna tuning capacitance moves to AdminMessage.sensor_config.as3935_config, persisted to /prefs/as3935.dat and defaulting to 96pF. The chip does not retain it across power loss. Disturbers are masked in the chip, since runOnce() now polls every second. The lightning telemetry log is guarded so nodes without the sensor no longer log it on every send. Requires meshtastic/protobufs#981. * Revert protobufs pointer to the develop baseline The submodule bump conflicts on merge and the generated headers come from an out of band CI job, so the pointer moves with that job rather than in this branch. * Report lightning strikes over a true rolling hour strikeCountWindow was zeroed on a fixed interval, so lightning_strike_count_1h reported strikes since the last reset rather than over the preceding hour. RollingCounter is a fixed memory sliding window: one counter per bucket, nothing stored per event, so a storm cannot grow it. The ring holds one bucket more than the window needs so none is recycled while part of it is still inside, and the oldest bucket contributes only the fraction still in range. Both are needed to hold the span at exactly the window length rather than letting it drift by a bucket either way. Expiry is exact to one bucket rather than to the event, which is below the 5 minute floor on mesh telemetry sends. The distance expires with the last strike in the window instead of on the interval reset. Covered by test/test_rolling_counter. * Widen the RollingCounter edge weighting to 64 bit counts * inWindow is a 32 bit product, so a bucket holding more than 2^32 / BucketMs events wraps. At a 5 minute width that is about 14k: a bucket of 50000 reported 11367 instead of 40000 once it reached the window edge. Below the threshold nothing changes, so lightning was unaffected, but the helper is meant to be reused by counters with far higher rates. test_large_burst_at_window_edge covers it. The existing burst test sampled only inside the window, where the bucket is whole and never weighted. * Trim RollingCounter comments to the house limit --------- Signed-off-by: Andrew Yong <me@ndoo.sg> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
1177 lines
49 KiB
C++
1177 lines
49 KiB
C++
#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(SENSECAP_INDICATOR)
|
|
#include "mesh/comms/I2CProxy.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 <QMA6100P.h>
|
|
|
|
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"
|
|
#include "../modules/Telemetry/Sensor/SEN6XSensor.h"
|
|
bool probeSEN5X(TwoWire *i2cBus, uint8_t address, ScanI2C::I2CPort port)
|
|
{
|
|
SEN5XSensor sen5xsensor;
|
|
return sen5xsensor.probe(i2cBus, address, port);
|
|
}
|
|
|
|
bool probeSEN6X(TwoWire *i2cBus, uint8_t address, ScanI2C::I2CPort port)
|
|
{
|
|
SEN6XSensor sen6xsensor;
|
|
return sen6xsensor.probe(i2cBus, address, port);
|
|
}
|
|
|
|
bool probeHM330x(TwoWire *i2cBus, uint8_t address)
|
|
{
|
|
|
|
i2cBus->beginTransmission(address);
|
|
i2cBus->write(0X88);
|
|
byte ret = i2cBus->endTransmission();
|
|
|
|
if (ret == 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#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) {
|
|
#if defined(SENSECAP_INDICATOR)
|
|
i2cBus = i2cProxy; // WIRE1 is bridged to the RP2040
|
|
#else
|
|
i2cBus = &Wire1;
|
|
#endif
|
|
} 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;
|
|
case BBQ10_KB_ADDR:
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
type = BBQ10KB;
|
|
logFoundDevice("BB Q10", (uint8_t)addr.address);
|
|
break;
|
|
SCAN_SIMPLE_CASE(TSTC8_KB_ADDR, STC8HKB, "STC8H KB", (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 HM330X, ES7210 and 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;
|
|
break;
|
|
}
|
|
// Silergy SQ52201 (INA226-compatible with different IDs)
|
|
else if (mfg == 0x190F && die == 0x0000) {
|
|
logFoundDevice("INA226 (SQ52201)", (uint8_t)addr.address);
|
|
type = INA226;
|
|
break;
|
|
}
|
|
// TI INA260
|
|
else if (mfg == 0x5449) {
|
|
logFoundDevice("INA260", (uint8_t)addr.address);
|
|
type = INA260;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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 (detectES7210(i2cBus, (uint8_t)addr.address)) {
|
|
LOG_INFO("ES7210 audio codec at 0x%x, not a power sensor", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && HAS_TELEMETRY
|
|
if (detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) {
|
|
logFoundDevice("SHTXX (SHT2X)", (uint8_t)addr.address);
|
|
type = SHTXX;
|
|
break;
|
|
}
|
|
#endif
|
|
// Get INA219 configuration register 0x00 expecting 0x399F after power-on reset
|
|
uint16_t registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 2);
|
|
if (registerValue == 0x399F) {
|
|
logFoundDevice("INA219", (uint8_t)addr.address);
|
|
type = INA219;
|
|
break;
|
|
}
|
|
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && HAS_TELEMETRY
|
|
// Assume HM330x as the weakest detection method if none of the above ones are found
|
|
if (probeHM330x(i2cBus, addr.address)) {
|
|
logFoundDevice("HM330X", (uint8_t)addr.address);
|
|
type = HM330X;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS248X", (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)
|
|
case DS248X_ADDR_ALT3:
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case QMC6310U_ADDR:
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
type = QMC6310U;
|
|
logFoundDevice("QMC6310U", (uint8_t)addr.address);
|
|
break;
|
|
|
|
case QMI8658_ADDR: // same as BQ25896_ADDR and SEN6X_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 {
|
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
|
if (probeSEN6X(i2cBus, addr.address, port)) {
|
|
type = SEN6X;
|
|
logFoundDevice("SEN6X", addr.address);
|
|
break;
|
|
}
|
|
#endif
|
|
type = QMI8658;
|
|
logFoundDevice("QMI8658", (uint8_t)addr.address);
|
|
}
|
|
break;
|
|
|
|
SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L", (uint8_t)addr.address)
|
|
case HMC5883L_ADDR:
|
|
// WHO_AM_I first: the DS248X probe below reads a reserved address on the IIS2MDCTR
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x4FU), 1); // get ID
|
|
if (registerValue == 0x40) {
|
|
type = IIS2MDCTR;
|
|
logFoundDevice("IIS2MDCTR", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
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);
|
|
break;
|
|
} else if ((registerValue & 0xFF00) == 0x1100) {
|
|
// Silan SC7A20: LIS3DH register map, but answers 0x11 here.
|
|
type = SC7A20;
|
|
logFoundDevice("SC7A20", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
|
|
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);
|
|
case DFROBOT_RAIN_ADDR:
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
type = DFROBOT_RAIN;
|
|
logFoundDevice("DFRobot Rain Gauge", (uint8_t)addr.address);
|
|
break;
|
|
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:
|
|
// Check status register (0xF0) for DS284X status and one-wire reset
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xF0), 1);
|
|
if (registerValue & 0x16) { // One-wire reset after power-on
|
|
type = DS248X;
|
|
logFoundDevice("DS2482-800", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
// ADS1X15 default config register is 8583h
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
|
|
if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) {
|
|
type = ADS1X15;
|
|
logFoundDevice("ADS1X15 ADC", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
|
|
LOG_INFO("FT6336U touchscreen found");
|
|
type = FT6336U;
|
|
break;
|
|
}
|
|
|
|
case ADS1X15_ADDR_ALT1:
|
|
case ADS1X15_ADDR_ALT2:
|
|
case ADS1X15_ADDR_ALT3: {
|
|
// ADS1X15 default config register is 8583h
|
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
|
|
if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) {
|
|
type = ADS1X15_ALT;
|
|
logFoundDevice("ADS1X15_ALT", (uint8_t)addr.address);
|
|
break;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
|
// AS3935 addresses (0x01-0x03) fall in the reserved range the loop above skips; probe
|
|
// them separately rather than widening that loop for every board.
|
|
static const uint8_t as3935Candidates[] = {AS3935_ADDR_ALT, AS3935_ADDR_ALT2, AS3935_ADDR};
|
|
for (uint8_t i = 0; i < sizeof(as3935Candidates); i++) {
|
|
// Respect the caller's address filter, same as the main loop above (line ~269).
|
|
if (asize != 0 && !in_array(address, asize, as3935Candidates[i]))
|
|
continue;
|
|
|
|
DeviceAddress as3935Addr(port, as3935Candidates[i]);
|
|
i2cBus->beginTransmission(as3935Candidates[i]);
|
|
uint8_t as3935Err = i2cBus->endTransmission();
|
|
if (as3935Err == 0) {
|
|
// No WHOAMI, and a POR-only check can't survive a warm reboot (initDevice rewrites
|
|
// REG0x00). Write a test pattern to bits[5:1] instead and confirm it reads back.
|
|
constexpr uint8_t AS3935_PROBE_PATTERN = 0b01010; // arbitrary, bits[5:1]
|
|
i2cBus->beginTransmission(as3935Candidates[i]);
|
|
i2cBus->write((uint8_t)0x00); // REG0x00 (AFE_GAIN)
|
|
i2cBus->write((uint8_t)(AS3935_PROBE_PATTERN << 1)); // PWD=0, gain bits = pattern
|
|
if (i2cBus->endTransmission() == 0) {
|
|
uint16_t reg0 = getRegisterValue(ScanI2CTwoWire::RegisterLocation(as3935Addr, 0x00), 1);
|
|
if (((reg0 >> 1) & 0x1F) == AS3935_PROBE_PATTERN) {
|
|
logFoundDevice("AS3935", as3935Candidates[i]);
|
|
deviceAddresses[AS3935] = as3935Addr;
|
|
foundDevices[as3935Addr] = AS3935;
|
|
break; // only one AS3935 expected per bus
|
|
} else {
|
|
LOG_DEBUG("Unexpected REG0x00 readback for AS3935: addr=0x%x val=0x%x", as3935Candidates[i], reg0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// The QMC6309 magnetometer sits at 0x7C, above the general scan ceiling (the loop above stops at 0x77 to
|
|
// avoid the reserved 0x78-0x7F block). Probe it explicitly. Gated on the SensorLib driver being present so
|
|
// only boards that can actually drive the chip poke this reserved address.
|
|
#if __has_include(<SensorQMC6309.hpp>)
|
|
addr.address = QMC6309_ADDR;
|
|
i2cBus->beginTransmission(addr.address);
|
|
if (i2cBus->endTransmission() == 0 &&
|
|
getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1) == 0x90 /* QMC6309 chip id */) {
|
|
deviceAddresses[QMC6309] = addr;
|
|
foundDevices[addr] = QMC6309;
|
|
logFoundDevice("QMC6309", (uint8_t)addr.address);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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 defined(SENSECAP_INDICATOR)
|
|
return i2cProxy; // WIRE1 is bridged to the RP2040
|
|
#elif 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
|