From ba9b36305856b38a9628502f47940b374c8cdf08 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 18:39:15 +0000 Subject: [PATCH] I2CManager_NonBlocking - Defer I2C speed changes for all drivers Following on from the change to I2CManager_SAMD.h, the capability of deferring a request to change the speed of the I2C has been removed from the SAMD driver and put into the common NonBlocking code, so that all native drivers benefit from it. --- I2CManager.h | 2 ++ I2CManager_NonBlocking.h | 17 ++++++++++-- I2CManager_SAMD.h | 59 +++++++++++++++++----------------------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/I2CManager.h b/I2CManager.h index 677c5f9..0fcc6c6 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -281,6 +281,8 @@ private: static volatile uint8_t operation; static volatile unsigned long startTime; + volatile uint32_t pendingClockSpeed = 0; + void startTransaction(); // Low-level hardware manipulation functions. diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fce158b..f5caefd 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -84,25 +84,36 @@ void I2CManagerClass::_initialise() queueHead = queueTail = NULL; state = I2C_STATE_FREE; I2C_init(); - I2C_setClock(_clockSpeed); + _setClock(_clockSpeed); } /*************************************************************************** * Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast) * on Arduino. Mega4809 supports 1000000 (Fast+) too. + * This function saves the desired clock speed and the startTransaction + * function acts on it before a new transaction, to avoid speed changes + * during an I2C transaction. ***************************************************************************/ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { - I2C_setClock(i2cClockSpeed); + pendingClockSpeed = i2cClockSpeed; } /*************************************************************************** * Helper function to start operations, if the I2C interface is free and * there is a queued request to be processed. + * If there's an I2C clock speed change pending, then implement it before + * starting the operation. ***************************************************************************/ -void I2CManagerClass::startTransaction() { +void I2CManagerClass::startTransaction() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; + // Check for pending clock speed change + if (pendingClockSpeed) { + // We're about to start a new I2C transaction, so set clock now. + I2C_setClock(pendingClockSpeed); + pendingClockSpeed = 0; + } startTime = micros(); currentRequest = queueHead; rxCount = txCount = 0; diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 63efcd6..affb6b3 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -49,7 +49,9 @@ void SERCOM3_Handler() { Sercom *s = SERCOM3; /*************************************************************************** - * Set I2C clock speed register. + * Set I2C clock speed register. This should only be called outside of + * a transmission. The I2CManagerClass::_setClock() function ensures + * that it is only called at the beginning of an I2C transaction. ***************************************************************************/ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { @@ -68,38 +70,24 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { i2cClockSpeed = 100000L; t_rise = 1000; } + + // Wait while the bus is busy + while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Calculate baudrate - using a rise time appropriate for the speed - pendingBaudRate = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); -} + s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); -/*************************************************************************** - * Internal function to actually change the baud rate register, executed from - * interrupt code to avoid in-progress I2C transactions. - ***************************************************************************/ -static void checkForPendingClockSpeedChange() { - if (pendingBaudRate > 0) { - // Wait while the bus is busy - while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + // Enable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 1 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - // Disable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 0 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - - // Update baudrate - s->I2CM.BAUD.bit.BAUD = pendingBaudRate; - - // Enable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 1 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - - // Setting bus idle mode and wait for sync - s->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); - - // Clear pending rate now it's been implemented. - pendingBaudRate = 0; - } - return; + // Setting bus idle mode and wait for sync + s->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); } /*************************************************************************** @@ -166,10 +154,6 @@ void I2CManagerClass::I2C_init() * Initiate a start bit for transmission. ***************************************************************************/ void I2CManagerClass::I2C_sendStart() { - // Check if the clock is to be changed, if so do it now. It doesn't matter - // what else is going on over the I2C bus as the clock change only affects - // this master. - checkForPendingClockSpeedChange(); // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; @@ -205,6 +189,13 @@ void I2CManagerClass::I2C_sendStop() { ***************************************************************************/ void I2CManagerClass::I2C_close() { I2C_sendStop(); + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + // Wait for up to 500us only. + unsigned long startTime = micros(); + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) { + if (micros() - startTime >= 500UL) break; + } } /***************************************************************************