1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-30 03:26:13 +01:00

I2CManager: Add support for I2C Multiplexers to Wire and AVR.

AVR Native (non-blocking) driver now supports up to 8 I2C Multiplexers, as does any controller that uses Wire for I2C.
Other native drivers will be updated in due course.
This commit is contained in:
Neil McKechnie 2023-02-07 16:38:30 +00:00
parent 261ccf2f3b
commit cb287f23a4
7 changed files with 279 additions and 92 deletions

View File

@ -41,6 +41,28 @@
#endif #endif
// Helper function for listing device types
static const FSH * guessI2CDeviceType(uint8_t address) {
if (address >= 0x20 && address <= 0x26)
return F("GPIO Expander");
else if (address == 0x27)
return F("GPIO Expander or LCD Display");
else if (address == 0x29)
return F("Time-of-flight sensor");
else if (address >= 0x3c && address <= 0x3c)
return F("OLED Display");
else if (address >= 0x48 && address <= 0x4f)
return F("Analogue Inputs or PWM");
else if (address >= 0x40 && address <= 0x4f)
return F("PWM");
else if (address >= 0x50 && address <= 0x5f)
return F("EEPROM");
else if (address >= 0x70 && address <= 0x77)
return F("I2C Mux");
else
return F("?");
}
// If not already initialised, initialise I2C // If not already initialised, initialise I2C
void I2CManagerClass::begin(void) { void I2CManagerClass::begin(void) {
if (!_beginCompleted) { if (!_beginCompleted) {
@ -60,34 +82,46 @@ void I2CManagerClass::begin(void) {
setTimeout(1000); // use 1ms timeout for probes setTimeout(1000); // use 1ms timeout for probes
#if defined(I2C_EXTENDED_ADDRESS) #if defined(I2C_EXTENDED_ADDRESS)
// First switch off all multiplexer subbuses. // First count the multiplexers and switch off all subbuses
_muxCount = 0;
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK)
_muxCount++;
} }
#endif #endif
// Enumerate devices that are visible
bool found = false; bool found = false;
for (uint8_t addr=0x08; addr<0x78; addr++) { for (uint8_t addr=0x08; addr<0x78; addr++) {
if (exists(addr)) { if (exists(addr)) {
found = true; found = true;
DIAG(F("I2C Device found at x%x"), addr); DIAG(F("I2C Device found at x%x, %S?"), addr, guessI2CDeviceType(addr));
} }
} }
#if defined(I2C_EXTENDED_ADDRESS) #if defined(I2C_EXTENDED_ADDRESS)
// Enumerate all I2C devices that are connected via multiplexer, // Enumerate all I2C devices that are connected via multiplexer,
// i.e. respond when only one multiplexer has one subBus enabled // i.e. that respond when only one multiplexer has one subBus enabled
// and the device doesn't respond when the mux subBus is disabled. // and the device doesn't respond when the mux subBus is disabled.
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo;
if (exists(muxAddr)) { if (exists(muxAddr)) {
// Select Mux Subbus
for (uint8_t subBus=0; subBus<=7; subBus++) { for (uint8_t subBus=0; subBus<=7; subBus++) {
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
for (uint8_t addr=0x08; addr<0x78; addr++) { for (uint8_t addr=0x08; addr<0x78; addr++) {
if (exists({(I2CMux)muxNo, (I2CSubBus)subBus, addr}) if (exists(addr)) {
&& !exists({(I2CMux)muxNo, SubBus_None, addr})) { // De-select subbus
found = true; muxSelectSubBus({(I2CMux)muxNo, SubBus_None});
DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}"), if (!exists(addr)) {
muxNo, subBus, addr); // Device responds when subbus selected but not when
// subbus disabled - ergo it must be on subbus!
found = true;
DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}, %S?"),
muxNo, subBus, addr, guessI2CDeviceType(addr));
}
// Re-select subbus
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
} }
} }
} }
@ -232,6 +266,14 @@ I2CManagerClass I2CManager = I2CManagerClass();
// try, and failure from timeout does not get retried. // try, and failure from timeout does not get retried.
unsigned long I2CManagerClass::timeout = 100000UL; unsigned long I2CManagerClass::timeout = 100000UL;
#if defined(I2C_EXTENDED_ADDRESS)
// Count of I2C multiplexers found when initialising. If there is only one
// MUX then the subbus does not de-selecting after use; however, if there
// is two or more, then the subbus must be deselected to avoid multiple
// sub-bus legs on different multiplexers being accessible simultaneously.
uint8_t I2CManagerClass::_muxCount = 0;
#endif
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Helper functions associated with I2C Request Block // Helper functions associated with I2C Request Block

