1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-23 21:01:25 +01:00

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.
This commit is contained in:
Neil McKechnie 2023-01-31 18:39:15 +00:00
parent bdffd36820
commit ba9b363058
3 changed files with 41 additions and 37 deletions

View File

@ -281,6 +281,8 @@ private:
static volatile uint8_t operation; static volatile uint8_t operation;
static volatile unsigned long startTime; static volatile unsigned long startTime;
volatile uint32_t pendingClockSpeed = 0;
void startTransaction(); void startTransaction();
// Low-level hardware manipulation functions. // Low-level hardware manipulation functions.

View File

@ -84,25 +84,36 @@ void I2CManagerClass::_initialise()
queueHead = queueTail = NULL; queueHead = queueTail = NULL;
state = I2C_STATE_FREE; state = I2C_STATE_FREE;
I2C_init(); I2C_init();
I2C_setClock(_clockSpeed); _setClock(_clockSpeed);
} }
/*************************************************************************** /***************************************************************************
* Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast) * Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast)
* on Arduino. Mega4809 supports 1000000 (Fast+) too. * 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) { void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
I2C_setClock(i2cClockSpeed); pendingClockSpeed = i2cClockSpeed;
} }
/*************************************************************************** /***************************************************************************
* Helper function to start operations, if the I2C interface is free and * Helper function to start operations, if the I2C interface is free and
* there is a queued request to be processed. * 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) { 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;
// 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(); startTime = micros();
currentRequest = queueHead; currentRequest = queueHead;
rxCount = txCount = 0; rxCount = txCount = 0;

View File

@ -49,7 +49,9 @@ void SERCOM3_Handler() {
Sercom *s = SERCOM3; 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) { void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
@ -68,38 +70,24 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
i2cClockSpeed = 100000L; i2cClockSpeed = 100000L;
t_rise = 1000; 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 // 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));
}
/*************************************************************************** // Enable the I2C master mode and wait for sync
* Internal function to actually change the baud rate register, executed from s->I2CM.CTRLA.bit.ENABLE = 1 ;
* interrupt code to avoid in-progress I2C transactions. while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
***************************************************************************/
static void checkForPendingClockSpeedChange() {
if (pendingBaudRate > 0) {
// Wait while the bus is busy
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
// Disable the I2C master mode and wait for sync // Setting bus idle mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 0 ; s->I2CM.STATUS.bit.BUSSTATE = 1 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); while (s->I2CM.SYNCBUSY.bit.SYSOP != 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;
} }
/*************************************************************************** /***************************************************************************
@ -166,10 +154,6 @@ void I2CManagerClass::I2C_init()
* Initiate a start bit for transmission. * Initiate a start bit for transmission.
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_sendStart() { 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. // Set counters here in case this is a retry.
bytesToSend = currentRequest->writeLen; bytesToSend = currentRequest->writeLen;
@ -205,6 +189,13 @@ void I2CManagerClass::I2C_sendStop() {
***************************************************************************/ ***************************************************************************/
void I2CManagerClass::I2C_close() { void I2CManagerClass::I2C_close() {
I2C_sendStop(); 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;
}
} }
/*************************************************************************** /***************************************************************************