Files
earlephilhower_arduino-pico/libraries/Wire/Wire.cpp
T
Earle F. Philhower, III 1fd0b0404d Add TinyUSB include to all libs (#180)
Fixes #167

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
2021-05-28 19:23:19 -07:00

354 lines
8.8 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) {
return false;
} else if ((1 << pin) & valid[i2c_hw_index(_i2c)]) {
_sda = pin;
return true;
} else {
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) {
return false;
} else if ((1 << pin) & valid[i2c_hw_index(_i2c)]) {
_scl = pin;
return true;
} else {
return false;
}
}
void TwoWire::setClock(uint32_t hz) {
_clkHz = 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 << 10) | (1 << 9) | (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;
}
void TwoWire::onIRQ() {
if (_i2c->hw->intr_stat & (1 << 10)) {
_buffLen = 0;
_buffOff = 0;
_slaveStartDet = true;
_i2c->hw->clr_start_det;
}
if (_i2c->hw->intr_stat & (1 << 9)) {
if (_onReceiveCallback) {
_onReceiveCallback(_buffLen);
}
_buffLen = 0;
_buffOff = 0;
_slaveStartDet = false;
_i2c->hw->clr_stop_det;
}
if (_i2c->hw->intr_stat & (1 << 5)) {
// RD_REQ
if (_onRequestCallback) {
_onRequestCallback();
}
_i2c->hw->clr_rd_req;
}
if (_i2c->hw->intr_stat & (1 << 2)) {
// RX_FULL
if (_slaveStartDet && (_buffLen < (int)sizeof(_buff))) {
_buff[_buffLen++] = _i2c->hw->data_cmd & 0xff;
} else {
_i2c->hw->data_cmd;
}
}
}
void TwoWire::end() {
if (!_running) {
// ERROR
return;
}
i2c_deinit(_i2c);
_running = false;
_txBegun = false;
}
void TwoWire::beginTransmission(uint8_t addr) {
if (!_running || _txBegun) {
// ERROR
return;
}
_addr = addr;
_buffLen = 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(50));
_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(50));
_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;
}
TwoWire Wire(i2c0, PIN_WIRE0_SDA, PIN_WIRE0_SCL);
TwoWire Wire1(i2c1, PIN_WIRE1_SDA, PIN_WIRE1_SCL);