View File

@ -195,7 +195,7 @@ private:
public: public:
// Constructors // Constructors
// For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax.
I2CAddress(I2CMux muxNumber, I2CSubBus subBus, uint8_t deviceAddress) { I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) {
_muxNumber = muxNumber; _muxNumber = muxNumber;
_subBus = subBus; _subBus = subBus;
_deviceAddress = deviceAddress; _deviceAddress = deviceAddress;
@ -218,6 +218,11 @@ public:
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) :
I2CAddress(muxNumber, subBus, 0x00) {} I2CAddress(muxNumber, subBus, 0x00) {}
// For I2CAddress in form "{i2cAddress, deviceAddress}"
// where deviceAddress is to be on the same subbus as i2cAddress.
I2CAddress(I2CAddress firstAddress, uint8_t newDeviceAddress) :
I2CAddress(firstAddress._muxNumber, firstAddress._subBus, newDeviceAddress) {}
// Conversion operator from I2CAddress to uint8_t // Conversion operator from I2CAddress to uint8_t
// For "uint8_t address = i2cAddress;" syntax // For "uint8_t address = i2cAddress;" syntax
// (device assumed to be on the main I2C bus or on a currently selected subbus. // (device assumed to be on the main I2C bus or on a currently selected subbus.
@ -240,7 +245,7 @@ public:
// Field accessors // Field accessors
I2CMux muxNumber() { return _muxNumber; } I2CMux muxNumber() { return _muxNumber; }
I2CSubBus subBus() { return _subBus; } I2CSubBus subBus() { return _subBus; }
uint8_t address() { return _deviceAddress; } uint8_t deviceAddress() { return _deviceAddress; }
}; };
#else #else
@ -271,6 +276,7 @@ enum : uint8_t {
I2C_STATE_ACTIVE=253, I2C_STATE_ACTIVE=253,
I2C_STATE_FREE=254, I2C_STATE_FREE=254,
I2C_STATE_CLOSING=255, I2C_STATE_CLOSING=255,
I2C_STATE_COMPLETED=252,
}; };
typedef enum : uint8_t typedef enum : uint8_t
@ -369,20 +375,23 @@ private:
bool _beginCompleted = false; bool _beginCompleted = false;
bool _clockSpeedFixed = false; bool _clockSpeedFixed = false;
static uint8_t retryCounter; // Count of retries static uint8_t retryCounter; // Count of retries
#if defined(__arm__) // Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD
uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 // and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds
#else // will not be useful. The speed can be overridden by I2CManager::forceClock().
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. uint32_t _clockSpeed = I2C_FREQ;
#endif
static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled.
// Finish off request block by waiting for completion and posting status. // Finish off request block by waiting for completion and posting status.
uint8_t finishRB(I2CRB *rb, uint8_t status); uint8_t finishRB(I2CRB *rb, uint8_t status);
void _initialise(); void _initialise();
void _setClock(unsigned long); void _setClock(unsigned long);
#if defined(I2C_EXTENDED_ADDRESS)
static uint8_t _muxCount;
uint8_t getMuxCount() { return _muxCount; }
#endif
#if !defined(I2C_USE_WIRE) #if !defined(I2C_USE_WIRE)
// I2CRB structs are queued on the following two links. // I2CRB structs are queued on the following two links.
// If there are no requests, both are NULL. // If there are no requests, both are NULL.
@ -395,6 +404,7 @@ private:
static I2CRB * volatile queueHead; static I2CRB * volatile queueHead;
static I2CRB * volatile queueTail; static I2CRB * volatile queueTail;
static volatile uint8_t state; static volatile uint8_t state;
static uint8_t completionStatus;
static I2CRB * volatile currentRequest; static I2CRB * volatile currentRequest;
static volatile uint8_t txCount; static volatile uint8_t txCount;
@ -403,7 +413,7 @@ private:
static volatile uint8_t bytesToReceive; static volatile uint8_t bytesToReceive;
static volatile uint8_t operation; static volatile uint8_t operation;
static volatile unsigned long startTime; static volatile unsigned long startTime;
static volatile uint8_t muxSendStep; static volatile uint8_t muxPhase;
volatile uint32_t pendingClockSpeed = 0; volatile uint32_t pendingClockSpeed = 0;

