Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make I2Cscan non-blocking #378

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 95 additions & 126 deletions lib/i2cscan/i2cscan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,94 @@

#ifdef ESP8266
uint8_t portArray[] = {16, 5, 4, 2, 14, 12, 13};
uint8_t portExclude[] = {LED_PIN};
String portMap[] = {"D0", "D1", "D2", "D4", "D5", "D6", "D7"};
// ESP32C3 has not as many ports as the ESP32
#elif defined(ESP32C3)
uint8_t portArray[] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
uint8_t portExclude[] = {18, 19, 20, 21, LED_PIN};
String portMap[] = {"2", "3", "4", "5", "6", "7", "8", "9", "10"};
// this is for the ESP32C6 has a lot of pins (10/11 only availiable on the WROOM modules but not on the "mini")
#elif defined(ESP32C6)
uint8_t portArray[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 18, 19, 20, 21, 22, 23};
String portMap[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "14", "15", "18", "19", "20", "21", "22", "23"};
uint8_t portExclude[] = {12, 13, 16, 17, LED_PIN}; // exclude USB D+,D- and serial TX/RX
#elif defined(ESP32)
uint8_t portArray[] = {4, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33};
String portMap[] = {"4", "13", "14", "15", "16", "17", "18", "19", "21", "22", "23", "25", "26", "27", "32", "33"};
uint8_t portExclude[] = {LED_PIN};
#endif

