Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

201 lines
6.4 KiB

/*
I2C MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if I2C_SUPPORT
unsigned int _i2c_locked[16] = {0};
#if I2C_USE_BRZO
#include "brzo_i2c.h"
unsigned long _i2c_scl_frequency = 0;
#else
#include "Wire.h"
#endif
// -----------------------------------------------------------------------------
// Private
// -----------------------------------------------------------------------------
int _i2cClearbus(int sda, int scl) {
#if defined(TWCR) && defined(TWEN)
// Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
TWCR &= ~(_BV(TWEN));
#endif
// Make SDA (data) and SCL (clock) pins inputs with pullup
pinMode(sda, INPUT_PULLUP);
pinMode(scl, INPUT_PULLUP);
delay(2500);
// Wait 2.5 secs. This is strictly only necessary on the first power
// up of the DS3231 module to allow it to initialize properly,
// but is also assists in reliable programming of FioV3 boards as it gives the
// IDE a chance to start uploaded the program
// before existing sketch confuses the IDE by sending Serial data.
// If it is held low the device cannot become the I2C master
// 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);
int clockCount = 20; // > 2x9 clock
// While SDA is low for at most 20 cycles
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); // The >5uS is so that even the slowest I2C devices are handled
// loop waiting for SCL to become high only wait 2sec
scl_low = (digitalRead(scl) == LOW);
int counter = 20;
while (scl_low && (counter > 0)) {
counter--;
delay(100);
scl_low = (digitalRead(scl) == LOW);
}
// If still low after 2 sec error
// 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
}
// If still low
// I2C bus error. Could not clear. SDA data line held low
if (sda_low) return 3;
// 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); // wait >5uS
pinMode(sda, INPUT); // and reset pins as tri-state inputs which is the default state on reset
pinMode(scl, INPUT);
// Everything OK
return 0;
}
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
void i2cClearBus() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
unsigned char scl = getSetting("i2cSCL", I2C_SCL_PIN).toInt();
DEBUG_MSG_P(PSTR("[I2C] Clear bus (response: %d)\n"), _i2cClearbus(sda, scl));
}
bool i2cCheck(unsigned char address) {
#if I2C_USE_BRZO
brzo_i2c_start_transaction(address, _i2c_scl_frequency);
brzo_i2c_ACK_polling(1000);
return brzo_i2c_end_transaction();
#else
Wire.beginTransmission(address);
return Wire.endTransmission();
#endif
}
bool i2cGetLock(unsigned char address) {
unsigned char index = address / 8;
unsigned char mask = 1 << (address % 8);
if (_i2c_locked[index] & mask) return false;
_i2c_locked[index] = _i2c_locked[index] | mask;
DEBUG_MSG_P(PSTR("[I2C] Address 0x%02X locked\n"), address);
return true;
}
bool i2cReleaseLock(unsigned char address) {
unsigned char index = address / 8;
unsigned char mask = 1 << (address % 8);
if (_i2c_locked[index] & mask) {
_i2c_locked[index] = _i2c_locked[index] & ~mask;
return true;
}
return false;
}
unsigned char i2cFind(size_t size, unsigned char * addresses, unsigned char &start) {
for (unsigned char i=start; i<size; i++) {
if (i2cCheck(addresses[i]) == 0) {
start = i;
return addresses[i];
}
}
return 0;
}
unsigned char i2cFind(size_t size, unsigned char * addresses) {
unsigned char start = 0;
return i2cFind(size, addresses, start);
}
unsigned char i2cFindAndLock(size_t size, unsigned char * addresses) {
unsigned char start = 0;
unsigned char address = 0;
while (address = i2cFind(size, addresses, start)) {
if (i2cGetLock(address)) break;
start++;
}
return address;
}
void i2cScan() {
unsigned char nDevices = 0;
for (unsigned char address = 1; address < 127; address++) {
unsigned char error = i2cCheck(address);
if (error == 0) {
DEBUG_MSG_P(PSTR("[I2C] Device found at address 0x%02X\n"), address);
nDevices++;
}
}
if (nDevices == 0) DEBUG_MSG_P(PSTR("[I2C] No devices found\n"));
}
void i2cSetup() {
unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt();
unsigned char scl = getSetting("i2cSCL", I2C_SCL_PIN).toInt();
#if I2C_USE_BRZO
unsigned long cst = getSetting("i2cCST", I2C_CLOCK_STRETCH_TIME).toInt();
_i2c_scl_frequency = getSetting("i2cFreq", I2C_SCL_FREQUENCY).toInt();
brzo_i2c_setup(sda, scl, cst);
#else
Wire.begin(sda, scl);
#endif
DEBUG_MSG_P(PSTR("[I2C] Using GPIO%u for SDA and GPIO%u for SCL\n"), sda, scl);
}
#endif