View File

@ -101,8 +101,9 @@ void I2CManagerClass::I2C_sendStart() {
#if defined(I2C_EXTENDED_ADDRESS) #if defined(I2C_EXTENDED_ADDRESS)
if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) {
// Send request to multiplexer // Send request to multiplexer
muxSendStep = 1; // When start bit interrupt comes in, send SLA+W to MUX muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX
} } else
muxPhase = 0;
#endif #endif
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
@ -130,38 +131,132 @@ void I2CManagerClass::I2C_close() {
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function * if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()). * (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() { void I2CManagerClass::I2C_handleInterrupt() {
if (!(TWCR & (1<<TWINT))) return; // Nothing to do. if (!(TWCR & (1<<TWINT))) return; // Nothing to do.
uint8_t twsr = TWSR & 0xF8; uint8_t twsr = TWSR & 0xF8;
#if defined(I2C_EXTENDED_ADDRESS)
// First process the MUX state machine.
// This does not need to be entered during passthru phase unless the
// application's send and receive have both completed.
if (muxPhase > MuxPhase_OFF && !(muxPhase==MuxPhase_PASSTHRU && (bytesToSend || bytesToReceive))) {
switch (twsr) {
case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received
if (muxPhase == MuxPhase_PROLOG) {
// Send MUX selecter mask to follow address
I2CSubBus subBus = currentRequest->i2cAddress.subBus();
TWDR = (subBus==SubBus_All) ? 0xff :
(subBus==SubBus_None) ? 0x00 :
1 << subBus;
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
return;
} else if (muxPhase == MuxPhase_EPILOG) {
TWDR = 0x00; // Disable all subbuses
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
return;
}
break;
case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received
if (muxPhase == MuxPhase_PASSTHRU && !bytesToSend && !bytesToReceive) {
if (_muxCount > 1) {
// Device transaction complete, prepare to deselect MUX by sending start bit
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTO)|(1<<TWSTA);
muxPhase = MuxPhase_EPILOG;
return;
} else {
// Only one MUX so no need to deselect it. Just finish off
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
state = I2C_STATE_COMPLETED;
muxPhase = MuxPhase_OFF;
return;
}
} else if (muxPhase == MuxPhase_PROLOG) {
// If device address is zero, then finish here (i.e. send mux subBus mask only)
if (currentRequest->i2cAddress.deviceAddress() == 0) {
// Send stop and post rb.
TWDR = 0xff;
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
state = I2C_STATE_COMPLETED;
muxPhase = MuxPhase_OFF;
return;
} else {
// Send stop followed by start, preparing to send device address
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTO)|(1<<TWSTA);
muxPhase = MuxPhase_PASSTHRU;
return;
}
} else if (muxPhase == MuxPhase_EPILOG) {
// Send stop and allow RB to be posted.
TWDR = 0xff;
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
state = I2C_STATE_COMPLETED;
muxPhase = MuxPhase_OFF;
return;
}
break;
case TWI_MRX_DATA_NACK: // Last data byte has been received and NACK transmitted
// We must read the data before processing the MUX, so do this here.
if (bytesToReceive > 0) {
currentRequest->readBuffer[rxCount++] = TWDR;
bytesToReceive--;
}
if (muxPhase == MuxPhase_PASSTHRU && _muxCount > 1) {
// Prepare to transmit epilog to mux - first send the stop bit and start bit
// (we don't need to reset mux if there is only one.
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTO)|(1<<TWSTA);
muxPhase = MuxPhase_EPILOG;
return;
} else {
// Finish up.
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO); // Send Stop
state = I2C_STATE_COMPLETED;
muxPhase = MuxPhase_OFF;
return;
}
break;
case TWI_START: // START has been transmitted
case TWI_REP_START: // Repeated START has been transmitted
if (muxPhase == MuxPhase_PROLOG || muxPhase == MuxPhase_EPILOG) {
// Send multiplexer address first
uint8_t muxAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber();
TWDR = (muxAddress << 1) | 0; // MUXaddress+Write
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
return;
}
break;
case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received
case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received
case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received
if (muxPhase == MuxPhase_PASSTHRU) {
// Data transaction was nak'd, update RB status but continue with mux cleardown
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTO)|(1<<TWSTA); // Send Stop and start
muxPhase = MuxPhase_EPILOG;
return;
} else if (muxPhase > MuxPhase_EPILOG) {
// Mux Cleardown was NAK'd, send stop and then finish.
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO); // Send Stop
state = I2C_STATE_COMPLETED;
return;
}
break;
}
}
#endif
// Now the main I2C interrupt handler, used for the device communications.
//
// Cases are ordered so that the most frequently used ones are tested first. // Cases are ordered so that the most frequently used ones are tested first.
switch (twsr) { switch (twsr) {
case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received
case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received
#if defined(I2C_EXTENDED_ADDRESS) // Support multiplexer selection
if (muxSendStep == 2) {
muxSendStep = 3;
// Send MUX selecter mask following address
I2CSubBus subBus = currentRequest->i2cAddress.subBus();
uint8_t subBusMask = (subBus==SubBus_All) ? 0xff :
(subBus==SubBus_None) ? 0x00 :
1 << subBus;
TWDR = subBusMask;
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
} else if (muxSendStep == 3) {
muxSendStep = 0; // Mux command complete, reset sequence step number
// If device address is zero, then finish here (i.e. send mux subBus mask only)
if (currentRequest->i2cAddress.address() == 0 && bytesToSend == 0) {
// Send stop and post rb.
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO);
state = I2C_STATUS_OK;
} else {
// Send stop followed by start, preparing to send device address
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTO)|(1<<TWSTA);
}
} else
#endif
if (bytesToSend) { // Send first. if (bytesToSend) { // Send first.
if (operation == OPERATION_SEND_P) if (operation == OPERATION_SEND_P)
TWDR = GETFLASH(currentRequest->writeBuffer + (txCount++)); TWDR = GETFLASH(currentRequest->writeBuffer + (txCount++));
@ -173,18 +268,20 @@ void I2CManagerClass::I2C_handleInterrupt() {
// Don't need to wait for stop, as the interface won't send the start until // Don't need to wait for stop, as the interface won't send the start until
// any in-progress stop condition has been sent. // any in-progress stop condition has been sent.
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTA); // Send Start TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTA); // Send Start
} else { // Nothing left to send or receive } else {
TWDR = 0xff; // Default condition = SDA released // Nothing left to send or receive
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
} }
break; break;
case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted
if (bytesToReceive > 0) { if (bytesToReceive > 0) {
currentRequest->readBuffer[rxCount++] = TWDR; currentRequest->readBuffer[rxCount++] = TWDR;
bytesToReceive--; bytesToReceive--;
} }
/* fallthrough */ /* fallthrough */
case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received
if (bytesToReceive <= 1) { if (bytesToReceive <= 1) {
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception
@ -193,24 +290,18 @@ void I2CManagerClass::I2C_handleInterrupt() {
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA); TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
} }
break; break;
case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted
if (bytesToReceive > 0) { if (bytesToReceive > 0) {
currentRequest->readBuffer[rxCount++] = TWDR; currentRequest->readBuffer[rxCount++] = TWDR;
bytesToReceive--; bytesToReceive--;
} }
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
break; break;
case TWI_START: // START has been transmitted case TWI_START: // START has been transmitted
case TWI_REP_START: // Repeated START has been transmitted case TWI_REP_START: // Repeated START has been transmitted
#if defined(I2C_EXTENDED_ADDRESS)
if (muxSendStep == 1) {
muxSendStep = 2;
// Send multiplexer address first
uint8_t muxAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber();
TWDR = (muxAddress << 1) | 0; // MUXaddress+Write
} else
#endif
{ {
// Set up address and R/W // Set up address and R/W
uint8_t deviceAddress = currentRequest->i2cAddress; uint8_t deviceAddress = currentRequest->i2cAddress;
@ -218,25 +309,29 @@ void I2CManagerClass::I2C_handleInterrupt() {
TWDR = (deviceAddress << 1) | 1; // SLA+R TWDR = (deviceAddress << 1) | 1; // SLA+R
else else
TWDR = (deviceAddress << 1) | 0; // SLA+W TWDR = (deviceAddress << 1) | 0; // SLA+W
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
} }
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
break; break;
case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received
case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received
case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received
TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
break; break;
case TWI_ARB_LOST: // Arbitration lost case TWI_ARB_LOST: // Arbitration lost
// Restart transaction from start. // Restart transaction from start.
I2C_sendStart(); I2C_sendStart();
break; break;
case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition
default: default:
TWDR = 0xff; // Default condition = SDA released TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATUS_TRANSMIT_ERROR; completionStatus = I2C_STATUS_TRANSMIT_ERROR;
state = I2C_STATE_COMPLETED;
} }
} }

