Files
earlephilhower_arduino-pico/libraries/Wire/src/Wire.cpp
T
Earle F. Philhower, III 777eacdfc3 Wire probe clock stretch up to 500us (#2493)
As reported by @Rasmusfk in #2455, allow for a longer
clock stretch period during probes.
2024-09-24 06:58:41 -07:00

749 lines
22 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/dma.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) {
#ifdef PICO_RP2350B
constexpr uint64_t valid[2] = { __bitset({0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44}) /* I2C0 */,
__bitset({2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46}) /* I2C1 */
};
#else
constexpr uint64_t valid[2] = { __bitset({0, 4, 8, 12, 16, 20, 24, 28}) /* I2C0 */,
__bitset({2, 6, 10, 14, 18, 22, 26}) /* I2C1 */
};
#endif
if ((!_running) && ((1LL << pin) & valid[i2c_hw_index(_i2c)])) {
_sda = pin;
return true;
}
if (_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) {
#ifdef PICO_RP2350B
constexpr uint64_t valid[2] = { __bitset({1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45}) /* I2C0 */,
__bitset({3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47}) /* I2C1 */
};
#else
constexpr uint64_t valid[2] = { __bitset({1, 5, 9, 13, 17, 21, 25, 29}) /* I2C0 */,
__bitset({3, 7, 11, 15, 19, 23, 27}) /* I2C1 */
};
#endif
if ((!_running) && ((1LL << pin) & valid[i2c_hw_index(_i2c)])) {
_scl = pin;
return true;
}
if (_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() {
#if defined(__WIRE0_DEVICE)
if (__WIRE0_DEVICE == i2c0) {
Wire.onIRQ();
} else {
Wire1.onIRQ();
}
#else
Wire.onIRQ();
#endif
}
static void _handler1() {
#if defined(__WIRE1_DEVICE)
if (__WIRE1_DEVICE == i2c0) {
Wire.onIRQ();
} else {
Wire1.onIRQ();
}
#else
Wire1.onIRQ();
#endif
}
// 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() {
endAsync();
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)) {
if (_buffLen == PICO_ERROR_TIMEOUT) {
_handleTimeout(_reset_with_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() + 500;
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;
}
void TwoWire::_handleTimeout(bool reset) {
_timeoutFlag = true;
if (reset) {
if (_slave) {
uint8_t prev_addr = _addr;
int prev_clkHz = _clkHz;
end();
setClock(prev_clkHz);
begin(prev_addr);
} else {
int prev_clkHz = _clkHz;
end();
// Attempt bus recovery if SDA is held LOW by another device
// See RP2040 datasheet "Bus clear feature" (not implemented in HW)
int delay = 5; //5us LOW/HIGH -> 10us period -> 100kHz freq
pinMode(_sda, INPUT_PULLUP);
pinMode(_scl, INPUT_PULLUP);
gpio_set_function(_scl, GPIO_FUNC_SIO);
gpio_set_function(_sda, GPIO_FUNC_SIO);
if (digitalRead(_sda) == LOW) {
int sclPulseCount = 0;
while (sclPulseCount < 9 && digitalRead(_sda) == LOW) {
sclPulseCount++;
digitalWrite(_scl, LOW);
sleep_us(delay);
digitalWrite(_scl, HIGH);
sleep_us(delay);
}
if (digitalRead(_sda) == HIGH) {
// Bus recovered : send a STOP
digitalWrite(_sda, LOW);
sleep_us(delay);
digitalWrite(_sda, HIGH);
}
}
setClock(prev_clkHz);
begin();
}
}
}
// Errors:
// 0 : Success
// 1 : Data too long
// 2 : NACK on transmit of address
// 3 : NACK on transmit of data
// 4 : Other error
// 5 : Timeout
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));
if (ret == PICO_ERROR_TIMEOUT) {
_handleTimeout(_reset_with_timeout);
return 5;
}
_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 and return in case of timeout
auto end = make_timeout_time_ms(_timeout);
while ((i2c_get_write_available(_i2c) == 0) && !time_reached(end)) { /* noop wait */ }
if (i2c_get_write_available(_i2c) > 0) {
_i2c->hw->data_cmd = ucData;
return 1;
} else {
_handleTimeout(_reset_with_timeout);
return 0;
}
} 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.
}
// DMA/asynchronous transfers. Do not combime with synchronous runs or bad stuff will happen
// All buffers must be valid for entire DMA and not touched until `finishedAsync()` returns true.
bool TwoWire::writeReadAsync(uint8_t address, const void *wbuffer, size_t wbytes, const void *rbuffer, size_t rbytes, bool sendStop) {
if (!_running || _txBegun || (wbytes == 0 && rbytes == 0)) {
return false;
}
if (!_dmaRunning) {
beginAsync();
if (!_dmaRunning) {
return false;
}
}
// Abort any ongoing transaction
abortAsync();
// Create or enlarge dma command buffer, we need one entry for every i2c byte we want to write/read
const size_t bufLen = (wbytes + rbytes) * 2;
if (_dmaSendBufferLen < bufLen) {
if (_dmaSendBuffer) {
free(_dmaSendBuffer);
_dmaSendBuffer = nullptr;
_dmaSendBufferLen = 0;
}
_dmaSendBuffer = (uint16_t *)malloc(bufLen);
if (!_dmaSendBuffer) {
return false;
}
}
// Fill the dma command buffer
for (size_t i = 0; i < wbytes; i++) {
_dmaSendBuffer[i] = ((uint8_t*) wbuffer)[i];
}
for (size_t i = 0; i < rbytes; i++) {
_dmaSendBuffer[wbytes + i] = I2C_IC_DATA_CMD_CMD_BITS; // -> 1 for read
}
if (_i2c->restart_on_next) {
_dmaSendBuffer[0] |= I2C_IC_DATA_CMD_RESTART_BITS;
}
if (wbytes > 0 && rbytes > 0) {
_dmaSendBuffer[wbytes + 0] |= I2C_IC_DATA_CMD_RESTART_BITS;
}
if (sendStop) {
_dmaSendBuffer[wbytes + rbytes - 1] |= I2C_IC_DATA_CMD_STOP_BITS;
}
// Cleanup and Setup dma send channel
dma_channel_cleanup(_dmaChannelSend);
dma_channel_config c = dma_channel_get_default_config(_dmaChannelSend);
channel_config_set_transfer_data_size(&c, DMA_SIZE_16); // 16b transfers into I2C FIFO
channel_config_set_read_increment(&c, true); // Reading incrementing addresses
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
channel_config_set_dreq(&c, i2c_get_dreq(_i2c, true)); // Wait for the TX FIFO specified
channel_config_set_chain_to(&c, _dmaChannelSend); // No chaining
channel_config_set_irq_quiet(&c, false); // Enable interrupt (can be disabled later with dma_channel_set_irq0_enabled() as needed)
dma_channel_configure(_dmaChannelSend, &c, &_i2c->hw->data_cmd, _dmaSendBuffer, wbytes + rbytes, false);
// Cleanup and setup dma receive channel
dma_channel_cleanup(_dmaChannelReceive);
c = dma_channel_get_default_config(_dmaChannelReceive);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8); // 8b transfers from I2C FIFO
channel_config_set_read_increment(&c, false); // Reading same FIFO address
channel_config_set_write_increment(&c, true); // Writing to the buffer
channel_config_set_dreq(&c, i2c_get_dreq(_i2c, false)); // Wait for the RX FIFO specified
channel_config_set_chain_to(&c, _dmaChannelReceive); // No chaining
channel_config_set_irq_quiet(&c, false); // Enable interrupt (can be disabled later with dma_channel_set_irq0_enabled() as needed)
dma_channel_configure(_dmaChannelReceive, &c, (void*) rbuffer, &_i2c->hw->data_cmd, rbytes, false);
// Enable dma completed interrupt
dma_channel_set_irq0_enabled(_dmaChannelSend, (rbytes == 0)); //write only, enable irq on Send channel
dma_channel_set_irq0_enabled(_dmaChannelReceive, (rbytes > 0)); //when reading, enable irq on Receive channel
// Setup i2c hardware
_i2c->hw->enable = 0;
_i2c->hw->tar = address;
_i2c->hw->dma_cr = 1 << 1 | (rbytes > 0 ? 1 : 0) ; // TDMAE + RDMAE when rbytes>0
_i2c->hw->enable = 1;
_i2c->restart_on_next = !sendStop;
// Start dma channel(s)
_txBegun = true;
_dmaFinished = false;
if (rbytes > 0) {
dma_channel_start(_dmaChannelReceive);
}
dma_channel_start(_dmaChannelSend);
return true;
}
bool TwoWire::writeAsync(uint8_t address, const void *buffer, size_t bytes, bool sendStop) {
return writeReadAsync(address, buffer, bytes, nullptr, 0, sendStop);
}
bool TwoWire::readAsync(uint8_t address, void *buffer, size_t bytes, bool sendStop) {
return writeReadAsync(address, nullptr, 0, buffer, bytes, sendStop);
}
bool TwoWire::finishedAsync() {
return _dmaFinished;
}
void TwoWire::abortAsync() {
if (!_dmaRunning) {
return;
}
if (!_dmaFinished) {
dma_channel_abort(_dmaChannelSend);
dma_channel_abort(_dmaChannelReceive);
_i2c->hw->dma_cr = 0;
}
_txBegun = false;
_dmaFinished = true;
}
void TwoWire::onFinishedAsync(void(*function)(void)) {
_dmaOnFinished = function;
}
// Dma irq mask and wire instance for low level dma completed handlers
static uint32_t _dma_i2c0_irq_mask = 0;
static uint32_t _dma_i2c1_irq_mask = 0;
static TwoWire * _dma_i2c0_wire_instance = nullptr;
static TwoWire * _dma_i2c1_wire_instance = nullptr;
// Low level dma completed handlers, calls TwoWire::_dma_irq_handler() to do the work
void _dma_i2c0_irq_handler() {
uint32_t status = dma_hw->ints0;
if (status & _dma_i2c0_irq_mask && _dma_i2c0_wire_instance) {
_dma_i2c0_wire_instance->_dma_irq_handler();
}
dma_hw->ints0 = (status & _dma_i2c0_irq_mask); //clear interrupt status
}
void _dma_i2c1_irq_handler() {
uint32_t status = dma_hw->ints0;
if (status & _dma_i2c1_irq_mask && _dma_i2c1_wire_instance) {
_dma_i2c1_wire_instance->_dma_irq_handler();
}
dma_hw->ints0 = (status & _dma_i2c1_irq_mask); //clear interrupt status
}
void TwoWire::_dma_irq_handler() {
_i2c->hw->dma_cr = 0;
_txBegun = false;
_dmaFinished = true;
// Disable the DMA IRQs
dma_channel_set_irq0_enabled(_dmaChannelSend, false);
dma_channel_set_irq0_enabled(_dmaChannelReceive, false);
// Call the user handler
if (_dmaOnFinished) {
_dmaOnFinished();
}
}
void TwoWire::beginAsync() {
if (_dmaRunning) {
return;
}
// Claim dma channels
_dmaChannelReceive = dma_claim_unused_channel(false);
if (_dmaChannelReceive == -1) {
return;
}
_dmaChannelSend = dma_claim_unused_channel(false);
if (_dmaChannelSend == -1) {
dma_channel_unclaim(_dmaChannelReceive);
return;
}
// Setup dma irq
if (i2c_hw_index(_i2c) == 0) {
_dma_i2c0_irq_mask = (1u << _dmaChannelReceive) | (1u << _dmaChannelSend);
_dma_i2c0_wire_instance = this;
irq_add_shared_handler(DMA_IRQ_0, _dma_i2c0_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
} else {
_dma_i2c1_irq_mask = (1u << _dmaChannelReceive) | (1u << _dmaChannelSend);
_dma_i2c1_wire_instance = this;
irq_add_shared_handler(DMA_IRQ_0, _dma_i2c1_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
}
irq_set_enabled(DMA_IRQ_0, true);
_dmaRunning = true;
}
void TwoWire::endAsync() {
if (!_dmaRunning) {
return;
}
if (i2c_hw_index(_i2c) == 0) {
_dma_i2c0_irq_mask = 0;
_dma_i2c0_wire_instance = nullptr;
irq_remove_handler(DMA_IRQ_0, _dma_i2c0_irq_handler);
} else {
_dma_i2c1_irq_mask = 0;
_dma_i2c1_wire_instance = nullptr;
irq_remove_handler(DMA_IRQ_0, _dma_i2c1_irq_handler);
}
if (_dmaChannelReceive >= 0) {
dma_channel_cleanup(_dmaChannelReceive);
dma_channel_unclaim(_dmaChannelReceive);
_dmaChannelReceive = -1;
}
if (_dmaChannelSend >= 0) {
dma_channel_cleanup(_dmaChannelSend);
dma_channel_unclaim(_dmaChannelSend);
_dmaChannelSend = -1;
}
_i2c->hw->dma_cr = 0;
free(_dmaSendBuffer);
_dmaSendBuffer = nullptr;
_dmaSendBufferLen = 0;
_txBegun = false;
_dmaFinished = true;
_dmaOnFinished = nullptr;
_dmaRunning = false;
}
void TwoWire::onReceive(void(*function)(int)) {
_onReceiveCallback = function;
}
void TwoWire::onRequest(void(*function)(void)) {
_onRequestCallback = function;
}
void TwoWire::setTimeout(uint32_t timeout, bool reset_with_timeout) {
_timeoutFlag = false;
Stream::setTimeout(timeout);
_reset_with_timeout = reset_with_timeout;
}
bool TwoWire::getTimeoutFlag() {
return _timeoutFlag;
}
void TwoWire::clearTimeoutFlag() {
_timeoutFlag = false;
}
#ifndef __WIRE0_DEVICE
#define __WIRE0_DEVICE i2c0
#endif
#ifndef __WIRE1_DEVICE
#define __WIRE1_DEVICE i2c1
#endif
#ifdef PIN_WIRE0_SDA
TwoWire Wire(__WIRE0_DEVICE, PIN_WIRE0_SDA, PIN_WIRE0_SCL);
#endif
#ifdef PIN_WIRE1_SDA
TwoWire Wire1(__WIRE1_DEVICE, PIN_WIRE1_SDA, PIN_WIRE1_SCL);
#endif