arduino-pico/libraries/Wire/src/Wire.cpp
2023-01-06 12:23:53 -08:00

409 lines
11 KiB
C++

/*
I2C Master/Slave library for the Raspberry Pi Pico RP2040
Copyright (c) 2021 Earle F. Philhower, III <earlephilhower@yahoo.com>
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 <Arduino.h>
#include <hardware/gpio.h>
#include <hardware/i2c.h>
#include <hardware/irq.h>
#include <hardware/regs/intctrl.h>
#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 <Adafruit_TinyUSB.h>
#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);