/* I2C MODULE Copyright (C) 2017-2019 by Xose PĂ©rez */ #include "espurna.h" #if I2C_SUPPORT #if I2C_USE_BRZO #include #else #include #endif #include "i2c.h" #include #include // ----------------------------------------------------------------------------- // Private // ----------------------------------------------------------------------------- namespace espurna { namespace i2c { namespace { struct Bus { unsigned char sda { GPIO_NONE }; unsigned char scl { GPIO_NONE }; #if I2C_USE_BRZO unsigned long frequency { 0 }; #endif }; namespace internal { Bus bus; } // namespace internal namespace lock { std::bitset<128> storage{}; void reset(uint8_t address) { storage.reset(address); } bool get(uint8_t address) { return storage.test(address); } bool set(uint8_t address) { if (!get(address)) { storage.set(address); return true; } return false; } } // namespace lock #if I2C_USE_BRZO void brzo_i2c_start_transaction(uint8_t address) { ::brzo_i2c_start_transaction(address, internal::bus.frequency); } #endif namespace build { constexpr unsigned char sda() { return I2C_SDA_PIN; } constexpr unsigned char scl() { return I2C_SCL_PIN; } constexpr bool performScanOnBoot() { return I2C_PERFORM_SCAN == 1; } #if I2C_USE_BRZO constexpr unsigned long cst() { return I2C_CLOCK_STRETCH_TIME; } constexpr unsigned long sclFrequency() { return I2C_SCL_FREQUENCY; } #endif } // namespace build namespace settings { unsigned char sda() { return getSetting("i2cSDA", build::sda()); } unsigned char scl() { return getSetting("i2cSCL", build::scl()); } #if I2C_USE_BRZO unsigned long cst() { return getSetting("i2cCST", build::cst()); } unsigned long sclFrequency() { return getSetting("i2cFreq", build::sclFrequency()); } #endif } // namespace settings // make note that both APIs return integer status codes // success is 0, everything else depends on the implementation // for example, for our Wire it is: // - 4 if line is busy // - 2 if NACK happened when writing address // - 3 if NACK happened when writing data bool find(uint8_t address) { #if I2C_USE_BRZO i2c::start_brzo_transaction(address); brzo_i2c_ACK_polling(1000); return 0 == brzo_i2c_end_transaction(); #else Wire.beginTransmission(address); return 0 == Wire.endTransmission(); #endif } template uint8_t find(const uint8_t* begin, const uint8_t* end, T&& filter) { for (const auto* it = begin; it != end; ++it) { if (filter(*it) && find(*it)) { return *it; } } return 0; } uint8_t find(const uint8_t* begin, const uint8_t* end) { return find(begin, end, [](uint8_t) { return true; }); } uint8_t findAndLock(const uint8_t* begin, const uint8_t* end) { const auto address = find(begin, end, [](uint8_t address) { return !lock::get(address); }); if (address != 0) { lock::set(address); } return address; } template void scan(T&& callback) { static constexpr uint8_t Min { 0x8 }; static constexpr uint8_t Max { 0x78 }; for (auto address = Min; address < Max; ++address) { if (find(address)) { callback(address); } } } void bootScan() { String addresses; scan([&](uint8_t address) { if (addresses.length()) { addresses += F(", "); } addresses += F("0x"); addresses += hexEncode(address); }); if (addresses.length()) { DEBUG_MSG_P(PSTR("[I2C] Found device(s): %s\n"), addresses.c_str()); } else { DEBUG_MSG_P(PSTR("[I2C] No devices found\n")); } } int clear(unsigned char sda, unsigned char 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); // 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. espurna::time::blockingDelay( espurna::duration::Milliseconds(2500)); // If it is held low the device cannot become the I2C master // I2C bus error. Could not clear SCL clock line held low bool scl_low = (digitalRead(scl) == LOW); if (scl_low) { return 1; } bool 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--; espurna::time::blockingDelay( espurna::duration::Milliseconds(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; } int clear(const Bus& bus) { return clear(bus.sda, bus.scl); } int clear() { return clear(internal::bus); } void init() { internal::bus.sda = settings::sda(); internal::bus.scl = settings::scl(); #if I2C_USE_BRZO internal::bus.frequency = settings::sclFrequency(); brzo_i2c_setup(internal::bus.sda, internal::bus.scl, settings::cst()); #else Wire.begin(internal::bus.sda, internal::bus.scl); #endif DEBUG_MSG_P(PSTR("[I2C] Initialized SDA @ GPIO%hhu and SCL @ GPIO%hhu\n"), internal::bus.sda, internal::bus.scl); #if I2C_CLEAR_BUS clear(internal::bus); #endif } #if TERMINAL_SUPPORT namespace terminal { PROGMEM_STRING(Locked, "I2C.LOCKED"); void locked(::terminal::CommandContext&& ctx) { for (size_t address = 0; address < lock::storage.size(); ++address) { if (lock::storage.test(address)) { ctx.output.printf_P(PSTR("0x%02X\n"), address); } } terminalOK(ctx); } PROGMEM_STRING(Scan, "I2C.SCAN"); void scan(::terminal::CommandContext&& ctx) { size_t devices { 0 }; i2c::scan([&](uint8_t address) { ++devices; ctx.output.printf_P(PSTR("0x%02X\n"), address); }); if (devices) { ctx.output.printf_P(PSTR("found %zu device(s)\n"), devices); terminalOK(ctx); return; } terminalError(ctx, F("no devices found")); } PROGMEM_STRING(Clear, "I2C.CLEAR"); void clear(::terminal::CommandContext&& ctx) { ctx.output.printf_P(PSTR("result %d\n"), i2c::clear()); terminalOK(ctx); } static constexpr ::terminal::Command Commands[] PROGMEM { {Locked, locked}, {Scan, scan}, {Clear, clear}, }; void setup() { espurna::terminal::add(Commands); } } // namespace terminal #endif // TERMINAL_SUPPORT } // namespace } // namespace i2c } // namespace espurna // --------------------------------------------------------------------- // I2C API // --------------------------------------------------------------------- #if I2C_USE_BRZO void i2c_wakeup(uint8_t address) { i2c::brzo_i2c_start_transaction(address); brzo_i2c_end_transaction(); } uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) { i2c::brzo_i2c_start_transaction(address); brzo_i2c_write(buffer, len, false); return brzo_i2c_end_transaction(); } uint8_t i2c_write_uint8(uint8_t address, uint8_t value) { uint8_t buffer[1] = {value}; return i2c_write_buffer(address, buffer, sizeof(buffer)); } uint8_t i2c_read_uint8(uint8_t address) { uint8_t buffer[1] = {0}; i2c::brzo_i2c_start_transaction(address); brzo_i2c_read(buffer, 1, false); brzo_i2c_end_transaction(); return buffer[0]; } uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) { uint8_t buffer[1] = {reg}; i2c::brzo_i2c_start_transaction(address); brzo_i2c_write(buffer, 1, true); brzo_i2c_read(buffer, 1, false); brzo_i2c_end_transaction(); return buffer[0]; } uint16_t i2c_read_uint16(uint8_t address) { uint8_t buffer[2] = {0, 0}; i2c::brzo_i2c_start_transaction(address); brzo_i2c_read(buffer, 2, false); brzo_i2c_end_transaction(); return (buffer[0] * 256) | buffer[1]; } uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) { uint8_t buffer[2] = {reg, 0}; i2c::brzo_i2c_start_transaction(address); brzo_i2c_write(buffer, 1, true); brzo_i2c_read(buffer, 2, false); brzo_i2c_end_transaction(); return (buffer[0] * 256) | buffer[1]; } void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) { i2c::start_brzo_transaction(address); brzo_i2c_read(buffer, len, false); brzo_i2c_end_transaction(); } #else // not I2C_USE_BRZO void i2c_wakeup(uint8_t address) { Wire.beginTransmission((uint8_t) address); Wire.endTransmission(); } uint8_t i2c_write_uint8(uint8_t address, uint8_t value) { Wire.beginTransmission((uint8_t) address); Wire.write((uint8_t) value); return Wire.endTransmission(); } uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) { Wire.beginTransmission((uint8_t) address); Wire.write(buffer, len); return Wire.endTransmission(); } uint8_t i2c_read_uint8(uint8_t address) { uint8_t value; Wire.requestFrom((uint8_t) address, (uint8_t) 1); value = Wire.read(); return value; } uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) { uint8_t value; Wire.beginTransmission((uint8_t) address); Wire.write((uint8_t) reg); Wire.endTransmission(); Wire.requestFrom((uint8_t) address, (uint8_t) 1); value = Wire.read(); return value; } uint16_t i2c_read_uint16(uint8_t address) { uint16_t value; Wire.requestFrom((uint8_t) address, (uint8_t) 2); value = (Wire.read() * 256) | Wire.read(); return value; } uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) { uint16_t value; Wire.beginTransmission((uint8_t) address); Wire.write((uint8_t) reg); Wire.endTransmission(); Wire.requestFrom((uint8_t) address, (uint8_t) 2); value = (Wire.read() * 256) | Wire.read(); return value; } void i2c_read_buffer(uint8_t address, uint8_t* buffer, size_t len) { Wire.requestFrom(address, (uint8_t) len); for (size_t i=0; i> 8) & 0xff); Wire.write(reg & 0xff); uint8_t buf[sizeof(input)]; std::memcpy(&buf[0], &input, sizeof(buf)); Wire.write(&buf[sizeof(buf) - size], size); Wire.endTransmission(); } } uint32_t i2c_read_uint(uint8_t address, uint16_t reg, size_t size, bool stop) { uint32_t out { 0 }; if (size <= sizeof(out)) { Wire.beginTransmission(address); Wire.write((reg >> 8) & 0xff); Wire.write(reg & 0xff); Wire.endTransmission(stop); if (size == Wire.requestFrom(address, size)) { for (size_t byte = 0; byte < size; --byte) { out = (out << 8ul) | static_cast(Wire.read()); } } } return out; } #endif // I2C_USE_BRZO uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value) { uint8_t buffer[2] = {reg, value}; return i2c_write_buffer(address, buffer, 2); } uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2) { uint8_t buffer[3] = {reg, value1, value2}; return i2c_write_buffer(address, buffer, 3); } uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value) { uint8_t buffer[3]; buffer[0] = reg; buffer[1] = (value >> 8) & 0xFF; buffer[2] = (value >> 0) & 0xFF; return i2c_write_buffer(address, buffer, 3); } uint8_t i2c_write_uint16(uint8_t address, uint16_t value) { uint8_t buffer[2]; buffer[0] = (value >> 8) & 0xFF; buffer[1] = (value >> 0) & 0xFF; return i2c_write_buffer(address, buffer, 2); } uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) { uint16_t temp = i2c_read_uint16(address, reg); return (temp / 256) | (temp * 256); } int16_t i2c_read_int16(uint8_t address, uint8_t reg) { return (int16_t) i2c_read_uint16(address, reg); } int16_t i2c_read_int16_le(uint8_t address, uint8_t reg) { return (int16_t) i2c_read_uint16_le(address, reg); } // ----------------------------------------------------------------------------- // Utils // ----------------------------------------------------------------------------- int i2cClearBus() { return espurna::i2c::clear(); } bool i2cLock(uint8_t address) { return espurna::i2c::lock::set(address); } void i2cUnlock(uint8_t address) { espurna::i2c::lock::reset(address); } uint8_t i2cFind(uint8_t address) { return espurna::i2c::find(address); } uint8_t i2cFind(const uint8_t* begin, const uint8_t* end) { return espurna::i2c::find(begin, end); } uint8_t i2cFindAndLock(const uint8_t* begin, const uint8_t* end) { return espurna::i2c::findAndLock(begin, end); } void i2cSetup() { espurna::i2c::init(); #if TERMINAL_SUPPORT espurna::i2c::terminal::setup(); #endif if (espurna::i2c::build::performScanOnBoot()) { espurna::i2c::bootScan(); } } #endif