View File

@ -110,14 +110,17 @@ void I2CManagerClass::I2C_handleInterrupt() {
I2C_sendStart(); // Reinitiate request I2C_sendStart(); // Reinitiate request
} else if (currentStatus & TWI_BUSERR_bm) { } else if (currentStatus & TWI_BUSERR_bm) {
// Bus error // Bus error
state = I2C_STATUS_BUS_ERROR; completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
TWI0.MSTATUS = currentStatus; // clear all flags TWI0.MSTATUS = currentStatus; // clear all flags
} else if (currentStatus & TWI_WIF_bm) { } else if (currentStatus & TWI_WIF_bm) {
// Master write completed // Master write completed
if (currentStatus & TWI_RXACK_bm) { if (currentStatus & TWI_RXACK_bm) {
// Nacked, send stop. // Nacked, send stop.
TWI0.MCTRLB = TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_MCMD_STOP_gc;
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) { } else if (bytesToSend) {
// Acked, so send next byte (don't need to use GETFLASH) // Acked, so send next byte (don't need to use GETFLASH)
TWI0.MDATA = currentRequest->writeBuffer[txCount++]; TWI0.MDATA = currentRequest->writeBuffer[txCount++];
@ -128,7 +131,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
} else { } else {
// No more data to send/receive. Initiate a STOP condition. // No more data to send/receive. Initiate a STOP condition.
TWI0.MCTRLB = TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_MCMD_STOP_gc;
state = I2C_STATUS_OK; // Done state = I2C_STATE_COMPLETED;
} }
} else if (currentStatus & TWI_RIF_bm) { } else if (currentStatus & TWI_RIF_bm) {
// Master read completed without errors // Master read completed without errors
@ -142,7 +145,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
} else { } else {
// Transaction finished, issue NACK and STOP. // Transaction finished, issue NACK and STOP.
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
state = I2C_STATUS_OK; state = I2C_STATE_COMPLETED;
} }
} }
} }

