/* I2C Master/Slave library for the Raspberry Pi Pico RP2040 Copyright (c) 2021 Earle F. Philhower, III Based off of TWI/I2C library for Arduino Zero Copyright (c) 2015 Arduino LLC. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "Wire.h" #ifdef USE_TINYUSB // For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE // will not link in libraries called from the core. Instead, add the header to all // the standard libraries in the hope it will still catch some user cases where they // use these libraries. // See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174 #include #endif TwoWire::TwoWire(i2c_inst_t *i2c, pin_size_t sda, pin_size_t scl) { _sda = sda; _scl = scl; _i2c = i2c; _clkHz = TWI_CLOCK; _running = false; _txBegun = false; _buffLen = 0; } bool TwoWire::setSDA(pin_size_t pin) { constexpr uint32_t valid[2] = { __bitset({0, 4, 8, 12, 16, 20, 24, 28}) /* I2C0 */, __bitset({2, 6, 10, 14, 18, 22, 26}) /* I2C1 */ }; if ((!_running) && ((1 << pin) & valid[i2c_hw_index(_i2c)])) { _sda = pin; return true; } if (_running) { panic("FATAL: Attempting to set Wire%s.SDA while running", i2c_hw_index(_i2c) ? "1" : ""); } else { panic("FATAL: Attempting to set Wire%s.SDA to illegal pin %d", i2c_hw_index(_i2c) ? "1" : "", pin); } return false; } bool TwoWire::setSCL(pin_size_t pin) { constexpr uint32_t valid[2] = { __bitset({1, 5, 9, 13, 17, 21, 25, 29}) /* I2C0 */, __bitset({3, 7, 11, 15, 19, 23, 27}) /* I2C1 */ }; if ((!_running) && ((1 << pin) & valid[i2c_hw_index(_i2c)])) { _scl = pin; return true; } if (_running) { panic("FATAL: Attempting to set Wire%s.SCL while running", i2c_hw_index(_i2c) ? "1" : ""); } else { panic("FATAL: Attempting to set Wire%s.SCL to illegal pin %d", i2c_hw_index(_i2c) ? "1" : "", pin); } return false; } void TwoWire::setClock(uint32_t hz) { _clkHz = hz; if (_running) { i2c_set_baudrate(_i2c, hz); } } // Master mode void TwoWire::begin() { if (_running) { // ERROR return; } _slave = false; i2c_init(_i2c, _clkHz); i2c_set_slave_mode(_i2c, false, 0); gpio_set_function(_sda, GPIO_FUNC_I2C); gpio_pull_up(_sda); gpio_set_function(_scl, GPIO_FUNC_I2C); gpio_pull_up(_scl); _running = true; _txBegun = false; _buffLen = 0; } static void _handler0() { Wire.onIRQ(); } static void _handler1() { Wire1.onIRQ(); } // Slave mode void TwoWire::begin(uint8_t addr) { if (_running) { // ERROR return; } _slave = true; i2c_init(_i2c, _clkHz); i2c_set_slave_mode(_i2c, true, addr); // Our callback IRQ _i2c->hw->intr_mask = (1 << 12) | (1 << 10) | (1 << 9) | (1 << 6) | (1 << 5) | (1 << 2); int irqNo = I2C0_IRQ + i2c_hw_index(_i2c); irq_set_exclusive_handler(irqNo, i2c_hw_index(_i2c) == 0 ? _handler0 : _handler1); irq_set_enabled(irqNo, true); gpio_set_function(_sda, GPIO_FUNC_I2C); gpio_pull_up(_sda); gpio_set_function(_scl, GPIO_FUNC_I2C); gpio_pull_up(_scl); _running = true; } // See: https://github.com/earlephilhower/arduino-pico/issues/979#issuecomment-1328237128 #pragma GCC push_options #pragma GCC optimize ("O0") void TwoWire::onIRQ() { // Make a local copy of the IRQ status up front. If it changes while we're // running the IRQ callback will fire again after returning. Avoids potential // race conditions uint32_t irqstat = _i2c->hw->intr_stat; if (irqstat == 0) { return; } // First, pull off any data available if (irqstat & (1 << 2)) { // RX_FULL if (_buffLen < (int)sizeof(_buff)) { _buff[_buffLen++] = _i2c->hw->data_cmd & 0xff; } else { _i2c->hw->data_cmd; } } // RD_REQ if (irqstat & (1 << 5)) { if (_onRequestCallback) { _onRequestCallback(); } _i2c->hw->clr_rd_req; } // TX_ABRT if (irqstat & (1 << 6)) { _i2c->hw->clr_tx_abrt; } // START_DET if (irqstat & (1 << 10)) { _slaveStartDet = true; _i2c->hw->clr_start_det; } // RESTART_DET if (irqstat & (1 << 12)) { if (_onReceiveCallback && _buffLen) { _onReceiveCallback(_buffLen); } _buffLen = 0; _buffOff = 0; _slaveStartDet = false; _i2c->hw->clr_restart_det; } // STOP_DET if (irqstat & (1 << 9)) { if (_onReceiveCallback && _buffLen) { _onReceiveCallback(_buffLen); } _buffLen = 0; _buffOff = 0; _slaveStartDet = false; _i2c->hw->clr_stop_det; } } #pragma GCC pop_options void TwoWire::end() { if (!_running) { // ERROR return; } if (_slave) { int irqNo = I2C0_IRQ + i2c_hw_index(_i2c); irq_remove_handler(irqNo, i2c_hw_index(_i2c) == 0 ? _handler0 : _handler1); irq_set_enabled(irqNo, false); } i2c_deinit(_i2c); pinMode(_sda, INPUT); pinMode(_scl, INPUT); _running = false; _txBegun = false; } void TwoWire::beginTransmission(uint8_t addr) { if (!_running || _txBegun) { // ERROR return; } _addr = addr; _buffLen = 0; _buffOff = 0; _txBegun = true; } size_t TwoWire::requestFrom(uint8_t address, size_t quantity, bool stopBit) { if (!_running || _txBegun || !quantity || (quantity > sizeof(_buff))) { return 0; } _buffLen = i2c_read_blocking_until(_i2c, address, _buff, quantity, !stopBit, make_timeout_time_ms(_timeout)); if ((_buffLen == PICO_ERROR_GENERIC) || (_buffLen == PICO_ERROR_TIMEOUT)) { _buffLen = 0; } _buffOff = 0; return _buffLen; } size_t TwoWire::requestFrom(uint8_t address, size_t quantity) { return requestFrom(address, quantity, true); } static bool _clockStretch(pin_size_t pin) { auto end = time_us_64() + 100; while ((time_us_64() < end) && (!digitalRead(pin))) { /* noop */ } return digitalRead(pin); } bool _probe(int addr, pin_size_t sda, pin_size_t scl, int freq) { int delay = (1000000 / freq) / 2; bool ack = false; pinMode(sda, INPUT_PULLUP); pinMode(scl, INPUT_PULLUP); gpio_set_function(scl, GPIO_FUNC_SIO); gpio_set_function(sda, GPIO_FUNC_SIO); digitalWrite(sda, HIGH); sleep_us(delay); digitalWrite(scl, HIGH); if (!_clockStretch(scl)) { goto stop; } digitalWrite(sda, LOW); sleep_us(delay); digitalWrite(scl, LOW); sleep_us(delay); for (int i = 0; i < 8; i++) { addr <<= 1; digitalWrite(sda, (addr & (1 << 7)) ? HIGH : LOW); sleep_us(delay); digitalWrite(scl, HIGH); sleep_us(delay); if (!_clockStretch(scl)) { goto stop; } digitalWrite(scl, LOW); sleep_us(5); // Ensure we don't change too close to clock edge } digitalWrite(sda, HIGH); sleep_us(delay); digitalWrite(scl, HIGH); if (!_clockStretch(scl)) { goto stop; } ack = digitalRead(sda) == LOW; sleep_us(delay); digitalWrite(scl, LOW); stop: sleep_us(delay); digitalWrite(sda, LOW); sleep_us(delay); digitalWrite(scl, HIGH); sleep_us(delay); digitalWrite(sda, HIGH); sleep_us(delay); gpio_set_function(scl, GPIO_FUNC_I2C); gpio_set_function(sda, GPIO_FUNC_I2C); return ack; } // Errors: // 0 : Success // 1 : Data too long // 2 : NACK on transmit of address // 3 : NACK on transmit of data // 4 : Other error uint8_t TwoWire::endTransmission(bool stopBit) { if (!_running || !_txBegun) { return 4; } _txBegun = false; if (!_buffLen) { // Special-case 0-len writes which are used for I2C probing return _probe(_addr, _sda, _scl, _clkHz) ? 0 : 2; } else { auto len = _buffLen; auto ret = i2c_write_blocking_until(_i2c, _addr, _buff, _buffLen, !stopBit, make_timeout_time_ms(_timeout)); _buffLen = 0; return (ret == len) ? 0 : 4; } } uint8_t TwoWire::endTransmission() { return endTransmission(true); } size_t TwoWire::write(uint8_t ucData) { if (!_running) { return 0; } if (_slave) { // Wait for a spot in the TX FIFO while (0 == (_i2c->hw->status & (1 << 1))) { /* noop wait */ } _i2c->hw->data_cmd = ucData; return 1; } else { if (!_txBegun || (_buffLen == sizeof(_buff))) { return 0; } _buff[_buffLen++] = ucData; return 1 ; } } size_t TwoWire::write(const uint8_t *data, size_t quantity) { for (size_t i = 0; i < quantity; ++i) { if (!write(data[i])) { return i; } } return quantity; } int TwoWire::available(void) { return _running ? _buffLen - _buffOff : 0; } int TwoWire::read(void) { if (available()) { return _buff[_buffOff++]; } return -1; // EOF } int TwoWire::peek(void) { if (available()) { return _buff[_buffOff]; } return -1; // EOF } void TwoWire::flush(void) { // Do nothing, use endTransmission(..) to force // data transfer. } void TwoWire::onReceive(void(*function)(int)) { _onReceiveCallback = function; } void TwoWire::onRequest(void(*function)(void)) { _onRequestCallback = function; } #ifndef __WIRE0_DEVICE #define __WIRE0_DEVICE i2c0 #endif #ifndef __WIRE1_DEVICE #define __WIRE1_DEVICE i2c1 #endif TwoWire Wire(__WIRE0_DEVICE, PIN_WIRE0_SDA, PIN_WIRE0_SCL); TwoWire Wire1(__WIRE1_DEVICE, PIN_WIRE1_SDA, PIN_WIRE1_SCL);