namespace I2CSCAN
{
enum class ScanState {
IDLE,
SCANNING,
DONE
};

ScanState scanState = ScanState::IDLE;
uint8_t currentSDA = 0;
uint8_t currentSCL = 0;
uint8_t currentAddress = 1;
bool found = false;

uint8_t pickDevice(uint8_t addr1, uint8_t addr2, bool scanIfNotFound) {
if(I2CSCAN::hasDevOnBus(addr1)) {
return addr1;
}
if(I2CSCAN::hasDevOnBus(addr2)) {
return addr2;
}
if (scanIfNotFound) {
Serial.println("[ERR] I2C: Can't find I2C device on provided addresses, scanning for all I2C devices and returning");
I2CSCAN::scani2cports();
} else {
Serial.println("[ERR] I2C: Can't find I2C device on provided addresses");
void scani2cports()
{
if (scanState != ScanState::IDLE) {
return;
}
return 0;
found = false;
currentSDA = 0;
currentSCL = 0;
currentAddress = 1;
scanState = ScanState::SCANNING;
}

void scani2cports()
void update()
{
bool found = false;
for (uint8_t i = 0; i < sizeof(portArray); i++)
if (scanState != ScanState::SCANNING) {
return;
}

if (currentAddress == 1) {
#if ESP32
Wire.end();
#endif
Wire.begin((int)portArray[currentSDA], (int)portArray[currentSCL]);
}

Wire.beginTransmission(currentAddress);
byte error = Wire.endTransmission();

if (error == 0)
{
for (uint8_t j = 0; j < sizeof(portArray); j++)
{
if ((i != j) && !inArray(portArray[i], portExclude, sizeof(portExclude)) && !inArray(portArray[j], portExclude, sizeof(portExclude)))
{
if(checkI2C(i, j))
found = true;
}
}
Serial.printf("[DBG] I2C (@ %s(%d) : %s(%d)): I2C device found at address 0x%02x !\n",
portMap[currentSDA].c_str(), portArray[currentSDA], portMap[currentSCL].c_str(), portArray[currentSCL], currentAddress);
found = true;
}
if(!found) {
Serial.println("[ERR] I2C: No I2C devices found");
else if (error == 4)
{
Serial.printf("[ERR] I2C (@ %s(%d) : %s(%d)): Unknown error at address 0x%02x\n",
portMap[currentSDA].c_str(), portArray[currentSDA], portMap[currentSCL].c_str(), portArray[currentSCL], currentAddress);
}

currentAddress++;
if (currentAddress >= 127) {
currentAddress = 1;
currentSCL++;
if (currentSCL >= sizeof(portArray)) {
currentSCL = 0;
currentSDA++;
}
}

if (currentSDA >= sizeof(portArray)) {
if (!found) {
Serial.println("[ERR] I2C: No I2C devices found");
}

#if ESP32
Wire.end();
Wire.end();
#endif

// Reset the I2C interface back to it's original values
Wire.begin(static_cast<int>(PIN_IMU_SDA), static_cast<int>(PIN_IMU_SCL));
Wire.begin(static_cast<int>(PIN_IMU_SDA), static_cast<int>(PIN_IMU_SCL));
scanState = ScanState::DONE;
}
}

bool inArray(uint8_t value, uint8_t* array, size_t arraySize)
Expand All @@ -78,43 +105,6 @@ namespace I2CSCAN

return false;
}

bool checkI2C(uint8_t i, uint8_t j)
{
bool found = false;

#if ESP32
Wire.end();
#endif

Wire.begin((int)portArray[i], (int)portArray[j]);

byte error, address;
int nDevices;
nDevices = 0;
for (address = 1; address < 127; address++)
{
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
error = Wire.endTransmission();

if (error == 0)
{
Serial.printf("[DBG] I2C (@ %s(%d) : %s(%d)): I2C device found at address 0x%02x !\n",
portMap[i].c_str(), portArray[i], portMap[j].c_str(), portArray[j], address);
nDevices++;
found = true;
}
else if (error == 4)
{
Serial.printf("[ERR] I2C (@ %s(%d) : %s(%d)): Unknown error at address 0x%02x\n",
portMap[i].c_str(), portArray[i], portMap[j].c_str(), portArray[j], address);
}
}
return found;
}

bool hasDevOnBus(uint8_t addr) {
byte error;
Expand All @@ -133,75 +123,54 @@ namespace I2CSCAN
return false;
}

/**
* This routine turns off the I2C bus and clears it
* on return SCA and SCL pins are tri-state inputs.
* You need to call Wire.begin() after this to re-enable I2C
* This routine does NOT use the Wire library at all.
*
* returns 0 if bus cleared
* 1 if SCL held low.
* 2 if SDA held low by slave clock stretch for > 2sec
* 3 if SDA held low after 20 clocks.
* From: http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html
* (c)2014 Forward Computing and Control Pty. Ltd.
* NSW Australia, www.forward.com.au
* This code may be freely used for both private and commerical use
*/
int clearBus(uint8_t SDA, uint8_t SCL) {
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
TWCR &= ~(_BV(TWEN));
#endif

pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
pinMode(SDA, INPUT_PULLUP);
pinMode(SCL, INPUT_PULLUP);

boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
if (SCL_LOW) { //If it is held low Arduno cannot become the I2C master.
return 1; //I2C bus error. Could not clear SCL clock line held low
boolean SCL_LOW = (digitalRead(SCL) == LOW);
if (SCL_LOW) {
return 1;
}

boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
int clockCount = 20; // > 2x9 clock
boolean SDA_LOW = (digitalRead(SDA) == LOW);
int clockCount = 20;

while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low,
while (SDA_LOW && (clockCount > 0)) {
clockCount--;
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
pinMode(SCL, OUTPUT); // then clock SCL Low
delayMicroseconds(10); // for >5uS
pinMode(SCL, INPUT); // release SCL LOW
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
// do not force high as slave may be holding it low for clock stretching.
delayMicroseconds(10); // for >5uS
// The >5uS is so that even the slowest I2C devices are handled.
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
int counter = 20;
while (SCL_LOW && (counter > 0)) { // loop waiting for SCL to become High only wait 2sec.
counter--;
delay(100);
pinMode(SCL, INPUT);
pinMode(SCL, OUTPUT);
delayMicroseconds(10);
pinMode(SCL, INPUT);
pinMode(SCL, INPUT_PULLUP);
delayMicroseconds(10);
SCL_LOW = (digitalRead(SCL) == LOW);
int counter = 20;
while (SCL_LOW && (counter > 0)) {
counter--;
delay(100);
SCL_LOW = (digitalRead(SCL) == LOW);
}
if (SCL_LOW) { // still low after 2 sec error
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
if (SCL_LOW) {
return 2;
}
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
SDA_LOW = (digitalRead(SDA) == LOW);
}
if (SDA_LOW) { // still low
return 3; // I2C bus error. Could not clear. SDA data line held low
if (SDA_LOW) {
return 3;
}

// else pull SDA line low for Start or Repeated Start
pinMode(SDA, INPUT); // remove pullup.
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
delayMicroseconds(10); // wait >5uS
pinMode(SDA, INPUT); // remove output low
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
delayMicroseconds(10); // x. wait >5uS
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(SDA, INPUT);
pinMode(SDA, OUTPUT);
delayMicroseconds(10);
pinMode(SDA, INPUT);
pinMode(SDA, INPUT_PULLUP);
delayMicroseconds(10);
pinMode(SDA, INPUT);
pinMode(SCL, INPUT);
return 0; // all ok
return 0;
}
}
}
1 change: 1 addition & 0 deletions lib/i2cscan/i2cscan.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace I2CSCAN {
void scani2cports();
void update();
bool checkI2C(uint8_t i, uint8_t j);
bool hasDevOnBus(uint8_t addr);
uint8_t pickDevice(uint8_t addr1, uint8_t addr2, bool scanIfNotFound);
Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void loop() {
sensorManager.update();
battery.Loop();
ledManager.update();
I2CSCAN::update();
#ifdef TARGET_LOOPTIME_MICROS
long elapsed = (micros() - loopTime);
if (elapsed < TARGET_LOOPTIME_MICROS) {
Expand Down
2 changes: 1 addition & 1 deletion src/sensors/SensorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void SensorManager::setup() {
if (activeSensorCount == 0) {
m_Logger.error(
"Can't find I2C device on provided addresses, scanning for all I2C devices "
"and returning"
"in the background"
);
I2CSCAN::scani2cports();
}
Expand Down
Loading