View File

@ -76,6 +76,14 @@ for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \
#undef I2C_USE_WIRE #undef I2C_USE_WIRE
#endif #endif
enum MuxPhase: uint8_t {
MuxPhase_OFF = 0,
MuxPhase_PROLOG,
MuxPhase_PASSTHRU,
MuxPhase_EPILOG,
} ;
/*************************************************************************** /***************************************************************************
* Initialise the I2CManagerAsync class. * Initialise the I2CManagerAsync class.
***************************************************************************/ ***************************************************************************/
@ -108,6 +116,7 @@ void I2CManagerClass::startTransaction() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) {
state = I2C_STATE_ACTIVE; state = I2C_STATE_ACTIVE;
completionStatus = I2C_STATUS_OK;
// Check for pending clock speed change // Check for pending clock speed change
if (pendingClockSpeed) { if (pendingClockSpeed) {
// We're about to start a new I2C transaction, so set clock now. // We're about to start a new I2C transaction, so set clock now.
@ -260,7 +269,7 @@ void I2CManagerClass::handleInterrupt() {
// and state isn't active then state contains the completion status of the request. // and state isn't active then state contains the completion status of the request.
if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { if (state != I2C_STATE_ACTIVE && currentRequest != NULL) {
// Operation has completed. // Operation has completed.
if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|| currentRequest->operation & OPERATION_NORETRY) || currentRequest->operation & OPERATION_NORETRY)
{ {
// Status is OK, or has failed and retry count exceeded, or retries disabled. // Status is OK, or has failed and retry count exceeded, or retries disabled.
@ -270,7 +279,7 @@ void I2CManagerClass::handleInterrupt() {
queueHead = t->nextRequest; queueHead = t->nextRequest;
if (!queueHead) queueTail = queueHead; if (!queueHead) queueTail = queueHead;
t->nBytes = rxCount; t->nBytes = rxCount;
t->status = state; t->status = completionStatus;
// I2C state machine is now free for next request // I2C state machine is now free for next request
currentRequest = NULL; currentRequest = NULL;
@ -297,6 +306,7 @@ I2CRB * volatile I2CManagerClass::queueHead = NULL;
I2CRB * volatile I2CManagerClass::queueTail = NULL; I2CRB * volatile I2CManagerClass::queueTail = NULL;
I2CRB * volatile I2CManagerClass::currentRequest = NULL; I2CRB * volatile I2CManagerClass::currentRequest = NULL;
volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE; volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE;
uint8_t I2CManagerClass::completionStatus;
volatile uint8_t I2CManagerClass::txCount; volatile uint8_t I2CManagerClass::txCount;
volatile uint8_t I2CManagerClass::rxCount; volatile uint8_t I2CManagerClass::rxCount;
volatile uint8_t I2CManagerClass::operation; volatile uint8_t I2CManagerClass::operation;
@ -306,7 +316,7 @@ volatile unsigned long I2CManagerClass::startTime;
uint8_t I2CManagerClass::retryCounter = 0; uint8_t I2CManagerClass::retryCounter = 0;
#if defined(I2C_EXTENDED_ADDRESS) #if defined(I2C_EXTENDED_ADDRESS)
volatile uint8_t I2CManagerClass::muxSendStep = 0; volatile uint8_t I2CManagerClass::muxPhase = 0;
#endif #endif
#endif #endif

View File

@ -155,6 +155,8 @@ void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry. // Set counters here in case this is a retry.
bytesToSend = currentRequest->writeLen; bytesToSend = currentRequest->writeLen;
bytesToReceive = currentRequest->readLen; bytesToReceive = currentRequest->readLen;
txCount = 0;
rxCount = 0;
// On a single-master I2C bus, the start bit won't be sent until the bus // On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a // state goes to IDLE so we can request it without waiting. On a
@ -207,13 +209,15 @@ void I2CManagerClass::I2C_handleInterrupt() {
I2C_sendStart(); // Reinitiate request I2C_sendStart(); // Reinitiate request
} else if (s->I2CM.STATUS.bit.BUSERR) { } else if (s->I2CM.STATUS.bit.BUSERR) {
// Bus error // Bus error
state = I2C_STATUS_BUS_ERROR; completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (s->I2CM.INTFLAG.bit.MB) { } else if (s->I2CM.INTFLAG.bit.MB) {
// Master write completed // Master write completed
if (s->I2CM.STATUS.bit.RXNACK) { if (s->I2CM.STATUS.bit.RXNACK) {
// Nacked, send stop. // Nacked, send stop.
I2C_sendStop(); I2C_sendStop();
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (bytesToSend) { } else if (bytesToSend) {
// Acked, so send next byte // Acked, so send next byte
s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++];
@ -222,9 +226,9 @@ void I2CManagerClass::I2C_handleInterrupt() {
// Last sent byte acked and no more to send. Send repeated start, address and read bit. // Last sent byte acked and no more to send. Send repeated start, address and read bit.
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
} else { } else {
// No more data to send/receive. Initiate a STOP condition. // No more data to send/receive. Initiate a STOP condition
I2C_sendStop(); I2C_sendStop();
state = I2C_STATUS_OK; // Done state = I2C_STATE_COMPLETED; // Completed OK
} }
} else if (s->I2CM.INTFLAG.bit.SB) { } else if (s->I2CM.INTFLAG.bit.SB) {
// Master read completed without errors // Master read completed without errors
@ -233,7 +237,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
I2C_sendStop(); // send stop I2C_sendStop(); // send stop
currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
bytesToReceive = 0; bytesToReceive = 0;
state = I2C_STATUS_OK; // done state = I2C_STATE_COMPLETED; // Completed OK
} else if (bytesToReceive) { } else if (bytesToReceive) {
s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte

View File

@ -69,38 +69,53 @@ void I2CManagerClass::setTimeout(unsigned long value) {
* Helper function for I2C Multiplexer operations * Helper function for I2C Multiplexer operations
********************************************************/ ********************************************************/
#ifdef I2C_EXTENDED_ADDRESS #ifdef I2C_EXTENDED_ADDRESS
static uint8_t muxSelect(I2CAddress &address) { static uint8_t muxSelect(I2CAddress address) {
// Select MUX sub bus. // Select MUX sub bus.
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+address.muxNumber()); I2CMux muxNo = address.muxNumber();
uint8_t data = address.subBus(); I2CSubBus subBus = address.subBus();
Wire.write(&data, 1); if (muxNo != I2CMux_None) {
return Wire.endTransmission(true); // have to release I2C bus for it to work Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
uint8_t data = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
(1 << subBus);
Wire.write(&data, 1);
return Wire.endTransmission(true); // have to release I2C bus for it to work
}
return I2C_STATUS_OK;
} }
#endif #endif
/*************************************************************************** /***************************************************************************
* Initiate a write to an I2C device (blocking operation on Wire) * Initiate a write to an I2C device (blocking operation on Wire)
***************************************************************************/ ***************************************************************************/
uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
uint8_t status = I2C_STATUS_OK; uint8_t status, muxStatus;
uint8_t retryCount = 0; uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set // If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block. // in the request block.
do { do {
status = I2C_STATUS_OK; status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS #ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None) { if (address.muxNumber() != I2CMux_None)
status = muxSelect(address); muxStatus = muxSelect(address);
}
#endif #endif
// Only send new transaction if address and size are both nonzero. // Only send new transaction if address is non-zero.
if (status == I2C_STATUS_OK && address != 0 && size != 0) { if (muxStatus == I2C_STATUS_OK && address != 0) {
Wire.beginTransmission(address); Wire.beginTransmission(address);
if (size > 0) Wire.write(buffer, size); if (size > 0) Wire.write(buffer, size);
status = Wire.endTransmission(); status = Wire.endTransmission();
} }
} while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES #ifdef I2C_EXTENDED_ADDRESS
|| rb->operation & OPERATION_NORETRY)); // Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK
&& address.deviceAddress() != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!(status == I2C_STATUS_OK
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->status = status; rb->status = status;
return I2C_STATUS_OK; return I2C_STATUS_OK;
} }
@ -123,20 +138,20 @@ uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uin
uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb) const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb)
{ {
uint8_t status = I2C_STATUS_OK; uint8_t status, muxStatus;
uint8_t nBytes = 0; uint8_t nBytes = 0;
uint8_t retryCount = 0; uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set // If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block. // in the request block.
do { do {
status = I2C_STATUS_OK; status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS #ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None) { if (address.muxNumber() != I2CMux_None) {
status = muxSelect(address); muxStatus = muxSelect(address);
} }
#endif #endif
// Only start new transaction if address and readSize are both nonzero. // Only start new transaction if address is non-zero.
if (status == I2C_STATUS_OK && address != 0 && writeSize > 0) { if (muxStatus == I2C_STATUS_OK && address != 0) {
if (writeSize > 0) { if (writeSize > 0) {
Wire.beginTransmission(address); Wire.beginTransmission(address);
Wire.write(writeBuffer, writeSize); Wire.write(writeBuffer, writeSize);
@ -161,8 +176,16 @@ uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t
#endif #endif
} }
} }
} while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES #ifdef I2C_EXTENDED_ADDRESS
|| rb->operation & OPERATION_NORETRY)); // Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK && address != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!((status == I2C_STATUS_OK)
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->nBytes = nBytes; rb->nBytes = nBytes;
rb->status = status; rb->status = status;