From 79437bbf379ac0eb0fb80f4a54e47f7280cd6c7d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 17:10:45 +0000 Subject: [PATCH 01/95] Update MotorDriver.cpp Remove unnecessary and undesirable interrupt disable/enable when writing to HAL driver. --- MotorDriver.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c41420a..e87755b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -144,16 +144,12 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { - noInterrupts(); IODevice::write(powerPin,invertPower ? LOW : HIGH); - interrupts(); if (isProgTrack) DCCWaveform::progTrack.clearResets(); } else { - noInterrupts(); IODevice::write(powerPin,invertPower ? HIGH : LOW); - interrupts(); } powerMode=mode; } From 3c5b7bbcfe3c32783a6812125b4bc4a29191ed04 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 17:15:30 +0000 Subject: [PATCH 02/95] HAL updates Remove redundant deferment of device _begin() calls (no longer necessary). Improve diagnostic loop measurement. --- IODevice.cpp | 44 +++++++++++++++++--------------------------- IODevice.h | 1 - 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 812d7ed..9cf1221 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -48,12 +48,14 @@ extern __attribute__((weak)) void exrailHalSetup(); // Create any standard device instances that may be required, such as the Arduino pins // and PCA9685. void IODevice::begin() { + // Initialise the IO subsystem defaults + ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access + // Call user's halSetup() function (if defined in the build in myHal.cpp). // The contents will depend on the user's system hardware configuration. // The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs. - - // This is done first so that the following defaults will detect an overlap and not - // create something that conflicts with the users vpin definitions. + // This is done early so that the subsequent defaults will detect an overlap and not + // create something that conflicts with the user's vpin definitions. if (halSetup) halSetup(); @@ -61,8 +63,6 @@ void IODevice::begin() { if (exrailHalSetup) exrailHalSetup(); - // Initialise the IO subsystem defaults - ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access // Predefine two PCA9685 modules 0x40-0x41 // Allocates 32 pins 100-131 PCA9685::create(100, 16, 0x40); @@ -72,12 +72,6 @@ void IODevice::begin() { // Allocates 32 pins 164-195 MCP23017::create(164, 16, 0x20); MCP23017::create(180, 16, 0x21); - - // Call the begin() methods of each configured device in turn - for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { - dev->_begin(); - } - _initPhase = false; } // Overarching static loop() method for the IODevice subsystem. Works through the @@ -109,18 +103,19 @@ void IODevice::loop() { // Report loop time if diags enabled #if defined(DIAG_LOOPTIMES) + unsigned long diagMicros = micros(); static unsigned long lastMicros = 0; - // Measure time since loop() method started. - unsigned long halElapsed = micros() - currentMicros; - // Measure time between loop() method entries. - unsigned long elapsed = currentMicros - lastMicros; + // Measure time since HAL's loop() method started. + unsigned long halElapsed = diagMicros - currentMicros; + // Measure time between loop() method entries (excluding this diagnostic). + unsigned long elapsed = diagMicros - lastMicros; static unsigned long maxElapsed = 0, maxHalElapsed = 0; static unsigned long lastOutputTime = 0; static unsigned long halTotal = 0, total = 0; static unsigned long count = 0; const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec - // Ignore long loop counts while message is still outputting + // Ignore long loop counts while message is still outputting (~3 milliseconds) if (currentMicros - lastOutputTime > 3000UL) { if (elapsed > maxElapsed) maxElapsed = elapsed; if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed; @@ -128,14 +123,16 @@ void IODevice::loop() { total += elapsed; count++; } - if (currentMicros - lastOutputTime > interval) { + if (diagMicros - lastOutputTime > interval) { if (lastOutputTime > 0) DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"), total/count, maxElapsed, halTotal/count, maxHalElapsed); maxElapsed = maxHalElapsed = total = halTotal = count = 0; - lastOutputTime = currentMicros; + lastOutputTime = diagMicros; } - lastMicros = currentMicros; + // Read microsecond count after calculations, so they aren't + // included in the overall timings. + lastMicros = micros(); #endif } @@ -272,11 +269,7 @@ void IODevice::addDevice(IODevice *newDevice) { lastDevice->_nextDevice = newDevice; } newDevice->_nextDevice = 0; - - // If the IODevice::begin() method has already been called, initialise device here. If not, - // the device's _begin() method will be called by IODevice::begin(). - if (!_initPhase) - newDevice->_begin(); + newDevice->_begin(); } // Private helper function to locate a device by VPIN. Returns NULL if not found. @@ -332,9 +325,6 @@ IODevice *IODevice::_firstDevice = 0; // Reference to next device to be called on _loop() method. IODevice *IODevice::_nextLoopDevice = 0; -// Flag which is reset when IODevice::begin has been called. -bool IODevice::_initPhase = true; - //================================================================================================================== // Instance members diff --git a/IODevice.h b/IODevice.h index ce47267..3b583ba 100644 --- a/IODevice.h +++ b/IODevice.h @@ -253,7 +253,6 @@ private: static IODevice *_firstDevice; static IODevice *_nextLoopDevice; - static bool _initPhase; }; From 6e69df2da8bbac6ef9e583a1f8b377d500beda4b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 18:18:57 +0000 Subject: [PATCH 03/95] Add I2C retries to Wire and to non-blocking I2CManager. --- I2CManager.h | 6 ++++++ I2CManager_NonBlocking.h | 27 ++++++++++++++++++--------- I2CManager_Wire.h | 37 +++++++++++++++++++++++-------------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/I2CManager.h b/I2CManager.h index 7df0803..f966d77 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -111,6 +111,11 @@ * */ +// Maximum number of retries on an I2C operation +// A value of zero will disable retries. +// Maximum value is 254 (unsigned byte counter) +#define MAX_I2C_RETRIES 2 + // Add following line to config.h to enable Wire library instead of native I2C drivers //#define I2C_USE_WIRE @@ -265,6 +270,7 @@ private: static volatile unsigned long startTime; static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + static uint8_t retryCounter; // Count of retries void startTransaction(); diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fbcb98a..2793823 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -223,16 +223,15 @@ void I2CManagerClass::handleInterrupt() { // Update hardware state machine I2C_handleInterrupt(); - // Enable interrupts to minimise effect on other interrupt code - interrupts(); - // Check if current request has completed. If there's a current request // and state isn't active then state contains the completion status of the request. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { - // Remove completed request from head of queue - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Operation has completed. + if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES) { + // Status is OK, or has failed and retry count exceeded. + // Remove completed request from head of queue I2CRB * t = queueHead; - if (t == queueHead) { + if (t == currentRequest) { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; t->nBytes = rxCount; @@ -241,12 +240,21 @@ void I2CManagerClass::handleInterrupt() { // I2C state machine is now free for next request currentRequest = NULL; state = I2C_STATE_FREE; - - // Start next request (if any) - I2CManager.startTransaction(); } + retryCounter = 0; + } else { + // Status is failed and retry permitted. + // Retry previous request. + state = I2C_STATE_FREE; } } + + if (state == I2C_STATE_FREE && queueHead != NULL) { + // Allow any pending interrupts before starting the next request. + interrupts(); + // Start next request + I2CManager.startTransaction(); + } } // Fields in I2CManager class specific to Non-blocking implementation. @@ -261,5 +269,6 @@ volatile uint8_t I2CManagerClass::bytesToSend; volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; unsigned long I2CManagerClass::timeout = 0; +uint8_t I2CManagerClass::retryCounter = 0; #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 87152e7..aa0189f 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -49,9 +49,14 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { - Wire.beginTransmission(address); - if (size > 0) Wire.write(buffer, size); - rb->status = Wire.endTransmission(); + uint8_t status = I2C_STATUS_OK; + uint8_t retryCount = 0; + do { + Wire.beginTransmission(address); + if (size > 0) Wire.write(buffer, size); + status = Wire.endTransmission(); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + rb->status = status; return I2C_STATUS_OK; } @@ -75,17 +80,21 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea { uint8_t status = I2C_STATUS_OK; uint8_t nBytes = 0; - if (writeSize > 0) { - Wire.beginTransmission(address); - Wire.write(writeBuffer, writeSize); - status = Wire.endTransmission(false); // Don't free bus yet - } - if (status == I2C_STATUS_OK) { - Wire.requestFrom(address, (size_t)readSize); - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; - } + uint8_t retryCount = 0; + do { + if (writeSize > 0) { + Wire.beginTransmission(address); + Wire.write(writeBuffer, writeSize); + status = Wire.endTransmission(false); // Don't free bus yet + } + if (status == I2C_STATUS_OK) { + Wire.requestFrom(address, (size_t)readSize); + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; + } + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + rb->nBytes = nBytes; rb->status = status; return I2C_STATUS_OK; From 538519dd9df13caa66dab93f85d992282f808a7e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 18:58:06 +0000 Subject: [PATCH 04/95] Add option to suppress I2C retries. I2CRB method suppressRetries() added to allow retries to be suppressed. --- I2CManager.cpp | 13 ++++++++++++- I2CManager.h | 3 +++ I2CManager_NonBlocking.h | 8 +++++--- I2CManager_Wire.h | 12 +++++++++--- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index a951a87..d7be935 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -81,8 +81,13 @@ void I2CManagerClass::forceClock(uint32_t speed) { // Check if specified I2C address is responding (blocking operation) // Returns I2C_STATUS_OK (0) if OK, or error code. +// Suppress retries. If it doesn't respond first time it's out of the running. uint8_t I2CManagerClass::checkAddress(uint8_t address) { - return write(address, NULL, 0); + I2CRB rb; + rb.setWriteParams(address, NULL, 0); + rb.suppressRetries(true); + queueRequest(&rb); + return rb.wait(); } @@ -244,3 +249,9 @@ void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8 this->status = I2C_STATUS_OK; } +void I2CRB::suppressRetries(bool suppress) { + if (suppress) + this->operation |= OPERATION_NORETRY; + else + this->operation &= ~OPERATION_NORETRY; +} diff --git a/I2CManager.h b/I2CManager.h index f966d77..bcb7092 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -157,6 +157,8 @@ typedef enum : uint8_t OPERATION_REQUEST = 2, OPERATION_SEND = 3, OPERATION_SEND_P = 4, + OPERATION_NORETRY = 0x80, // OR with operation to suppress retries. + OPERATION_MASK = 0x7f, // mask for extracting the operation code } OperationEnum; @@ -178,6 +180,7 @@ public: void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen); void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); + void suppressRetries(bool suppress); uint8_t writeLen; uint8_t readLen; diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2793823..7769d17 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -105,7 +105,7 @@ void I2CManagerClass::startTransaction() { currentRequest = queueHead; rxCount = txCount = 0; // Copy key fields to static data for speed. - operation = currentRequest->operation; + operation = currentRequest->operation & OPERATION_MASK; // Start the I2C process going. I2C_sendStart(); startTime = micros(); @@ -227,8 +227,10 @@ void I2CManagerClass::handleInterrupt() { // and state isn't active then state contains the completion status of the request. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { // Operation has completed. - if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES) { - // Status is OK, or has failed and retry count exceeded. + if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES + || currentRequest->operation & OPERATION_NORETRY) + { + // Status is OK, or has failed and retry count exceeded, or retries disabled. // Remove completed request from head of queue I2CRB * t = queueHead; if (t == currentRequest) { diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index aa0189f..aea91aa 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -51,11 +51,14 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t status = I2C_STATUS_OK; uint8_t retryCount = 0; + // If request fails, retry up to the defined limit, unless the NORETRY flag is set + // in the request block. do { Wire.beginTransmission(address); if (size > 0) Wire.write(buffer, size); status = Wire.endTransmission(); - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES + || rb->operation & OPERATION_NORETRY)); rb->status = status; return I2C_STATUS_OK; } @@ -81,6 +84,8 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea uint8_t status = I2C_STATUS_OK; uint8_t nBytes = 0; uint8_t retryCount = 0; + // If request fails, retry up to the defined limit, unless the NORETRY flag is set + // in the request block. do { if (writeSize > 0) { Wire.beginTransmission(address); @@ -93,7 +98,8 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea readBuffer[nBytes++] = Wire.read(); if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES + || rb->operation & OPERATION_NORETRY)); rb->nBytes = nBytes; rb->status = status; @@ -109,7 +115,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea * the non-blocking version. ***************************************************************************/ void I2CManagerClass::queueRequest(I2CRB *req) { - switch (req->operation) { + switch (req->operation & OPERATION_MASK) { case OPERATION_READ: read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); break; From 7c25f2293998b18306007f608d8c04aaf4749c63 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 23:50:33 +0000 Subject: [PATCH 05/95] Fix error reported in IO_DFPlayer.h when compiling for some platforms --- IO_DFPlayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index d4684e7..ad09ca5 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -261,7 +261,7 @@ private: // Output some pad characters to add an // artificial delay between commands for (int i=0; iwrite(0); + _serial->write((uint8_t)0); } // Now output the command From abf62dfd85799334f08bb351a8ec4b316cce4382 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 16 Jan 2023 23:00:58 +0000 Subject: [PATCH 06/95] IO_VL53L0X driver: improve I2C error checking and reporting. --- IO_VL53L0X.h | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 9d51345..040014e 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -115,6 +115,7 @@ private: STATE_CHECKSTATUS = 5, STATE_GETRESULTS = 6, STATE_DECODERESULTS = 7, + STATE_FAILED = 8, }; // Register addresses @@ -190,11 +191,12 @@ protected: // Set 2.8V mode write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); + _nextState = STATE_INITIATESCAN; } else { DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); _deviceState = DEVSTATE_FAILED; + _nextState = STATE_FAILED; } - _nextState = STATE_INITIATESCAN; break; case STATE_INITIATESCAN: // Not scanning, so initiate a scan @@ -207,13 +209,11 @@ protected: status = _rb.status; if (status == I2C_STATUS_PENDING) return; // try next time if (status != I2C_STATUS_OK) { - DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); - _deviceState = DEVSTATE_FAILED; - _value = false; + reportError(status); + _nextState = STATE_FAILED; } else - _nextState = 2; + _nextState = STATE_GETRESULTS; delayUntil(currentMicros + 95000); // wait for 95 ms before checking. - _nextState = STATE_GETRESULTS; break; case STATE_GETRESULTS: // Ranging completed. Request results @@ -240,15 +240,29 @@ protected: else if (_distance > _offThreshold) _value = false; } + // Completed. Restart scan on next loop entry. + _nextState = STATE_INITIATESCAN; + } else { + reportError(status); + _nextState = STATE_FAILED; } - // Completed. Restart scan on next loop entry. - _nextState = STATE_INITIATESCAN; + break; + case STATE_FAILED: + // Do nothing. + delayUntil(currentMicros+1000000UL); break; default: break; } } + // Function to report a failed I2C operation. Put the device off-line. + void reportError(uint8_t status) { + DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + _value = false; + } + // For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level. int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; From ccf463b5075b6f05922dbd4c744b1be8b6ba02a5 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 16 Jan 2023 23:03:53 +0000 Subject: [PATCH 07/95] IODevice.cpp: Fix error in overlap checking. The checkNoOverlap() function didn't work correctly in the case where one device has nPins=0. All devices configured after that were rejected, even when no overlap was present. --- IODevice.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 9cf1221..a51c84b 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -292,16 +292,17 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { - // check for pin range overlaps (verbose but compiler will fix that) - VPIN firstDevPin=dev->_firstVpin; - VPIN lastDevPin=firstDevPin+dev->_nPins-1; - bool noOverlap= firstPin>lastDevPin || lastPin 0 && dev->_nPins > 0) { + // check for pin range overlaps (verbose but compiler will fix that) + VPIN firstDevPin=dev->_firstVpin; + VPIN lastDevPin=firstDevPin+dev->_nPins-1; + bool noOverlap= firstPin>lastDevPin || lastPin_I2CAddress==i2cAddress) { DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); From e079a9e395489fcfa440d58481c5246eff582769 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 12:37:16 +0000 Subject: [PATCH 08/95] Update IO_VL53L0X.h Improve address changing logic. --- IO_VL53L0X.h | 68 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 040014e..6a83970 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -70,7 +70,8 @@ * lowThreshold is the distance at which the digital vpin state is set to 1 (in mm), * highThreshold is the distance at which the digital vpin state is set to 0 (in mm), * and xshutPin is the VPIN number corresponding to a digital output that is connected to the - * XSHUT terminal on the module. + * XSHUT terminal on the module. The digital output may be an Arduino pin or an + * I/O extender pin. * * Example: * In mySetup function within mySetup.cpp: @@ -101,21 +102,24 @@ private: uint16_t _offThreshold; VPIN _xshutPin; bool _value; - uint8_t _nextState = 0; + uint8_t _nextState = STATE_INIT; I2CRB _rb; uint8_t _inBuffer[12]; uint8_t _outBuffer[2]; + static bool _addressConfigInProgress; + // State machine states. enum : uint8_t { STATE_INIT = 0, - STATE_CONFIGUREADDRESS = 1, - STATE_SKIP = 2, - STATE_CONFIGUREDEVICE = 3, - STATE_INITIATESCAN = 4, - STATE_CHECKSTATUS = 5, - STATE_GETRESULTS = 6, - STATE_DECODERESULTS = 7, - STATE_FAILED = 8, + STATE_RESTARTMODULE = 1, + STATE_CONFIGUREADDRESS = 2, + STATE_SKIP = 3, + STATE_CONFIGUREDEVICE = 4, + STATE_INITIATESCAN = 5, + STATE_CHECKSTATUS = 6, + STATE_GETRESULTS = 7, + STATE_DECODERESULTS = 8, + STATE_FAILED = 9, }; // Register addresses @@ -146,14 +150,13 @@ protected: addDevice(this); } void _begin() override { - if (_xshutPin == VPIN_NONE) { - // Check if device is already responding on the nominated address. - if (I2CManager.exists(_i2cAddress)) { - // Yes, it's already on this address, so skip the address initialisation. - _nextState = STATE_CONFIGUREDEVICE; - } else { - _nextState = STATE_INIT; - } + // If there's only one device, then the XSHUT pin need not be connected. However, + // the device will not respond on its default address if it has + // already been changed. Therefore, we skip the address configuration if the + // desired address is already responding on the I2C bus. + if (_xshutPin == VPIN_NONE && I2CManager.exists(_i2cAddress)) { + // Device already present on this address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; } } @@ -161,21 +164,32 @@ protected: uint8_t status; switch (_nextState) { case STATE_INIT: - // On first entry to loop, reset this module by pulling XSHUT low. All modules - // will be reset in turn. + // On first entry to loop, reset this module by pulling XSHUT low. Each module + // will be addressed in turn, until all are in the reset state. + // If no XSHUT pin is configured, then only one device is supported. if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + _nextState = STATE_RESTARTMODULE; + delayUntil(currentMicros+1000); + break; + case STATE_RESTARTMODULE: + // On second entry, set XSHUT pin high to allow this module to restart. + // On the module, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V. + // Ensure this is done for only one module at a time by using a + // shared flag accessible to all device instances. + if (_addressConfigInProgress) return; + _addressConfigInProgress = true; + // Set XSHUT pin (if connected) + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Allow the module time to restart + delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; break; case STATE_CONFIGUREADDRESS: - // On second entry, set XSHUT pin high to allow the module to restart. - // On the module, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); - // Allow the module time to restart - delay(10); // Then write the desired I2C address to the device, while this is the only // module responding to the default address. I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); + _addressConfigInProgress = false; _nextState = STATE_SKIP; break; case STATE_SKIP: @@ -311,4 +325,6 @@ private: } }; +bool VL53L0X::_addressConfigInProgress = false; + #endif // IO_VL53L0X_h From bfbc45674f67fda5af1ab42080fe51e287df018b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 12:38:24 +0000 Subject: [PATCH 09/95] Update IO_AnalogueInputs.h Add I2C initialisation calls (previously missing). --- IO_AnalogueInputs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 1af351d..5f9d0e0 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -73,6 +73,11 @@ private: addDevice(this); } void _begin() { + // Initialise I2C + I2CManager.begin(); + // ADS111x support high-speed I2C (4.3MHz) but that requires special + // processing. So stick to fast mode (400kHz maximum). + I2CManager.setClock(400000); // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { _nextState = STATE_STARTSCAN; From 705617239fa69d7110077b9053d16d0e48fd25bf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 13:13:20 +0000 Subject: [PATCH 10/95] Sort out I2C timeout handling, and further I2C diagnostics. Timeout handling and recovery in loop() function now operative. Start-up check for I2C signals short to ground added. Initial I2C device probe speed up. Possible infinite loops in I2C AVR native driver during fault conditions removed. --- I2CManager.cpp | 66 +++++++++++++++++++++++----------------- I2CManager.h | 22 ++++++++------ I2CManager_AVR.h | 15 +++++---- I2CManager_NonBlocking.h | 35 +++++++++++++-------- I2CManager_Wire.h | 43 +++++++++++++++++++++----- 5 files changed, 114 insertions(+), 67 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index d7be935..eafdb70 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -1,6 +1,6 @@ /* + * © 2023, Neil McKechnie * © 2022 Paul M Antoine - * © 2021, Neil McKechnie * All rights reserved. * * This file is part of CommandStation-EX @@ -43,12 +43,21 @@ // If not already initialised, initialise I2C void I2CManagerClass::begin(void) { - //setTimeout(25000); // 25 millisecond timeout if (!_beginCompleted) { _beginCompleted = true; _initialise(); - // Probe and list devices. + // Check for short-circuits on I2C + if (!digitalRead(SDA)) + DIAG(F("WARNING: Possible short-circuit on I2C SDA line")); + if (!digitalRead(SCL)) + DIAG(F("WARNING: Possible short-circuit on I2C SCL line")); + + // Probe and list devices. Use standard mode + // (clock speed 100kHz) for best device compatibility. + _setClock(100000); + unsigned long originalTimeout = timeout; + setTimeout(1000); // use 1ms timeout for probes bool found = false; for (byte addr=1; addr<127; addr++) { if (exists(addr)) { @@ -57,6 +66,8 @@ void I2CManagerClass::begin(void) { } } if (!found) DIAG(F("No I2C Devices found")); + _setClock(_clockSpeed); + setTimeout(originalTimeout); // set timeout back to original } } @@ -65,18 +76,17 @@ void I2CManagerClass::begin(void) { void I2CManagerClass::setClock(uint32_t speed) { if (speed < _clockSpeed && !_clockSpeedFixed) { _clockSpeed = speed; + DIAG(F("I2C clock speed set to %l Hz"), _clockSpeed); } _setClock(_clockSpeed); } -// Force clock speed to that specified. It can then only -// be overridden by calling Wire.setClock directly. +// Force clock speed to that specified. void I2CManagerClass::forceClock(uint32_t speed) { - if (!_clockSpeedFixed) { - _clockSpeed = speed; - _clockSpeedFixed = true; - _setClock(_clockSpeed); - } + _clockSpeed = speed; + _clockSpeedFixed = true; + _setClock(_clockSpeed); + DIAG(F("I2C clock speed forced to %l Hz"), _clockSpeed); } // Check if specified I2C address is responding (blocking operation) @@ -181,40 +191,40 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { ***************************************************************************/ I2CManagerClass I2CManager = I2CManagerClass(); +// Default timeout 100ms on I2C request block completion. +// A full 32-byte transmission takes about 8ms at 100kHz, +// so this value allows lots of headroom. +// It can be modified by calling I2CManager.setTimeout() function. +// When retries are enabled, the timeout applies to each +// try, and failure from timeout does not get retried. +unsigned long I2CManagerClass::timeout = 100000UL; + ///////////////////////////////////////////////////////////////////////////// // Helper functions associated with I2C Request Block ///////////////////////////////////////////////////////////////////////////// /*************************************************************************** - * Block waiting for request block to complete, and return completion status. - * Since such a loop could potentially last for ever if the RB status doesn't - * change, we set a high limit (1sec, 1000ms) on the wait time and, if it - * hasn't changed by that time we assume it's not going to, and just return - * a timeout status. This means that CS will not lock up. + * Block waiting for request to complete, and return completion status. + * Timeout monitoring is performed in the I2CManager.loop() function. ***************************************************************************/ uint8_t I2CRB::wait() { - unsigned long waitStart = millis(); - do { + while (status==I2C_STATUS_PENDING) { I2CManager.loop(); - // Rather than looping indefinitely, let's set a very high timeout (1s). - if ((millis() - waitStart) > 1000UL) { - DIAG(F("I2C TIMEOUT I2C:x%x I2CRB:x%x"), i2cAddress, this); - status = I2C_STATUS_TIMEOUT; - // Note that, although the timeout is posted, the request may yet complete. - // TODO: Ideally we would like to cancel the request. - return status; - } - } while (status==I2C_STATUS_PENDING); + }; return status; } /*************************************************************************** * Check whether request is still in progress. + * Timeout monitoring is performed in the I2CManager.loop() function. ***************************************************************************/ bool I2CRB::isBusy() { - I2CManager.loop(); - return (status==I2C_STATUS_PENDING); + if (status==I2C_STATUS_PENDING) { + I2CManager.loop(); + return true; + } else + return false; } /*************************************************************************** diff --git a/I2CManager.h b/I2CManager.h index bcb7092..677c5f9 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -1,6 +1,6 @@ /* + * © 2023, Neil McKechnie. All rights reserved. * © 2022 Paul M Antoine - * © 2021, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -29,7 +29,7 @@ * of the Wire class, but also has a native implementation for AVR * which supports non-blocking queued I/O requests. * - * Helps to avoid calling Wire.begin() multiple times (which is not) + * Helps to avoid calling Wire.begin() multiple times (which is not * entirely benign as it reinitialises). * * Also helps to avoid the Wire clock from being set, by another device @@ -76,6 +76,8 @@ * Timeout monitoring is possible, but requires that the following call is made * reasonably frequently in the program's loop() function: * I2CManager.loop(); + * So that the application doesn't need to do this explicitly, this call is performed + * from the I2CRB::isBusy() or I2CRB::wait() functions. * */ @@ -111,9 +113,11 @@ * */ -// Maximum number of retries on an I2C operation +// Maximum number of retries on an I2C operation. // A value of zero will disable retries. // Maximum value is 254 (unsigned byte counter) +// Note that timeout failures are not retried, but any timeout +// configured applies to each try separately. #define MAX_I2C_RETRIES 2 // Add following line to config.h to enable Wire library instead of native I2C drivers @@ -203,6 +207,8 @@ public: void setClock(uint32_t speed); // Force clock speed void forceClock(uint32_t speed); + // setTimeout sets the timout value for I2C transactions (milliseconds). + void setTimeout(unsigned long); // Check if specified I2C address is responding. uint8_t checkAddress(uint8_t address); inline bool exists(uint8_t address) { @@ -239,11 +245,14 @@ public: private: bool _beginCompleted = false; bool _clockSpeedFixed = false; + static uint8_t retryCounter; // Count of retries #if defined(__arm__) uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 #else uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. #endif + static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); @@ -272,9 +281,6 @@ private: static volatile uint8_t operation; static volatile unsigned long startTime; - static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. - static uint8_t retryCounter; // Count of retries - void startTransaction(); // Low-level hardware manipulation functions. @@ -286,10 +292,6 @@ private: static void I2C_close(); public: - // setTimeout sets the timout value for I2C transactions. - // TODO: Get I2C timeout working before uncommenting the code below. - void setTimeout(unsigned long value) { (void)value; /* timeout = value; */ }; - // handleInterrupt needs to be public to be called from the ISR function! static void handleInterrupt(); #endif diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 6492e00..267a921 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -96,9 +96,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; - // We may have initiated a stop bit before this without waiting for it. - // Wait for stop bit to be sent before sending start. - while (TWCR & (1<operation & OPERATION_MASK; // Start the I2C process going. I2C_sendStart(); - startTime = micros(); } } } @@ -167,21 +168,30 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r return I2C_STATUS_OK; } +/*************************************************************************** + * Set I2C timeout value in microseconds. The timeout applies to the entire + * I2CRB request, e.g. where a write+read is performed, the timer is not + * reset before the read. + ***************************************************************************/ +void I2CManagerClass::setTimeout(unsigned long value) { + timeout = value; +}; + /*************************************************************************** * checkForTimeout() function, called from isBusy() and wait() to cancel - * requests that are taking too long to complete. - * This function doesn't fully work as intended so is not currently called. - * Instead we check for an I2C hang-up and report an error from - * I2CRB::wait(), but we aren't able to recover from the hang-up. Such faults + * requests that are taking too long to complete. Such faults * may be caused by an I2C wire short for example. ***************************************************************************/ void I2CManagerClass::checkForTimeout() { - unsigned long currentMicros = micros(); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { I2CRB *t = queueHead; if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) { // Check for timeout - if (currentMicros - startTime > timeout) { + unsigned long elapsed = micros() - startTime; + if (elapsed > timeout) { +#ifdef DIAG_IO + //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), t->i2cAddress, currentRequest); +#endif // Excessive time. Dequeue request queueHead = t->nextRequest; if (!queueHead) queueTail = NULL; @@ -192,7 +202,9 @@ void I2CManagerClass::checkForTimeout() { // Try close and init, not entirely satisfactory but sort of works... I2C_close(); // Shutdown and restart twi interface I2C_init(); + _setClock(_clockSpeed); state = I2C_STATE_FREE; +// I2C_sendStop(); // in case device is waiting for a stop condition // Initiate next queued request if any. startTransaction(); @@ -208,10 +220,8 @@ void I2CManagerClass::loop() { #if !defined(I2C_USE_INTERRUPTS) handleInterrupt(); #endif - // Timeout is now reported in I2CRB::wait(), not here. - // I've left the code, commented out, as a reminder to look at this again - // in the future. - //checkForTimeout(); + // Call function to monitor for stuch I2C operations. + checkForTimeout(); } /*************************************************************************** @@ -270,7 +280,6 @@ volatile uint8_t I2CManagerClass::operation; volatile uint8_t I2CManagerClass::bytesToSend; volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; -unsigned long I2CManagerClass::timeout = 0; uint8_t I2CManagerClass::retryCounter = 0; #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index aea91aa..9749565 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -30,11 +30,19 @@ #define I2C_USE_WIRE #endif +// Older versions of Wire don't have setWireTimeout function. AVR does. +#ifdef ARDUINO_ARCH_AVR +#define WIRE_HAS_TIMEOUT +#endif + /*************************************************************************** * Initialise I2C interface software ***************************************************************************/ void I2CManagerClass::_initialise() { Wire.begin(); +#if defined(WIRE_HAS_TIMEOUT) + Wire.setWireTimeout(timeout, true); +#endif } /*************************************************************************** @@ -45,6 +53,18 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { Wire.setClock(i2cClockSpeed); } +/*************************************************************************** + * Set I2C timeout value in microseconds. The timeout applies to each + * Wire call separately, i.e. in a write+read, the timer is reset before the + * read is started. + ***************************************************************************/ +void I2CManagerClass::setTimeout(unsigned long value) { + timeout = value; +#if defined(WIRE_HAS_TIMEOUT) + Wire.setWireTimeout(value, true); +#endif +} + /*************************************************************************** * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ @@ -93,10 +113,21 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea status = Wire.endTransmission(false); // Don't free bus yet } if (status == I2C_STATUS_OK) { +#ifdef WIRE_HAS_TIMEOUT + Wire.clearWireTimeoutFlag(); +#endif Wire.requestFrom(address, (size_t)readSize); - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; +#ifdef WIRE_HAS_TIMEOUT + if (!Wire.getWireTimeoutFlag()) { +#endif + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; +#ifdef WIRE_HAS_TIMEOUT + } else { + status = I2C_STATUS_TIMEOUT; + } +#endif } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); @@ -136,8 +167,4 @@ void I2CManagerClass::queueRequest(I2CRB *req) { ***************************************************************************/ void I2CManagerClass::loop() {} -// Loop function -void I2CManagerClass::checkForTimeout() {} - - #endif \ No newline at end of file From 682c47f7dd011afff651ed20060c83ecc28d8d01 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:23:05 +0000 Subject: [PATCH 11/95] I2CManager_Mega4809.h - allow other I2C clock speeds. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index 0b8e8ca..c378a5d 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) { - i2cClockSpeed = 100000; + if (i2cClockSpeed < 200000) t_rise = 1000; - } else if (i2cClockSpeed < 800000) { - i2cClockSpeed = 400000; + else if (i2cClockSpeed < 800000) t_rise = 300; - } else if (i2cClockSpeed < 1200000) { - i2cClockSpeed = 1000000; + else t_rise = 120; - } else { - i2cClockSpeed = 100000; - t_rise = 1000; - } + + if (t_rise == 120) + TWI0.CTRLA |= TWI_FMPEN_bm; + else + TWI0.CTRLA &= ~TWI_FMPEN_bm; + uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; + if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; + I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif - I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,6 +70,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -89,7 +91,10 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - I2C_sendStop(); + + TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C + TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; + delayMicroseconds(10); // Wait for things to stabilise (hopefully) } /*************************************************************************** @@ -114,11 +119,9 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + // Acked, so send next byte (don't need to use GETFLASH) + txCount++; + TWI0.MDATA = *sendPointer++; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. @@ -131,13 +134,10 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte + rxCount++; + *receivePointer++ = TWI0.MDATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; - } + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From 10c8915d330cccac3d6f483f2fc64f944189eff6 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:28:43 +0000 Subject: [PATCH 12/95] Ensure correct functions are called for strcpy_P, strncmp_P, strlen_P etc. on non-AVR targets.. --- DCCEXParser.cpp | 4 ++-- FSH.h | 11 ++++++++++- RingStream.cpp | 2 +- WifiInterface.cpp | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cbb152e..221d8b0 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -190,9 +190,9 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) // Parse an F() string void DCCEXParser::parse(const FSH * cmd) { DIAG(F("SETUP(\"%S\")"),cmd); - int size=strlen_P((char *)cmd)+1; + int size=STRLEN_P((char *)cmd)+1; char buffer[size]; - strcpy_P(buffer,(char *)cmd); + STRCPY_P(buffer,(char *)cmd); parse(&USB_SERIAL,(byte *)buffer,NULL); } diff --git a/FSH.h b/FSH.h index f4bf47e..d031935 100644 --- a/FSH.h +++ b/FSH.h @@ -47,7 +47,11 @@ typedef __FlashStringHelper FSH; #define FLASH PROGMEM #define GETFLASH(addr) pgm_read_byte_near(addr) - +#define STRCPY_P strcpy_P +#define STRCMP_P strcmp_P +#define STRNCPY_P strncpy_P +#define STRNCMP_P strncmp_P +#define STRLEN_P strlen_P #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) // AVR_MEGA memory deliberately placed at end of link may need _far functions @@ -80,5 +84,10 @@ typedef char FSH; #define GETFLASH(addr) (*(const byte *)(addr)) #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) #define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset)) +#define STRCPY_P strcpy +#define STRCMP_P strcmp +#define STRNCPY_P strncpy +#define STRNCMP_P strncmp +#define STRLEN_P strlen #endif #endif diff --git a/RingStream.cpp b/RingStream.cpp index 9377a0a..12dbaa1 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -83,7 +83,7 @@ size_t RingStream::printFlash(const FSH * flashBuffer) { // Establish the actual length of the progmem string. char * flash=(char *)flashBuffer; -int16_t plength=strlen_P(flash); +int16_t plength=STRLEN_P(flash); if (plength==0) return 0; // just ignore empty string // Retain the buffer count as it will be modified by the marker+address insert diff --git a/WifiInterface.cpp b/WifiInterface.cpp index bdc8dad..f69cc2f 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -184,8 +184,8 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, checkForOK(1000, true); // Not always OK, sometimes "no change" const char *yourNetwork = "Your network "; - if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) { - if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)SSid, 13) == 0 || STRNCMP_P("", (const char*)SSid, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { // If the source code looks unconfigured, check if the // ESP8266 is preconfigured in station mode. // We check the first 13 chars of the SSid and the password @@ -258,7 +258,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, i=0; do { - if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { // unconfigured StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"), oldCmd ? "" : "_CUR", macTail, macTail, channel); From e8e00f69d6d270de8bab24698008efa97d8600cf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:31:33 +0000 Subject: [PATCH 13/95] Non-blocking I2C - reset byte counters on timeout. --- I2CManager_NonBlocking.h | 1 + 1 file changed, 1 insertion(+) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index c55219a..fce158b 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -196,6 +196,7 @@ void I2CManagerClass::checkForTimeout() { queueHead = t->nextRequest; if (!queueHead) queueTail = NULL; currentRequest = NULL; + bytesToReceive = bytesToSend = 0; // Post request as timed out. t->status = I2C_STATUS_TIMEOUT; // Reset TWI interface so it is able to continue From a3d4255fee33e5a317c701bc468f7b725d5c232b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 24 Jan 2023 09:33:17 +0000 Subject: [PATCH 14/95] Revert "I2CManager_Mega4809.h - allow other I2C clock speeds." This reverts commit 682c47f7dd011afff651ed20060c83ecc28d8d01. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index c378a5d..0b8e8ca 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2023, Neil McKechnie. All rights reserved. + * © 2021, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) + if (i2cClockSpeed < 200000) { + i2cClockSpeed = 100000; t_rise = 1000; - else if (i2cClockSpeed < 800000) + } else if (i2cClockSpeed < 800000) { + i2cClockSpeed = 400000; t_rise = 300; - else + } else if (i2cClockSpeed < 1200000) { + i2cClockSpeed = 1000000; t_rise = 120; - - if (t_rise == 120) - TWI0.CTRLA |= TWI_FMPEN_bm; - else - TWI0.CTRLA &= ~TWI_FMPEN_bm; - + } else { + i2cClockSpeed = 100000; + t_rise = 1000; + } uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; - if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; - I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif + I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,8 +70,6 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; - txCount = 0; - rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -91,10 +89,7 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - - TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C - TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; - delayMicroseconds(10); // Wait for things to stabilise (hopefully) + I2C_sendStop(); } /*************************************************************************** @@ -119,9 +114,11 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte (don't need to use GETFLASH) - txCount++; - TWI0.MDATA = *sendPointer++; + // Acked, so send next byte + if (currentRequest->operation == OPERATION_SEND_P) + TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); + else + TWI0.MDATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. @@ -134,10 +131,13 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - rxCount++; - *receivePointer++ = TWI0.MDATA; // Store received byte + currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; - } + } else { + // Buffer full, issue nack/stop + TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; + state = I2C_STATUS_OK; + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From d0ce59b19f7a18cbf431dc35cec4ca55b73cda89 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 24 Jan 2023 09:35:10 +0000 Subject: [PATCH 15/95] I2CManager_Mega4809.h - allow other I2C clock speeds. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index 0b8e8ca..41c70fe 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) { - i2cClockSpeed = 100000; + if (i2cClockSpeed < 200000) t_rise = 1000; - } else if (i2cClockSpeed < 800000) { - i2cClockSpeed = 400000; + else if (i2cClockSpeed < 800000) t_rise = 300; - } else if (i2cClockSpeed < 1200000) { - i2cClockSpeed = 1000000; + else t_rise = 120; - } else { - i2cClockSpeed = 100000; - t_rise = 1000; - } + + if (t_rise == 120) + TWI0.CTRLA |= TWI_FMPEN_bm; + else + TWI0.CTRLA &= ~TWI_FMPEN_bm; + uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; + if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; + I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif - I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,6 +70,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -89,7 +91,10 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - I2C_sendStop(); + + TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C + TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; + delayMicroseconds(10); // Wait for things to stabilise (hopefully) } /*************************************************************************** @@ -114,15 +119,12 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + // Acked, so send next byte (don't need to use GETFLASH) + TWI0.MDATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { - // Last sent byte acked and no more to send. Send repeated start, address and read bit. - TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; + // Last sent byte acked and no more to send. Send repeated start, address and read bit. + TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; @@ -133,11 +135,7 @@ void I2CManagerClass::I2C_handleInterrupt() { if (bytesToReceive) { currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; - } + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From 291a331f3ee49bdfef51342953f0e8d6cad0c6f4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 00:36:57 +0000 Subject: [PATCH 16/95] Fix read operations on I2CManager for SAMD --- I2CManager_SAMD.h | 49 +++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index cd57507..38ee996 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -1,6 +1,6 @@ /* * © 2022 Paul M Antoine - * © 2021, Neil McKechnie + * © 2023, Neil McKechnie * All rights reserved. * * This file is part of CommandStation-EX @@ -107,8 +107,8 @@ void I2CManagerClass::I2C_init() s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* | SERCOM_I2CM_CTRLA_SCLSM*/ ; - // Enable Smart mode and Quick Command - s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN; + // Enable Smart mode (but not Quick Command) + s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; #if defined(I2C_USE_INTERRUPTS) // Setting NVIC @@ -141,7 +141,7 @@ void I2CManagerClass::I2C_init() PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg = PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg = - PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; + PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; } /*************************************************************************** @@ -152,18 +152,21 @@ void I2CManagerClass::I2C_sendStart() { bytesToReceive = currentRequest->readLen; // We may have initiated a stop bit before this without waiting for it. - // Wait for stop bit to be sent before sending start. - while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); + // However, the state machine ensures that the start bit isn't sent + // until the stop bit is complete. + //while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { + // Wait while the I2C bus is BUSY + //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); // Send start and address with read/write flag or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { // Wait while the I2C bus is BUSY - while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; } } @@ -203,14 +206,11 @@ void I2CManagerClass::I2C_handleInterrupt() { state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // 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 { // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); @@ -218,25 +218,16 @@ void I2CManagerClass::I2C_handleInterrupt() { } } else if (s->I2CM.INTFLAG.bit.SB) { // Master read completed without errors - if (bytesToReceive) { + if (bytesToReceive == 1) { + s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte + I2C_sendStop(); // send stop + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive = 0; + state = I2C_STATUS_OK; // done + } else if (bytesToReceive) { + s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - s->I2CM.CTRLB.bit.ACKACT = 1; - I2C_sendStop(); - state = I2C_STATUS_OK; - } - if (bytesToReceive) { - // PMA - I think Smart Mode means we have nothing to do... - // More bytes to receive, issue ack and start another read - } - else - { - // Transaction finished, issue NACK and STOP. - s->I2CM.CTRLB.bit.ACKACT = 1; - I2C_sendStop(); - state = I2C_STATUS_OK; } } } From 4d350040ba8298e284112692826f8d80c0ba58e1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 12:28:51 +0000 Subject: [PATCH 17/95] I2CManager_SAMD.h - avoid bus hangs on speed changes The speed change is deferred until the next transmission is about to start to avoid issues with the I2C module being disabled and enabled during a transmission. --- I2CManager_SAMD.h | 64 +++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 38ee996..63efcd6 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -29,6 +29,9 @@ //#include #include +// Storage for new baud rate. Zero means no change pending +static uint32_t pendingBaudRate = 0; + /*************************************************************************** * Interrupt handler. * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero @@ -65,22 +68,37 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { i2cClockSpeed = 100000L; t_rise = 1000; } - - // 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 - s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + pendingBaudRate = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); +} - // Enable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 1 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); +/*************************************************************************** + * 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); - // Setting bus idle mode and wait for sync - s->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (s->I2CM.SYNCBUSY.bit.SYSOP != 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; } @@ -148,25 +166,29 @@ 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; bytesToReceive = currentRequest->readLen; - // We may have initiated a stop bit before this without waiting for it. - // However, the state machine ensures that the start bit isn't sent - // until the stop bit is complete. - //while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); + // 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 + // multi-master bus, the bus may be BUSY under control of another master, + // in which case we can avoid some arbitration failures by waiting until + // the bus state is IDLE. We don't do that here. // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { - // Wait while the I2C bus is BUSY - //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); - // Send start and address with read/write flag or'd in + // Send start and address with read flag (1) or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { - // Wait while the I2C bus is BUSY - //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + // Send start and address with write flag (0) or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; } } From bdffd368206347a1b59dd3e52aa221a6963ba07d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 15:24:38 +0000 Subject: [PATCH 18/95] IODevice.h - change visibility of findDevice to protected. To support nested drivers efficiently (i.e. to allow the higher driver to call another driver directly, without searching for a VPIN every time), the visibility of the IODevice::findDevice() function has been changed from private to protected. --- IODevice.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/IODevice.h b/IODevice.h index 77b440d..5155aaf 100644 --- a/IODevice.h +++ b/IODevice.h @@ -93,6 +93,8 @@ public: CONFIGURE_INPUT = 1, CONFIGURE_SERVO = 2, CONFIGURE_OUTPUT = 3, + CONFIGURE_ANALOGOUTPUT = 4, + CONFIGURE_ANALOGINPUT = 5, } ConfigTypeEnum; typedef enum : uint8_t { @@ -174,9 +176,12 @@ protected: _I2CAddress=0; } - // Method to perform initialisation of the device (optionally implemented within device class) + // Method to perform initialisation of the device (optionally implemented within device class) virtual void _begin() {} + // Method to check whether the vpin corresponds to this device + bool owns(VPIN vpin); + // Method to configure device (optionally implemented within device class) virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { (void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning. @@ -239,14 +244,13 @@ protected: // Static support function for subclass creation static void addDevice(IODevice *newDevice); + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); + // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; private: - // Method to check whether the vpin corresponds to this device - bool owns(VPIN vpin); - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; From ba9b36305856b38a9628502f47940b374c8cdf08 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 18:39:15 +0000 Subject: [PATCH 19/95] 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; + } } /*************************************************************************** From be88344407b51bdabec5971d3a2f7da09b576f66 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 2 Feb 2023 07:12:47 +0800 Subject: [PATCH 20/95] PCF8575 16-bit port device support added --- IODevice.h | 2 + IO_PCF8575.h | 106 ++++++++++++++++++++++++++++++++++++++++++ myHal.cpp_example.txt | 15 ++++++ 3 files changed, 123 insertions(+) create mode 100644 IO_PCF8575.h diff --git a/IODevice.h b/IODevice.h index 5155aaf..99394b4 100644 --- a/IODevice.h +++ b/IODevice.h @@ -1,4 +1,5 @@ /* + * © 2023, Paul Antoine, Discord user @ADUBOURG * © 2021, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API @@ -410,6 +411,7 @@ private: #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_PCF8575.h" #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" diff --git a/IO_PCF8575.h b/IO_PCF8575.h new file mode 100644 index 0000000..1b271ec --- /dev/null +++ b/IO_PCF8575.h @@ -0,0 +1,106 @@ +/* + * © 2023, Paul Antoine, and Discord user @ADUBOURG + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * The PCF8575 is a simple device; it only has one register. The device + * input/output mode and pullup are configured through this, and the + * output state is written and the input state read through it too. + * + * This is accomplished by having a weak resistor in series with the output, + * and a read-back of the other end of the resistor. As an output, the + * pin state is set to 1 or 0, and the output voltage goes to +5V or 0V + * (through the weak resistor). + * + * In order to use the pin as an input, the output is written as + * a '1' in order to pull up the resistor. Therefore the input will be + * 1 unless the pin is pulled down externally, in which case it will be 0. + * + * As a consequence of this approach, it is not possible to use the device for + * inputs without pullups. + */ + +#ifndef IO_PCF8575_H +#define IO_PCF8575_H + +#include "IO_GPIOBase.h" +#include "FSH.h" + +class PCF8575 : public GPIOBase { +public: + static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), I2CAddress, interruptPin); + } + +private: + PCF8575(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, I2CAddress, interruptPin) + { + requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); + } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writeGpioPort() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. + // Therefore, writing '1' in _writePortModes is enough to set the module to input mode + // and enable pull-up. + void _writePullups() override { } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writePortModes() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. + // When not in immediate mode, it initiates a request using the request block and returns. + // When the request completes, _processCompletion finishes the operation. + void _readGpioPort(bool immediate) override { + if (immediate) { + uint8_t buffer[2]; + I2CManager.read(_I2CAddress, buffer, 2); + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + } else { + requestBlock.wait(); // Wait for preceding operation to complete + // Issue new request to read GPIO register + I2CManager.queueRequest(&requestBlock); + } + } + + // This function is invoked when an I/O operation on the requestBlock completes. + void _processCompletion(uint8_t status) override { + if (status == I2C_STATUS_OK) + _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + else + _portInputState = 0xffff; + } + + // Set up device ports + void _setupDevice() override { + _writePortModes(); + _writeGpioPort(); + _writePullups(); + } + + uint8_t inputBuffer[2]; +}; + +#endif \ No newline at end of file diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 5470f76..1c5e701 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -88,6 +88,21 @@ void halSetup() { //PCF8574::create(200, 8, 0x23, 40); + //======================================================================= + // The following directive defines a PCF8575 16-port I2C GPIO Extender module. + //======================================================================= + // The parameters are: + // First Vpin=200 + // Number of VPINs=16 (numbered 200-215) + // I2C address of module=0x23 + + //PCF8575::create(200, 16, 0x23); + + + // Alternative form using INT pin (see above) + + //PCF8575::create(200, 16, 0x23, 40); + //======================================================================= // The following directive defines an HCSR04 ultrasonic ranging module. //======================================================================= From 49713badb2a464725042b60709ffba3c477dc197 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 2 Feb 2023 12:21:35 +0000 Subject: [PATCH 21/95] Update I2CManager_NonBlocking.h Add code to try and recover from stuck bus following a timeout. --- I2CManager_NonBlocking.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index f5caefd..3a33719 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -213,10 +213,21 @@ void I2CManagerClass::checkForTimeout() { // Reset TWI interface so it is able to continue // Try close and init, not entirely satisfactory but sort of works... I2C_close(); // Shutdown and restart twi interface + + // If SDA is stuck low, issue up to 9 clock pulses to attempt to free it. + pinMode(SCL, INPUT_PULLUP); + pinMode(SDA, INPUT_PULLUP); + for (int i=0; !digitalRead(SDA) && i<9; i++) { + digitalWrite(SCL, 0); + pinMode(SCL, OUTPUT); // Force clock low + delayMicroseconds(10); // ... for 5us + pinMode(SCL, INPUT_PULLUP); // ... then high + delayMicroseconds(10); // ... for 5us (100kHz Clock) + } + // Whether that's succeeded or not, now try reinitialising. I2C_init(); _setClock(_clockSpeed); state = I2C_STATE_FREE; -// I2C_sendStop(); // in case device is waiting for a stop condition // Initiate next queued request if any. startTransaction(); From 847ced2f497b003ac2564dde1b0f50e04b9f2f92 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:46:38 +0000 Subject: [PATCH 22/95] Update IO_VL53L0X.h Improve comments; drive XSHUT pin through pullup resistor, not directly. --- IO_VL53L0X.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 6a83970..cb3b6f7 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -42,14 +42,17 @@ * If you have more than one module, then you will need to specify a digital VPIN (Arduino * digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now, * when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once - * all VL53L0X modules are turned off, the driver works through each module in turn by - * setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address. + * all VL53L0X modules are turned off, the driver works through each module in turn, + * setting XSHUT to HIGH to turn that module on, then writing that module's desired I2C address. * In this way, many VL53L0X modules can be connected to the one I2C bus, each one - * using a distinct I2C address. + * using a distinct I2C address. The process is described in ST Microelectronics application + * note AN4846. * * WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise, - * and the device may even reset when handled. If you're not using XSHUT, then it's - * best to tie it to +5V. + * and the device may reset spontaneously or when handled and the device will stop responding + * on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor + * (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example) + * include a pull-up on the module, but others don't. * * The driver is configured as follows: * @@ -173,14 +176,17 @@ protected: break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. - // On the module, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V. - // Ensure this is done for only one module at a time by using a + // On some modules, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V, but we can provide additional + // protection by enabling the pull-up resistor on the microcontroller + // instead of driving the output directly. + // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. if (_addressConfigInProgress) return; _addressConfigInProgress = true; - // Set XSHUT pin (if connected) - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Set XSHUT pin (if connected). Because of supply voltage differences, + // drive the signal through the digital output's pull-up resistor. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, 1); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; From 81559998ec3fc834af939b5ea59f30140def1faf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:55:25 +0000 Subject: [PATCH 23/95] Update IODevice base class to better support filter drivers Filter drivers provide extra functionality above a hardware driver. For example, a hardware driver for a PWM module may just set the PWM ratio, but a separate filter driver could animate motors or servos over time, calling the PWM driver to output the pulses. This would allow the animations to be easily implemented on a different type of PWM module. --- IODevice.cpp | 43 +++++++++++++++++++++++++++++------------- IODevice.h | 53 ++++++++++++++++++++++++++++------------------------ 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index a51c84b..ea4301f 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -255,20 +255,26 @@ void IODevice::setGPIOInterruptPin(int16_t pinNumber) { _gpioInterruptPin = pinNumber; } -// Private helper function to add a device to the chain of devices. -void IODevice::addDevice(IODevice *newDevice) { - // Link new object to the end of the chain. Thereby, the first devices to be declared/created - // will be located faster by findDevice than those which are created later. - // Ideally declare/create the digital IO pins first, then servos, then more esoteric devices. - IODevice *lastDevice; - if (_firstDevice == 0) +// Helper function to add a new device to the device chain. If +// slaveDevice is NULL then the device is added to the end of the chain. +// Otherwise, the chain is searched for slaveDevice and the new device linked +// in front of it (to support filter devices that share the same VPIN range +// as the devices they control). If slaveDevice isn't found, then the +// device is linked to the end of the chain. +void IODevice::addDevice(IODevice *newDevice, IODevice *slaveDevice /* = NULL */) { + if (slaveDevice == _firstDevice) { + newDevice->_nextDevice = _firstDevice; _firstDevice = newDevice; - else { - for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) - lastDevice = dev; - lastDevice->_nextDevice = newDevice; + } else { + for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { + if (dev->_nextDevice == slaveDevice || dev->_nextDevice == NULL) { + // Link new device between dev and slaveDevice (or at end of chain) + newDevice->_nextDevice = dev->_nextDevice; + dev->_nextDevice = newDevice; + break; + } + } } - newDevice->_nextDevice = 0; newDevice->_begin(); } @@ -283,6 +289,17 @@ IODevice *IODevice::findDevice(VPIN vpin) { return NULL; } +// Instance helper function for filter devices (layered over others). Looks for +// a device that is further down the chain than the current device. +IODevice *IODevice::findDeviceFollowing(VPIN vpin) { + for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) { + VPIN firstVpin = dev->_firstVpin; + if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins) + return dev; + } + return NULL; +} + // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { @@ -320,7 +337,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) // Chain of callback blocks (identifying registered callback functions for state changes) IONotifyCallback *IONotifyCallback::first = 0; -// Start of chain of devices. +// Start and end of chain of devices. IODevice *IODevice::_firstDevice = 0; // Reference to next device to be called on _loop() method. diff --git a/IODevice.h b/IODevice.h index 5155aaf..3e86ca1 100644 --- a/IODevice.h +++ b/IODevice.h @@ -163,9 +163,35 @@ public: // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); - // Method to check if pins will overlap before creating new device. + // Method to check if pins will overlap before creating new device. static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - + + // Method used by IODevice filters to locate slave pins that may be overlayed by their own + // pin range. + IODevice *findDeviceFollowing(VPIN vpin); + + // Method to write new state (optionally implemented within device class) + virtual void _write(VPIN vpin, int value) { + (void)vpin; (void)value; + }; + + // Method to write an 'analogue' value (optionally implemented within device class) + virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) { + (void)vpin; (void)value; (void) param1; (void)param2; + }; + + // Method to read digital pin state (optionally implemented within device class) + virtual int _read(VPIN vpin) { + (void)vpin; + return 0; + }; + + // Method to read analogue pin state (optionally implemented within device class) + virtual int _readAnalogue(VPIN vpin) { + (void)vpin; + return 0; + }; + protected: // Constructor @@ -188,27 +214,6 @@ protected: return false; }; - // Method to write new state (optionally implemented within device class) - virtual void _write(VPIN vpin, int value) { - (void)vpin; (void)value; - }; - - // Method to write an 'analogue' value (optionally implemented within device class) - virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { - (void)vpin; (void)value; (void) param1; (void)param2; - }; - - // Method to read digital pin state (optionally implemented within device class) - virtual int _read(VPIN vpin) { - (void)vpin; - return 0; - }; - - // Method to read analogue pin state (optionally implemented within device class) - virtual int _readAnalogue(VPIN vpin) { - (void)vpin; - return 0; - }; virtual int _configureAnalogIn(VPIN vpin) { (void)vpin; return 0; @@ -242,7 +247,7 @@ protected: int16_t _gpioInterruptPin = -1; // Static support function for subclass creation - static void addDevice(IODevice *newDevice); + static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL); // Method to find device handling Vpin static IODevice *findDevice(VPIN vpin); From 27ddc7b30b00a0ef826f8f3801dbe8b8c02a2334 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:56:19 +0000 Subject: [PATCH 24/95] IO_ExampleSerial - refactor and update comments To more directly reflect the bulk of HAL drivers, the .h/.cpp split has been removed and the class is fully defined in the .h file. --- IO_ExampleSerial.cpp | 129 ------------------------------------------- IO_ExampleSerial.h | 129 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 140 deletions(-) delete mode 100644 IO_ExampleSerial.cpp diff --git a/IO_ExampleSerial.cpp b/IO_ExampleSerial.cpp deleted file mode 100644 index 12476db..0000000 --- a/IO_ExampleSerial.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * © 2021, Neil McKechnie. All rights reserved. - * - * This file is part of DCC++EX API - * - * This is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * It 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -#include -#include "IO_ExampleSerial.h" -#include "FSH.h" - -// Constructor -IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { - _firstVpin = firstVpin; - _nPins = nPins; - _pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t)); - _baud = baud; - - // Save reference to serial port driver - _serial = serial; - - addDevice(this); -} - -// Static create method for one module. -void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { - if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud); -} - -// Device-specific initialisation -void IO_ExampleSerial::_begin() { - _serial->begin(_baud); -#if defined(DIAG_IO) - _display(); -#endif - - // Send a few # characters to the output - for (uint8_t i=0; i<3; i++) - _serial->write('#'); -} - -// Device-specific write function. Write a string in the form "#Wm,n#" -// where m is the vpin number, and n is the value. -void IO_ExampleSerial::_write(VPIN vpin, int value) { - int pin = vpin -_firstVpin; - #ifdef DIAG_IO - DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); - #endif - // Send a command string over the serial line - _serial->print('#'); - _serial->print('W'); - _serial->print(pin); - _serial->print(','); - _serial->print(value); - _serial->println('#'); - DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value); - } - -// Device-specific read function. -int IO_ExampleSerial::_read(VPIN vpin) { - - // Return a value for the specified vpin. - int result = _pinValues[vpin-_firstVpin]; - - return result; -} - -// Loop function to do background scanning of the input port. State -// machine parses the incoming command as it is received. Command -// is in the form "#Nm,n#" where m is the index and n is the value. -void IO_ExampleSerial::_loop(unsigned long currentMicros) { - (void)currentMicros; // Suppress compiler warnings - if (_serial->available()) { - // Input data available to read. Read a character. - char c = _serial->read(); - switch (_inputState) { - case 0: // Waiting for start of command - if (c == '#') // Start of command received. - _inputState = 1; - break; - case 1: // Expecting command character - if (c == 'N') { // 'Notify' character received - _inputState = 2; - _inputValue = _inputIndex = 0; - } else - _inputState = 0; // Unexpected char, reset - break; - case 2: // reading first parameter (index) - if (isdigit(c)) - _inputIndex = _inputIndex * 10 + (c-'0'); - else if (c==',') - _inputState = 3; - else - _inputState = 0; // Unexpected char, reset - break; - case 3: // reading reading second parameter (value) - if (isdigit(c)) - _inputValue = _inputValue * 10 - (c-'0'); - else if (c=='#') { // End of command - // Complete command received, do something with it. - DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue); - if (_inputIndex < _nPins) { // Store value - _pinValues[_inputIndex] = _inputValue; - } - _inputState = 0; // Done, start again. - } else - _inputState = 0; // Unexpected char, reset - break; - } - } -} - -void IO_ExampleSerial::_display() { - DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, - (int)_firstVpin+_nPins-1); -} - diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h index 9b20399..da421c5 100644 --- a/IO_ExampleSerial.h +++ b/IO_ExampleSerial.h @@ -35,24 +35,131 @@ #include "IODevice.h" class IO_ExampleSerial : public IODevice { -public: - static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); - -protected: - IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); - void _begin() override; - void _loop(unsigned long currentMicros) override; - void _write(VPIN vpin, int value) override; - int _read(VPIN vpin) override; - void _display() override; - private: + // Here we define the device-specific variables. HardwareSerial *_serial; uint8_t _inputState = 0; int _inputIndex = 0; int _inputValue = 0; uint16_t *_pinValues; // Pointer to block of memory containing pin values unsigned long _baud; + +public: + // Static function to handle "IO_ExampleSerial::create(...)" calls. + static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { + if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud); + } + +protected: + // Constructor. This should initialise variables etc. but not call other objects yet + // (e.g. Serial, I2CManager, and other parts of the CS functionality). + // defer those until the _begin() function. The 'addDevice' call is required unless + // the device is not to be added (e.g. because of incorrect parameters). + IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { + _firstVpin = firstVpin; + _nPins = nPins; + _pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t)); + _baud = baud; + + // Save reference to serial port driver + _serial = serial; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + _serial->begin(_baud); +#if defined(DIAG_IO) + _display(); +#endif + + // Send a few # characters to the output + for (uint8_t i=0; i<3; i++) + _serial->write('#'); + } + + // Device-specific write function. Write a string in the form "#Wm,n#" + // where m is the vpin number, and n is the value. + void _write(VPIN vpin, int value) { + int pin = vpin -_firstVpin; + #ifdef DIAG_IO + DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); + #endif + // Send a command string over the serial line + _serial->print('#'); + _serial->print('W'); + _serial->print(pin); + _serial->print(','); + _serial->print(value); + _serial->println('#'); + DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value); + } + + // Device-specific read function. + int _read(VPIN vpin) { + + // Return a value for the specified vpin. + int result = _pinValues[vpin-_firstVpin]; + + return result; + } + + // Loop function to do background scanning of the input port. State + // machine parses the incoming command as it is received. Command + // is in the form "#Nm,n#" where m is the index and n is the value. + void _loop(unsigned long currentMicros) { + (void)currentMicros; // Suppress compiler warnings + if (_serial->available()) { + // Input data available to read. Read a character. + char c = _serial->read(); + switch (_inputState) { + case 0: // Waiting for start of command + if (c == '#') // Start of command received. + _inputState = 1; + break; + case 1: // Expecting command character + if (c == 'N') { // 'Notify' character received + _inputState = 2; + _inputValue = _inputIndex = 0; + } else + _inputState = 0; // Unexpected char, reset + break; + case 2: // reading first parameter (index) + if (isdigit(c)) + _inputIndex = _inputIndex * 10 + (c-'0'); + else if (c==',') + _inputState = 3; + else + _inputState = 0; // Unexpected char, reset + break; + case 3: // reading reading second parameter (value) + if (isdigit(c)) + _inputValue = _inputValue * 10 - (c-'0'); + else if (c=='#') { // End of command + // Complete command received, do something with it. + DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue); + if (_inputIndex >= 0 && _inputIndex < _nPins) { // Store value + _pinValues[_inputIndex] = _inputValue; + } + _inputState = 0; // Done, start again. + } else + _inputState = 0; // Unexpected char, reset + break; + } + } + } + + // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). + // Here we display the current values held for the pins. + void _display() { + DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, + (int)_firstVpin+_nPins-1); + for (int i=0; i<_nPins; i++) + DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]); + } + + }; #endif // IO_EXAMPLESERIAL_H \ No newline at end of file From bd62939713678e2f94c7228dd4a81807e5618667 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:55:14 +0000 Subject: [PATCH 25/95] Fix handling of hex numbers to avoid extending MSB (sign bit) Also, added format %X (unsigned long) to complement %x (unsigned int). --- StringFormatter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index cc78714..c1f20c4 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -108,7 +108,8 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; - case 'x': stream->print(va_arg(args, int), HEX); break; + case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break; + case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break; //case 'f': stream->print(va_arg(args, double), 2); break; //format width prefix case '-': From 2f46a8e083dbbe27540060e366df59f0f3291946 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:56:12 +0000 Subject: [PATCH 26/95] Add EX-RAIL 'ANOUT' function for general analogue outputs. --- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 1 + 2 files changed, 3 insertions(+) diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 32e28a2..6c004ff 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -28,6 +28,7 @@ #undef AFTER #undef ALIAS #undef AMBER +#undef ANOUT #undef AT #undef ATGTE #undef ATLT @@ -143,6 +144,7 @@ #define AFTER(sensor_id) #define ALIAS(name,value...) #define AMBER(signal_id) +#define ANOUT(vpin,value,param1,param2) #define AT(sensor_id) #define ATGTE(sensor_id,value) #define ATLT(sensor_id,value) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index b5e78d9..304443c 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -241,6 +241,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), #define ALIAS(name,value...) #define AMBER(signal_id) OPCODE_AMBER,V(signal_id), +#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2), #define AT(sensor_id) OPCODE_AT,V(sensor_id), #define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value), #define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value), From 6f5680fce01e03739a5b61980fcb680c27a4f885 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:59:19 +0000 Subject: [PATCH 27/95] DFPlayer: Avoid jumps in volume when switching song and reducing volume at the same time. --- IO_DFPlayer.h | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index ad09ca5..4439fbc 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -70,10 +70,12 @@ class DFPlayer : public IODevice { private: + const uint8_t MAXVOLUME=30; HardwareSerial *_serial; bool _playing = false; uint8_t _inputIndex = 0; unsigned long _commandSendTime; // Allows timeout processing + uint8_t _lastVolumeLevel = MAXVOLUME; // When two commands are sent in quick succession, the device sometimes // fails to execute one. A delay is required between successive commands. @@ -179,41 +181,45 @@ protected: // Volume may be specified as second parameter to writeAnalogue. // If value is zero, the player stops playing. // WriteAnalogue on second pin sets the output volume. + // If starting a new file and setting volume, then avoid a short burst of loud noise by + // the following strategy: + // - If the volume is increasing, start playing the song before setting the volume, + // - If the volume is decreasing, decrease it and then start playing. + // void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { uint8_t pin = vpin - _firstVpin; + #ifdef DIAG_IO + DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume); + #endif + // Validate parameter. - volume = min((uint8_t)30,volume); + if (volume > MAXVOLUME) volume = MAXVOLUME; if (pin == 0) { // Play track if (value > 0) { - #ifdef DIAG_IO - DIAG(F("DFPlayer: Play %d"), value); - #endif - sendPacket(0x03, value); // Play track - _playing = true; - if (volume > 0) { - #ifdef DIAG_IO - DIAG(F("DFPlayer: Volume %d"), volume); - #endif - sendPacket(0x06, volume); // Set volume + if (volume != 0) { + if (volume <= _lastVolumeLevel) + sendPacket(0x06, volume); // Set volume before starting + sendPacket(0x03, value); // Play track + _playing = true; + if (volume > _lastVolumeLevel) + sendPacket(0x06, volume); // Set volume after starting + _lastVolumeLevel = volume; + } else { + // Volume not changed, just play + sendPacket(0x03, value); + _playing = true; } } else { - #ifdef DIAG_IO - DIAG(F("DFPlayer: Stop")); - #endif sendPacket(0x16); // Stop play _playing = false; } } else if (pin == 1) { // Set volume (0-30) - if (value > 30) value = 30; - else if (value < 0) value = 0; - #ifdef DIAG_IO - DIAG(F("DFPlayer: Volume %d"), value); - #endif - sendPacket(0x06, value); + sendPacket(0x06, value); + _lastVolumeLevel = volume; } } From a590245e93a20bc8a45e65b86b7b667ec2ce5909 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 21:01:02 +0000 Subject: [PATCH 28/95] I2CManager_SAMD.h: Remove unneeded declaration. --- I2CManager_SAMD.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index affb6b3..08e34f2 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -29,9 +29,6 @@ //#include #include -// Storage for new baud rate. Zero means no change pending -static uint32_t pendingBaudRate = 0; - /*************************************************************************** * Interrupt handler. * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero From 13bd6ef9ebe47e8bbb0639f576c1ebccdf517a93 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 23:57:22 +0000 Subject: [PATCH 29/95] HAL: Add support for Extended Addresses and I2C Multiplexer Change I2C addresses from uint8_t to I2CAddress, in preparation for MUX support. Currently, by default, I2CAddress is typedef'd to uint8_t. MUX support implemented for AVR and Wire versions. --- I2CManager.cpp | 53 +++++++++++--- I2CManager.h | 154 +++++++++++++++++++++++++++++++++++---- I2CManager_AVR.h | 55 ++++++++++++-- I2CManager_NonBlocking.h | 12 ++- I2CManager_Wire.h | 79 ++++++++++++++------ IODevice.h | 6 +- IO_GPIOBase.h | 20 ++--- IO_MCP23008.h | 8 +- IO_MCP23017.h | 8 +- IO_PCA9685.cpp | 14 ++-- IO_PCF8574.h | 8 +- IO_PCF8575.h | 8 +- IO_VL53L0X.h | 23 +++--- LiquidCrystal_I2C.cpp | 2 +- LiquidCrystal_I2C.h | 2 +- SSD1306Ascii.cpp | 69 +++++++++++------- SSD1306Ascii.h | 11 ++- 17 files changed, 391 insertions(+), 141 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index eafdb70..b00fc8a 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -58,13 +58,46 @@ void I2CManagerClass::begin(void) { _setClock(100000); unsigned long originalTimeout = timeout; setTimeout(1000); // use 1ms timeout for probes + + #if defined(I2C_EXTENDED_ADDRESS) + // First switch off all multiplexer subbuses. + for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { + I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + } + #endif + bool found = false; - for (byte addr=1; addr<127; addr++) { + for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { found = true; DIAG(F("I2C Device found at x%x"), addr); } } + +#if defined(I2C_EXTENDED_ADDRESS) + // Enumerate all I2C devices that are connected via multiplexer, + // i.e. respond when only one multiplexer has one subBus enabled + // and the device doesn't respond when the mux subBus is disabled. + for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { + uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; + if (exists(muxAddr)) { + for (uint8_t subBus=0; subBus<=7; subBus++) { + for (uint8_t addr=0x08; addr<0x78; addr++) { + if (exists({(I2CMux)muxNo, (I2CSubBus)subBus, addr}) + && !exists({(I2CMux)muxNo, SubBus_None, addr})) { + found = true; + DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}"), + muxNo, subBus, addr); + } + } + } + // Probe mux address again with SubBus_None to deselect all + // subBuses for that mux. Otherwise its devices will continue to + // respond when other muxes are being probed. + I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + } + } +#endif if (!found) DIAG(F("No I2C Devices found")); _setClock(_clockSpeed); setTimeout(originalTimeout); // set timeout back to original @@ -92,7 +125,7 @@ void I2CManagerClass::forceClock(uint32_t speed) { // Check if specified I2C address is responding (blocking operation) // Returns I2C_STATUS_OK (0) if OK, or error code. // Suppress retries. If it doesn't respond first time it's out of the running. -uint8_t I2CManagerClass::checkAddress(uint8_t address) { +uint8_t I2CManagerClass::checkAddress(I2CAddress address) { I2CRB rb; rb.setWriteParams(address, NULL, 0); rb.suppressRetries(true); @@ -104,7 +137,7 @@ uint8_t I2CManagerClass::checkAddress(uint8_t address) { /*************************************************************************** * Write a transmission to I2C using a list of data (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) { +uint8_t I2CManagerClass::write(I2CAddress address, uint8_t nBytes, ...) { uint8_t buffer[nBytes]; va_list args; va_start(args, nBytes); @@ -117,7 +150,7 @@ uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) { /*************************************************************************** * Initiate a write to an I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) { +uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) { I2CRB req; uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req); return finishRB(&req, status); @@ -126,7 +159,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8_t dataLen) { +uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * data, uint8_t dataLen) { I2CRB req; uint8_t status = write_P(i2cAddress, data, dataLen, &req); return finishRB(&req, status); @@ -135,7 +168,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8 /*************************************************************************** * Initiate a write (optional) followed by a read from the I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen) { I2CRB req; @@ -146,7 +179,7 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r /*************************************************************************** * Overload of read() to allow command to be specified as a series of bytes (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, +uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, uint8_t writeSize, ...) { va_list args; // Copy the series of bytes into an array. @@ -230,7 +263,7 @@ bool I2CRB::isBusy() { /*************************************************************************** * Helper functions to fill the I2CRequest structure with parameters. ***************************************************************************/ -void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen) { +void I2CRB::setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen) { this->i2cAddress = i2cAddress; this->writeLen = 0; this->readBuffer = readBuffer; @@ -239,7 +272,7 @@ void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readL this->status = I2C_STATUS_OK; } -void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +void I2CRB::setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen) { this->i2cAddress = i2cAddress; this->writeBuffer = writeBuffer; @@ -250,7 +283,7 @@ void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t re this->status = I2C_STATUS_OK; } -void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) { +void I2CRB::setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) { this->i2cAddress = i2cAddress; this->writeBuffer = writeBuffer; this->writeLen = writeLen; diff --git a/I2CManager.h b/I2CManager.h index 0fcc6c6..4ecf155 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -131,6 +131,124 @@ #define I2C_USE_INTERRUPTS #endif +// I2C Extended Address support I2C Multiplexers and allows various properties to be +// associated with an I2C address such as the MUX and SubBus. In the future, this +// may be extended to include multiple buses, and other features. +// Uncomment to enable extended address. +// +// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc, +// it should be cast to (int) to ensure that the address value is passed rather than +// the struct. + +//#define I2C_EXTENDED_ADDRESS + + +// Type to hold I2C address +#if defined(I2C_EXTENDED_ADDRESS) + +///////////////////////////////////////////////////////////////////////////////////// +// Extended I2C Address type to facilitate extended I2C addresses including +// I2C multiplexer support. +///////////////////////////////////////////////////////////////////////////////////// + +// Currently I2CAddress supports one I2C bus, with up to eight +// multipexers (MUX) attached. Each MUX can have up to eight sub-buses. +enum I2CMux : uint8_t { + I2CMux_0 = 0, + I2CMux_1 = 1, + I2CMux_2 = 2, + I2CMux_3 = 3, + I2CMux_4 = 4, + I2CMux_5 = 5, + I2CMux_6 = 6, + I2CMux_7 = 7, + I2CMux_None = 255, // Address doesn't need mux switching +}; + +enum I2CSubBus : uint8_t { + SubBus_0 = 0, // Enable individual sub-buses... + SubBus_1 = 1, + SubBus_2 = 2, + SubBus_3 = 3, + SubBus_4 = 4, + SubBus_5 = 5, + SubBus_6 = 6, + SubBus_7 = 7, + SubBus_None = 254, // Disable all sub-buses on selected mux + SubBus_All = 255, // Enable all sub-buses +}; + +// First MUX address (they range between 0x70-0x77). +#define I2C_MUX_BASE_ADDRESS 0x70 + +// Currently I2C address supports one I2C bus, with up to eight +// multiplexers (MUX) attached. Each MUX can have up to eight sub-buses. +// This structure could be extended in the future (if there is a need) +// to support 10-bit I2C addresses, different I2C clock speed for each +// sub-bus, multiple I2C buses, and other features not yet thought of. +struct I2CAddress { +private: + // Fields + I2CMux _muxNumber; + I2CSubBus _subBus; + uint8_t _deviceAddress; +public: + // Constructors + // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(I2CMux muxNumber, I2CSubBus subBus, uint8_t deviceAddress) { + _muxNumber = muxNumber; + _subBus = subBus; + _deviceAddress = deviceAddress; + } + + // Basic constructor + I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {} + + // For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70) + I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) : + I2CAddress(I2CMux_0, subBus, deviceAddress) {} + + // Conversion from uint8_t to I2CAddress + // For I2CAddress in form "0x23" + // (device assumed to be on the main I2C bus). + I2CAddress(const uint8_t deviceAddress) : + I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {} + + // For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector) + I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : + I2CAddress(muxNumber, subBus, 0x00) {} + + // Conversion operator from I2CAddress to uint8_t + // For "uint8_t address = i2cAddress;" syntax + // (device assumed to be on the main I2C bus or on a currently selected subbus. + operator uint8_t () const { return _deviceAddress; } + + // Comparison operator + int operator == (I2CAddress &a) const { + if (_deviceAddress != a._deviceAddress) + return false; // Different device address so no match + if (_muxNumber == I2CMux_None || a._muxNumber == I2CMux_None) + return true; // Same device address, one or other on main bus + if (_subBus == SubBus_None || a._subBus == SubBus_None) + return true; // Same device address, one or other on main bus + if (_muxNumber != a._muxNumber) + return false; // Connected to a subbus on a different mux + if (_subBus != a._subBus) + return false; // different subbus + return true; // Same address on same mux and same subbus + } + // Field accessors + I2CMux muxNumber() { return _muxNumber; } + I2CSubBus subBus() { return _subBus; } + uint8_t address() { return _deviceAddress; } +}; + +#else +// Legacy single-byte I2C address type for compact code and smooth changeover. +typedef uint8_t I2CAddress; +#endif // I2C_EXTENDED_ADDRESS + + // Status codes for I2CRB structures. enum : uint8_t { // Codes used by Wire and by native drivers @@ -181,19 +299,19 @@ public: uint8_t wait(); bool isBusy(); - void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen); - void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); - void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); + void setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen); + void setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); + void setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); void suppressRetries(bool suppress); uint8_t writeLen; uint8_t readLen; uint8_t operation; - uint8_t i2cAddress; + I2CAddress i2cAddress; uint8_t *readBuffer; const uint8_t *writeBuffer; #if !defined(I2C_USE_WIRE) - I2CRB *nextRequest; + I2CRB *nextRequest; // Used by non-blocking devices for I2CRB queue management. #endif }; @@ -210,25 +328,30 @@ public: // setTimeout sets the timout value for I2C transactions (milliseconds). void setTimeout(unsigned long); // Check if specified I2C address is responding. - uint8_t checkAddress(uint8_t address); - inline bool exists(uint8_t address) { + uint8_t checkAddress(I2CAddress address); + inline bool exists(I2CAddress address) { return checkAddress(address)==I2C_STATUS_OK; } + // Select/deselect Mux Sub-Bus (if using legacy addresses, just checks address) + // E.g. muxSelectSubBus({I2CMux_0, SubBus_3}); + uint8_t muxSelectSubBus(I2CAddress address) { + return checkAddress(address); + } // Write a complete transmission to I2C from an array in RAM - uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size); - uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); + uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size); + uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb); // Write a complete transmission to I2C from an array in Flash - uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size); - uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); + uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size); + uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb); // Write a transmission to I2C from a list of bytes. - uint8_t write(uint8_t address, uint8_t nBytes, ...); + uint8_t write(I2CAddress address, uint8_t nBytes, ...); // Write a command from an array in RAM and read response - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0); - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb); // Write a command from an arbitrary list of bytes and read response - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, uint8_t writeSize, ...); void queueRequest(I2CRB *req); @@ -280,6 +403,7 @@ private: static volatile uint8_t bytesToReceive; static volatile uint8_t operation; static volatile unsigned long startTime; + static volatile uint8_t muxSendStep; volatile uint32_t pendingClockSpeed = 0; diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 267a921..24f6376 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -98,7 +98,14 @@ void I2CManagerClass::I2C_sendStart() { bytesToReceive = currentRequest->readLen; rxCount = 0; txCount = 0; +#if defined(I2C_EXTENDED_ADDRESS) + if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { + // Send request to multiplexer + muxSendStep = 1; // When start bit interrupt comes in, send SLA+W to MUX + } +#endif TWCR = (1<i2cAddress.subBus(); + uint8_t subBusMask = (subBus==SubBus_All) ? 0xff : + (subBus==SubBus_None) ? 0x00 : + 1 << subBus; + TWDR = subBusMask; + TWCR = (1<i2cAddress.address() == 0 && bytesToSend == 0) { + // Send stop and post rb. + TWCR = (1<writeBuffer + (txCount++)); else TWDR = currentRequest->writeBuffer[txCount++]; bytesToSend--; - TWCR = (1<i2cAddress << 1) | 1; // SLA+R - else - TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W +#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 + uint8_t deviceAddress = currentRequest->i2cAddress; + if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend)) + TWDR = (deviceAddress << 1) | 1; // SLA+R + else + TWDR = (deviceAddress << 1) | 0; // SLA+W + } TWCR = (1<wait(); req->setWriteParams(i2cAddress, writeBuffer, writeLen); @@ -156,7 +156,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, u /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) { +uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) { // Make sure previous request has completed. req->wait(); req->setWriteParams(i2cAddress, writeBuffer, writeLen); @@ -169,7 +169,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer * Initiate a read from the I2C device, optionally preceded by a write * (non-blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) { // Make sure previous request has completed. @@ -201,7 +201,7 @@ void I2CManagerClass::checkForTimeout() { unsigned long elapsed = micros() - startTime; if (elapsed > timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), t->i2cAddress, currentRequest); + //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), (int)t->i2cAddress, currentRequest); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; @@ -305,4 +305,8 @@ volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; uint8_t I2CManagerClass::retryCounter = 0; +#if defined(I2C_EXTENDED_ADDRESS) +volatile uint8_t I2CManagerClass::muxSendStep = 0; +#endif + #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 9749565..7ea75c5 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -65,18 +65,40 @@ void I2CManagerClass::setTimeout(unsigned long value) { #endif } +/******************************************************** + * Helper function for I2C Multiplexer operations + ********************************************************/ +#ifdef I2C_EXTENDED_ADDRESS +static uint8_t muxSelect(I2CAddress &address) { + // Select MUX sub bus. + Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+address.muxNumber()); + uint8_t data = address.subBus(); + Wire.write(&data, 1); + return Wire.endTransmission(true); // have to release I2C bus for it to work +} +#endif + /*************************************************************************** * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t 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 retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - Wire.beginTransmission(address); - if (size > 0) Wire.write(buffer, size); - status = Wire.endTransmission(); + status = I2C_STATUS_OK; +#ifdef I2C_EXTENDED_ADDRESS + if (address.muxNumber() != I2CMux_None) { + status = muxSelect(address); + } +#endif + // Only send new transaction if address and size are both nonzero. + if (status == I2C_STATUS_OK && address != 0 && size != 0) { + Wire.beginTransmission(address); + if (size > 0) Wire.write(buffer, size); + status = Wire.endTransmission(); + } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); rb->status = status; @@ -86,7 +108,7 @@ uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { +uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t ramBuffer[size]; const uint8_t *p1 = buffer; for (uint8_t i=0; i 0) { - Wire.beginTransmission(address); - Wire.write(writeBuffer, writeSize); - status = Wire.endTransmission(false); // Don't free bus yet + status = I2C_STATUS_OK; +#ifdef I2C_EXTENDED_ADDRESS + if (address.muxNumber() != I2CMux_None) { + status = muxSelect(address); } - if (status == I2C_STATUS_OK) { -#ifdef WIRE_HAS_TIMEOUT - Wire.clearWireTimeoutFlag(); #endif - Wire.requestFrom(address, (size_t)readSize); -#ifdef WIRE_HAS_TIMEOUT - if (!Wire.getWireTimeoutFlag()) { -#endif - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; -#ifdef WIRE_HAS_TIMEOUT - } else { - status = I2C_STATUS_TIMEOUT; + // Only start new transaction if address and readSize are both nonzero. + if (status == I2C_STATUS_OK && address != 0 && writeSize > 0) { + if (writeSize > 0) { + Wire.beginTransmission(address); + Wire.write(writeBuffer, writeSize); + status = Wire.endTransmission(false); // Don't free bus yet } + if (status == I2C_STATUS_OK) { +#ifdef WIRE_HAS_TIMEOUT + Wire.clearWireTimeoutFlag(); + Wire.requestFrom(address, (size_t)readSize); + if (!Wire.getWireTimeoutFlag()) { + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; + } else { + status = I2C_STATUS_TIMEOUT; + } +#else + Wire.requestFrom(address, (size_t)readSize); + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; #endif + } } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); @@ -137,6 +169,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea return I2C_STATUS_OK; } + /*************************************************************************** * Function to queue a request block and initiate operations. * diff --git a/IODevice.h b/IODevice.h index e451870..a546b6d 100644 --- a/IODevice.h +++ b/IODevice.h @@ -239,7 +239,7 @@ protected: // Common object fields. VPIN _firstVpin; int _nPins; - uint8_t _I2CAddress; + I2CAddress _I2CAddress; // Flag whether the device supports callbacks. bool _hasCallback = false; @@ -272,7 +272,7 @@ private: class PCA9685 : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t I2CAddress); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress); enum ProfileType : uint8_t { Instant = 0, // Moves immediately between positions (if duration not specified) UseDuration = 0, // Use specified duration @@ -285,7 +285,7 @@ public: private: // Constructor - PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress); + PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress); // Device-specific initialisation void _begin() override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 1a66b3d..2d9f8ab 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -34,7 +34,7 @@ class GPIOBase : public IODevice { protected: // Constructor - GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin); + GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin); // Device-specific initialisation void _begin() override; // Device-specific pin configuration function. @@ -80,11 +80,11 @@ protected: // Constructor template -GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) : +GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) : IODevice(firstVpin, nPins) { _deviceName = deviceName; - _I2CAddress = I2CAddress; + _I2CAddress = i2cAddress; _gpioInterruptPin = interruptPin; _hasCallback = true; // Add device to list of devices. @@ -110,7 +110,7 @@ void GPIOBase::_begin() { _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { - DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress); + DIAG(F("%S I2C:x%x Device not detected"), _deviceName, (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; } } @@ -125,7 +125,7 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun bool pullup = params[0]; int pin = vpin - _firstVpin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, pullup); + DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, pullup); #endif uint16_t mask = 1 << pin; if (pullup) @@ -155,7 +155,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { _deviceState = DEVSTATE_NORMAL; } else { _deviceState = DEVSTATE_FAILED; - DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, _I2CAddress, status, + DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); } _processCompletion(status); @@ -178,7 +178,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { #ifdef DIAG_IO if (differences) - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); + DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); #endif } @@ -199,7 +199,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress, + DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -208,7 +208,7 @@ void GPIOBase::_write(VPIN vpin, int value) { int pin = vpin - _firstVpin; T mask = 1 << pin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value); + DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, value); #endif // Set port mode output if currently not output mode @@ -244,7 +244,7 @@ int GPIOBase::_read(VPIN vpin) { // Set unused pin and write mode pin value to 1 _portInputState |= ~_portInUse | _portMode; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); + DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); #endif } return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) diff --git a/IO_MCP23008.h b/IO_MCP23008.h index bf4d521..188d3ea 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -25,14 +25,14 @@ class MCP23008 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin); } private: // Constructor - MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) { + MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); diff --git a/IO_MCP23017.h b/IO_MCP23017.h index 65769f6..f8176fa 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -30,14 +30,14 @@ class MCP23017 : public GPIOBase { public: - static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, min(nPins,16), i2cAddress, interruptPin); } private: // Constructor - MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin) + MCP23017(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 3d7c347..13e4968 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -38,8 +38,8 @@ static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. -void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress); +void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress); } // Configure a port on the PCA9685. @@ -73,10 +73,10 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i } // Constructor -PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; _nPins = min(nPins, 16); - _I2CAddress = I2CAddress; + _I2CAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. for (int i=0; i<_nPins; i++) @@ -239,13 +239,13 @@ void PCA9685::updatePosition(uint8_t pin) { // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void PCA9685::writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); + DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); #endif // Wait for previous request to complete uint8_t status = requestBlock.wait(); if (status != I2C_STATUS_OK) { _deviceState = DEVSTATE_FAILED; - DIAG(F("PCA9685 I2C:x%x failed %S"), _I2CAddress, I2CManager.getErrorMessage(status)); + DIAG(F("PCA9685 I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; @@ -259,7 +259,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCF8574.h b/IO_PCF8574.h index beeeb7c..0815511 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -43,13 +43,13 @@ class PCF8574 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin); } private: - PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) + PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } diff --git a/IO_PCF8575.h b/IO_PCF8575.h index 1b271ec..c749e56 100644 --- a/IO_PCF8575.h +++ b/IO_PCF8575.h @@ -44,13 +44,13 @@ class PCF8575 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), i2cAddress, interruptPin); } private: - PCF8575(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, I2CAddress, interruptPin) + PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index cb3b6f7..bcb937e 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -97,7 +97,6 @@ class VL53L0X : public IODevice { private: - uint8_t _i2cAddress; uint16_t _ambient; uint16_t _distance; uint16_t _signal; @@ -145,7 +144,7 @@ protected: VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { _firstVpin = firstVpin; _nPins = min(nPins, 3); - _i2cAddress = i2cAddress; + _I2CAddress = i2cAddress; _onThreshold = onThreshold; _offThreshold = offThreshold; _xshutPin = xshutPin; @@ -157,7 +156,7 @@ protected: // the device will not respond on its default address if it has // already been changed. Therefore, we skip the address configuration if the // desired address is already responding on the I2C bus. - if (_xshutPin == VPIN_NONE && I2CManager.exists(_i2cAddress)) { + if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { // Device already present on this address, so skip the address initialisation. _nextState = STATE_CONFIGUREDEVICE; } @@ -194,7 +193,7 @@ protected: case STATE_CONFIGUREADDRESS: // Then write the desired I2C address to the device, while this is the only // module responding to the default address. - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); _addressConfigInProgress = false; _nextState = STATE_SKIP; break; @@ -204,7 +203,7 @@ protected: break; case STATE_CONFIGUREDEVICE: // On next entry, check if device address has been set. - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { #ifdef DIAG_IO _display(); #endif @@ -213,7 +212,7 @@ protected: read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); _nextState = STATE_INITIATESCAN; } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); + DIAG(F("VL53L0X I2C:x%x device not responding"), (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; _nextState = STATE_FAILED; } @@ -222,7 +221,7 @@ protected: // Not scanning, so initiate a scan _outBuffer[0] = VL53L0X_REG_SYSRANGE_START; _outBuffer[1] = 0x01; - I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb); + I2CManager.write(_I2CAddress, _outBuffer, 2, &_rb); _nextState = STATE_CHECKSTATUS; break; case STATE_CHECKSTATUS: @@ -238,7 +237,7 @@ protected: case STATE_GETRESULTS: // Ranging completed. Request results _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; - I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb); + I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb); _nextState = 3; delayUntil(currentMicros + 5000); // Allow 5ms to get data _nextState = STATE_DECODERESULTS; @@ -278,7 +277,7 @@ protected: // Function to report a failed I2C operation. Put the device off-line. void reportError(uint8_t status) { - DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("VL53L0X I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } @@ -308,7 +307,7 @@ protected: void _display() override { DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), - _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, + (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -322,11 +321,11 @@ private: uint8_t outBuffer[2]; outBuffer[0] = reg; outBuffer[1] = data; - return I2CManager.write(_i2cAddress, outBuffer, 2); + return I2CManager.write(_I2CAddress, outBuffer, 2); } uint8_t read_reg(uint8_t reg) { // read byte from register and return value - I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1); + I2CManager.read(_I2CAddress, _inBuffer, 1, ®, 1); return _inBuffer[0]; } }; diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index e036b98..b18614f 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -41,7 +41,7 @@ // can't assume that its in that state when a sketch starts (and the // LiquidCrystal constructor is called). -LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols, +LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, uint8_t lcd_rows) { _Addr = lcd_Addr; lcdRows = lcd_rows; diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 6881a69..20dc126 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -64,7 +64,7 @@ class LiquidCrystal_I2C : public LCDDisplay { public: - LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); + LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); void begin(); void clearNative() override; void setRowNative(byte line) override; diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 2fc23c2..7373a74 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,9 +144,32 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor +SSD1306AsciiWire::SSD1306AsciiWire() {} + +// CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed + + // Probe for I2C device on 0x3c and 0x3d. + for (uint8_t address = 0x3c; address <= 0x3d; address++) { + if (I2CManager.exists(address)) { + begin(address, width, height); + // Set singleton Address so CS is able to call it. + lcdDisplay = this; + return; + } + } + DIAG(F("OLED display not found")); +} + +bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { + if (m_initialised) return true; + + m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; + // Set size in characters in base class lcdRows = height / 8; lcdCols = width / 6; @@ -154,35 +177,25 @@ SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { m_row = 0; m_colOffset = 0; - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - for (byte address = 0x3c; address <= 0x3d; address++) { - if (I2CManager.exists(address)) { - m_i2cAddr = address; - if (m_displayWidth==132 && m_displayHeight==64) { - // SH1106 display. This uses 128x64 centered within a 132x64 OLED. - m_colOffset = 2; - I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init)); - } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { - // SSD1306 128x64 or 128x32 - I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); - if (m_displayHeight == 32) - I2CManager.write(address, 5, 0, // Set command mode - SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 - SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap - } else { - DIAG(F("OLED configuration option not recognised")); - return; - } - // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address); - // Set singleton address - lcdDisplay = this; - clear(); - return; - } + if (m_displayWidth==132 && m_displayHeight==64) { + // SH1106 display. This uses 128x64 centered within a 132x64 OLED. + m_colOffset = 2; + I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init)); + } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { + // SSD1306 128x64 or 128x32 + I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); + if (m_displayHeight == 32) + I2CManager.write(m_i2cAddr, 5, 0, // Set command mode + SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 + SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap + } else { + DIAG(F("OLED configuration option not recognised")); + return false; } - DIAG(F("OLED display not found")); + // Device found + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + clear(); + return true; } /* Clear screen by writing blank pixels. */ diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index 312a62f..f4c9ba7 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -36,11 +36,12 @@ class SSD1306AsciiWire : public LCDDisplay { public: - // Constructor - SSD1306AsciiWire(int width, int height); + // Constructors + SSD1306AsciiWire(int width, int height); // Auto-detects I2C address + SSD1306AsciiWire(); // Requires call to 'begin()' // Initialize the display controller. - void begin(uint8_t i2cAddr); + bool begin(I2CAddress address, int width, int height); // Clear the display and set the cursor to (0, 0). void clearNative() override; @@ -66,6 +67,8 @@ class SSD1306AsciiWire : public LCDDisplay { uint8_t m_colOffset = 0; // Current font. const uint8_t* const m_font = System5x7; + // Flag to prevent calling begin() twice + uint8_t m_initialised = false; // Only fixed size 5x7 fonts in a 6x8 cell are supported. static const uint8_t fontWidth = 5; @@ -74,7 +77,7 @@ class SSD1306AsciiWire : public LCDDisplay { static const uint8_t m_fontFirstChar = 0x20; static const uint8_t m_fontCharCount = 0x61; - uint8_t m_i2cAddr; + I2CAddress m_i2cAddr; I2CRB requestBlock; uint8_t outputBuffer[fontWidth+letterSpacing+1]; From d5a394d4e6d568a1d517b2906273818a87c4003f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 14:55:14 +0000 Subject: [PATCH 30/95] Prepare HAL device drivers to support Extended I2C Addresses Update I2C addresses of HAL devices to type I2CAddress (to support extended address functions). Cast I2CAddress variables in DIAG calls to (int). Remove uses of max() function (not available on some platforms. --- IO_AnalogueInputs.h | 21 ++++++++++----------- IO_EXIOExpander.h | 12 ++++++------ IO_EXTurntable.h | 10 +++++----- IO_GPIOBase.h | 20 ++++++++------------ IO_MCP23008.h | 6 +++--- IO_MCP23017.h | 10 +++++----- IO_PCA9685.cpp | 4 ++-- IO_PCF8574.h | 15 ++++++++------- IO_PCF8575.h | 15 +++++++++------ IO_VL53L0X.h | 16 ++++++++++++---- 10 files changed, 68 insertions(+), 61 deletions(-) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 5f9d0e0..77afc58 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -59,14 +59,14 @@ **********************************************************************************************/ class ADS111x: public IODevice { public: - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress); } private: - ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + ADS111x(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; - _nPins = min(nPins,4); - _i2cAddress = i2cAddress; + _nPins = (nPins > 4) ? 4 : nPins; + _I2CAddress = i2cAddress; _currentPin = 0; for (int8_t i=0; i<_nPins; i++) _value[i] = -1; @@ -79,13 +79,13 @@ private: // processing. So stick to fast mode (400kHz maximum). I2CManager.setClock(400000); // Initialise ADS device - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { _nextState = STATE_STARTSCAN; #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); + DIAG(F("ADS111x device not found, I2C:%x"), (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; } } @@ -103,7 +103,7 @@ private: _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off // Write command, without waiting for completion. - I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); + I2CManager.write(_I2CAddress, _outBuffer, 3, &_i2crb); delayUntil(currentMicros + scanInterval); _nextState = STATE_STARTREAD; @@ -112,7 +112,7 @@ private: case STATE_STARTREAD: // Reading the pin value _outBuffer[0] = 0x00; // Conversion register address - I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register + I2CManager.read(_I2CAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register _nextState = STATE_GETVALUE; break; @@ -131,7 +131,7 @@ private: break; } } else { // error status - DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:x%d Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } @@ -142,7 +142,7 @@ private: } void _display() override { - DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, + DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -159,7 +159,6 @@ private: STATE_GETVALUE, }; uint16_t _value[4]; - uint8_t _i2cAddress; uint8_t _outBuffer[3]; uint8_t _inBuffer[2]; uint8_t _currentPin; // ADC pin currently being scanned diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1e20fac..3b13839 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,13 +54,13 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; @@ -79,7 +79,7 @@ private: // Send config, if EXIORDY returned, we're good, otherwise go offline I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); if (_digitalInBuffer[0] != EXIORDY) { - DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); + DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), (int)_i2cAddress); _deviceState = DEVSTATE_FAILED; return; } @@ -91,12 +91,12 @@ private: _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), - _i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + (int)_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress); + DIAG(F("EX-IOExpander device not found, I2C:x%x"), (int)_i2cAddress); _deviceState = DEVSTATE_FAILED; } } @@ -163,7 +163,7 @@ private: _lastAnalogue = _firstVpin + _nPins - 1; } DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), - _i2cAddress, _majorVer, _minorVer, _patchVer, + (int)_i2cAddress, _majorVer, _minorVer, _patchVer, _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, _numAnaloguePins, _firstAnalogue, _lastAnalogue, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 2dc9e6b..5b501b3 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -35,12 +35,12 @@ #include "I2CManager.h" #include "DIAG.h" -void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { new EXTurntable(firstVpin, nPins, I2CAddress); } // Constructor -EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { _firstVpin = firstVpin; _nPins = nPins; _I2CAddress = I2CAddress; @@ -106,15 +106,15 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), - _I2CAddress, stepsMSB, stepsLSB, activity); + (int)_I2CAddress, stepsMSB, stepsLSB, activity); #endif _stepperStatus = 1; // Tell the device driver Turntable-EX is busy - I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); + I2CManager.write((int)_I2CAddress, 3, stepsMSB, stepsLSB, activity); } // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 2d9f8ab..17e72d6 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -49,13 +49,13 @@ protected: // Data fields // Allocate enough space for all input pins - T _portInputState; - T _portOutputState; - T _portMode; - T _portPullup; - T _portInUse; - // Interval between refreshes of each input port - static const int _portTickTime = 4000; + T _portInputState; // 1=high (inactive), 0=low (activated) + T _portOutputState; // 1 =high, 0=low + T _portMode; // 0=input, 1=output + T _portPullup; // 0=nopullup, 1=pullup + T _portInUse; // 0=not in use, 1=in use + // Target interval between refreshes of each input port + static const int _portTickTime = 4000; // 4ms // Virtual functions for interfacing with I2C GPIO Device virtual void _writeGpioPort() = 0; @@ -69,10 +69,6 @@ protected: I2CRB requestBlock; FSH *_deviceName; -#if defined(ARDUINO_ARCH_ESP32) - // workaround: Has somehow no min function for all types - static inline T min(T a, int b) { return a < b ? a : b; }; -#endif }; // Because class GPIOBase is a template, the implementation (below) must be contained within the same @@ -83,6 +79,7 @@ template GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) : IODevice(firstVpin, nPins) { + if (_nPins > (int)sizeof(T)*8) _nPins = sizeof(T)*8; // Ensure nPins is consistent with the number of bits in T _deviceName = deviceName; _I2CAddress = i2cAddress; _gpioInterruptPin = interruptPin; @@ -117,7 +114,6 @@ void GPIOBase::_begin() { // Configuration parameters for inputs: // params[0]: enable pullup -// params[1]: invert input (optional) template bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { if (configType != CONFIGURE_INPUT) return false; diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 188d3ea..9598a50 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -32,7 +32,7 @@ public: private: // Constructor MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { + : GPIOBase((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); @@ -60,7 +60,7 @@ private: if (immediate) { uint8_t buffer; I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO); - _portInputState = buffer; + _portInputState = buffer | _portMode; } else { // Queue new request requestBlock.wait(); // Wait for preceding operation to complete @@ -71,7 +71,7 @@ private: // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = inputBuffer[0]; + _portInputState = inputBuffer[0] | _portMode; else _portInputState = 0xff; } diff --git a/IO_MCP23017.h b/IO_MCP23017.h index f8176fa..7bdc288 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -30,13 +30,13 @@ class MCP23017 : public GPIOBase { public: - static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, min(nPins,16), i2cAddress, interruptPin); + static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin); } private: // Constructor - MCP23017(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) + MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), @@ -65,7 +65,7 @@ private: if (immediate) { uint8_t buffer[2]; I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA); - _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode; } else { // Queue new request requestBlock.wait(); // Wait for preceding operation to complete @@ -76,7 +76,7 @@ private: // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; else _portInputState = 0xffff; } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 13e4968..f7a6882 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -75,7 +75,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i // Constructor PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; - _nPins = min(nPins, 16); + _nPins = (nPins > 16) ? 16 : nPins; _I2CAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. @@ -272,5 +272,5 @@ static void writeRegister(byte address, byte reg, byte value) { // The profile below is in the range 0-100% and should be combined with the desired limits // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH PCA9685::_bounceProfile[30] = +const uint8_t FLASH PCA9685::_bounceProfile[30] = {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 0815511..d71a32a 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -49,14 +49,16 @@ public: private: PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) + : GPIOBase((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. + // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. + // Unused pins are driven '0'. void _writeGpioPort() override { - I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); + I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse); } // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. @@ -64,9 +66,8 @@ private: // and enable pull-up. void _writePullups() override { } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. void _writePortModes() override { - I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); + _writeGpioPort(); } // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. @@ -76,7 +77,7 @@ private: if (immediate) { uint8_t buffer[1]; I2CManager.read(_I2CAddress, buffer, 1); - _portInputState = buffer[0]; + _portInputState = buffer[0] | _portMode; } else { requestBlock.wait(); // Wait for preceding operation to complete // Issue new request to read GPIO register @@ -87,7 +88,7 @@ private: // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = inputBuffer[0]; + _portInputState = inputBuffer[0] | _portMode; else _portInputState = 0xff; } diff --git a/IO_PCF8575.h b/IO_PCF8575.h index c749e56..0674617 100644 --- a/IO_PCF8575.h +++ b/IO_PCF8575.h @@ -45,7 +45,7 @@ class PCF8575 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), i2cAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin); } private: @@ -55,9 +55,12 @@ private: requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. + // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. + // Unused pins are driven '0'. void _writeGpioPort() override { - I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + uint16_t bits = (_portOutputState | ~_portMode) & _portInUse; + I2CManager.write(_I2CAddress, 2, bits, bits>>8); } // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. @@ -67,7 +70,7 @@ private: // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. void _writePortModes() override { - I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + _writeGpioPort(); } // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. @@ -77,7 +80,7 @@ private: if (immediate) { uint8_t buffer[2]; I2CManager.read(_I2CAddress, buffer, 2); - _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + _portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode; } else { requestBlock.wait(); // Wait for preceding operation to complete // Issue new request to read GPIO register @@ -88,7 +91,7 @@ private: // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; else _portInputState = 0xffff; } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index bcb937e..86d9624 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -136,14 +136,14 @@ private: public: - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); } protected: - VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + VL53L0X(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { _firstVpin = firstVpin; - _nPins = min(nPins, 3); + _nPins = (nPins > 3) ? 3 : nPins; _I2CAddress = i2cAddress; _onThreshold = onThreshold; _offThreshold = offThreshold; @@ -193,7 +193,15 @@ protected: case STATE_CONFIGUREADDRESS: // Then write the desired I2C address to the device, while this is the only // module responding to the default address. - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); + { + #if defined(I2C_EXTENDED_ADDRESS) + // Add subbus reference for desired address to the device default address. + I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS}; + I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); + #else + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); + #endif + } _addressConfigInProgress = false; _nextState = STATE_SKIP; break; From 9435869ee367221a4b316ae34e8397d14b7dd939 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:04:03 +0000 Subject: [PATCH 31/95] Prepare HAL device drivers to support Extended I2C Addresses Cast I2CAddress variables in DIAG calls to (int). --- IODevice.cpp | 2 +- IO_AnalogueInputs.h | 2 +- IO_RotaryEncoder.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index ea4301f..be26b92 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -322,7 +322,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) } // Check for overlapping I2C address if (i2cAddress && dev->_I2CAddress==i2cAddress) { - DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); + DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),(int)i2cAddress); return false; } } diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 77afc58..a03d90f 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -131,7 +131,7 @@ private: break; } } else { // error status - DIAG(F("ADS111x I2C:x%d Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index b4d538c..6de97af 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ private: } void _display() override { - DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), (int)_I2CAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } From 278a9c52a64d979ec99adc0535378d492f7b338e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:07:29 +0000 Subject: [PATCH 32/95] Refactor SSD1306 initialisation to support different initialisation sequences. To support a HAL Display driver, the SSD1306 driver can be created (new) and then the I2C address assigned explicitly in the begin() call. The original approach of looking for the I2C device address has also been retained in a different constructor. --- SSD1306Ascii.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 7373a74..f57252f 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,7 +144,10 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() {} +SSD1306AsciiWire::SSD1306AsciiWire() { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed +} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -193,7 +196,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); clear(); return true; } @@ -203,8 +206,9 @@ void SSD1306AsciiWire::clearNative() { const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; + for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes) { + uint8_t len = m_displayWidth-c; + if (len > maxBytes) len = maxBytes; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 261ccf2f3b7b0eff4e684ffe1ccf25116b4aaa9a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:09:21 +0000 Subject: [PATCH 33/95] HAL - change variable type for PCA9685 data. --- IODevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IODevice.h b/IODevice.h index a546b6d..d9c851f 100644 --- a/IODevice.h +++ b/IODevice.h @@ -315,7 +315,7 @@ private: struct ServoData *_servoData [16]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; + static const uint8_t FLASH _bounceProfile[30]; const unsigned int refreshInterval = 50; // refresh every 50ms From cb287f23a4164fd61e744abd560b394698f32e07 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 16:38:30 +0000 Subject: [PATCH 34/95] 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. --- I2CManager.cpp | 60 +++++++++++-- I2CManager.h | 28 +++++-- I2CManager_AVR.h | 177 ++++++++++++++++++++++++++++++--------- I2CManager_Mega4809.h | 11 ++- I2CManager_NonBlocking.h | 16 +++- I2CManager_SAMD.h | 14 ++-- I2CManager_Wire.h | 65 +++++++++----- 7 files changed, 279 insertions(+), 92 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index b00fc8a..00aa71b 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -41,6 +41,28 @@ #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 void I2CManagerClass::begin(void) { if (!_beginCompleted) { @@ -60,34 +82,46 @@ void I2CManagerClass::begin(void) { setTimeout(1000); // use 1ms timeout for probes #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++) { - I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK) + _muxCount++; } #endif + // Enumerate devices that are visible bool found = false; for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { 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) // 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. for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { + // Select Mux Subbus for (uint8_t subBus=0; subBus<=7; subBus++) { + muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { - if (exists({(I2CMux)muxNo, (I2CSubBus)subBus, addr}) - && !exists({(I2CMux)muxNo, SubBus_None, addr})) { - found = true; - DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}"), - muxNo, subBus, addr); + if (exists(addr)) { + // De-select subbus + muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); + if (!exists(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. 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 diff --git a/I2CManager.h b/I2CManager.h index 4ecf155..2a3d80c 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -195,7 +195,7 @@ private: public: // Constructors // 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; _subBus = subBus; _deviceAddress = deviceAddress; @@ -218,6 +218,11 @@ public: I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : 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 // For "uint8_t address = i2cAddress;" syntax // (device assumed to be on the main I2C bus or on a currently selected subbus. @@ -240,7 +245,7 @@ public: // Field accessors I2CMux muxNumber() { return _muxNumber; } I2CSubBus subBus() { return _subBus; } - uint8_t address() { return _deviceAddress; } + uint8_t deviceAddress() { return _deviceAddress; } }; #else @@ -271,6 +276,7 @@ enum : uint8_t { I2C_STATE_ACTIVE=253, I2C_STATE_FREE=254, I2C_STATE_CLOSING=255, + I2C_STATE_COMPLETED=252, }; typedef enum : uint8_t @@ -369,20 +375,23 @@ private: bool _beginCompleted = false; bool _clockSpeedFixed = false; static uint8_t retryCounter; // Count of retries -#if defined(__arm__) - uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 -#else - uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. -#endif + // Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD + // and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds + // will not be useful. The speed can be overridden by I2CManager::forceClock(). + uint32_t _clockSpeed = I2C_FREQ; static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. - // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); void _initialise(); void _setClock(unsigned long); +#if defined(I2C_EXTENDED_ADDRESS) + static uint8_t _muxCount; + uint8_t getMuxCount() { return _muxCount; } +#endif + #if !defined(I2C_USE_WIRE) // I2CRB structs are queued on the following two links. // If there are no requests, both are NULL. @@ -395,6 +404,7 @@ private: static I2CRB * volatile queueHead; static I2CRB * volatile queueTail; static volatile uint8_t state; + static uint8_t completionStatus; static I2CRB * volatile currentRequest; static volatile uint8_t txCount; @@ -403,7 +413,7 @@ private: static volatile uint8_t bytesToReceive; static volatile uint8_t operation; static volatile unsigned long startTime; - static volatile uint8_t muxSendStep; + static volatile uint8_t muxPhase; volatile uint32_t pendingClockSpeed = 0; diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 24f6376..e5e1863 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -101,8 +101,9 @@ void I2CManagerClass::I2C_sendStart() { #if defined(I2C_EXTENDED_ADDRESS) if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { // 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 TWCR = (1< 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< 1) { + // Device transaction complete, prepare to deselect MUX by sending start bit + TWCR = (1<i2cAddress.deviceAddress() == 0) { + // Send stop and post rb. + TWDR = 0xff; + TWCR = (1< 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<i2cAddress.muxNumber(); + TWDR = (muxAddress << 1) | 0; // MUXaddress+Write + TWCR = (1< MuxPhase_EPILOG) { + // Mux Cleardown was NAK'd, send stop and then finish. + TWCR = (1<i2cAddress.subBus(); - uint8_t subBusMask = (subBus==SubBus_All) ? 0xff : - (subBus==SubBus_None) ? 0x00 : - 1 << subBus; - TWDR = subBusMask; - TWCR = (1<i2cAddress.address() == 0 && bytesToSend == 0) { - // Send stop and post rb. - TWCR = (1<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 // any in-progress stop condition has been sent. TWCR = (1< 0) { currentRequest->readBuffer[rxCount++] = TWDR; bytesToReceive--; } /* fallthrough */ + case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received if (bytesToReceive <= 1) { TWCR = (1< 0) { currentRequest->readBuffer[rxCount++] = TWDR; bytesToReceive--; } TWCR = (1<i2cAddress.muxNumber(); - TWDR = (muxAddress << 1) | 0; // MUXaddress+Write - } else -#endif { // Set up address and R/W uint8_t deviceAddress = currentRequest->i2cAddress; @@ -218,25 +309,29 @@ void I2CManagerClass::I2C_handleInterrupt() { TWDR = (deviceAddress << 1) | 1; // SLA+R else TWDR = (deviceAddress << 1) | 0; // SLA+W + TWCR = (1<writeBuffer[txCount++]; @@ -128,7 +131,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; } } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors @@ -142,7 +145,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else { // Transaction finished, issue NACK and STOP. TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; + state = I2C_STATE_COMPLETED; } } } diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 5892bc2..4214ef9 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -76,6 +76,14 @@ for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ #undef I2C_USE_WIRE #endif +enum MuxPhase: uint8_t { + MuxPhase_OFF = 0, + MuxPhase_PROLOG, + MuxPhase_PASSTHRU, + MuxPhase_EPILOG, +} ; + + /*************************************************************************** * Initialise the I2CManagerAsync class. ***************************************************************************/ @@ -108,6 +116,7 @@ void I2CManagerClass::startTransaction() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; + completionStatus = I2C_STATUS_OK; // Check for pending clock speed change if (pendingClockSpeed) { // 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. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { // 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) { // Status is OK, or has failed and retry count exceeded, or retries disabled. @@ -270,7 +279,7 @@ void I2CManagerClass::handleInterrupt() { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; t->nBytes = rxCount; - t->status = state; + t->status = completionStatus; // I2C state machine is now free for next request currentRequest = NULL; @@ -297,6 +306,7 @@ I2CRB * volatile I2CManagerClass::queueHead = NULL; I2CRB * volatile I2CManagerClass::queueTail = NULL; I2CRB * volatile I2CManagerClass::currentRequest = NULL; volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE; +uint8_t I2CManagerClass::completionStatus; volatile uint8_t I2CManagerClass::txCount; volatile uint8_t I2CManagerClass::rxCount; volatile uint8_t I2CManagerClass::operation; @@ -306,7 +316,7 @@ volatile unsigned long I2CManagerClass::startTime; uint8_t I2CManagerClass::retryCounter = 0; #if defined(I2C_EXTENDED_ADDRESS) -volatile uint8_t I2CManagerClass::muxSendStep = 0; +volatile uint8_t I2CManagerClass::muxPhase = 0; #endif #endif \ No newline at end of file diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 08e34f2..5eb612b 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -155,6 +155,8 @@ void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // 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 @@ -207,13 +209,15 @@ void I2CManagerClass::I2C_handleInterrupt() { I2C_sendStart(); // Reinitiate request } else if (s->I2CM.STATUS.bit.BUSERR) { // 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) { // Master write completed if (s->I2CM.STATUS.bit.RXNACK) { // Nacked, send stop. I2C_sendStop(); - state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + state = I2C_STATE_COMPLETED; // Completed with error } else if (bytesToSend) { // Acked, so send next byte 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. s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { - // No more data to send/receive. Initiate a STOP condition. + // No more data to send/receive. Initiate a STOP condition I2C_sendStop(); - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; // Completed OK } } else if (s->I2CM.INTFLAG.bit.SB) { // Master read completed without errors @@ -233,7 +237,7 @@ void I2CManagerClass::I2C_handleInterrupt() { I2C_sendStop(); // send stop currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive = 0; - state = I2C_STATUS_OK; // done + state = I2C_STATE_COMPLETED; // Completed OK } else if (bytesToReceive) { s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 7ea75c5..67ef306 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -69,38 +69,53 @@ void I2CManagerClass::setTimeout(unsigned long value) { * Helper function for I2C Multiplexer operations ********************************************************/ #ifdef I2C_EXTENDED_ADDRESS -static uint8_t muxSelect(I2CAddress &address) { +static uint8_t muxSelect(I2CAddress address) { // Select MUX sub bus. - Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+address.muxNumber()); - uint8_t data = address.subBus(); - Wire.write(&data, 1); - return Wire.endTransmission(true); // have to release I2C bus for it to work + I2CMux muxNo = address.muxNumber(); + I2CSubBus subBus = address.subBus(); + if (muxNo != I2CMux_None) { + 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 + /*************************************************************************** * 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 status = I2C_STATUS_OK; + uint8_t status, muxStatus; uint8_t retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - status = I2C_STATUS_OK; + status = muxStatus = I2C_STATUS_OK; #ifdef I2C_EXTENDED_ADDRESS - if (address.muxNumber() != I2CMux_None) { - status = muxSelect(address); - } + if (address.muxNumber() != I2CMux_None) + muxStatus = muxSelect(address); #endif - // Only send new transaction if address and size are both nonzero. - if (status == I2C_STATUS_OK && address != 0 && size != 0) { + // Only send new transaction if address is non-zero. + if (muxStatus == I2C_STATUS_OK && address != 0) { Wire.beginTransmission(address); if (size > 0) Wire.write(buffer, size); status = Wire.endTransmission(); } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES - || rb->operation & OPERATION_NORETRY)); +#ifdef I2C_EXTENDED_ADDRESS + // 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; 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, 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 retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - status = I2C_STATUS_OK; + status = muxStatus = I2C_STATUS_OK; #ifdef I2C_EXTENDED_ADDRESS if (address.muxNumber() != I2CMux_None) { - status = muxSelect(address); + muxStatus = muxSelect(address); } #endif - // Only start new transaction if address and readSize are both nonzero. - if (status == I2C_STATUS_OK && address != 0 && writeSize > 0) { + // Only start new transaction if address is non-zero. + if (muxStatus == I2C_STATUS_OK && address != 0) { if (writeSize > 0) { Wire.beginTransmission(address); Wire.write(writeBuffer, writeSize); @@ -161,8 +176,16 @@ uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t #endif } } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES - || rb->operation & OPERATION_NORETRY)); +#ifdef I2C_EXTENDED_ADDRESS + // 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->status = status; From 0c88c74706db84fe90d5934125a5ea0959d6ad08 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:01:45 +0000 Subject: [PATCH 35/95] Revert "Refactor SSD1306 initialisation to support different initialisation sequences." This reverts commit 278a9c52a64d979ec99adc0535378d492f7b338e. --- SSD1306Ascii.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index f57252f..7373a74 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,10 +144,7 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() { - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed -} +SSD1306AsciiWire::SSD1306AsciiWire() {} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -196,7 +193,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); clear(); return true; } @@ -206,9 +203,8 @@ void SSD1306AsciiWire::clearNative() { const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes) { - uint8_t len = m_displayWidth-c; - if (len > maxBytes) len = maxBytes; + for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { + uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 7b79680de28c97d045c4255ce038ef3383972119 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:09:17 +0000 Subject: [PATCH 36/95] First stab at HAL-installable OLED Display Driver. --- IO_OLEDDisplay.h | 178 +++++++++++++++++++++++++++++++++++++++++++++++ SSD1306Ascii.cpp | 11 ++- 2 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 IO_OLEDDisplay.h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h new file mode 100644 index 0000000..e158cd9 --- /dev/null +++ b/IO_OLEDDisplay.h @@ -0,0 +1,178 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This driver provides a more immediate interface into the OLED display + * than the one installed through the config.h file. When an LCD(...) call + * is made, the text is output immediately to the specified display line, + * without waiting for the next 2.5 second refresh. However, no scrolling + * takes place, so if the line specified is off the screen then the text + * will instead be shown on the bottom line of the screen. + * + * To install, use the following command in myHal.cpp: + + * OLEDDisplay::create(address, width, height); + * + * where address is the I2C address (0x3c or 0x3d), + * width is the width in pixels of the display, and + * height is the height in pixels of the display. + * + * Valid width and height are 128x32 (SSD1306 controller), + * 128x64 (SSD1306) and 132x64 (SH1106). The driver uses + * a 5x7 character set in a 6x8 pixel cell. + */ + + +#ifndef IO_OLEDDISPLAY_H +#define IO_OLEDDISPLAY_H + +#include "IODevice.h" +#include "DisplayInterface.h" +#include "SSD1306Ascii.h" +#include "version.h" + +class OLEDDisplay : public IODevice, DisplayInterface { +private: + // Here we define the device-specific variables. + uint8_t _height; // in pixels + uint8_t _width; // in pixels + SSD1306AsciiWire *oled; + uint8_t _rowNo = 0; // Row number being written by caller + uint8_t _colNo = 0; // Position in line being written by caller + uint8_t _numRows; + uint8_t _numCols; + char *_buffer = NULL; + uint8_t *_rowGeneration = NULL; + uint8_t *_lastRowGeneration = NULL; + uint8_t _rowNoToScreen = 0; + uint8_t _charPosToScreen = 0; + +public: + // Static function to handle "OLEDDisplay::create(...)" calls. + static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(i2cAddress, width, height); + } + +protected: + // Constructor + OLEDDisplay(I2CAddress i2cAddress, int width, int height) { + _I2CAddress = i2cAddress; + _width = width; + _height = height; + _numCols = _width / 6; // character block 6 x 8 + _numRows = _height / 8; + + // Allocate arrays + _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + // Create OLED driver + oled = new SSD1306AsciiWire(); + // Initialise device + if (oled->begin(_I2CAddress, _width, _height)) { + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to lcdDisplay methods. + DisplayInterface::lcdDisplay = this; + + DIAG(F("OLEDDisplay installed on address x%x"), (int)_I2CAddress); + + // First clear the entire screen + oled->clearNative(); + + // Set first two lines on screen + LCD(0,F("DCC++ EX v%S"),F(VERSION)); + LCD(1,F("Lic GPLv3")); + } + } + + ///////////////////////////////////////////////// + // DisplayInterface functions + // + // TODO: Limit to one call to setRowNative or writeNative + // per entry so that the function doesn't block waiting + // for I2C to complete. + ///////////////////////////////////////////////// + DisplayInterface* loop2(bool force) override { + + // Loop through the buffer and if a row has changed + // (rowGeneration[row] is changed) then start writing the + // characters from the buffer, one character per entry, + // to the screen until that row has been refreshed. + // TODO: Currently this is done all in one go! Split + // it up so that at most one call is made to either + // setRowNative or writeNative per loop entry - then + // we shan't have to wait for I2C. + for (uint8_t row = 0; row < _numRows; row++) { + if (_rowGeneration[row] != _lastRowGeneration[row]) { + // Row has been modified, write to screen + oled->setRowNative(row); + for (uint8_t _col = 0; _col < _numCols; _col++) { + oled->writeNative(_buffer[(uint16_t)row*_numCols+_col]); + } + _lastRowGeneration[row] = _rowGeneration[row]; + } + } + return NULL; + } + + // Position on nominated line number (0 to number of lines -1) + // Clear the line in the buffer ready for updating + void setRow(byte line) override { + if (line >= _numRows) line = _numRows-1; + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry. + _rowGeneration[_rowNo]++; + } + + // Write blanks to all of the screen (blocks until complete) + void clear () override { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(_rowNo); + } + _rowNo = 0; + } + + // Write one character + size_t write(uint8_t c) override { + // Write character to buffer (if space) + if (_colNo < _numCols) + _buffer[_rowNo*_numCols+_colNo++] = c; + return 1; + } + + // Display information about the device. + void _display() { + DIAG(F("OLEDDisplay Configured addr x%x"), (int)_I2CAddress); + } + +}; + +#endif // IO_OLEDDISPLAY_H \ No newline at end of file diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 7373a74..12d8702 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,7 +144,11 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() {} +SSD1306AsciiWire::SSD1306AsciiWire() { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed + +} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -193,7 +197,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); clear(); return true; } @@ -204,7 +208,8 @@ void SSD1306AsciiWire::clearNative() { for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; + uint8_t len = m_displayWidth-c+1; + if (len > maxBytes) len = maxBytes; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 19070d33ba6e9dbb0b18c8f70f803e46514415bb Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:13:36 +0000 Subject: [PATCH 37/95] Add Arduino M0, and display time on serial monitor. --- platformio.ini | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 82167f2..40dc89e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,6 +30,7 @@ include_dir = . [env] build_flags = -Wall -Wextra +monitor_filters = time [env:samd21-dev-usb] platform = atmelsam @@ -51,6 +52,15 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 +[env:Arduino M0] +platform = atmelsam +board = mzeroUSB +framework = arduino +lib_deps = ${env.lib_deps} +monitor_speed = 115200 +monitor_echo = yes +build_flags = -std=c++17 -DI2C_EXTENDED_ADDRESS ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO + [env:mega2560-debug] platform = atmelavr board = megaatmega2560 @@ -61,7 +71,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DDIAG_IO -DDIAG_LOOPTIMES +build_flags = -DI2C_EXTENDED_ADDRESS -DDIAG_IO -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr From aad0d28d1f7ff1a68822a61c0c6b53b33dcf1dc1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:24:05 +0000 Subject: [PATCH 38/95] Servo driver - split PCA9685 into a filter + PWM driver. Two new drivers: PCA9685pwm drives the PCA9685 I2C device directly, and Servo driver which acts as a 'shim' over the top to control animations. This is aimed at supporting devices like the EXIOExpander by allowing the Servo driver to talk to the EXIOExpander driver instead, to animate servos on another controller. --- IO_PCA9685pwm.h | 149 ++++++++++++++++++++++++ IO_Servo.cpp | 33 ++++++ IO_Servo.h | 298 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 IO_PCA9685pwm.h create mode 100644 IO_Servo.cpp create mode 100644 IO_Servo.h diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h new file mode 100644 index 0000000..5a28f6e --- /dev/null +++ b/IO_PCA9685pwm.h @@ -0,0 +1,149 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This driver performs the basic interface between the HAL and an + * I2C-connected PCA9685 16-channel PWM module. When requested, it + * commands the device to set the PWM mark-to-period ratio accordingly. + * The call to IODevice::writeAnalogue(vpin, value) specifies the + * desired value in the range 0-4095 (0=0% and 4095=100%). + */ + +#ifndef PCA9685_BASIC_H +#define PCA9685_BASIC_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +/* + * IODevice subclass for PCA9685 16-channel PWM module. + */ + +class PCA9685pwm : public IODevice { +public: + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress); + } + +private: + + // structures for setting up non-blocking writes to servo controller + I2CRB requestBlock; + uint8_t outputBuffer[5]; + + // REGISTER ADDRESSES + const uint8_t PCA9685_MODE1=0x00; // Mode Register + const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first servo register ON*/ + const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ + // MODE1 bits + const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ + const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */ + const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */ + + const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); + const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed + + // Constructor + PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + _firstVpin = firstVpin; + _nPins = (nPins>16) ? 16 : nPins; + _I2CAddress = i2cAddress; + addDevice(this); + + // Initialise structure used for setting pulse rate + requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C + // In reality, other devices including the Arduino will limit + // the clock speed to a lower rate. + + // Initialise I/O module here. + if (I2CManager.exists(_I2CAddress)) { + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); + // In theory, we should wait 500us before sending any other commands to each device, to allow + // the PWM oscillator to get running. However, we don't do any specific wait, as there's + // plenty of other stuff to do before we will send a command. + #if defined(DIAG_IO) + _display(); + #endif + } else + _deviceState = DEVSTATE_FAILED; + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + writeDevice(pin, value); + } + + // Display details of this device. + void _display() override { + DIAG(F("PCA9685pwm I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } + + // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value + // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. + void writeDevice(uint8_t pin, int value) { + #ifdef DIAG_IO + DIAG(F("PCA9685pwm I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + #endif + // Wait for previous request to complete + uint8_t status = requestBlock.wait(); + if (status != I2C_STATUS_OK) { + _deviceState = DEVSTATE_FAILED; + DIAG(F("PCA9685pwm I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); + } else { + // Set up new request. + outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; + outputBuffer[1] = 0; + outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on + outputBuffer[3] = value & 0xff; + outputBuffer[4] = value >> 8; + I2CManager.queueRequest(&requestBlock); + } + } + + // Internal helper function for this device + static void writeRegister(I2CAddress address, uint8_t reg, uint8_t value) { + I2CManager.write(address, 2, reg, value); + } + +}; + +#endif \ No newline at end of file diff --git a/IO_Servo.cpp b/IO_Servo.cpp new file mode 100644 index 0000000..d59c1de --- /dev/null +++ b/IO_Servo.cpp @@ -0,0 +1,33 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#include "IO_Servo.h" +#include "FSH.h" + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +// +// Note: This has been put into its own .CPP file to ensure that duplicates aren't created +// if the IO_Servo.h library is #include'd in multiple source files. +// +const uint8_t FLASH Servo::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + diff --git a/IO_Servo.h b/IO_Servo.h new file mode 100644 index 0000000..8f95463 --- /dev/null +++ b/IO_Servo.h @@ -0,0 +1,298 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This device is a layered device which is designed to sit on top of another + * device. The underlying device class is expected to accept writeAnalogue calls + * which will normally cause some physical movement of something. The device may be a servo, + * a motor or some other kind of positioner, and the something might be a turnout, + * a semaphore signal or something else. One user has used this capability for + * moving a figure along the platform on their layout! + * + * Example of use: + * In myHal.cpp, + * + * #include "IO_Servo.h" + * ... + * PCA9685::create(100,16,0x40); // First create the hardware interface device + * Servo::create(300,16,100); // Then create the higher level device which + * // references pins 100-115 or a subset of them. + * + * Then any reference to pins 300-315 will cause the servo driver to send output + * PWM commands to the corresponding PCA9685 driver pins 100-115. The PCA9685 driver may + * be substituted with any other driver which provides analogue output + * capability, e.g. EX-IOExpander devices, as long as they are capable of interpreting + * the writeAnalogue() function calls. + */ + +#include "IODevice.h" + +#ifndef IO_SERVO_H +#define IO_SERVO_H + +#include "I2CManager.h" +#include "DIAG.h" + +class Servo : IODevice { + +public: + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin=VPIN_NONE) { + new Servo(firstVpin, nPins, firstSlavePin); + } + +private: + VPIN _firstSlavePin; + IODevice *_slaveDevice = NULL; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + + + // Configure a port on the Servo. + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + if (_deviceState == DEVSTATE_FAILED) return false; + if (configType != CONFIGURE_SERVO) return false; + if (paramCount != 5) return false; + #ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); + #endif + + int8_t pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + + if (state != -1) { + // Position servo to initial state + writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition); + } + return true; + } + + // Constructor + Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin = VPIN_NONE) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; + if (firstSlavePin == VPIN_NONE) + _firstSlavePin = firstVpin; + else + _firstSlavePin = firstSlavePin; + + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) + _servoData[i] = NULL; + + // Get reference to slave device. + _slaveDevice = findDevice(_firstSlavePin); + if (!_slaveDevice) { + DIAG(F("Servo: Slave device not found on pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) { + DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + + addDevice(this, _slaveDevice); // Link device ahead of slave device to intercept requests + } + + // Device-specific initialisation + void _begin() override { + #if defined(DIAG_IO) + _display(); + #endif + } + + // Device-specific write function, invoked from IODevice::write(). + // For this function, the configured profile is used. + void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; + #ifdef DIAG_IO + DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + #endif + int pin = vpin - _firstVpin; + if (value) value = 1; + + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // Profile is as follows: + // Bit 7: 0=Set output to 0% to power off servo motor when finished + // 1=Keep output at final position (better with LEDs, which will stay lit) + // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) + // 1 (Fast) Move servo in 0.5 seconds + // 2 (Medium) Move servo in 1.0 seconds + // 3 (Slow) Move servo in 2.0 seconds + // 4 (Bounce) Servo 'bounces' at extremes. + // Duration is in deciseconds (tenths of a second) and defaults to 0. + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + // _read returns true if the device is currently in executing an animation, + // changing the output over a period of time. + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) + return false; // No structure means no animation! + else + return (s->stepNumber < s->numSteps); + } + + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } + delayUntil(currentMicros + refreshInterval * 1000UL); + } + + // Private function to reposition servo + // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command to output driver + _slaveDevice->_writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off output driver to avoid servo buzz. + _slaveDevice->_writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } + } + + // Display details of this device. + void _display() override { + DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } +}; + +#endif \ No newline at end of file From 5439dd3158b6571c3d6da8d20c214fff3793f3fd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:59:40 +0000 Subject: [PATCH 39/95] Suppress compiler warnings. --- IO_OLEDDisplay.h | 3 ++- IO_PCA9685pwm.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index e158cd9..b3fa283 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -100,7 +100,7 @@ protected: // First clear the entire screen oled->clearNative(); - + // Set first two lines on screen LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); @@ -115,6 +115,7 @@ protected: // for I2C to complete. ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { + (void)force; // suppress compiler warning // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 5a28f6e..72884a9 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -98,7 +98,8 @@ private: // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); From 31ce2e3feff7e5221914e2146948abb22197a870 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 18:36:48 +0000 Subject: [PATCH 40/95] EXTurntable - change I2C address type to I2CAddress. --- IODevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IODevice.h b/IODevice.h index d9c851f..f5c7c6f 100644 --- a/IODevice.h +++ b/IODevice.h @@ -385,9 +385,9 @@ private: class EXTurntable : public IODevice { public: - static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress); + static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress); // Constructor - EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress); + EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress); enum ActivityNumber : uint8_t { Turn = 0, // Rotate turntable, maintain phase Turn_PInvert = 1, // Rotate turntable, invert phase From e2e9b7bae728280194aa9009ca9adc21a1795018 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 22:04:09 +0000 Subject: [PATCH 41/95] Update version.h --- version.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/version.h b/version.h index 6788645..b2d1f8b 100644 --- a/version.h +++ b/version.h @@ -5,6 +5,20 @@ #define VERSION "4.2.14" +// 4.2.15 I2C Multiplexer support through Extended Addresses, +// added for Wire, 4209 and AVR I2C drivers. +// I2C retries when fail. +// I2C timeout handling and recovery completed. +// I2C SAMD Driver Read code completed. +// PCF8575 I2C GPIO driver added. +// EX-RAIL ANOUT function for triggering analogue +// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// Installable HAL OLED Display Driver. +// Layered HAL Drivers PCA9685pwm and Servo added for +// (1) native PWM on PCA9685 module and +// (2) animations of servo movement via PCA9685pwm. +// This is intended to support EXIOExpander and also +// replace the existing PCA9685 driver. // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From 47cb43d1e9378bcb05a9b9a8ecc1d12599238771 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:16:00 +0000 Subject: [PATCH 42/95] Update IO_OLEDDisplay.h Make display updates non-blocking (after initialisation completes). --- IO_OLEDDisplay.h | 77 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index b3fa283..9aefbaa 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -21,9 +21,11 @@ * This driver provides a more immediate interface into the OLED display * than the one installed through the config.h file. When an LCD(...) call * is made, the text is output immediately to the specified display line, - * without waiting for the next 2.5 second refresh. However, no scrolling - * takes place, so if the line specified is off the screen then the text - * will instead be shown on the bottom line of the screen. + * without waiting for the next 2.5 second refresh. However, if the line + * specified is off the screen then the text in the bottom line will be + * overwritten. There is however a special case that if line 255 is specified, + * the existing text will scroll up and the new line added to the bottom + * line of the screen. * * To install, use the following command in myHal.cpp: @@ -78,6 +80,8 @@ protected: _numCols = _width / 6; // character block 6 x 8 _numRows = _height / 8; + _charPosToScreen = _numCols; + // Allocate arrays _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); @@ -107,41 +111,62 @@ protected: } } - ///////////////////////////////////////////////// - // DisplayInterface functions - // - // TODO: Limit to one call to setRowNative or writeNative - // per entry so that the function doesn't block waiting - // for I2C to complete. - ///////////////////////////////////////////////// - DisplayInterface* loop2(bool force) override { - (void)force; // suppress compiler warning + + void _loop(unsigned long) override { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the // characters from the buffer, one character per entry, // to the screen until that row has been refreshed. - // TODO: Currently this is done all in one go! Split - // it up so that at most one call is made to either - // setRowNative or writeNative per loop entry - then - // we shan't have to wait for I2C. - for (uint8_t row = 0; row < _numRows; row++) { - if (_rowGeneration[row] != _lastRowGeneration[row]) { - // Row has been modified, write to screen - oled->setRowNative(row); - for (uint8_t _col = 0; _col < _numCols; _col++) { - oled->writeNative(_buffer[(uint16_t)row*_numCols+_col]); + + // First check if the OLED driver is still busy from a previous + // call. If so, don't to anything until the next entry. + if (!oled->isBusy()) { + // Check if we've just done the end of a row or just started + if (_charPosToScreen >= _numCols) { + // Move to next line + if (++_rowNoToScreen >= _numRows) + _rowNoToScreen = 0; // Wrap to first row + + if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { + // Row content has changed, so start outputting it + _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; + oled->setRowNative(_rowNoToScreen); + _charPosToScreen = 0; // Prepare to output first character on next entry + } else { + // Row not changed, don't bother writing it. } - _lastRowGeneration[row] = _rowGeneration[row]; - } + } else { + // output character at current position + oled->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + } } - return NULL; + return; } + ///////////////////////////////////////////////// + // DisplayInterface functions + // + ///////////////////////////////////////////////// + DisplayInterface* loop2(bool force) override { + (void)force; // suppress compiler warning + return NULL; + } + // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating void setRow(byte line) override { - if (line >= _numRows) line = _numRows-1; + if (line == 255) { + // LCD(255, "xxx") - scroll the contents of the buffer + // and put the new line at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + _rowNo = line; // Fill line with blanks for (_colNo = 0; _colNo < _numCols; _colNo++) From 73a7d3e0ca02830cdf780f7fb09ed0c84edf7350 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:16:37 +0000 Subject: [PATCH 43/95] Update I2CManager_AVR.h Bug fix in native driver MUX code. --- I2CManager_AVR.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index e5e1863..7613132 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -22,6 +22,7 @@ #include #include "I2CManager.h" +#include "I2CManager_NonBlocking.h" // to satisfy intellisense #include #include @@ -139,9 +140,7 @@ void I2CManagerClass::I2C_handleInterrupt() { #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))) { + if (muxPhase > MuxPhase_OFF) { switch (twsr) { case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received if (muxPhase == MuxPhase_PROLOG) { From efb26660603eb02d056b1d6c9c7a7b4124213269 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:18:59 +0000 Subject: [PATCH 44/95] DCCTimer_AVR - incorporate Haba's optimisations to ADC scanning --- DCCTimer.h | 16 ++++----- DCCTimerAVR.cpp | 91 +++++++++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 56 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 75be8cd..2214851 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,5 +1,5 @@ /* - * © 2022-2023 Paul M. Antoine + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker @@ -102,14 +102,9 @@ private: // that an offset can be initialized. class ADCee { public: - // begin is called for any setup that must be done before - // **init** can be called. On some architectures this involves ADC - // initialisation and clock routing, sampling times etc. - static void begin(); - // init adds the pin to the list of scanned pins (if this + // init does add the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value (which is why it required begin to have been called first!) - // It must be called before the regular scan is started. + // read value. It is called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -118,6 +113,9 @@ public: static int read(uint8_t pin, bool fromISR=false); // returns possible max value that the ADC can return static int16_t ADCmax(); + // begin is called for any setup that must be done before + // scan can be called. + static void begin(); private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. @@ -126,6 +124,8 @@ private: static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; + // ids to scan (new way) + static byte *idarr; // friend so that we can call scan() and begin() friend class DCCWaveform; }; diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 9b16c47..40ce0fb 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -123,13 +123,14 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 15 +#define NUM_ADC_INPUTS 16 #else -#define NUM_ADC_INPUTS 7 +#define NUM_ADC_INPUTS 8 #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; -bool ADCusesHighPort = false; +byte *ADCee::idarr = NULL; +static bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -138,16 +139,28 @@ bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - if (id > NUM_ADC_INPUTS) + byte n; + if (id >= NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); - if (analogvals == NULL) - analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); - analogvals[id] = value; - usedpins |= (1<setBrake(0); + analogvals[idarr[num]] = (high << 8) | low; waiting = false; - id++; - mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; - } } if (!waiting) { - if (usedpins == 0) // otherwise we would loop forever - return; - // look for a valid track to sample or until we are around - while (true) { - if (mask & usedpins) { - // start new ADC aquire on id + // cycle around in-use analogue pins + num++; + if (idarr[num] == 255) + num = 0; + // start new ADC aquire on id #if defined(ADCSRB) && defined(MUX5) - if (ADCusesHighPort) { // if we ever have started to use high pins) - if (id > 7) // if we use a high ADC pin - bitSet(ADCSRB, MUX5); // set MUX5 bit - else - bitClear(ADCSRB, MUX5); - } -#endif - ADMUX=(1<setBrake(1); - waiting = true; - return; - } - id++; - mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; - } + if (ADCusesHighPort) { // if we ever have started to use high pins) + if (idarr[num] > 7) // if we use a high ADC pin + bitSet(ADCSRB, MUX5); // set MUX5 bit + else + bitClear(ADCSRB, MUX5); } +#endif + ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX + bitSet(ADCSRA, ADSC); // start conversion + waiting = true; } } #pragma GCC pop_options @@ -236,4 +231,4 @@ void ADCee::begin() { //bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX interrupts(); } -#endif +#endif \ No newline at end of file From a0f0b860eb3aa4844bc422d655ce2e5c84199725 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:21:23 +0000 Subject: [PATCH 45/95] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index b2d1f8b..d6ad661 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.14" +#define VERSION "4.2.15" // 4.2.15 I2C Multiplexer support through Extended Addresses, // added for Wire, 4209 and AVR I2C drivers. // I2C retries when fail. From 3fbcd6f300c3477d93d2c5a20ba2141c63decd08 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 8 Feb 2023 10:04:18 +0800 Subject: [PATCH 46/95] STM32 native I2C driver initial edits --- I2CManager.cpp | 3 + I2CManager_STM32.h | 244 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 I2CManager_STM32.h diff --git a/I2CManager.cpp b/I2CManager.cpp index 00aa71b..564c7b0 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -35,6 +35,9 @@ #elif defined(ARDUINO_ARCH_SAMD) #include "I2CManager_NonBlocking.h" #include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later +#elif defined(ARDUINO_ARCH_STM32) +#include "I2CManager_NonBlocking.h" +#include "I2CManager_STM32.h" // STM32F411RE for now... more later #else #define I2C_USE_WIRE #include "I2CManager_Wire.h" // Other platforms diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h new file mode 100644 index 0000000..0e944e7 --- /dev/null +++ b/I2CManager_STM32.h @@ -0,0 +1,244 @@ +/* + * © 2022-23 Paul M Antoine + * © 2023, Neil McKechnie + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#ifndef I2CMANAGER_STM32_H +#define I2CMANAGER_STM32_H + +#include +#include "I2CManager.h" + +//#include +//#include +#include + +/*************************************************************************** + * Interrupt handler. + * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero + * compatible variants such as the Sparkfun SAMD21 Dev Breakout etc. + * Later we may wish to allow use of an alternate I2C bus, or more than one I2C + * bus on the SAMD architecture + ***************************************************************************/ +#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32) +void I2C1_IRQHandler() { + I2CManagerClass::handleInterrupt(); +} +#endif + +// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants +I2C_TypeDef *s = I2C1; + +/*************************************************************************** + * 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) { + + // Calculate a rise time appropriate to the requested bus speed + int t_rise; + if (i2cClockSpeed < 200000L) { + i2cClockSpeed = 100000L; + t_rise = 1000; + } else if (i2cClockSpeed < 800000L) { + i2cClockSpeed = 400000L; + t_rise = 300; + } else if (i2cClockSpeed < 1200000L) { + i2cClockSpeed = 1000000L; + t_rise = 120; + } else { + i2cClockSpeed = 100000L; + t_rise = 1000; + } + + + // 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 + // s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + + // 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); +} + +/*************************************************************************** + * Initialise I2C registers. + ***************************************************************************/ +void I2CManagerClass::I2C_init() +{ + //Setting up the clocks + RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK + RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9 + // Standard I2C pins are SCL on PB8 and SDA on PB9 + // Bits (17:16)= 1:0 --> Alternate Function for Pin PB8; + // Bits (19:18)= 1:0 --> Alternate Function for Pin PB9 + GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function + GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability + GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode + GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability + // Alt Function High register routing pins PB8 and PB9 for I2C1: + // Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8 + // Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9 + GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up + + // Software reset the I2C peripheral + s->CR1 |= (1<<15); // reset the I2C + s->CR1 &= ~(1<<15); // Normal operation + + // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings + s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz + + // Configure the Clock Control Register for 100KHz SCL frequency + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = 0x0050; + + // Configure the rise time register - max allowed in 1000ns + s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 + +#if defined(I2C_USE_INTERRUPTS) + // Setting NVIC + NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities + NVIC_EnableIRQ(I2C1_EV_IRQn); + + // CR2 Interrupt Settings + // Bit 15-13: reserved + // Bit 12: LAST - DMA last transfer + // Bit 11: DMAEN - DMA enable + // Bit 10: ITBUFEN - Buffer interrupt enable + // Bit 9: ITEVTEN - Event interrupt enable + // Bit 8: ITERREN - Error interrupt enable + // Bit 7-6: reserved + // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) + // Enable all interrupts + s->CR2 |= 0x0700; +#endif + + // Calculate baudrate and set default rate for now + + // Enable the I2C master mode and wait for sync + + // Setting bus idle mode and wait for sync +} + +/*************************************************************************** + * Initiate a start bit for transmission. + ***************************************************************************/ +void I2CManagerClass::I2C_sendStart() { + + // Set counters here in case this is a retry. + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + + // 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 + // multi-master bus, the bus may be BUSY under control of another master, + // in which case we can avoid some arbitration failures by waiting until + // the bus state is IDLE. We don't do that here. + + // If anything to send, initiate write. Otherwise initiate read. + if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) + { + // Send start and address with read flag (1) or'd in + // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + } + else { + // Send start and address with write flag (0) or'd in + // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + } +} + +/*************************************************************************** + * Initiate a stop bit for transmission (does not interrupt) + ***************************************************************************/ +void I2CManagerClass::I2C_sendStop() { + // s->I2CM.CTRLB.bit.CMD = 3; // Stop condition +} + +/*************************************************************************** + * Close I2C down + ***************************************************************************/ +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; + // } +} + +/*************************************************************************** + * Main state machine for I2C, called from interrupt handler or, + * if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function + * (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()). + ***************************************************************************/ +void I2CManagerClass::I2C_handleInterrupt() { + + if (s->I2CM.STATUS.bit.ARBLOST) { + // Arbitration lost, restart + I2C_sendStart(); // Reinitiate request + } else if (s->I2CM.STATUS.bit.BUSERR) { + // Bus error + state = I2C_STATUS_BUS_ERROR; + } else if (s->I2CM.INTFLAG.bit.MB) { + // Master write completed + if (s->I2CM.STATUS.bit.RXNACK) { + // Nacked, send stop. + I2C_sendStop(); + state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + } else if (bytesToSend) { + // Acked, so send next byte + s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + bytesToSend--; + } else if (bytesToReceive) { + // 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; + } else { + // No more data to send/receive. Initiate a STOP condition. + I2C_sendStop(); + state = I2C_STATUS_OK; // Done + } + } else if (s->I2CM.INTFLAG.bit.SB) { + // Master read completed without errors + if (bytesToReceive == 1) { + s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte + I2C_sendStop(); // send stop + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive = 0; + state = I2C_STATUS_OK; // done + } else if (bytesToReceive) { + s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive--; + } + } +} + +#endif /* I2CMANAGER_STM32_H */ From d7f92d7b88d5ab2653d80eadd3354cb3f18a474a Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 8 Feb 2023 13:06:11 +0800 Subject: [PATCH 47/95] STM32 native I2C driver updates --- I2CManager_STM32.h | 93 ++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 0e944e7..7008750 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -44,6 +44,7 @@ void I2C1_IRQHandler() { // Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants I2C_TypeDef *s = I2C1; +#define I2C_IRQn I2C1_EV_IRQn /*************************************************************************** * Set I2C clock speed register. This should only be called outside of @@ -109,22 +110,13 @@ void I2CManagerClass::I2C_init() s->CR1 |= (1<<15); // reset the I2C s->CR1 &= ~(1<<15); // Normal operation - // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings + // Program the peripheral input clock in CR2 Register in order to generate correct timings s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz - // Configure the Clock Control Register for 100KHz SCL frequency - // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode - // Bit 14: Duty, fast mode duty cycle - // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) - s->CCR = 0x0050; - - // Configure the rise time register - max allowed in 1000ns - s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 - #if defined(I2C_USE_INTERRUPTS) // Setting NVIC - NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities - NVIC_EnableIRQ(I2C1_EV_IRQn); + NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities + NVIC_EnableIRQ(I2C_IRQn); // CR2 Interrupt Settings // Bit 15-13: reserved @@ -135,14 +127,21 @@ void I2CManagerClass::I2C_init() // Bit 8: ITERREN - Error interrupt enable // Bit 7-6: reserved // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) - // Enable all interrupts - s->CR2 |= 0x0700; + s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts #endif // Calculate baudrate and set default rate for now + // Configure the Clock Control Register for 100KHz SCL frequency + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = 0x0050; - // Enable the I2C master mode and wait for sync + // Configure the rise time register - max allowed in 1000ns + s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 + // Enable the I2C master mode + s->CR1 |= (1<<0); // Enable I2C // Setting bus idle mode and wait for sync } @@ -154,6 +153,7 @@ void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + uint8_t temp; // 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 @@ -164,12 +164,30 @@ void I2CManagerClass::I2C_sendStart() { // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { - // Send start and address with read flag (1) or'd in - // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + // Send start for read operation + s->CR1 |= (1<<10); // Enable the ACK + s->CR1 |= (1<<8); // Generate START + // Send address with read flag (1) or'd in + s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address + while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + // Special case for 1 byte reads! + if (bytesToReceive == 1) + { + s->CR1 &= ~(1<<10); // clear the ACK bit + temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition + s->CR1 |= (1<<9); // Stop I2C + } + else + temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } else { - // Send start and address with write flag (0) or'd in - // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + // Send start for write operation + s->CR1 |= (1<<10); // Enable the ACK + s->CR1 |= (1<<8); // Generate START + // Send address with write flag (0) or'd in + s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address + while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -177,7 +195,7 @@ void I2CManagerClass::I2C_sendStart() { * Initiate a stop bit for transmission (does not interrupt) ***************************************************************************/ void I2CManagerClass::I2C_sendStop() { - // s->I2CM.CTRLB.bit.CMD = 3; // Stop condition + s->CR1 |= (1<<9); // Stop I2C } /*************************************************************************** @@ -186,12 +204,12 @@ 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. + s->CR1 &= ~(1<<0); // Disable I2C peripheral + // Should never happen, but wait for up to 500us only. unsigned long startTime = micros(); - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) { - // if (micros() - startTime >= 500UL) break; - // } + while ((s->CR1 && 1) != 0) { + if (micros() - startTime >= 500UL) break; + } } /*************************************************************************** @@ -201,41 +219,44 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { - if (s->I2CM.STATUS.bit.ARBLOST) { + if (s->SR1 && (1<<9)) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request - } else if (s->I2CM.STATUS.bit.BUSERR) { + } else if (s->SR1 && (1<<8)) { // Bus error state = I2C_STATUS_BUS_ERROR; - } else if (s->I2CM.INTFLAG.bit.MB) { + } else if (s->SR1 && (1<<7)) { // Master write completed - if (s->I2CM.STATUS.bit.RXNACK) { + if (s->SR1 && (1<<10)) { // Nacked, send stop. I2C_sendStop(); state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { // Acked, so send next byte - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->DR = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // 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 { + // Check both TxE/BTF == 1 before generating stop + while (!(s->SR1 && (1<<7))); // Check TxE + while (!(s->SR1 && (1<<2))); // Check BTF // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); state = I2C_STATUS_OK; // Done } - } else if (s->I2CM.INTFLAG.bit.SB) { + } else if (s->SR1 && (1<<6)) { // Master read completed without errors if (bytesToReceive == 1) { - s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte +// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive = 0; state = I2C_STATUS_OK; // done } else if (bytesToReceive) { - s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte +// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte + currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive--; } } From dd0ee8b50a766e800395c350dea862c6a0151dd6 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:13:23 +0000 Subject: [PATCH 48/95] Additional support for Extended I2C Addresses --- I2CManager.cpp | 7 +++- I2CManager.h | 80 +++++++++++++++++++++++++++++++++++++++- I2CManager_NonBlocking.h | 2 +- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 00aa71b..9608165 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -95,7 +95,7 @@ void I2CManagerClass::begin(void) { for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { found = true; - DIAG(F("I2C Device found at x%x, %S?"), addr, guessI2CDeviceType(addr)); + DIAG(F("I2C Device found at 0x%x, %S?"), addr, guessI2CDeviceType(addr)); } } @@ -117,7 +117,7 @@ void I2CManagerClass::begin(void) { // 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?"), + DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,0x%x}, %S?"), muxNo, subBus, addr, guessI2CDeviceType(addr)); } // Re-select subbus @@ -266,6 +266,9 @@ I2CManagerClass I2CManager = I2CManagerClass(); // try, and failure from timeout does not get retried. unsigned long I2CManagerClass::timeout = 100000UL; +// Buffer for conversion of I2CAddress to char*. +/* static */ char I2CAddress::addressBuffer[30]; + #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 diff --git a/I2CManager.h b/I2CManager.h index 2a3d80c..d50d9ce 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -192,6 +192,7 @@ private: I2CMux _muxNumber; I2CSubBus _subBus; uint8_t _deviceAddress; + static char addressBuffer[]; public: // Constructors // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. @@ -228,6 +229,41 @@ public: // (device assumed to be on the main I2C bus or on a currently selected subbus. operator uint8_t () const { return _deviceAddress; } + // Conversion from I2CAddress to char* (uses static storage so only + // one conversion can be done at a time). So don't call it twice in a + // single DIAG statement for example. + const char* toString() { + char *ptr = addressBuffer; + if (_muxNumber != I2CMux_None) { + strcpy_P(ptr, (const char*)F("{I2CMux_")); + ptr += 8; + *ptr++ = '0' + _muxNumber; + strcpy_P(ptr, (const char*)F(",Subbus_")); + ptr += 8; + if (_subBus == SubBus_None) { + strcpy_P(ptr, (const char*)F("None")); + ptr += 4; + } else if (_subBus == SubBus_All) { + strcpy_P(ptr, (const char*)F("All")); + ptr += 3; + } else + *ptr++ = '0' + _subBus; + *ptr++ = ','; + } + uint8_t temp = _deviceAddress; + *ptr++ = '0'; + *ptr++ = 'x'; + for (uint8_t index = 0; index<2; index++) { + uint8_t bits = (temp >> 4) & 0x0f; + *ptr++ = bits > 9 ? bits-10+'A' : bits+'0'; + temp <<= 4; + } + if (_muxNumber != I2CMux_None) + *ptr++ = '}'; + *ptr = 0; // terminate string + return addressBuffer; + } + // Comparison operator int operator == (I2CAddress &a) const { if (_deviceAddress != a._deviceAddress) @@ -249,8 +285,50 @@ public: }; #else +struct I2CAddress { +private: + uint8_t _deviceAddress; + static char addressBuffer[]; +public: + // Constructors + I2CAddress(const uint8_t deviceAddress) { + _deviceAddress = deviceAddress; + } + + // Basic constructor + I2CAddress() : I2CAddress(0) {} + + // Conversion operator from I2CAddress to uint8_t + // For "uint8_t address = i2cAddress;" syntax + operator uint8_t () const { return _deviceAddress; } + + // Conversion from I2CAddress to char* (uses static storage so only + // one conversion can be done at a time). So don't call it twice in a + // single DIAG statement for example. + const char* toString () { + char *ptr = addressBuffer; + // Just display hex value, two digits. + uint8_t temp = _deviceAddress; + *ptr++ = '0'; + *ptr++ = 'x'; + for (uint8_t index = 0; index<2; index++) { + uint8_t bits = (temp >> 4) & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; + temp <<= 4; + } + *ptr = 0; // terminate string + return addressBuffer; + } + + // Comparison operator + int operator == (I2CAddress &a) const { + if (_deviceAddress != a._deviceAddress) + return false; // Different device address so no match + return true; // Same address on same mux and same subbus + } +}; // Legacy single-byte I2C address type for compact code and smooth changeover. -typedef uint8_t I2CAddress; +//typedef uint8_t I2CAddress; #endif // I2C_EXTENDED_ADDRESS diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 4214ef9..4da932f 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -210,7 +210,7 @@ void I2CManagerClass::checkForTimeout() { unsigned long elapsed = micros() - startTime; if (elapsed > timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), (int)t->i2cAddress, currentRequest); + //DIAG(F("I2CManager Timeout on %s, I2CRB=%s"), t->i2cAddress.toString(), currentRequest); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; From 9dd9990979f14de3d64831a84b81c896c4f361ad Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:16:06 +0000 Subject: [PATCH 49/95] Improve formatting of I2CAddress data type in diagnostics. --- IODevice.cpp | 13 ++++++++++--- IODevice.h | 6 +++++- IO_AnalogueInputs.h | 6 +++--- IO_EXFastclock.h | 7 +++---- IO_EXIOExpander.h | 31 +++++++++++++++---------------- IO_EXTurntable.h | 6 +++--- IO_GPIOBase.h | 14 +++++++------- IO_OLEDDisplay.h | 24 +++++++++++++++--------- IO_PCA9685.cpp | 6 +++--- IO_PCA9685pwm.h | 10 +++++----- IO_RotaryEncoder.h | 2 +- IO_VL53L0X.h | 30 ++++++++++++++---------------- LiquidCrystal_I2C.cpp | 2 +- SSD1306Ascii.cpp | 2 +- 14 files changed, 86 insertions(+), 73 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index be26b92..32e6445 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -74,6 +74,13 @@ void IODevice::begin() { MCP23017::create(180, 16, 0x21); } +// reset() function to reinitialise all devices +void IODevice::reset() { + for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) { + dev->_begin(); + } +} + // Overarching static loop() method for the IODevice subsystem. Works through the // list of installed devices and calls their individual _loop() method. // Devices may or may not implement this, but if they do it is useful for things like animations @@ -302,9 +309,9 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) { // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device -bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { +bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) { #ifdef DIAG_IO - DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress); + DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString()); #endif VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { @@ -322,7 +329,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) } // Check for overlapping I2C address if (i2cAddress && dev->_I2CAddress==i2cAddress) { - DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),(int)i2cAddress); + DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString()); return false; } } diff --git a/IODevice.h b/IODevice.h index f5c7c6f..8fd5efd 100644 --- a/IODevice.h +++ b/IODevice.h @@ -113,6 +113,10 @@ public: // Also, the _begin method of any existing instances is called from here. static void begin(); + // reset function to invoke all driver's _begin() methods again, to + // reset the state of the devices and reinitialise. + static void reset(); + // configure is used invoke an IODevice instance's _configure method static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]); @@ -165,7 +169,7 @@ public: void setGPIOInterruptPin(int16_t pinNumber); // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0); // Method used by IODevice filters to locate slave pins that may be overlayed by their own // pin range. diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index a03d90f..8ff8683 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -85,7 +85,7 @@ private: _display(); #endif } else { - DIAG(F("ADS111x device not found, I2C:%x"), (int)_I2CAddress); + DIAG(F("ADS111x device not found, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -131,7 +131,7 @@ private: break; } } else { // error status - DIAG(F("ADS111x I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } @@ -142,7 +142,7 @@ private: } void _display() override { - DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, + DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 0e1bf76..442799b 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -56,7 +56,7 @@ static void create(uint8_t _I2CAddress) { // XXXX change thistosave2 bytes if (_checkforclock == 0) { FAST_CLOCK_EXISTS = true; - //DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); + //DIAG(F("I2C Fast Clock found at %s"), _I2CAddress.toString()); new EXFastClock(_I2CAddress); } else { @@ -68,7 +68,6 @@ static void create(uint8_t _I2CAddress) { } private: -uint8_t _I2CAddress; // Initialisation of Fastclock @@ -84,7 +83,7 @@ void _begin() override { } else { _deviceState = DEVSTATE_FAILED; //LCD(6,F("CLOCK NOT FOUND")); - DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress); + DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString()); } } } @@ -120,7 +119,7 @@ void _loop(unsigned long currentMicros) override{ // Display EX-FastClock device driver info. void _display() { - DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } }; diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d2269f6..06cbc24 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -63,7 +63,7 @@ private: EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; - _i2cAddress = i2cAddress; + _I2CAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; _digitalPinBytes = (numDigitalPins+7)/8; @@ -76,31 +76,31 @@ private: void _begin() { // Initialise EX-IOExander device I2CManager.begin(); - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); + I2CManager.read(_I2CAddress, _commandBuffer, 1, _digitalOutBuffer, 3); if (_commandBuffer[0] != EXIORDY) { - DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), (int)_i2cAddress); + DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; return; } // Attempt to get version, if we don't get it, we don't care, don't go offline // Using digital in buffer in reverse to save RAM _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _versionBuffer, 3, _commandBuffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; - DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), - (int)_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"), + _I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("EX-IOExpander device not found, I2C:x%x"), (int)_i2cAddress); + DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -117,7 +117,7 @@ private: _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.write(_I2CAddress, _digitalOutBuffer, 3); return true; } @@ -130,16 +130,16 @@ private: int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIOENAN; _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + I2CManager.write(_I2CAddress, _analogueOutBuffer, 2); return true; } void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); } int _readAnalogue(VPIN vpin) override { @@ -164,7 +164,7 @@ private: _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.write(_I2CAddress, _digitalOutBuffer, 3); } void _display() override { @@ -176,14 +176,13 @@ private: _firstAnalogue = _firstVpin + _numDigitalPins; _lastAnalogue = _firstVpin + _nPins - 1; } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), - (int)_i2cAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, _numAnaloguePins, _firstAnalogue, _lastAnalogue, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } - uint8_t _i2cAddress; uint8_t _numDigitalPins; uint8_t _numAnaloguePins; byte _analogueOutBuffer[2]; diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 5b501b3..02a87e3 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -106,15 +106,15 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), - (int)_I2CAddress, stepsMSB, stepsLSB, activity); + _I2CAddress.toString(), stepsMSB, stepsLSB, activity); #endif _stepperStatus = 1; // Tell the device driver Turntable-EX is busy - I2CManager.write((int)_I2CAddress, 3, stepsMSB, stepsLSB, activity); + I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); } // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 17e72d6..0f7019c 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -107,7 +107,7 @@ void GPIOBase::_begin() { _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { - DIAG(F("%S I2C:x%x Device not detected"), _deviceName, (int)_I2CAddress); + DIAG(F("%S I2C:%s Device not detected"), _deviceName, _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -121,7 +121,7 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun bool pullup = params[0]; int pin = vpin - _firstVpin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, pullup); + DIAG(F("%S I2C:%s Config Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, pullup); #endif uint16_t mask = 1 << pin; if (pullup) @@ -151,7 +151,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { _deviceState = DEVSTATE_NORMAL; } else { _deviceState = DEVSTATE_FAILED; - DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, (int)_I2CAddress, status, + DIAG(F("%S I2C:%s Error:%d %S"), _deviceName, _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); } _processCompletion(status); @@ -174,7 +174,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { #ifdef DIAG_IO if (differences) - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); + DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState); #endif } @@ -195,7 +195,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, (int)_I2CAddress, + DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -204,7 +204,7 @@ void GPIOBase::_write(VPIN vpin, int value) { int pin = vpin - _firstVpin; T mask = 1 << pin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, value); + DIAG(F("%S I2C:%s Write Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, value); #endif // Set port mode output if currently not output mode @@ -240,7 +240,7 @@ int GPIOBase::_read(VPIN vpin) { // Set unused pin and write mode pin value to 1 _portInputState |= ~_portInUse | _portMode; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); + DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState); #endif } return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 9aefbaa..1ef8ed1 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -86,32 +86,38 @@ protected: _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + // Fill buffer with spaces + memset(_buffer, ' ', _numCols*_numRows); + + // Create OLED driver + oled = new SSD1306AsciiWire(); + + // Clear the entire screen + oled->clearNative(); addDevice(this); } - + // Device-specific initialisation void _begin() override { - // Create OLED driver - oled = new SSD1306AsciiWire(); // Initialise device if (oled->begin(_I2CAddress, _width, _height)) { // Store pointer to this object into CS display hook, so that we // will intercept any subsequent calls to lcdDisplay methods. DisplayInterface::lcdDisplay = this; - DIAG(F("OLEDDisplay installed on address x%x"), (int)_I2CAddress); - - // First clear the entire screen - oled->clearNative(); + DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); // Set first two lines on screen LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; } } - void _loop(unsigned long) override { // Loop through the buffer and if a row has changed @@ -196,7 +202,7 @@ protected: // Display information about the device. void _display() { - DIAG(F("OLEDDisplay Configured addr x%x"), (int)_I2CAddress); + DIAG(F("OLEDDisplay Configured addr %s"), _I2CAddress.toString()); } }; diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index f7a6882..68e44a1 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -239,13 +239,13 @@ void PCA9685::updatePosition(uint8_t pin) { // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void PCA9685::writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + DIAG(F("PCA9685 I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete uint8_t status = requestBlock.wait(); if (status != I2C_STATUS_OK) { _deviceState = DEVSTATE_FAILED; - DIAG(F("PCA9685 I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); + DIAG(F("PCA9685 I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; @@ -259,7 +259,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 72884a9..ef92118 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -101,8 +101,8 @@ private: void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO - DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), + vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; @@ -114,7 +114,7 @@ private: // Display details of this device. void _display() override { - DIAG(F("PCA9685pwm I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -122,13 +122,13 @@ private: // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685pwm I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete uint8_t status = requestBlock.wait(); if (status != I2C_STATUS_OK) { _deviceState = DEVSTATE_FAILED; - DIAG(F("PCA9685pwm I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); + DIAG(F("PCA9685pwm I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 6de97af..9cf4e65 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ private: } void _display() override { - DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), (int)_I2CAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 86d9624..a1e1dcc 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -159,7 +159,9 @@ protected: if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { // Device already present on this address, so skip the address initialisation. _nextState = STATE_CONFIGUREDEVICE; - } + } else + _nextState = STATE_INIT; + } void _loop(unsigned long currentMicros) override { @@ -171,7 +173,7 @@ protected: // If no XSHUT pin is configured, then only one device is supported. if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); _nextState = STATE_RESTARTMODULE; - delayUntil(currentMicros+1000); + delayUntil(currentMicros+10000); break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. @@ -183,9 +185,8 @@ protected: // shared flag accessible to all device instances. if (_addressConfigInProgress) return; _addressConfigInProgress = true; - // Set XSHUT pin (if connected). Because of supply voltage differences, - // drive the signal through the digital output's pull-up resistor. - if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, 1); + // Set XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; @@ -202,15 +203,12 @@ protected: I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); #endif } - _addressConfigInProgress = false; - _nextState = STATE_SKIP; - break; - case STATE_SKIP: - // Do nothing on the third entry. - _nextState = STATE_CONFIGUREDEVICE; + delayUntil(currentMicros+10000); break; case STATE_CONFIGUREDEVICE: - // On next entry, check if device address has been set. + // Allow next VL53L0X device to be configured + _addressConfigInProgress = false; + // Now check if device address has been set. if (I2CManager.exists(_I2CAddress)) { #ifdef DIAG_IO _display(); @@ -220,7 +218,7 @@ protected: read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); _nextState = STATE_INITIATESCAN; } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), (int)_I2CAddress); + DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; _nextState = STATE_FAILED; } @@ -285,7 +283,7 @@ protected: // Function to report a failed I2C operation. Put the device off-line. void reportError(uint8_t status) { - DIAG(F("VL53L0X I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } @@ -314,8 +312,8 @@ protected: } void _display() override { - DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), - (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, + DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), + _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index b18614f..a5c2e16 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -53,7 +53,7 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. if (I2CManager.exists(lcd_Addr)) { - DIAG(F("%dx%d LCD configured on I2C:x%x"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); + DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; begin(); backlight(); diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 12d8702..deae5ac 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -197,7 +197,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString()); clear(); return true; } From 7de46a0c17d4ccc1aef40af5b55aee14ddf47dc4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:16:39 +0000 Subject: [PATCH 50/95] Add command to attempt to reset failed devices. --- DCCEXParser.cpp | 2 ++ version.h | 1 + 2 files changed, 3 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 478b1f8..c7d23e9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -948,6 +948,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_HAL: if (p[1] == HASH_KEYWORD_SHOW) IODevice::DumpAll(); + else if (p[1] == HASH_KEYWORD_RESET) + IODevice::reset(); break; #endif diff --git a/version.h b/version.h index d6ad661..0a263b7 100644 --- a/version.h +++ b/version.h @@ -19,6 +19,7 @@ // (2) animations of servo movement via PCA9685pwm. // This is intended to support EXIOExpander and also // replace the existing PCA9685 driver. +// Add to reinitialise failed drivers. // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From 9e0e110b5d42bad2f6f227fa5436c8da9d1aa1f3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:17:31 +0000 Subject: [PATCH 51/95] Update defines.h - inappropriate define NO_INTERRUPTS replaced with I2C_USE_WIRE. --- defines.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/defines.h b/defines.h index 9ad5851..e9ae631 100644 --- a/defines.h +++ b/defines.h @@ -64,8 +64,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY35) #define ARDUINO_TYPE "TEENSY35" @@ -76,8 +76,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY36) #define ARDUINO_TYPE "TEENSY36" @@ -87,8 +87,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY40) #define ARDUINO_TYPE "TEENSY40" @@ -98,8 +98,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY41) #define ARDUINO_TYPE "TEENSY41" @@ -109,8 +109,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" @@ -135,8 +135,8 @@ #define DISABLE_EEPROM #endif // STM32 support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif From fd07402aec5b0cf7722011333b2a6c4bfd9e6d71 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 9 Feb 2023 15:12:16 +0800 Subject: [PATCH 52/95] STM32 better I2C still work in progress --- I2CManager_SAMD.h | 2 +- I2CManager_STM32.h | 131 ++++++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 44 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 5eb612b..bdfe702 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -55,7 +55,7 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { // Calculate a rise time appropriate to the requested bus speed int t_rise; if (i2cClockSpeed < 200000L) { - i2cClockSpeed = 100000L; + i2cClockSpeed = 100000L; // NB: this overrides a "force clock" of lower than 100KHz! t_rise = 1000; } else if (i2cClockSpeed < 800000L) { i2cClockSpeed = 400000L; diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 7008750..b462a3c 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -45,6 +45,39 @@ void I2C1_IRQHandler() { // Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants I2C_TypeDef *s = I2C1; #define I2C_IRQn I2C1_EV_IRQn +#define I2C_BUSFREQ 16 + +// I2C SR1 Status Register #1 bit definitions for convenience +// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert +// #define I2C_SR1_TIMEOUT (1<<14) // Timeout of Tlow error +// #define I2C_SR1_PECERR (1<<12) // PEC error in reception +// #define I2C_SR1_OVR (1<<11) // Overrun/Underrun error +// #define I2C_SR1_AF (1<<10) // Acknowledge failure +// #define I2C_SR1_ARLO (1<<9) // Arbitration lost (master mode) +// #define I2C_SR1_BERR (1<<8) // Bus error (misplaced start or stop condition) +// #define I2C_SR1_TxE (1<<7) // Data register empty on transmit +// #define I2C_SR1_RxNE (1<<6) // Data register not empty on receive +// #define I2C_SR1_STOPF (1<<4) // Stop detection (slave mode) +// #define I2C_SR1_ADD10 (1<<3) // 10 bit header sent +// #define I2C_SR1_BTF (1<<2) // Byte transfer finished - data transfer done +// #define I2C_SR1_ADDR (1<<1) // Address sent (master) or matched (slave) +// #define I2C_SR1_SB (1<<0) // Start bit (master mode) 1=start condition generated + +// I2C CR1 Control Register #1 bit definitions for convenience +// #define I2C_CR1_SWRST (1<<15) // Software reset - places peripheral under reset +// #define I2C_CR1_ALERT (1<<13) // SMBus alert assertion +// #define I2C_CR1_PEC (1<<12) // Packet Error Checking transfer in progress +// #define I2C_CR1_POS (1<<11) // Acknowledge/PEC Postion (for data reception in PEC mode) +// #define I2C_CR1_ACK (1<<10) // Acknowledge enable - ACK returned after byte is received (address or data) +// #define I2C_CR1_STOP (1<<9) // STOP generated +// #define I2C_CR1_START (1<<8) // START generated +// #define I2C_CR1_NOSTRETCH (1<<7) // Clock stretching disable (slave mode) +// #define I2C_CR1_ENGC (1<<6) // General call (broadcast) enable (address 00h is ACKed) +// #define I2C_CR1_ENPEC (1<<5) // PEC Enable +// #define I2C_CR1_ENARP (1<<4) // ARP enable (SMBus) +// #define I2C_CR1_SMBTYPE (1<<3) // SMBus type, 1=host, 0=device +// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C +// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable /*************************************************************************** * Set I2C clock speed register. This should only be called outside of @@ -54,36 +87,47 @@ I2C_TypeDef *s = I2C1; void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { // Calculate a rise time appropriate to the requested bus speed - int t_rise; + // Use 10x the rise time spec to enable integer divide of 62.5ns clock period + uint16_t t_rise; + uint32_t ccr_freq; if (i2cClockSpeed < 200000L) { - i2cClockSpeed = 100000L; - t_rise = 1000; - } else if (i2cClockSpeed < 800000L) { + // i2cClockSpeed = 100000L; + t_rise = 0x11; // (1000ns /62.5ns) + 1; + } + else if (i2cClockSpeed < 800000L) + { i2cClockSpeed = 400000L; - t_rise = 300; - } else if (i2cClockSpeed < 1200000L) { - i2cClockSpeed = 1000000L; - t_rise = 120; - } else { + t_rise = 0x06; // (300ns / 62.5ns) + 1; + // } else if (i2cClockSpeed < 1200000L) { + // i2cClockSpeed = 1000000L; + // t_rise = 120; + } + else + { i2cClockSpeed = 100000L; - t_rise = 1000; + t_rise = 0x11; // (1000ns /62.5ns) + 1; } - - // Disable the I2C master mode and wait for sync - // s->I2CM.CTRLA.bit.ENABLE = 0 ; - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Enable the I2C master mode + s->CR1 &= ~(I2C_CR1_PE); // Enable I2C + // Software reset the I2C peripheral + // s->CR1 |= I2C_CR1_SWRST; // reset the I2C + // Release reset + // s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation // Calculate baudrate - using a rise time appropriate for the speed - // s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2; - // Enable the I2C master mode and wait for sync - // s->I2CM.CTRLA.bit.ENABLE = 1 ; - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = (uint16_t)ccr_freq; - // Setting bus idle mode and wait for sync - // s->I2CM.STATUS.bit.BUSSTATE = 1 ; - // while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + // Configure the rise time register + s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1 + + // Enable the I2C master mode + s->CR1 |= I2C_CR1_PE; // Enable I2C } /*************************************************************************** @@ -107,11 +151,11 @@ void I2CManagerClass::I2C_init() GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up // Software reset the I2C peripheral - s->CR1 |= (1<<15); // reset the I2C - s->CR1 &= ~(1<<15); // Normal operation + s->CR1 |= I2C_CR1_SWRST; // reset the I2C + s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation // Program the peripheral input clock in CR2 Register in order to generate correct timings - s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz + s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz #if defined(I2C_USE_INTERRUPTS) // Setting NVIC @@ -127,7 +171,8 @@ void I2CManagerClass::I2C_init() // Bit 8: ITERREN - Error interrupt enable // Bit 7-6: reserved // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) - s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts + // s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts + s->CR2 |= 0x0300; // Enable Event and Error interrupts #endif // Calculate baudrate and set default rate for now @@ -141,7 +186,7 @@ void I2CManagerClass::I2C_init() s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 // Enable the I2C master mode - s->CR1 |= (1<<0); // Enable I2C + s->CR1 |= I2C_CR1_PE; // Enable I2C // Setting bus idle mode and wait for sync } @@ -165,28 +210,28 @@ void I2CManagerClass::I2C_sendStart() { if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { // Send start for read operation - s->CR1 |= (1<<10); // Enable the ACK - s->CR1 |= (1<<8); // Generate START + s->CR1 |= I2C_CR1_ACK; // Enable the ACK + s->CR1 |= I2C_CR1_START; // Generate START // Send address with read flag (1) or'd in s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address - while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set // Special case for 1 byte reads! if (bytesToReceive == 1) { - s->CR1 &= ~(1<<10); // clear the ACK bit + s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition - s->CR1 |= (1<<9); // Stop I2C + s->CR1 |= I2C_CR1_STOP; // Stop I2C } else temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } else { // Send start for write operation - s->CR1 |= (1<<10); // Enable the ACK - s->CR1 |= (1<<8); // Generate START + s->CR1 |= I2C_CR1_ACK; // Enable the ACK + s->CR1 |= I2C_CR1_START; // Generate START // Send address with write flag (0) or'd in s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address - while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + while (!(s->SR1 && (1<<1))); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -195,7 +240,7 @@ void I2CManagerClass::I2C_sendStart() { * Initiate a stop bit for transmission (does not interrupt) ***************************************************************************/ void I2CManagerClass::I2C_sendStop() { - s->CR1 |= (1<<9); // Stop I2C + s->CR1 |= I2C_CR1_STOP; // Stop I2C } /*************************************************************************** @@ -204,10 +249,10 @@ void I2CManagerClass::I2C_sendStop() { void I2CManagerClass::I2C_close() { I2C_sendStop(); // Disable the I2C master mode and wait for sync - s->CR1 &= ~(1<<0); // Disable I2C peripheral + s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral // Should never happen, but wait for up to 500us only. unsigned long startTime = micros(); - while ((s->CR1 && 1) != 0) { + while ((s->CR1 && I2C_CR1_PE) != 0) { if (micros() - startTime >= 500UL) break; } } @@ -219,13 +264,13 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { - if (s->SR1 && (1<<9)) { + if (s->SR1 && I2C_SR1_ARLO) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request - } else if (s->SR1 && (1<<8)) { + } else if (s->SR1 && I2C_SR1_BERR) { // Bus error state = I2C_STATUS_BUS_ERROR; - } else if (s->SR1 && (1<<7)) { + } else if (s->SR1 && I2C_SR1_TXE) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. @@ -240,13 +285,13 @@ void I2CManagerClass::I2C_handleInterrupt() { // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { // Check both TxE/BTF == 1 before generating stop - while (!(s->SR1 && (1<<7))); // Check TxE - while (!(s->SR1 && (1<<2))); // Check BTF + while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE + while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); state = I2C_STATUS_OK; // Done } - } else if (s->SR1 && (1<<6)) { + } else if (s->SR1 && I2C_SR1_RXNE) { // Master read completed without errors if (bytesToReceive == 1) { // s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte From 7e2487ffbb7ffa5bf5b377cf70a14964aa6264fd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:29:09 +0000 Subject: [PATCH 53/95] Avoid compiler error when no HAL installed. --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c7d23e9..bcc25d7 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -944,7 +944,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1])); break; -#if !defined(IO_MINIMAL_HAL) +#if !defined(IO_NO_HAL) case HASH_KEYWORD_HAL: if (p[1] == HASH_KEYWORD_SHOW) IODevice::DumpAll(); From 5f9705d1b702bc5aef353fa8e063f73bb8236d59 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:30:35 +0000 Subject: [PATCH 54/95] Improve IODevice::reset function Ensure that the _loop() function is able to run after a device is reset. --- IODevice.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IODevice.cpp b/IODevice.cpp index 32e6445..e907c23 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -76,7 +76,12 @@ void IODevice::begin() { // reset() function to reinitialise all devices void IODevice::reset() { + unsigned long currentMicros = micros(); for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) { + dev->_deviceState = DEVSTATE_DORMANT; + // First ensure that _loop isn't delaying + dev->delayUntil(currentMicros); + // Then invoke _begin to restart driver dev->_begin(); } } From f358880f30bb648bbe6acf3086b21c897fc517ed Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:32:41 +0000 Subject: [PATCH 55/95] IO_VL53L0X: Some bug fixes. Modify state model, and improve recovery after . --- IO_VL53L0X.h | 81 ++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index a1e1dcc..9aa3d9c 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -48,7 +48,7 @@ * using a distinct I2C address. The process is described in ST Microelectronics application * note AN4846. * - * WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise, + * WARNING: If the device's XSHUT pin is not connected, then it may be prone to noise, * and the device may reset spontaneously or when handled and the device will stop responding * on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor * (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example) @@ -112,16 +112,15 @@ private: // State machine states. enum : uint8_t { - STATE_INIT = 0, - STATE_RESTARTMODULE = 1, - STATE_CONFIGUREADDRESS = 2, - STATE_SKIP = 3, - STATE_CONFIGUREDEVICE = 4, - STATE_INITIATESCAN = 5, - STATE_CHECKSTATUS = 6, - STATE_GETRESULTS = 7, - STATE_DECODERESULTS = 8, - STATE_FAILED = 9, + STATE_INIT, + STATE_RESTARTMODULE, + STATE_CONFIGUREADDRESS, + STATE_CONFIGUREDEVICE, + STATE_INITIATESCAN, + STATE_CHECKSTATUS, + STATE_GETRESULTS, + STATE_DECODERESULTS, + STATE_FAILED, }; // Register addresses @@ -156,24 +155,25 @@ protected: // the device will not respond on its default address if it has // already been changed. Therefore, we skip the address configuration if the // desired address is already responding on the I2C bus. - if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { - // Device already present on this address, so skip the address initialisation. - _nextState = STATE_CONFIGUREDEVICE; - } else - _nextState = STATE_INIT; - + _nextState = STATE_INIT; + _addressConfigInProgress = false; } void _loop(unsigned long currentMicros) override { uint8_t status; switch (_nextState) { case STATE_INIT: - // On first entry to loop, reset this module by pulling XSHUT low. Each module - // will be addressed in turn, until all are in the reset state. - // If no XSHUT pin is configured, then only one device is supported. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); - _nextState = STATE_RESTARTMODULE; - delayUntil(currentMicros+10000); + if (I2CManager.exists(_I2CAddress)) { + // Device already present on the nominated address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; + } else { + // On first entry to loop, reset this module by pulling XSHUT low. Each module + // will be addressed in turn, until all are in the reset state. + // If no XSHUT pin is configured, then only one device is supported. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + _nextState = STATE_RESTARTMODULE; + delayUntil(currentMicros+10000); + } break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. @@ -183,13 +183,14 @@ protected: // instead of driving the output directly. // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. - if (_addressConfigInProgress) return; - _addressConfigInProgress = true; - // Set XSHUT pin (if connected) to bring the module out of sleep mode. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); - // Allow the module time to restart - delayUntil(currentMicros+10000); - _nextState = STATE_CONFIGUREADDRESS; + if (!_addressConfigInProgress) { + _addressConfigInProgress = true; + // Set XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, true); + // Allow the module time to restart + delayUntil(currentMicros+10000); + _nextState = STATE_CONFIGUREADDRESS; + } break; case STATE_CONFIGUREADDRESS: // Then write the desired I2C address to the device, while this is the only @@ -198,12 +199,16 @@ protected: #if defined(I2C_EXTENDED_ADDRESS) // Add subbus reference for desired address to the device default address. I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS}; - I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); + status = I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); #else - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); + status = I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); #endif + if (status != I2C_STATUS_OK) { + reportError(status); + } } delayUntil(currentMicros+10000); + _nextState = STATE_CONFIGUREDEVICE; break; case STATE_CONFIGUREDEVICE: // Allow next VL53L0X device to be configured @@ -214,9 +219,12 @@ protected: _display(); #endif // Set 2.8V mode - write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + status = write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); - _nextState = STATE_INITIATESCAN; + if (status != I2C_STATUS_OK) { + reportError(status); + } else + _nextState = STATE_INITIATESCAN; } else { DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; @@ -235,7 +243,6 @@ protected: if (status == I2C_STATUS_PENDING) return; // try next time if (status != I2C_STATUS_OK) { reportError(status); - _nextState = STATE_FAILED; } else _nextState = STATE_GETRESULTS; delayUntil(currentMicros + 95000); // wait for 95 ms before checking. @@ -244,7 +251,6 @@ protected: // Ranging completed. Request results _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb); - _nextState = 3; delayUntil(currentMicros + 5000); // Allow 5ms to get data _nextState = STATE_DECODERESULTS; break; @@ -269,7 +275,6 @@ protected: _nextState = STATE_INITIATESCAN; } else { reportError(status); - _nextState = STATE_FAILED; } break; case STATE_FAILED: @@ -281,7 +286,7 @@ protected: } } - // Function to report a failed I2C operation. Put the device off-line. + // Function to report a failed I2C operation. void reportError(uint8_t status) { DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; From 1ffb3a9836a691855b31187f2b1b0c01100b3e71 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:34:13 +0000 Subject: [PATCH 56/95] Update IO_OLEDDisplay.h Round up number of characters per line, so that the last few pixels on the line are erased when writing blanks. --- IO_OLEDDisplay.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 1ef8ed1..859f82d 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -77,8 +77,8 @@ protected: _I2CAddress = i2cAddress; _width = width; _height = height; - _numCols = _width / 6; // character block 6 x 8 - _numRows = _height / 8; + _numCols = (_width+5) / 6; // character block 6 x 8, round up + _numRows = _height / 8; // Round down _charPosToScreen = _numCols; @@ -91,10 +91,7 @@ protected: // Create OLED driver oled = new SSD1306AsciiWire(); - - // Clear the entire screen - oled->clearNative(); - + addDevice(this); } @@ -115,6 +112,10 @@ protected: // Force all rows to be redrawn for (uint8_t row=0; row<_numRows; row++) _rowGeneration[row]++; + + // Start with top line (looks better) + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; } } From 18b148ed1f42fa5a20d949a6b2a8a1621b096eed Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:35:17 +0000 Subject: [PATCH 57/95] IO_EXFastClock - fix compile error due to closing brace outside of #if block. --- IO_EXFastclock.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 442799b..923f922 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -109,16 +109,14 @@ void _loop(unsigned long currentMicros) override{ // As the minimum clock increment is 2 seconds delay a bit - say 1 sec. // Clock interval is 60/ clockspeed i.e 60/b seconds delayUntil(currentMicros + ((60/b) * 1000000)); - - } #endif } - +} // Display EX-FastClock device driver info. - void _display() { + void _display() override { DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } From 553a94bf67678212f07b534aabeb6c7981b2c619 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:46:50 +0000 Subject: [PATCH 58/95] I2CManager: Refactor common driver code. Put mux handling into I2CManager_NonBlocking.h to keep the native (controller-specific) drivers more simple. Remove almost all but one of the static definitions, in preparation for supporting multiple I2C buses. --- I2CManager.cpp | 38 +++++++------- I2CManager.h | 129 ++++++++++++++++++++++++++++++---------------- I2CManager_Wire.h | 4 +- 3 files changed, 107 insertions(+), 64 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 543eead..dbba1a0 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -72,6 +72,10 @@ void I2CManagerClass::begin(void) { _beginCompleted = true; _initialise(); + #if defined(I2C_USE_WIRE) + DIAG(F("I2CManager: Using Wire library")); + #endif + // Check for short-circuits on I2C if (!digitalRead(SDA)) DIAG(F("WARNING: Possible short-circuit on I2C SDA line")); @@ -81,7 +85,7 @@ void I2CManagerClass::begin(void) { // Probe and list devices. Use standard mode // (clock speed 100kHz) for best device compatibility. _setClock(100000); - unsigned long originalTimeout = timeout; + unsigned long originalTimeout = _timeout; setTimeout(1000); // use 1ms timeout for probes #if defined(I2C_EXTENDED_ADDRESS) @@ -110,7 +114,7 @@ void I2CManagerClass::begin(void) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { // Select Mux Subbus - for (uint8_t subBus=0; subBus<=7; subBus++) { + for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) { muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { @@ -261,26 +265,9 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { ***************************************************************************/ I2CManagerClass I2CManager = I2CManagerClass(); -// Default timeout 100ms on I2C request block completion. -// A full 32-byte transmission takes about 8ms at 100kHz, -// so this value allows lots of headroom. -// It can be modified by calling I2CManager.setTimeout() function. -// When retries are enabled, the timeout applies to each -// try, and failure from timeout does not get retried. -unsigned long I2CManagerClass::timeout = 100000UL; - // Buffer for conversion of I2CAddress to char*. /* static */ char I2CAddress::addressBuffer[30]; -#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 ///////////////////////////////////////////////////////////////////////////// @@ -346,3 +333,16 @@ void I2CRB::suppressRetries(bool suppress) { else this->operation &= ~OPERATION_NORETRY; } + + +// Helper function for converting a uint8_t to four characters (e.g. 0x23). +void I2CAddress::toHex(const uint8_t value, char *buffer) { + char *ptr = buffer; + // Just display hex value, two digits. + *ptr++ = '0'; + *ptr++ = 'x'; + uint8_t bits = (value >> 4) & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; + bits = value & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; +} \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h index d50d9ce..542c92f 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -23,6 +23,7 @@ #include #include "FSH.h" +#include "defines.h" /* * Manager for I2C communications. For portability, it allows use @@ -104,7 +105,7 @@ * * Non-interrupting I2C: * - * I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state + * Non-blocking I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state * machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function. * The speed at which I2C operations can be performed then becomes highly dependent on the frequency that * the loop() function is called, and may be adequate under some circumstances. @@ -151,6 +152,11 @@ // I2C multiplexer support. ///////////////////////////////////////////////////////////////////////////////////// +// Currently only one bus supported, and one instance of I2CManager to handle it. +enum I2CBus : uint8_t { + I2CBus_0 = 0, +}; + // Currently I2CAddress supports one I2C bus, with up to eight // multipexers (MUX) attached. Each MUX can have up to eight sub-buses. enum I2CMux : uint8_t { @@ -168,12 +174,17 @@ enum I2CMux : uint8_t { enum I2CSubBus : uint8_t { SubBus_0 = 0, // Enable individual sub-buses... SubBus_1 = 1, +#if !defined(I2CMUX_PCA9542) SubBus_2 = 2, SubBus_3 = 3, +#if !defined(I2CMUX_PCA9544) SubBus_4 = 4, SubBus_5 = 5, SubBus_6 = 6, SubBus_7 = 7, +#endif +#endif + SubBus_No, // Number of subbuses (highest + 1) SubBus_None = 254, // Disable all sub-buses on selected mux SubBus_All = 255, // Enable all sub-buses }; @@ -189,14 +200,16 @@ enum I2CSubBus : uint8_t { struct I2CAddress { private: // Fields + I2CBus _busNumber; I2CMux _muxNumber; I2CSubBus _subBus; uint8_t _deviceAddress; static char addressBuffer[]; public: // Constructors - // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. - I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) { + // For I2CAddress "{I2CBus_0, Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(const I2CBus busNumber, const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) { + _busNumber = busNumber; _muxNumber = muxNumber; _subBus = subBus; _deviceAddress = deviceAddress; @@ -205,6 +218,10 @@ public: // Basic constructor I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {} + // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) : + I2CAddress(I2CBus_0, muxNumber, subBus, deviceAddress) {} + // For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70) I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) : I2CAddress(I2CMux_0, subBus, deviceAddress) {} @@ -214,6 +231,12 @@ public: // (device assumed to be on the main I2C bus). I2CAddress(const uint8_t deviceAddress) : I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {} + + // Conversion from uint8_t to I2CAddress + // For I2CAddress in form "{I2CBus_1, 0x23}" + // (device not connected via multiplexer). + I2CAddress(const I2CBus bus, const uint8_t deviceAddress) : + I2CAddress(bus, I2CMux_None, SubBus_None, deviceAddress) {} // For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector) I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : @@ -250,14 +273,8 @@ public: *ptr++ = '0' + _subBus; *ptr++ = ','; } - uint8_t temp = _deviceAddress; - *ptr++ = '0'; - *ptr++ = 'x'; - for (uint8_t index = 0; index<2; index++) { - uint8_t bits = (temp >> 4) & 0x0f; - *ptr++ = bits > 9 ? bits-10+'A' : bits+'0'; - temp <<= 4; - } + toHex(_deviceAddress, ptr); + ptr += 4; if (_muxNumber != I2CMux_None) *ptr++ = '}'; *ptr = 0; // terminate string @@ -282,6 +299,10 @@ public: I2CMux muxNumber() { return _muxNumber; } I2CSubBus subBus() { return _subBus; } uint8_t deviceAddress() { return _deviceAddress; } + +private: + // Helper function for converting byte to four-character hex string (e.g. 0x23). + void toHex(const uint8_t value, char *buffer); }; #else @@ -308,14 +329,8 @@ public: const char* toString () { char *ptr = addressBuffer; // Just display hex value, two digits. - uint8_t temp = _deviceAddress; - *ptr++ = '0'; - *ptr++ = 'x'; - for (uint8_t index = 0; index<2; index++) { - uint8_t bits = (temp >> 4) & 0xf; - *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; - temp <<= 4; - } + toHex(_deviceAddress, ptr); + ptr += 4; *ptr = 0; // terminate string return addressBuffer; } @@ -326,9 +341,10 @@ public: return false; // Different device address so no match return true; // Same address on same mux and same subbus } +private: + // Helper function for converting byte to four-character hex string (e.g. 0x23). + void toHex(const uint8_t value, char *buffer); }; -// Legacy single-byte I2C address type for compact code and smooth changeover. -//typedef uint8_t I2CAddress; #endif // I2C_EXTENDED_ADDRESS @@ -452,12 +468,19 @@ public: private: bool _beginCompleted = false; bool _clockSpeedFixed = false; - static uint8_t retryCounter; // Count of retries + uint8_t retryCounter; // Count of retries // Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD // and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds // will not be useful. The speed can be overridden by I2CManager::forceClock(). uint32_t _clockSpeed = I2C_FREQ; - static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + // Default timeout 100ms on I2C request block completion. + // A full 32-byte transmission takes about 8ms at 100kHz, + // so this value allows lots of headroom. + // It can be modified by calling I2CManager.setTimeout() function. + // When retries are enabled, the timeout applies to each + // try, and failure from timeout does not get retried. + // A value of 0 means disable timeout monitoring. + unsigned long _timeout = 100000UL; // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); @@ -466,7 +489,11 @@ private: void _setClock(unsigned long); #if defined(I2C_EXTENDED_ADDRESS) - static uint8_t _muxCount; +// 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 +// are two or more, then the subbus must be deselected to avoid multiple +// sub-bus legs on different multiplexers being accessible simultaneously. + uint8_t _muxCount = 0; uint8_t getMuxCount() { return _muxCount; } #endif @@ -479,40 +506,56 @@ private: // Within the queue, each request's nextRequest field points to the // next request, or NULL. // Mark volatile as they are updated by IRC and read/written elsewhere. - static I2CRB * volatile queueHead; - static I2CRB * volatile queueTail; - static volatile uint8_t state; - static uint8_t completionStatus; + I2CRB * volatile queueHead = NULL; + I2CRB * volatile queueTail = NULL; - static I2CRB * volatile currentRequest; - static volatile uint8_t txCount; - static volatile uint8_t rxCount; - static volatile uint8_t bytesToSend; - static volatile uint8_t bytesToReceive; - static volatile uint8_t operation; - static volatile unsigned long startTime; - static volatile uint8_t muxPhase; + // State is set to I2C_STATE_FREE when the interrupt handler has finished + // the current request and is ready to complete. + uint8_t state = I2C_STATE_FREE; + + // CompletionStatus may be set by the interrupt handler at any time but is + // not written to the I2CRB until the state is I2C_STATE_FREE. + uint8_t completionStatus = I2C_STATUS_OK; + uint8_t overallStatus = I2C_STATUS_OK; + + I2CRB * currentRequest = NULL; + uint8_t txCount = 0; + uint8_t rxCount = 0; + uint8_t bytesToSend = 0; + uint8_t bytesToReceive = 0; + uint8_t operation = 0; + unsigned long startTime = 0; + uint8_t muxPhase = 0; + uint8_t muxAddress = 0; + uint8_t muxData[1]; + uint8_t deviceAddress; + const uint8_t *sendBuffer; + uint8_t *receiveBuffer; volatile uint32_t pendingClockSpeed = 0; void startTransaction(); // Low-level hardware manipulation functions. - static void I2C_init(); - static void I2C_setClock(unsigned long i2cClockSpeed); - static void I2C_handleInterrupt(); - static void I2C_sendStart(); - static void I2C_sendStop(); - static void I2C_close(); + void I2C_init(); + void I2C_setClock(unsigned long i2cClockSpeed); + void I2C_handleInterrupt(); + void I2C_sendStart(); + void I2C_sendStop(); + void I2C_close(); public: // handleInterrupt needs to be public to be called from the ISR function! - static void handleInterrupt(); + void handleInterrupt(); #endif }; +// Pointer to class instance (Note: if there is more than one bus, each will have +// its own instance of I2CManager, selected by the queueRequest function from +// the I2CBus field within the request block's I2CAddress). extern I2CManagerClass I2CManager; + #endif diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 67ef306..04d99bd 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -41,7 +41,7 @@ void I2CManagerClass::_initialise() { Wire.begin(); #if defined(WIRE_HAS_TIMEOUT) - Wire.setWireTimeout(timeout, true); + Wire.setWireTimeout(_timeout, true); #endif } @@ -59,7 +59,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * read is started. ***************************************************************************/ void I2CManagerClass::setTimeout(unsigned long value) { - timeout = value; + _timeout = value; #if defined(WIRE_HAS_TIMEOUT) Wire.setWireTimeout(value, true); #endif From 0b307a67e4dc4e6b37a6691587d0c0431fd57cf0 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:47:44 +0000 Subject: [PATCH 59/95] I2CManager: Update native drivers for MUX support from the common code. --- I2CManager_AVR.h | 157 +++++++-------------------------------- I2CManager_Mega4809.h | 10 +-- I2CManager_NonBlocking.h | 119 +++++++++++++++++++++-------- I2CManager_SAMD.h | 10 +-- I2CManager_STM32.h | 31 ++++---- 5 files changed, 140 insertions(+), 187 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 7613132..c2d7312 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -95,17 +95,16 @@ void I2CManagerClass::I2C_init() * Initiate a start bit for transmission. ***************************************************************************/ void I2CManagerClass::I2C_sendStart() { - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; rxCount = 0; txCount = 0; -#if defined(I2C_EXTENDED_ADDRESS) +#if defined(I2C_EXTENDED_ADDRESSXXXXXXXXXXXX) if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { // Send request to multiplexer muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX } else muxPhase = 0; #endif + while(TWCR & (1< MuxPhase_OFF) { - 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< 1) { - // Device transaction complete, prepare to deselect MUX by sending start bit - TWCR = (1<i2cAddress.deviceAddress() == 0) { - // Send stop and post rb. - TWDR = 0xff; - TWCR = (1< 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<i2cAddress.muxNumber(); - TWDR = (muxAddress << 1) | 0; // MUXaddress+Write - TWCR = (1< MuxPhase_EPILOG) { - // Mux Cleardown was NAK'd, send stop and then finish. - TWCR = (1<writeBuffer + (txCount++)); + TWDR = GETFLASH(sendBuffer + (txCount++)); else - TWDR = currentRequest->writeBuffer[txCount++]; + TWDR = sendBuffer[txCount++]; bytesToSend--; TWCR = (1< 0) { - currentRequest->readBuffer[rxCount++] = TWDR; + receiveBuffer[rxCount++] = TWDR; bytesToReceive--; } /* fallthrough */ @@ -292,7 +187,7 @@ void I2CManagerClass::I2C_handleInterrupt() { case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted if (bytesToReceive > 0) { - currentRequest->readBuffer[rxCount++] = TWDR; + receiveBuffer[rxCount++] = TWDR; bytesToReceive--; } TWCR = (1<i2cAddress; - if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend)) - TWDR = (deviceAddress << 1) | 1; // SLA+R - else - TWDR = (deviceAddress << 1) | 0; // SLA+W - TWCR = (1<writeLen; - bytesToReceive = currentRequest->readLen; txCount = 0; rxCount = 0; @@ -123,11 +121,11 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (bytesToSend) { // Acked, so send next byte (don't need to use GETFLASH) - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + TWI0.MDATA = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; + TWI0.MADDR = (deviceAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; @@ -136,7 +134,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte + receiveBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; } if (bytesToReceive) { @@ -155,7 +153,7 @@ void I2CManagerClass::I2C_handleInterrupt() { * Interrupt handler. ***************************************************************************/ ISR(TWI0_TWIM_vect) { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 4da932f..2565c15 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -79,7 +79,7 @@ for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ enum MuxPhase: uint8_t { MuxPhase_OFF = 0, MuxPhase_PROLOG, - MuxPhase_PASSTHRU, + MuxPhase_PAYLOAD, MuxPhase_EPILOG, } ; @@ -107,7 +107,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { } /*************************************************************************** - * Helper function to start operations, if the I2C interface is free and + * Start an I2C transaction, 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. @@ -126,9 +126,47 @@ void I2CManagerClass::startTransaction() { startTime = micros(); currentRequest = queueHead; rxCount = txCount = 0; - // Copy key fields to static data for speed. - operation = currentRequest->operation & OPERATION_MASK; + // Start the I2C process going. +#if defined(I2C_EXTENDED_ADDRESS) + I2CMux muxNumber = currentRequest->i2cAddress.muxNumber(); + if (muxNumber != I2CMux_None) { + muxPhase = MuxPhase_PROLOG; + uint8_t subBus = currentRequest->i2cAddress.subBus(); + muxData[0] = (subBus == SubBus_All) ? 0xff : + (subBus == SubBus_None) ? 0x00 : +#if defined(I2CMUX_PCA9547) + 0x08 | subBus; +#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544) + 0x04 | subBus; // NB Only 2 or 4 subbuses respectively +#else + // Default behaviour for most MUXs is to use a mask + // with a bit set for the subBus to be enabled + 1 << subBus; +#endif + deviceAddress = I2C_MUX_BASE_ADDRESS + muxNumber; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + } else { + // Send/receive payload for device only. + muxPhase = MuxPhase_OFF; + deviceAddress = currentRequest->i2cAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; + } +#else + deviceAddress = currentRequest->i2cAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; +#endif I2C_sendStart(); } } @@ -194,7 +232,7 @@ uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_ * reset before the read. ***************************************************************************/ void I2CManagerClass::setTimeout(unsigned long value) { - timeout = value; + _timeout = value; }; /*************************************************************************** @@ -205,12 +243,12 @@ void I2CManagerClass::setTimeout(unsigned long value) { void I2CManagerClass::checkForTimeout() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { I2CRB *t = queueHead; - if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) { + if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) { // Check for timeout unsigned long elapsed = micros() - startTime; - if (elapsed > timeout) { + if (elapsed > _timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on %s, I2CRB=%s"), t->i2cAddress.toString(), currentRequest); + //DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString()); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; @@ -267,19 +305,58 @@ void I2CManagerClass::handleInterrupt() { // Check if current request has completed. If there's a current 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_COMPLETED && currentRequest != NULL) { // Operation has completed. if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES || currentRequest->operation & OPERATION_NORETRY) { // Status is OK, or has failed and retry count exceeded, or retries disabled. +#if defined(I2C_EXTENDED_ADDRESS) + if (muxPhase == MuxPhase_PROLOG ) { + overallStatus = completionStatus; + uint8_t rbAddress = currentRequest->i2cAddress.deviceAddress(); + if (completionStatus == I2C_STATUS_OK && rbAddress != 0) { + // Mux request OK, start handling application request. + muxPhase = MuxPhase_PAYLOAD; + deviceAddress = rbAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } + } else if (muxPhase == MuxPhase_PAYLOAD) { + // Application request completed, now send epilogue to mux + overallStatus = completionStatus; + currentRequest->nBytes = rxCount; // Save number of bytes read into rb + muxPhase = MuxPhase_EPILOG; + deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); + muxData[0] = 0x00; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } else if (muxPhase == MuxPhase_EPILOG) { + // Epilog finished, ignore completionStatus + muxPhase = MuxPhase_OFF; + } else + overallStatus = completionStatus; +#else + overallStatus = completionStatus; + currentRequest->nBytes = rxCount; +#endif + // Remove completed request from head of queue I2CRB * t = queueHead; if (t == currentRequest) { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; - t->nBytes = rxCount; - t->status = completionStatus; + t->status = overallStatus; // I2C state machine is now free for next request currentRequest = NULL; @@ -295,28 +372,10 @@ void I2CManagerClass::handleInterrupt() { if (state == I2C_STATE_FREE && queueHead != NULL) { // Allow any pending interrupts before starting the next request. - interrupts(); + //interrupts(); // Start next request I2CManager.startTransaction(); } } -// Fields in I2CManager class specific to Non-blocking implementation. -I2CRB * volatile I2CManagerClass::queueHead = NULL; -I2CRB * volatile I2CManagerClass::queueTail = NULL; -I2CRB * volatile I2CManagerClass::currentRequest = NULL; -volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE; -uint8_t I2CManagerClass::completionStatus; -volatile uint8_t I2CManagerClass::txCount; -volatile uint8_t I2CManagerClass::rxCount; -volatile uint8_t I2CManagerClass::operation; -volatile uint8_t I2CManagerClass::bytesToSend; -volatile uint8_t I2CManagerClass::bytesToReceive; -volatile unsigned long I2CManagerClass::startTime; -uint8_t I2CManagerClass::retryCounter = 0; - -#if defined(I2C_EXTENDED_ADDRESS) -volatile uint8_t I2CManagerClass::muxPhase = 0; -#endif - #endif \ No newline at end of file diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 5eb612b..a50ccba 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -153,8 +153,6 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; txCount = 0; rxCount = 0; @@ -220,11 +218,11 @@ void I2CManagerClass::I2C_handleInterrupt() { state = I2C_STATE_COMPLETED; // Completed with error } else if (bytesToSend) { // Acked, so send next byte - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->I2CM.DATA.bit.DATA = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // 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 = (deviceAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition I2C_sendStop(); @@ -235,12 +233,12 @@ void I2CManagerClass::I2C_handleInterrupt() { if (bytesToReceive == 1) { s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive = 0; state = I2C_STATE_COMPLETED; // Completed OK } else if (bytesToReceive) { s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive--; } } diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 7008750..6c74235 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -38,7 +38,7 @@ ***************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32) void I2C1_IRQHandler() { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif @@ -151,8 +151,7 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; + rxCount = txCount = 0; uint8_t temp; // On a single-master I2C bus, the start bit won't be sent until the bus @@ -168,7 +167,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= (1<<10); // Enable the ACK s->CR1 |= (1<<8); // Generate START // Send address with read flag (1) or'd in - s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address + s->DR = (deviceAddress << 1) | 1; // send the address while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set // Special case for 1 byte reads! if (bytesToReceive == 1) @@ -185,7 +184,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= (1<<10); // Enable the ACK s->CR1 |= (1<<8); // Generate START // Send address with write flag (0) or'd in - s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address + s->DR = (deviceAddress << 1) | 0; // send the address while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } @@ -219,44 +218,48 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { + if (!s) return; + if (s->SR1 && (1<<9)) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request } else if (s->SR1 && (1<<8)) { // Bus error - state = I2C_STATUS_BUS_ERROR; + completionStatus = I2C_STATUS_BUS_ERROR; + state = I2C_STATE_COMPLETED; } else if (s->SR1 && (1<<7)) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. I2C_sendStop(); - state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + state = I2C_STATE_COMPLETED; } else if (bytesToSend) { // Acked, so send next byte - s->DR = currentRequest->writeBuffer[txCount++]; + s->DR = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // 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 = (deviceAddress << 1) | 1; } else { // Check both TxE/BTF == 1 before generating stop while (!(s->SR1 && (1<<7))); // Check TxE while (!(s->SR1 && (1<<2))); // Check BTF - // No more data to send/receive. Initiate a STOP condition. + // No more data to send/receive. Initiate a STOP condition and finish I2C_sendStop(); - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; } } else if (s->SR1 && (1<<6)) { // Master read completed without errors if (bytesToReceive == 1) { // s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte + receiveBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive = 0; - state = I2C_STATUS_OK; // done + state = I2C_STATE_COMPLETED; } else if (bytesToReceive) { // s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte + receiveBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive--; } } From 98697427a364d6b26e83373fc59b935c6ea54dfd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:21:06 +0000 Subject: [PATCH 60/95] Update I2CManager_SAMD.h Fix compile errors following other changes --- I2CManager_SAMD.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index f763e3c..76774a9 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -38,7 +38,7 @@ ***************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO) void SERCOM3_Handler() { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif @@ -166,11 +166,11 @@ void I2CManagerClass::I2C_sendStart() { if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { // Send start and address with read flag (1) or'd in - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1; } else { // Send start and address with write flag (0) or'd in - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1ul) | 0; } } From ad4cedfccff7cb5def7c1c87daff2dcb88c17709 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:21:50 +0000 Subject: [PATCH 61/95] Update I2CManager_NonBlocking.h Rationalise ATOMIC_BLOCK macro definition and remove reliance on atomic.h. --- I2CManager_NonBlocking.h | 69 ++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2565c15..fb39fba 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -24,51 +24,39 @@ #include #include "I2CManager.h" -#if defined(I2C_USE_INTERRUPTS) -// atomic.h isn't available on SAMD, and likely others too... + +// Support for atomic isolation (i.e. a block with interrupts disabled). +// E.g. +// ATOMIC_BLOCK() { +// doSomethingWithInterruptsDisabled(); +// } +// This has the advantage over simple noInterrupts/Interrupts that the +// original interrupt state is restored when the block finishes. +// #if defined(__AVR__) -#include +static inline uint8_t _deferInterrupts(void) { + noInterrupts(); + return 1; +} +#define ATOMIC_BLOCK(x) \ +for (uint8_t _int_saved=SREG,_ToDo=_deferInterrupts(); \ + _ToDo; _ToDo=0, SREG=_int_saved) #elif defined(__arm__) -// Helper assembly language functions -static __inline__ uint8_t my_iSeiRetVal(void) -{ - __asm__ __volatile__ ("cpsie i" ::); - return 1; +static inline uint8_t _deferInterrupts(void) { + __set_PRIMASK(1); + return 1; } - -static __inline__ uint8_t my_iCliRetVal(void) -{ - __asm__ __volatile__ ("cpsid i" ::); - return 1; -} - -static __inline__ void my_iRestore(const uint32_t *__s) -{ - uint32_t res = *__s; - __asm__ __volatile__ ("MSR primask, %0" : : "r" (res) ); -} - -static __inline__ uint32_t my_iGetIReg( void ) -{ - uint32_t reg; - __asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) ); - return reg; -} -// Macros for atomic isolation -#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \ - __attribute__((__cleanup__(my_iRestore))) = my_iGetIReg() - -#define ATOMIC() \ -for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ - _done; _done = 0 ) - -#define ATOMIC_BLOCK(x) ATOMIC() -#define ATOMIC_RESTORESTATE -#endif +#define ATOMIC_BLOCK(x) \ +for (uint8_t _int_saved=__get_PRIMASK(),_ToDo=_deferInterrupts(); \ + _ToDo; _ToDo=0, __set_PRIMASK(_int_saved)) #else -#define ATOMIC_BLOCK(x) -#define ATOMIC_RESTORESTATE +// If it's not a recognised target, don't use interrupts in the I2C driver +#ifdef I2C_USE_INTERRUPTS +//#undef I2C_USE_INTERRUPTS #endif +#define ATOMIC_BLOCK(x) // expand to nothing. +#endif + // This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here // to get intellisense to work correctly. @@ -178,7 +166,6 @@ void I2CManagerClass::startTransaction() { void I2CManagerClass::queueRequest(I2CRB *req) { req->status = I2C_STATUS_PENDING; req->nextRequest = NULL; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if (!queueTail) queueHead = queueTail = req; // Only item on queue From 1cfe5a1e46cdfdcf5710f69a75053bf9b734d71d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:22:35 +0000 Subject: [PATCH 62/95] Update I2CManager_STM32.h Fix some merge errors. --- I2CManager_STM32.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index d7ea665..79a3726 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -230,7 +230,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= I2C_CR1_START; // Generate START // Send address with write flag (0) or'd in s->DR = (deviceAddress << 1) | 0; // send the address - while (!(s->SR1 && (1<SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -270,7 +270,7 @@ void I2CManagerClass::I2C_handleInterrupt() { // Bus error completionStatus = I2C_STATUS_BUS_ERROR; state = I2C_STATE_COMPLETED; - } else if (s->SR1 && (1<SR1 && I2C_SR1_TXE) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. From c315895cd96b5cfb185efd8ce71207575583b70a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 19:52:24 +0000 Subject: [PATCH 63/95] Update I2CManager_NonBlocking.h Rework ATOMIC_BLOCK to further simplify and clarify. --- I2CManager_NonBlocking.h | 41 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fb39fba..2511a8c 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -33,28 +33,35 @@ // This has the advantage over simple noInterrupts/Interrupts that the // original interrupt state is restored when the block finishes. // -#if defined(__AVR__) +// (This should really be defined in an include file somewhere more global, so +// it can replace use of noInterrupts/interrupts in other parts of DCC-EX. +// static inline uint8_t _deferInterrupts(void) { noInterrupts(); return 1; } -#define ATOMIC_BLOCK(x) \ -for (uint8_t _int_saved=SREG,_ToDo=_deferInterrupts(); \ - _ToDo; _ToDo=0, SREG=_int_saved) -#elif defined(__arm__) -static inline uint8_t _deferInterrupts(void) { - __set_PRIMASK(1); - return 1; +static inline void _enableInterruptsIf(uint8_t wasEnabled) { + if (wasEnabled) interrupts(); } -#define ATOMIC_BLOCK(x) \ -for (uint8_t _int_saved=__get_PRIMASK(),_ToDo=_deferInterrupts(); \ - _ToDo; _ToDo=0, __set_PRIMASK(_int_saved)) +#if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc. + #define ATOMIC_BLOCK(x) \ + for (bool _int_saved=(SREG & (1< Date: Fri, 10 Feb 2023 19:57:42 +0000 Subject: [PATCH 64/95] Update I2CManager_AVR.h Avoid loop in I2C_sendStart function. --- I2CManager_AVR.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index c2d7312..e75b32f 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -97,15 +97,10 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { rxCount = 0; txCount = 0; -#if defined(I2C_EXTENDED_ADDRESSXXXXXXXXXXXX) - if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { - // Send request to multiplexer - muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX - } else - muxPhase = 0; -#endif - while(TWCR & (1< Date: Fri, 10 Feb 2023 22:57:15 +0000 Subject: [PATCH 65/95] Update I2CManager_NonBlocking.h Reduce platform-specific part of ATOMIC_BLOCK definition to one inline function _getInterruptState() --- I2CManager_NonBlocking.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2511a8c..991b45c 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -40,22 +40,23 @@ static inline uint8_t _deferInterrupts(void) { noInterrupts(); return 1; } -static inline void _enableInterruptsIf(uint8_t wasEnabled) { - if (wasEnabled) interrupts(); +static inline void _conditionalEnableInterrupts(bool *wasEnabled) { + if (*wasEnabled) interrupts(); } +#define ATOMIC_BLOCK(x) \ +for (bool _int_saved __attribute__((__cleanup__(_conditionalEnableInterrupts))) \ + =_getInterruptState(),_ToDo=_deferInterrupts(); _ToDo; _ToDo=0) + #if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc. - #define ATOMIC_BLOCK(x) \ - for (bool _int_saved=(SREG & (1< Date: Sat, 11 Feb 2023 15:47:50 +0000 Subject: [PATCH 66/95] Support for multiple displays (OLED etc). New EXRAIL command LCD2(display,row,"text"). Display 0 is the usual one, other displays can be configured through HAL. --- DIAG.h | 1 + DisplayInterface.h | 12 +++- EXRAIL2.cpp | 7 ++- EXRAIL2.h | 6 +- EXRAIL2MacroReset.h | 4 +- EXRAILMacros.h | 14 ++++- IO_OLEDDisplay.h | 133 ++++++++++++++++++++++++++++++-------------- StringFormatter.cpp | 10 ++++ StringFormatter.h | 1 + 9 files changed, 140 insertions(+), 48 deletions(-) diff --git a/DIAG.h b/DIAG.h index c942e59..adb7e07 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,4 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd +#define LCD2 StringFormatter::lcd #endif diff --git a/DisplayInterface.h b/DisplayInterface.h index 64fc1de..d9afdc4 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -28,8 +28,18 @@ class DisplayInterface : public Print { public: virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; virtual void setRow(byte line) { (void)line; }; - virtual void clear() {}; + virtual void clear() { }; virtual size_t write(uint8_t c) { (void)c; return 0; }; + // Additional functions to support multiple displays. + // Display number zero is the default one and the original display + // drivers overloaded the above calls only. Newer display drivers + // (e.g. HAL IO_OledDisplay) should override all functions. + virtual void setRow(uint8_t displayNo, byte line) { + if (!displayNo) setRow(line); + } + virtual void clear(uint8_t displayNo) { + if (!displayNo) clear(); + } static DisplayInterface *lcdDisplay; }; diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e925d96..11ad4a9 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1209,6 +1209,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_parse: case thrunge_broadcast: case thrunge_lcd: + default: // thrunge_lcd+1, ... if (!buffer) buffer=new StringBuffer(); buffer->flush(); stream=buffer; @@ -1244,7 +1245,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_lcd: LCD(id,F("%s"),buffer->getString()); break; - - default: break; + default: // thrunge_lcd+1, ... + if (mode > thrunge_lcd) + LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display + break; } } diff --git a/EXRAIL2.h b/EXRAIL2.h index 12bca3b..2c4ad96 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -73,11 +73,15 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFLOCO }; +// Ensure thrunge_lcd is put last as there may be more than one display, +// sequentially numbered from thrunge_lcd. enum thrunger: byte { thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial4, thrunge_serial5, thrunge_serial6, - thrunge_lcd, thrunge_lcn}; + thrunge_lcn, + thrunge_lcd, // Must be last!! + }; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index e54e48b..b81c86f 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -80,6 +80,7 @@ #undef KILLALL #undef LATCH #undef LCD +#undef LCD2 #undef LCN #undef MOVETT #undef ONACTIVATE @@ -198,7 +199,8 @@ #define JOIN #define KILLALL #define LATCH(sensor_id) -#define LCD(row,msg) +#define LCD(row,msg) +#define LCD2(display,row,msg) #define LCN(msg) #define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 3d3fece..599664b 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -45,7 +45,7 @@ // Descriptive texts for routes and animations are created in a sepaerate function which // can be called to emit a list of routes/automatuions in a form suitable for Withrottle. -// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create +// PRINT(msg), LCD(row,msg) and LCD2(display,row,msg) are implemented in a separate pass to create // a getMessageText(id) function. // CAUTION: The macros below are multiple passed over myAutomation.h @@ -142,6 +142,15 @@ const int StringMacroTracker1=__COUNTER__; tmode=thrunge_lcd; \ lcdid=id;\ break;\ + } +#undef LCD2 +#define LCD2(display,id,msg) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=(thrunger)(thrunge_lcd+display); \ + lcdid=id;\ + break;\ } void RMFT2::printMessage(uint16_t id) { @@ -298,6 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) +#define LCD2(display,id,msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -368,6 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { // Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd +#undef LCD2 +#define LCD2 StringFormatter::lcd #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 859f82d..7cdb34e 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -51,6 +51,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { private: + uint8_t _displayNo = 0; // Here we define the device-specific variables. uint8_t _height; // in pixels uint8_t _width; // in pixels @@ -64,16 +65,22 @@ private: uint8_t *_lastRowGeneration = NULL; uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 0; public: // Static function to handle "OLEDDisplay::create(...)" calls. static void create(I2CAddress i2cAddress, int width = 128, int height=64) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); + } + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor - OLEDDisplay(I2CAddress i2cAddress, int width, int height) { + OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayNo = displayNo; _I2CAddress = i2cAddress; _width = width; _height = height; @@ -89,9 +96,26 @@ protected: // Fill buffer with spaces memset(_buffer, ' ', _numCols*_numRows); + // Is this the main display? + if (_displayNo == 0) { + // Set first two lines on screen + setRow(0); + print(F("DCC++ EX v")); + print(F(VERSION)); + setRow(1); + print(F("Lic GPLv3")); + } + // Create OLED driver oled = new SSD1306AsciiWire(); + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to lcdDisplay methods. + // Make a note of the existing display reference, to that we can + // pass on anything we're not interested in. + _nextDisplay = DisplayInterface::lcdDisplay; + DisplayInterface::lcdDisplay = this; + addDevice(this); } @@ -99,15 +123,9 @@ protected: void _begin() override { // Initialise device if (oled->begin(_I2CAddress, _width, _height)) { - // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to lcdDisplay methods. - DisplayInterface::lcdDisplay = this; DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); - // Set first two lines on screen - LCD(0,F("DCC++ EX v%S"),F(VERSION)); - LCD(1,F("Lic GPLv3")); // Force all rows to be redrawn for (uint8_t row=0; row<_numRows; row++) @@ -120,7 +138,10 @@ protected: } void _loop(unsigned long) override { + screenUpdate(); + } + void screenUpdate() { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the // characters from the buffer, one character per entry, @@ -156,54 +177,82 @@ protected: // ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { - (void)force; // suppress compiler warning + //screenUpdate(); + if (_nextDisplay) + return _nextDisplay->loop2(force); // continue to next display return NULL; } // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating - void setRow(byte line) override { - if (line == 255) { - // LCD(255, "xxx") - scroll the contents of the buffer - // and put the new line at the bottom of the screen - for (int row=1; row<_numRows; row++) { - strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); - _rowGeneration[row-1]++; - } - line = _numRows-1; - } else if (line >= _numRows) - line = _numRows - 1; // Overwrite bottom line. + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. - _rowNo = line; - // Fill line with blanks - for (_colNo = 0; _colNo < _numCols; _colNo++) - _buffer[_rowNo*_numCols+_colNo] = ' '; - _colNo = 0; - // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry. - _rowGeneration[_rowNo]++; + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry. + _rowGeneration[_rowNo]++; + + } else if (_nextDisplay) + _nextDisplay->setRow(displayNo, line); // Pass to next display + + } + + // Write one character to the screen referenced in the last setRow() call. + size_t write(uint8_t c) override { + if (_selectedDisplayNo == _displayNo) { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + return 1; + } else if (_nextDisplay) + return _nextDisplay->write(c); + else + return 0; } // Write blanks to all of the screen (blocks until complete) - void clear () override { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(_rowNo); - } - _rowNo = 0; + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } else if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display } - - // Write one character - size_t write(uint8_t c) override { - // Write character to buffer (if space) - if (_colNo < _numCols) - _buffer[_rowNo*_numCols+_colNo++] = c; - return 1; + + // Overloads of above, for compatibility + void setRow(uint8_t line) override { + setRow(0, line); + } + void clear() override { + clear(0); } // Display information about the device. void _display() { - DIAG(F("OLEDDisplay Configured addr %s"), _I2CAddress.toString()); + DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); } }; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index c1f20c4..382c484 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -51,6 +51,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(LCDDisplay::lcdDisplay,input,args); } +void StringFormatter::lcd(uint8_t display, byte row, const FSH* input...) { + va_list args; + + if (!LCDDisplay::lcdDisplay) return; + LCDDisplay::lcdDisplay->setRow(display, row); + va_start(args, input); + send2(LCDDisplay::lcdDisplay,input,args); + va_end(args); +} + void StringFormatter::send(Print * stream, const FSH* input...) { va_list args; va_start(args, input); diff --git a/StringFormatter.h b/StringFormatter.h index 4787715..80543b9 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -46,6 +46,7 @@ class StringFormatter // DIAG support static void diag( const FSH* input...); static void lcd(byte row, const FSH* input...); + static void lcd(uint8_t display, byte row, const FSH* input...); static void printEscapes(char * input); static void printEscape( char c); From 65f7a4917f9ea641fb09869578ce95795cf08874 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 11 Feb 2023 17:04:18 +0000 Subject: [PATCH 67/95] Separate out lcd (write to default display) and lcd2 (any display). --- DIAG.h | 2 +- EXRAILMacros.h | 4 ++-- IO_OLEDDisplay.h | 7 ++++--- StringFormatter.cpp | 3 +-- StringFormatter.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DIAG.h b/DIAG.h index adb7e07..0595505 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,5 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd -#define LCD2 StringFormatter::lcd +#define LCD2 StringFormatter::lcd2 #endif diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 599664b..868cbc9 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -307,7 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) -#define LCD2(display,id,msg) +#define LCD2(display,id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -379,7 +379,7 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { #undef LCD #define LCD StringFormatter::lcd #undef LCD2 -#define LCD2 StringFormatter::lcd +#define LCD2 StringFormatter::lcd2 #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 7cdb34e..307b7d7 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -138,7 +138,7 @@ protected: } void _loop(unsigned long) override { - screenUpdate(); + //screenUpdate(); } void screenUpdate() { @@ -177,7 +177,7 @@ protected: // ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { - //screenUpdate(); + screenUpdate(); if (_nextDisplay) return _nextDisplay->loop2(force); // continue to next display return NULL; @@ -208,7 +208,8 @@ protected: _buffer[_rowNo*_numCols+_colNo] = ' '; _colNo = 0; // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry. + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. _rowGeneration[_rowNo]++; } else if (_nextDisplay) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 382c484..2341886 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -51,14 +51,13 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(LCDDisplay::lcdDisplay,input,args); } -void StringFormatter::lcd(uint8_t display, byte row, const FSH* input...) { +void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; if (!LCDDisplay::lcdDisplay) return; LCDDisplay::lcdDisplay->setRow(display, row); va_start(args, input); send2(LCDDisplay::lcdDisplay,input,args); - va_end(args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/StringFormatter.h b/StringFormatter.h index 80543b9..4c95ea6 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -46,7 +46,7 @@ class StringFormatter // DIAG support static void diag( const FSH* input...); static void lcd(byte row, const FSH* input...); - static void lcd(uint8_t display, byte row, const FSH* input...); + static void lcd2(uint8_t display, byte row, const FSH* input...); static void printEscapes(char * input); static void printEscape( char c); From 35f3cca9b33e36b6f83ec090d0a2b2e1bad7764d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 11 Feb 2023 23:37:09 +0000 Subject: [PATCH 68/95] Rename LCDDisplay class to Display; renameEXRAIL LCD2 macro to SCREEN --- DCCEX.h | 2 +- DIAG.h | 2 +- LCDDisplay.cpp => Display.cpp | 16 ++++++++-------- LCDDisplay.h => Display.h | 6 +++--- LCD_Implementation.h => Display_Implementation.h | 10 +++++----- EXRAIL2.cpp | 2 +- EXRAIL2MacroReset.h | 4 ++-- EXRAILMacros.h | 12 ++++++------ IO_OLEDDisplay.h | 2 +- LiquidCrystal_I2C.h | 4 ++-- SSD1306Ascii.h | 4 ++-- StringFormatter.cpp | 14 +++++++------- StringFormatter.h | 2 +- 13 files changed, 40 insertions(+), 40 deletions(-) rename LCDDisplay.cpp => Display.cpp (94%) rename LCDDisplay.h => Display.h (95%) rename LCD_Implementation.h => Display_Implementation.h (85%) diff --git a/DCCEX.h b/DCCEX.h index f2eee51..2dc8eb7 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -40,7 +40,7 @@ #if ETHERNET_ON == true #include "EthernetInterface.h" #endif -#include "LCD_Implementation.h" +#include "Display_Implementation.h" #include "LCN.h" #include "IODevice.h" #include "Turnouts.h" diff --git a/DIAG.h b/DIAG.h index 0595505..5aa54aa 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,5 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd -#define LCD2 StringFormatter::lcd2 +#define SCREEN StringFormatter::lcd2 #endif diff --git a/LCDDisplay.cpp b/Display.cpp similarity index 94% rename from LCDDisplay.cpp rename to Display.cpp index c6609ce..c85d7f4 100644 --- a/LCDDisplay.cpp +++ b/Display.cpp @@ -45,20 +45,20 @@ */ -#include "LCDDisplay.h" +#include "Display.h" -void LCDDisplay::clear() { +void Display::clear() { clearNative(); for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0'; topRow = -1; // loop2 will fill from row 0 } -void LCDDisplay::setRow(byte line) { +void Display::setRow(byte line) { hotRow = line; hotCol = 0; } -size_t LCDDisplay::write(uint8_t b) { +size_t Display::write(uint8_t b) { if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1; rowBuffer[hotRow][hotCol] = b; hotCol++; @@ -66,12 +66,12 @@ size_t LCDDisplay::write(uint8_t b) { return 1; } -void LCDDisplay::loop() { +void Display::loop() { if (!lcdDisplay) return; lcdDisplay->loop2(false); } -LCDDisplay *LCDDisplay::loop2(bool force) { +Display *Display::loop2(bool force) { if (!lcdDisplay) return NULL; // If output device is busy, don't do anything on this loop @@ -150,7 +150,7 @@ LCDDisplay *LCDDisplay::loop2(bool force) { return NULL; } -void LCDDisplay::moveToNextRow() { +void Display::moveToNextRow() { rowNext = (rowNext + 1) % MAX_LCD_ROWS; #if SCROLLMODE == 1 // Finished if we've looped back to row 0 @@ -161,7 +161,7 @@ void LCDDisplay::moveToNextRow() { #endif } -void LCDDisplay::skipBlankRows() { +void Display::skipBlankRows() { while (!done && rowBuffer[rowNext][0] == 0) moveToNextRow(); } diff --git a/LCDDisplay.h b/Display.h similarity index 95% rename from LCDDisplay.h rename to Display.h index 6e2c69d..84cdbde 100644 --- a/LCDDisplay.h +++ b/Display.h @@ -34,16 +34,16 @@ // This class is created in LCDisplay_Implementation.h -class LCDDisplay : public DisplayInterface { +class Display : public DisplayInterface { public: - LCDDisplay() {}; + Display() {}; static const int MAX_LCD_ROWS = 8; static const int MAX_LCD_COLS = MAX_MSG_SIZE; static const long LCD_SCROLL_TIME = 3000; // 3 seconds // Internally handled functions static void loop(); - LCDDisplay* loop2(bool force) override; + Display* loop2(bool force) override; void setRow(byte line) override; void clear() override; diff --git a/LCD_Implementation.h b/Display_Implementation.h similarity index 85% rename from LCD_Implementation.h rename to Display_Implementation.h index 95b0f81..4c90dd9 100644 --- a/LCD_Implementation.h +++ b/Display_Implementation.h @@ -27,15 +27,15 @@ #ifndef LCD_Implementation_h #define LCD_Implementation_h -#include "LCDDisplay.h" +#include "Display.h" #include "SSD1306Ascii.h" #include "LiquidCrystal_I2C.h" -// Implement the LCDDisplay shim class as a singleton. -// The DisplayInterface class implements a displayy handler with no code (null device); -// The LCDDisplay class sub-classes DisplayInterface to provide the common display code; -// Then LCDDisplay class is subclassed to the specific device type classes: +// Implement the Display shim class as a singleton. +// The DisplayInterface class implements a display handler with no code (null device); +// The Display class sub-classes DisplayInterface to provide the common display code; +// Then Display class is subclassed to the specific device type classes: // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 11ad4a9..48f91cc 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1247,7 +1247,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { break; default: // thrunge_lcd+1, ... if (mode > thrunge_lcd) - LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display + SCREEN(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display break; } } diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index b81c86f..b4ffc6d 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -80,7 +80,7 @@ #undef KILLALL #undef LATCH #undef LCD -#undef LCD2 +#undef SCREEN #undef LCN #undef MOVETT #undef ONACTIVATE @@ -200,7 +200,7 @@ #define KILLALL #define LATCH(sensor_id) #define LCD(row,msg) -#define LCD2(display,row,msg) +#define SCREEN(display,row,msg) #define LCN(msg) #define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 868cbc9..ee20c1f 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -45,7 +45,7 @@ // Descriptive texts for routes and animations are created in a sepaerate function which // can be called to emit a list of routes/automatuions in a form suitable for Withrottle. -// PRINT(msg), LCD(row,msg) and LCD2(display,row,msg) are implemented in a separate pass to create +// PRINT(msg), LCD(row,msg) and SCREEN(display,row,msg) are implemented in a separate pass to create // a getMessageText(id) function. // CAUTION: The macros below are multiple passed over myAutomation.h @@ -143,8 +143,8 @@ const int StringMacroTracker1=__COUNTER__; lcdid=id;\ break;\ } -#undef LCD2 -#define LCD2(display,id,msg) \ +#undef SCREEN +#define SCREEN(display,id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ @@ -307,7 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) -#define LCD2(display,id,msg) PRINT(msg) +#define SCREEN(display,id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -378,8 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { // Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd -#undef LCD2 -#define LCD2 StringFormatter::lcd2 +#undef SCREEN +#define SCREEN StringFormatter::lcd2 #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 307b7d7..b381bd8 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -191,7 +191,7 @@ protected: _selectedDisplayNo = displayNo; if (displayNo == _displayNo) { if (line == 255) { - // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") - + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - // scroll the contents of the buffer and put the new line // at the bottom of the screen for (int row=1; row<_numRows; row++) { diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 20dc126..5d5a741 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -22,7 +22,7 @@ #define LiquidCrystal_I2C_h #include -#include "LCDDisplay.h" +#include "Display.h" #include "I2CManager.h" // commands @@ -62,7 +62,7 @@ #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit -class LiquidCrystal_I2C : public LCDDisplay { +class LiquidCrystal_I2C : public Display { public: LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); void begin(); diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index f4c9ba7..d40eac6 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -23,7 +23,7 @@ #include "Arduino.h" #include "FSH.h" -#include "LCDDisplay.h" +#include "Display.h" #include "I2CManager.h" #include "DIAG.h" @@ -33,7 +33,7 @@ //------------------------------------------------------------------------------ // Constructor -class SSD1306AsciiWire : public LCDDisplay { +class SSD1306AsciiWire : public Display { public: // Constructors diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 2341886..4054b1e 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -18,7 +18,7 @@ */ #include "StringFormatter.h" #include -#include "LCDDisplay.h" +#include "Display.h" bool Diag::ACK=false; bool Diag::CMD=false; @@ -45,19 +45,19 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!LCDDisplay::lcdDisplay) return; - LCDDisplay::lcdDisplay->setRow(row); + if (!Display::lcdDisplay) return; + Display::lcdDisplay->setRow(row); va_start(args, input); - send2(LCDDisplay::lcdDisplay,input,args); + send2(Display::lcdDisplay,input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!LCDDisplay::lcdDisplay) return; - LCDDisplay::lcdDisplay->setRow(display, row); + if (!Display::lcdDisplay) return; + Display::lcdDisplay->setRow(display, row); va_start(args, input); - send2(LCDDisplay::lcdDisplay,input,args); + send2(Display::lcdDisplay,input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/StringFormatter.h b/StringFormatter.h index 4c95ea6..6923c10 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -21,7 +21,7 @@ #include #include "FSH.h" #include "RingStream.h" -#include "LCDDisplay.h" +#include "Display.h" class Diag { public: static bool ACK; From ec73ac69d9332358aafef8c5442783dda19b982e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 12 Feb 2023 09:01:16 +0000 Subject: [PATCH 69/95] A few more name changes to more generic names --- Display.cpp | 6 +++--- DisplayInterface.cpp | 2 +- DisplayInterface.h | 2 +- Display_Implementation.h | 8 ++++---- IO_OLEDDisplay.h | 6 +++--- LiquidCrystal_I2C.cpp | 2 +- SSD1306Ascii.cpp | 2 +- StringFormatter.cpp | 12 ++++++------ version.h | 3 ++- 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Display.cpp b/Display.cpp index c85d7f4..5af85b8 100644 --- a/Display.cpp +++ b/Display.cpp @@ -67,12 +67,12 @@ size_t Display::write(uint8_t b) { } void Display::loop() { - if (!lcdDisplay) return; - lcdDisplay->loop2(false); + if (!displayHandler) return; + displayHandler->loop2(false); } Display *Display::loop2(bool force) { - if (!lcdDisplay) return NULL; + if (!displayHandler) return NULL; // If output device is busy, don't do anything on this loop // This avoids blocking while waiting for the device to complete. diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index 8fe45d3..6ef13a4 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,4 +21,4 @@ #include "DisplayInterface.h" -DisplayInterface *DisplayInterface::lcdDisplay = 0; +DisplayInterface *DisplayInterface::displayHandler = 0; diff --git a/DisplayInterface.h b/DisplayInterface.h index d9afdc4..82b38d6 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -41,7 +41,7 @@ public: if (!displayNo) clear(); } - static DisplayInterface *lcdDisplay; + static DisplayInterface *displayHandler; }; #endif diff --git a/Display_Implementation.h b/Display_Implementation.h index 4c90dd9..f04b7d2 100644 --- a/Display_Implementation.h +++ b/Display_Implementation.h @@ -40,14 +40,14 @@ // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. #if defined(OLED_DRIVER) - #define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #elif defined(LCD_DRIVER) - #define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #else - // Create null display handler just in case someone calls lcdDisplay->something without checking if lcdDisplay is NULL! - #define CONDITIONAL_LCD_START { new DisplayInterface(); } + // Create null display handler just in case someone calls displayHandler->something without checking if displayHandler is NULL! + #define CONDITIONAL_DISPLAY_START { new DisplayInterface(); } #endif #endif // LCD_Implementation_h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index b381bd8..12b571f 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -110,11 +110,11 @@ protected: oled = new SSD1306AsciiWire(); // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to lcdDisplay methods. + // will intercept any subsequent calls to displayHandler methods. // Make a note of the existing display reference, to that we can // pass on anything we're not interested in. - _nextDisplay = DisplayInterface::lcdDisplay; - DisplayInterface::lcdDisplay = this; + _nextDisplay = DisplayInterface::displayHandler; + DisplayInterface::displayHandler = this; addDevice(this); } diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index a5c2e16..c9d8113 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -57,7 +57,7 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; begin(); backlight(); - lcdDisplay = this; + displayHandler = this; } } diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index deae5ac..5e24c7a 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -160,7 +160,7 @@ SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { if (I2CManager.exists(address)) { begin(address, width, height); // Set singleton Address so CS is able to call it. - lcdDisplay = this; + displayHandler = this; return; } } diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 4054b1e..a659fc4 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -45,19 +45,19 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!Display::lcdDisplay) return; - Display::lcdDisplay->setRow(row); + if (!Display::displayHandler) return; + Display::displayHandler->setRow(row); va_start(args, input); - send2(Display::lcdDisplay,input,args); + send2(Display::displayHandler,input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!Display::lcdDisplay) return; - Display::lcdDisplay->setRow(display, row); + if (!Display::displayHandler) return; + Display::displayHandler->setRow(display, row); va_start(args, input); - send2(Display::lcdDisplay,input,args); + send2(Display::displayHandler,input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/version.h b/version.h index 0a263b7..2e6d81b 100644 --- a/version.h +++ b/version.h @@ -13,7 +13,8 @@ // PCF8575 I2C GPIO driver added. // EX-RAIL ANOUT function for triggering analogue // HAL drivers (e.g. analogue outputs, DFPlayer, PWM). -// Installable HAL OLED Display Driver. +// Installable HAL OLED Display Driver, with +// support for multiple displays. // Layered HAL Drivers PCA9685pwm and Servo added for // (1) native PWM on PCA9685 module and // (2) animations of servo movement via PCA9685pwm. From f1f1be8ad9a5fa03702a58579e1383e0014e1a3f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 12 Feb 2023 09:18:02 +0000 Subject: [PATCH 70/95] Oops- forgot to push this one. Name changes again. --- CommandStation-EX.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 67a7e58..1f57dc4 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -74,7 +74,7 @@ void setup() DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - CONDITIONAL_LCD_START { + CONDITIONAL_DISPLAY_START { // This block is still executed for DIAGS if LCD not in use LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); @@ -160,7 +160,7 @@ void loop() LCN::loop(); #endif - LCDDisplay::loop(); // ignored if LCD not in use + Display::loop(); // ignored if LCD not in use // Handle/update IO devices. IODevice::loop(); From 3292c93192bb8c24cf1ffe786d1624f8c9a3dea4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 14 Feb 2023 21:56:11 +0000 Subject: [PATCH 71/95] Update I2CManager.cpp Report address 0x3d as (probably) OLED Display. If an I2C probe for a device times out, assume that there's a bus problem on the MUX sub-bus, and skip the rest of the devices on that bus. This reduces the impact of the long timeout on some Wire implementations. --- I2CManager.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index dbba1a0..05feb28 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -52,7 +52,7 @@ static const FSH * guessI2CDeviceType(uint8_t address) { return F("GPIO Expander or LCD Display"); else if (address == 0x29) return F("Time-of-flight sensor"); - else if (address >= 0x3c && address <= 0x3c) + else if (address >= 0x3c && address <= 0x3d) return F("OLED Display"); else if (address >= 0x48 && address <= 0x4f) return F("Analogue Inputs or PWM"); @@ -110,6 +110,8 @@ void I2CManagerClass::begin(void) { // Enumerate all I2C devices that are connected via multiplexer, // i.e. that respond when only one multiplexer has one subBus enabled // and the device doesn't respond when the mux subBus is disabled. + // If any probes time out, then assume that the subbus is dead and + // don't do any more on that subbus. for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { @@ -117,7 +119,8 @@ void I2CManagerClass::begin(void) { for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) { muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { - if (exists(addr)) { + uint8_t status = checkAddress(addr); + if (status == I2C_STATUS_OK) { // De-select subbus muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); if (!exists(addr)) { @@ -129,11 +132,13 @@ void I2CManagerClass::begin(void) { } // Re-select subbus muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); + } else if (status == I2C_STATUS_TIMEOUT) { + // Bus stuck, skip to next one. + break; } } } - // Probe mux address again with SubBus_None to deselect all - // subBuses for that mux. Otherwise its devices will continue to + // Deselect all subBuses for this mux. Otherwise its devices will continue to // respond when other muxes are being probed. I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux } @@ -251,7 +256,7 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)"); case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)"); case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error"); - case I2C_STATUS_TIMEOUT: return F("Timeout"); + case I2C_STATUS_TIMEOUT: return F("I2C bus timeout"); case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost"); case I2C_STATUS_BUS_ERROR: return F("I2C bus error"); case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error"); From 67bd886a98a7c7d006ce7d75d82518b0f232a3ec Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 15 Feb 2023 08:51:21 +0800 Subject: [PATCH 72/95] USB Serial fixes for EX-RAIL & debug --- CommandDistributor.cpp | 2 +- EXRAIL2.cpp | 11 +++++------ Turnouts.cpp | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 652d852..4b05c68 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -48,7 +48,7 @@ template void CommandDistributor::broadcastReply(clientType t // on a single USB connection config, write direct to Serial and ignore flush/shove template void CommandDistributor::broadcastReply(clientType type, Targs... msg){ (void)type; //shut up compiler warning - StringFormatter::send(&Serial, msg...); + StringFormatter::send(&USB_SERIAL, msg...); } #endif diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 48f91cc..3bd8da7 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1165,11 +1165,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { // Find out where the string is going switch (mode) { case thrunge_print: - StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco); - stream=&Serial; + StringFormatter::send(&USB_SERIAL,F("<* EXRAIL(%d) "),loco); + stream=&USB_SERIAL; break; - case thrunge_serial: stream=&Serial; break; + case thrunge_serial: stream=&USB_SERIAL; break; case thrunge_serial1: #ifdef SERIAL1_COMMANDS stream=&Serial1; @@ -1200,7 +1200,6 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { stream=&Serial6; #endif break; - // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; case thrunge_lcn: #if defined(LCN_SERIAL) stream=&LCN_SERIAL; @@ -1233,11 +1232,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { // and decide what to do next switch (mode) { case thrunge_print: - StringFormatter::send(&Serial,F(" *>\n")); + StringFormatter::send(&USB_SERIAL,F(" *>\n")); break; // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; case thrunge_parse: - DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL); + DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL); break; case thrunge_broadcast: // TODO CommandDistributor::broadcastText(buffer->getString()); diff --git a/Turnouts.cpp b/Turnouts.cpp index 636922d..582ea7d 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -207,7 +207,7 @@ } #ifdef EESTOREDEBUG - printAll(&Serial); + printAll(&USB_SERIAL); #endif return tt; } From 21c82b37b0ade7510ff6a78940b618180507391d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Feb 2023 22:29:21 +0000 Subject: [PATCH 73/95] Allow frequency of PWM to be set for PCA9685 drivers. It's a parameter on the create() call, e.g. PCA9685::create(vpin, npins, address, frequency); --- IODevice.h | 1 + IO_PCA9685.cpp | 19 +++++++++++-------- IO_PCA9685pwm.h | 40 ++++++++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/IODevice.h b/IODevice.h index 8fd5efd..eaebaf7 100644 --- a/IODevice.h +++ b/IODevice.h @@ -326,6 +326,7 @@ private: // structures for setting up non-blocking writes to servo controller I2CRB requestBlock; uint8_t outputBuffer[5]; + uint8_t prescaler; // clock prescaler for setting PWM frequency }; ///////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 68e44a1..25b0745 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -31,22 +31,21 @@ static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ static const byte MODE1_RESTART=0x80; /**< Restart enabled */ static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ -static const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed // Predeclare helper function static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. -void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress); +void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency); } // Configure a port on the PCA9685. bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -73,10 +72,14 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i } // Constructor -PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { +PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { _firstVpin = firstVpin; _nPins = (nPins > 16) ? 16 : nPins; _I2CAddress = i2cAddress; + // Calculate prescaler value for PWM clock + if (frequency > 1526) frequency = 1526; + else if (frequency < 24) frequency = 24; + prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. for (int i=0; i<_nPins; i++) @@ -98,7 +101,7 @@ void PCA9685::_begin() { // Initialise I/O module here. if (I2CManager.exists(_I2CAddress)) { writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); // In theory, we should wait 500us before sending any other commands to each device, to allow @@ -114,7 +117,7 @@ void PCA9685::_begin() { // Device-specific write function, invoked from IODevice::write(). // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; @@ -141,7 +144,7 @@ void PCA9685::_write(VPIN vpin, int value) { // 4 (Bounce) Servo 'bounces' at extremes. // void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index ef92118..d950bbf 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -23,6 +23,22 @@ * commands the device to set the PWM mark-to-period ratio accordingly. * The call to IODevice::writeAnalogue(vpin, value) specifies the * desired value in the range 0-4095 (0=0% and 4095=100%). + * + * This driver can be used for simple servo control by writing values between + * about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250 + * for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length). + * A value of zero will switch off the servo. To create the device, use + * the following syntax: + * + * PCA9685_basic::create(vpin, npins, i2caddress); + * + * For LED control, a value of 0 is fully off, and 4095 is fully on. It is + * recommended, to reduce flicker of LEDs, that the frequency be configured + * to a value higher than the default of 50Hz. To do this, create the device + * as follows, for a frequency of 200Hz.: + * + * PCA9685_basic::create(vpin, npins, i2caddress, 200); + * */ #ifndef PCA9685_BASIC_H @@ -39,34 +55,38 @@ class PCA9685pwm : public IODevice { public: // Create device driver instance. - static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress); + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency); } private: - // structures for setting up non-blocking writes to servo controller + // structures for setting up non-blocking writes to PWM controller I2CRB requestBlock; uint8_t outputBuffer[5]; + uint16_t prescaler; // REGISTER ADDRESSES const uint8_t PCA9685_MODE1=0x00; // Mode Register - const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first servo register ON*/ + const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/ const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ // MODE1 bits const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */ const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */ - const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint32_t FREQUENCY_OSCILLATOR=25000000; /** Accurate enough for our purposes */ const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed // Constructor - PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { _firstVpin = firstVpin; _nPins = (nPins>16) ? 16 : nPins; _I2CAddress = i2cAddress; + if (frequency > 1526) frequency = 1526; + else if (frequency < 24) frequency = 24; + prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency; addDevice(this); // Initialise structure used for setting pulse rate @@ -82,8 +102,8 @@ private: // Initialise I/O module here. if (I2CManager.exists(_I2CAddress)) { - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); // In theory, we should wait 500us before sending any other commands to each device, to allow @@ -100,7 +120,7 @@ private: // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif @@ -121,7 +141,7 @@ private: // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void writeDevice(uint8_t pin, int value) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete From d0445f157c1ba0ced587b10d5b10344d0f4d8286 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Feb 2023 22:34:37 +0000 Subject: [PATCH 74/95] Update IODevice.h Finish changes relating to PWM frequency (doh!) --- IODevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IODevice.h b/IODevice.h index eaebaf7..3ff1dbe 100644 --- a/IODevice.h +++ b/IODevice.h @@ -276,7 +276,7 @@ private: class PCA9685 : public IODevice { public: - static void create(VPIN vpin, int nPins, I2CAddress i2cAddress); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50); enum ProfileType : uint8_t { Instant = 0, // Moves immediately between positions (if duration not specified) UseDuration = 0, // Use specified duration @@ -289,7 +289,7 @@ public: private: // Constructor - PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress); + PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency); // Device-specific initialisation void _begin() override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; From 8ed3bbd8451b1455130aa60ca6d01436a2fc3c40 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:41:13 +0000 Subject: [PATCH 75/95] Support for multiple displays Refactor display handling so DisplayInterface class passes the relevant commands to the relevant display objects. --- CommandStation-EX.ino | 12 +- Display.cpp | 70 ++++--- Display.h | 62 +++--- DisplayInterface.cpp | 5 +- DisplayInterface.h | 84 ++++++-- Display_Implementation.h | 23 ++- IO_OLEDDisplay.h | 212 +++++++++----------- LiquidCrystal_I2C.cpp | 38 ++-- LiquidCrystal_I2C.h | 15 +- SSD1306Ascii.cpp | 411 ++++++++++++++++++++++++++------------- SSD1306Ascii.h | 32 +-- StringFormatter.cpp | 12 +- defines.h | 4 + 13 files changed, 598 insertions(+), 382 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 1f57dc4..3c40bba 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -49,6 +49,7 @@ */ #include "DCCEX.h" +#include "Display_Implementation.h" #ifdef CPU_TYPE_ERROR #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h @@ -74,11 +75,11 @@ void setup() DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - CONDITIONAL_DISPLAY_START { - // This block is still executed for DIAGS if LCD not in use - LCD(0,F("DCC++ EX v%S"),F(VERSION)); + DISPLAY_START ( + // This block is still executed for DIAGS if display not in use + LCD(0,F("DCC-EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); - } + ); // Responsibility 2: Start all the communications before the DCC engine // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi @@ -160,7 +161,8 @@ void loop() LCN::loop(); #endif - Display::loop(); // ignored if LCD not in use + // Display refresh + DisplayInterface::loop(); // Handle/update IO devices. IODevice::loop(); diff --git a/Display.cpp b/Display.cpp index 5af85b8..587a107 100644 --- a/Display.cpp +++ b/Display.cpp @@ -47,42 +47,65 @@ #include "Display.h" -void Display::clear() { - clearNative(); - for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0'; +// Constructor - allocates device driver. +Display::Display(DisplayDevice *deviceDriver) { + _deviceDriver = deviceDriver; + // Get device dimensions in characters (e.g. 16x2). + numCharacterColumns = _deviceDriver->getNumCols(); + numCharacterRows = _deviceDriver->getNumRows();; + for (uint8_t row=0; rowbegin(); + _deviceDriver->clearNative(); +} + +void Display::_clear() { + _deviceDriver->clearNative(); + for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) + rowBuffer[row][0] = '\0'; topRow = -1; // loop2 will fill from row 0 } -void Display::setRow(byte line) { +void Display::_setRow(uint8_t line) { hotRow = line; hotCol = 0; + rowBuffer[hotRow][hotCol] = 0; // Clear existing text } -size_t Display::write(uint8_t b) { - if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1; +size_t Display::_write(uint8_t b) { + if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1; rowBuffer[hotRow][hotCol] = b; hotCol++; rowBuffer[hotRow][hotCol] = 0; return 1; } -void Display::loop() { - if (!displayHandler) return; - displayHandler->loop2(false); +// Refresh screen completely (will block until complete). Used +// during start-up. +void Display::_refresh() { + loop2(true); +} + +// On normal loop entries, loop will only make one output request on each +// entry, to avoid blocking while waiting for the I2C. +void Display::_displayLoop() { + // If output device is busy, don't do anything on this loop + // This avoids blocking while waiting for the device to complete. + if (!_deviceDriver->isBusy()) loop2(false); } Display *Display::loop2(bool force) { - if (!displayHandler) return NULL; - - // If output device is busy, don't do anything on this loop - // This avoids blocking while waiting for the device to complete. - if (isBusy()) return NULL; - unsigned long currentMillis = millis(); if (!force) { // See if we're in the time between updates - if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME) + if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME) return NULL; } else { // force full screen update from the beginning. @@ -104,7 +127,7 @@ Display *Display::loop2(bool force) { buffer[i] = rowBuffer[rowNext][i]; } else buffer[0] = '\0'; // Empty line - setRowNative(slot); // Set position for display + _deviceDriver->setRowNative(slot); // Set position for display charIndex = 0; bufferPointer = &buffer[0]; @@ -113,12 +136,12 @@ Display *Display::loop2(bool force) { // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { - writeNative(ch); + _deviceDriver->writeNative(ch); bufferPointer++; } else - writeNative(' '); + _deviceDriver->writeNative(' '); - if (++charIndex >= MAX_LCD_COLS) { + if (++charIndex >= MAX_CHARACTER_COLS) { // Screen slot completed, move to next slot on screen slot++; bufferPointer = 0; @@ -128,7 +151,7 @@ Display *Display::loop2(bool force) { } } - if (slot >= lcdRows) { + if (slot >= numCharacterRows) { // Last slot finished, reset ready for next screen update. #if SCROLLMODE==2 if (!done) { @@ -151,7 +174,8 @@ Display *Display::loop2(bool force) { } void Display::moveToNextRow() { - rowNext = (rowNext + 1) % MAX_LCD_ROWS; + rowNext = rowNext + 1; + if (rowNext >= MAX_CHARACTER_ROWS) rowNext = 0; #if SCROLLMODE == 1 // Finished if we've looped back to row 0 if (rowNext == 0) done = true; @@ -164,4 +188,4 @@ void Display::moveToNextRow() { void Display::skipBlankRows() { while (!done && rowBuffer[rowNext][0] == 0) moveToNextRow(); -} +} \ No newline at end of file diff --git a/Display.h b/Display.h index 84cdbde..7bbbc78 100644 --- a/Display.h +++ b/Display.h @@ -16,15 +16,18 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ -#ifndef LCDDisplay_h -#define LCDDisplay_h +#ifndef Display_h +#define Display_h #include #include "defines.h" #include "DisplayInterface.h" // Allow maximum message length to be overridden from config.h #if !defined(MAX_MSG_SIZE) -#define MAX_MSG_SIZE 20 +// On a screen that's 128 pixels wide, character 22 overlaps end of screen +// However, by making the line longer than the screen, we don't have to +// clear the screen, we just overwrite what was there. +#define MAX_MSG_SIZE 22 #endif // Set default scroll mode (overridable in config.h) @@ -32,36 +35,17 @@ #define SCROLLMODE 1 #endif -// This class is created in LCDisplay_Implementation.h +// This class is created in Display_Implementation.h class Display : public DisplayInterface { - public: - Display() {}; - static const int MAX_LCD_ROWS = 8; - static const int MAX_LCD_COLS = MAX_MSG_SIZE; - static const long LCD_SCROLL_TIME = 3000; // 3 seconds +public: + Display(DisplayDevice *deviceDriver); + static const int MAX_CHARACTER_ROWS = 8; + static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; + static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds - // Internally handled functions - static void loop(); - Display* loop2(bool force) override; - void setRow(byte line) override; - void clear() override; - - size_t write(uint8_t b) override; - -protected: - uint8_t lcdRows; - uint8_t lcdCols; - - private: - void moveToNextRow(); - void skipBlankRows(); - - // Relay functions to the live driver in the subclass - virtual void clearNative() = 0; - virtual void setRowNative(byte line) = 0; - virtual size_t writeNative(uint8_t b) = 0; - virtual bool isBusy() = 0; +private: + DisplayDevice *_deviceDriver; unsigned long lastScrollTime = 0; int8_t hotRow = 0; @@ -71,11 +55,25 @@ protected: int8_t rowFirst = -1; int8_t rowNext = 0; int8_t charIndex = 0; - char buffer[MAX_LCD_COLS + 1]; + char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; bool done = false; + uint16_t numCharacterRows; + uint16_t numCharacterColumns = MAX_CHARACTER_COLS; + + char *rowBuffer[MAX_CHARACTER_ROWS]; + +public: + void begin() override; + void _clear() override; + void _setRow(uint8_t line) override; + size_t _write(uint8_t b) override; + void _refresh() override; + void _displayLoop() override; + Display *loop2(bool force); + void moveToNextRow(); + void skipBlankRows(); - char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1]; }; #endif diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index 6ef13a4..f2c144e 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,4 +21,7 @@ #include "DisplayInterface.h" -DisplayInterface *DisplayInterface::displayHandler = 0; +// Install null display driver initially - will be replaced if required. +DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface(); + +uint8_t DisplayInterface::_selectedDisplayNo = 255; diff --git a/DisplayInterface.h b/DisplayInterface.h index 82b38d6..f1d598d 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -25,23 +25,75 @@ // Definition of base class for displays. The base class does nothing. class DisplayInterface : public Print { -public: - virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; - virtual void setRow(byte line) { (void)line; }; - virtual void clear() { }; - virtual size_t write(uint8_t c) { (void)c; return 0; }; - // Additional functions to support multiple displays. - // Display number zero is the default one and the original display - // drivers overloaded the above calls only. Newer display drivers - // (e.g. HAL IO_OledDisplay) should override all functions. - virtual void setRow(uint8_t displayNo, byte line) { - if (!displayNo) setRow(line); - } - virtual void clear(uint8_t displayNo) { - if (!displayNo) clear(); - } +protected: + static DisplayInterface *_displayHandler; + static uint8_t _selectedDisplayNo; // Nothing selected. + DisplayInterface *_nextHandler = NULL; + uint8_t _displayNo = 0; - static DisplayInterface *displayHandler; +public: + // Add display object to list of displays + void addDisplay(uint8_t displayNo) { + _nextHandler = _displayHandler; + _displayHandler = this; + _displayNo = displayNo; + } + static DisplayInterface *getDisplayHandler() { + return _displayHandler; + } + uint8_t getDisplayNo() { + return _displayNo; + } + + // The next functions are to provide compatibility with calls to the LCD function + // which does not specify a display number. These always apply to display '0'. + static void refresh() { refresh(0); }; + static void setRow(uint8_t line) { setRow(0, line); }; + static void clear() { clear(0); }; + + // Additional functions to support multiple displays. These perform a + // multicast to all displays that match the selected displayNo. + // Display number zero is the default one. + static void setRow(uint8_t displayNo, uint8_t line) { + _selectedDisplayNo = displayNo; + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) { + if (displayNo == p->_displayNo) p->_setRow(line); + } + } + size_t write (uint8_t c) override { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (_selectedDisplayNo == p->_displayNo) p->_write(c); + return _displayHandler ? 1 : 0; + } + static void clear(uint8_t displayNo) { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (displayNo == p->_displayNo) p->_clear(); + } + static void refresh(uint8_t displayNo) { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (displayNo == p->_displayNo) p->_refresh(); + } + static void loop() { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + p->_displayLoop(); + }; + // The following are overridden within the specific device class + virtual void begin() {}; + virtual size_t _write(uint8_t c) { (void)c; return 0; }; + virtual void _setRow(uint8_t line) {} + virtual void _clear() {} + virtual void _refresh() {} + virtual void _displayLoop() {} }; +class DisplayDevice { +public: + virtual bool begin() { return true; } + virtual void clearNative() = 0; + virtual void setRowNative(uint8_t line) = 0; + virtual size_t writeNative(uint8_t c) = 0; + virtual bool isBusy() = 0; + virtual uint16_t getNumRows() = 0; + virtual uint16_t getNumCols() = 0; +}; #endif diff --git a/Display_Implementation.h b/Display_Implementation.h index f04b7d2..ca19bd7 100644 --- a/Display_Implementation.h +++ b/Display_Implementation.h @@ -27,7 +27,7 @@ #ifndef LCD_Implementation_h #define LCD_Implementation_h -#include "Display.h" +#include "DisplayInterface.h" #include "SSD1306Ascii.h" #include "LiquidCrystal_I2C.h" @@ -35,19 +35,26 @@ // Implement the Display shim class as a singleton. // The DisplayInterface class implements a display handler with no code (null device); // The Display class sub-classes DisplayInterface to provide the common display code; -// Then Display class is subclassed to the specific device type classes: +// Then Display class talks to the specific device type classes: // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. #if defined(OLED_DRIVER) - #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define DISPLAY_START(xxx) { \ + DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \ + t->begin(); \ + xxx; \ + t->refresh(); \ + } #elif defined(LCD_DRIVER) - #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) - + #define DISPLAY_START(xxx) { \ + DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \ + t->begin(); \ + xxx; \ + t->refresh();} #else - // Create null display handler just in case someone calls displayHandler->something without checking if displayHandler is NULL! - #define CONDITIONAL_DISPLAY_START { new DisplayInterface(); } -#endif + #define DISPLAY_START(xxx) {} +#endif #endif // LCD_Implementation_h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 12b571f..26c24b3 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -49,13 +49,15 @@ #include "SSD1306Ascii.h" #include "version.h" -class OLEDDisplay : public IODevice, DisplayInterface { +typedef SSD1306AsciiWire OLED; + +template +class OLEDDisplay : public IODevice, public DisplayInterface { private: - uint8_t _displayNo = 0; // Here we define the device-specific variables. uint8_t _height; // in pixels uint8_t _width; // in pixels - SSD1306AsciiWire *oled; + T *_displayDriver; uint8_t _rowNo = 0; // Row number being written by caller uint8_t _colNo = 0; // Position in line being written by caller uint8_t _numRows; @@ -66,26 +68,25 @@ private: uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; DisplayInterface *_nextDisplay = NULL; - uint8_t _selectedDisplayNo = 0; public: // Static function to handle "OLEDDisplay::create(...)" calls. - static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + static void create(I2CAddress i2cAddress, int width, int height) { /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); } - static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - _displayNo = displayNo; + _displayDriver = new T(i2cAddress, width, height); _I2CAddress = i2cAddress; _width = width; _height = height; - _numCols = (_width+5) / 6; // character block 6 x 8, round up - _numRows = _height / 8; // Round down + _numCols = _displayDriver->getNumCols(); + _numRows = _displayDriver->getNumRows(); _charPosToScreen = _numCols; @@ -96,51 +97,28 @@ protected: // Fill buffer with spaces memset(_buffer, ' ', _numCols*_numRows); + _displayDriver->clearNative(); + + // Add device to list of HAL devices (not necessary but allows + // status to be displayed using and device to be + // reinitialised using ). + IODevice::addDevice(this); + + // Also add this display to list of display handlers + DisplayInterface::addDisplay(displayNo); + // Is this the main display? - if (_displayNo == 0) { + if (displayNo == 0) { // Set first two lines on screen - setRow(0); - print(F("DCC++ EX v")); + this->setRow(displayNo, 0); + print(F("DCC-EX v")); print(F(VERSION)); - setRow(1); + setRow(displayNo, 1); print(F("Lic GPLv3")); } - - // Create OLED driver - oled = new SSD1306AsciiWire(); - - // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to displayHandler methods. - // Make a note of the existing display reference, to that we can - // pass on anything we're not interested in. - _nextDisplay = DisplayInterface::displayHandler; - DisplayInterface::displayHandler = this; - - addDevice(this); } - // Device-specific initialisation - void _begin() override { - // Initialise device - if (oled->begin(_I2CAddress, _width, _height)) { - - DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); - - - // Force all rows to be redrawn - for (uint8_t row=0; row<_numRows; row++) - _rowGeneration[row]++; - - // Start with top line (looks better) - _rowNoToScreen = _numRows; - _charPosToScreen = _numCols; - } - } - - void _loop(unsigned long) override { - //screenUpdate(); - } - + void screenUpdate() { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the @@ -149,8 +127,8 @@ protected: // First check if the OLED driver is still busy from a previous // call. If so, don't to anything until the next entry. - if (!oled->isBusy()) { - // Check if we've just done the end of a row or just started + if (!_displayDriver->isBusy()) { + // Check if we've just done the end of a row if (_charPosToScreen >= _numCols) { // Move to next line if (++_rowNoToScreen >= _numRows) @@ -159,101 +137,103 @@ protected: if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { // Row content has changed, so start outputting it _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; - oled->setRowNative(_rowNoToScreen); + _displayDriver->setRowNative(_rowNoToScreen); _charPosToScreen = 0; // Prepare to output first character on next entry } else { // Row not changed, don't bother writing it. } } else { // output character at current position - oled->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); } } return; } + + ///////////////////////////////////////////////// + // IODevice Class Member Overrides + ///////////////////////////////////////////////// + + // Device-specific initialisation + void _begin() override { + // Initialise device + if (_displayDriver->begin()) { + + _display(); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; + + // Start with top line (looks better). + // The numbers will wrap round on the first loop2 entry. + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; + } + } + + void _loop(unsigned long) override { + screenUpdate(); + } + + // Display information about the device. + void _display() { + DIAG(F("OLEDDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); + } ///////////////////////////////////////////////// // DisplayInterface functions // ///////////////////////////////////////////////// - DisplayInterface* loop2(bool force) override { + +public: + void _displayLoop() override { screenUpdate(); - if (_nextDisplay) - return _nextDisplay->loop2(force); // continue to next display - return NULL; } // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating // The displayNo referenced here is remembered and any following // calls to write() will be directed to that display. - void setRow(uint8_t displayNo, byte line) override { - _selectedDisplayNo = displayNo; - if (displayNo == _displayNo) { - if (line == 255) { - // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - - // scroll the contents of the buffer and put the new line - // at the bottom of the screen - for (int row=1; row<_numRows; row++) { - strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); - _rowGeneration[row-1]++; - } - line = _numRows-1; - } else if (line >= _numRows) - line = _numRows - 1; // Overwrite bottom line. - - _rowNo = line; - // Fill line with blanks - for (_colNo = 0; _colNo < _numCols; _colNo++) - _buffer[_rowNo*_numCols+_colNo] = ' '; - _colNo = 0; - // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry, by which time - // the line should have been written to the buffer. - _rowGeneration[_rowNo]++; - - } else if (_nextDisplay) - _nextDisplay->setRow(displayNo, line); // Pass to next display + void _setRow(byte line) override { + if (line == 255) { + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. + _rowGeneration[_rowNo]++; } // Write one character to the screen referenced in the last setRow() call. - size_t write(uint8_t c) override { - if (_selectedDisplayNo == _displayNo) { - // Write character to buffer (if there's space) - if (_colNo < _numCols) { - _buffer[_rowNo*_numCols+_colNo++] = c; - } - return 1; - } else if (_nextDisplay) - return _nextDisplay->write(c); - else - return 0; + virtual size_t _write(uint8_t c) override { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + return 1; } - // Write blanks to all of the screen (blocks until complete) - void clear (uint8_t displayNo) override { - if (displayNo == _displayNo) { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(displayNo, _rowNo); - } - _rowNo = 0; - } else if (_nextDisplay) - _nextDisplay->clear(displayNo); // Pass to next display - } - - // Overloads of above, for compatibility - void setRow(uint8_t line) override { - setRow(0, line); - } - void clear() override { - clear(0); - } - - // Display information about the device. - void _display() { - DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); + // Write blanks to all of the screen buffer + void _clear() { + // Clear buffer + memset(_buffer, ' ', _numCols*_numRows); + _colNo = 0; + _rowNo = 0; } }; diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index c9d8113..3bf98ef 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -44,24 +44,25 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, uint8_t lcd_rows) { _Addr = lcd_Addr; - lcdRows = lcd_rows; - lcdCols = lcd_cols; - + lcdRows = lcd_rows; // Number of character rows (typically 2 or 4). + lcdCols = lcd_cols; // Number of character columns (typically 16 or 20) _backlightval = 0; + } + +bool LiquidCrystal_I2C::begin() { I2CManager.begin(); I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. - if (I2CManager.exists(lcd_Addr)) { - DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); + if (I2CManager.exists(_Addr)) { + DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString()); _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; - begin(); backlight(); - displayHandler = this; + } else { + DIAG(F("LCD not found on I2C:%s"), _Addr.toString()); + return false; } -} -void LiquidCrystal_I2C::begin() { if (lcdRows > 1) { _displayfunction |= LCD_2LINE; } @@ -99,26 +100,23 @@ void LiquidCrystal_I2C::begin() { _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; display(); - // clear it off - clear(); - // Initialize to default text direction (for roman languages) _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; // set the entry mode command(LCD_ENTRYMODESET | _displaymode); - setRowNative(0); + return true; } /********** high level commands, for the user! */ void LiquidCrystal_I2C::clearNative() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero - delayMicroseconds(2000); // this command takes 1.52ms + delayMicroseconds(1600); // this command takes 1.52ms } void LiquidCrystal_I2C::setRowNative(byte row) { - int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; + uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54}; if (row >= lcdRows) { row = lcdRows - 1; // we count rows starting w/0 } @@ -146,6 +144,10 @@ size_t LiquidCrystal_I2C::writeNative(uint8_t value) { return 1; } +bool LiquidCrystal_I2C::isBusy() { + return rb.isBusy(); +} + /*********** mid level commands, for sending data/cmds */ inline void LiquidCrystal_I2C::command(uint8_t value) { @@ -196,7 +198,7 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { outputBuffer[len++] = highnib; outputBuffer[len++] = lownib|En; outputBuffer[len++] = lownib; - I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously } // write 4 data bits to the HD44780 LCD controller. @@ -208,12 +210,12 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { uint8_t len = 0; outputBuffer[len++] = _data|En; outputBuffer[len++] = _data; - I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously } // write a byte to the PCF8574 I2C interface. We don't need to set // the enable pin for this. void LiquidCrystal_I2C::expanderWrite(uint8_t value) { outputBuffer[0] = value | _backlightval; - I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously } \ No newline at end of file diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 5d5a741..daecd61 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -62,33 +62,38 @@ #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit -class LiquidCrystal_I2C : public Display { +class LiquidCrystal_I2C : public DisplayDevice { public: LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); - void begin(); + bool begin() override; void clearNative() override; void setRowNative(byte line) override; size_t writeNative(uint8_t c) override; + // I/O is synchronous, so if this is called we're not busy! + bool isBusy() override; void display(); void noBacklight(); void backlight(); void command(uint8_t); + uint16_t getNumCols() { return lcdCols; } + uint16_t getNumRows() { return lcdRows; } + private: void send(uint8_t, uint8_t); void write4bits(uint8_t); void expanderWrite(uint8_t); - uint8_t _Addr; + uint8_t lcdCols=0, lcdRows=0; + I2CAddress _Addr; uint8_t _displayfunction; uint8_t _displaycontrol; uint8_t _displaymode; uint8_t _backlightval; uint8_t outputBuffer[4]; - // I/O is synchronous, so if this is called we're not busy! - bool isBusy() override { return false; } + I2CRB rb; }; #endif diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 5e24c7a..f5dd281 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,39 +144,38 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() { - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - +SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { + m_i2cAddr = 0; + m_displayWidth = width; + m_displayHeight = height; } // CS auto-detect and configure constructor -SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - - // Probe for I2C device on 0x3c and 0x3d. - for (uint8_t address = 0x3c; address <= 0x3d; address++) { - if (I2CManager.exists(address)) { - begin(address, width, height); - // Set singleton Address so CS is able to call it. - displayHandler = this; - return; - } - } - DIAG(F("OLED display not found")); -} - -bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { - if (m_initialised) return true; - +SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) { m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; - - // Set size in characters in base class - lcdRows = height / 8; - lcdCols = width / 6; +} + +bool SSD1306AsciiWire::begin() { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speede + + if (m_i2cAddr == 0) { + // Probe for I2C device on 0x3c and 0x3d. + for (uint8_t address = 0x3c; address <= 0x3d; address++) { + if (I2CManager.exists(address)) { + m_i2cAddr = address; + break; + } + } + if (m_i2cAddr == 0) + DIAG(F("OLED display not found")); + } + + // Set size in characters + m_charsPerColumn = m_displayHeight / fontHeight; + m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up m_col = 0; m_row = 0; m_colOffset = 0; @@ -186,7 +185,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { m_colOffset = 2; I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init)); } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { - // SSD1306 128x64 or 128x32 + // SSD1306 or SSD1309 128x64 or 128x32 I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); if (m_displayHeight == 32) I2CManager.write(m_i2cAddr, 5, 0, // Set command mode @@ -198,19 +197,18 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { } // Device found DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString()); - clear(); return true; } /* Clear screen by writing blank pixels. */ void SSD1306AsciiWire::clearNative() { - const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire + const int maxBytes = sizeof(blankPixels) - 1; // max number of pixel columns (bytes) per transmission for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = m_displayWidth-c+1; + for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) { + uint8_t len = m_displayWidth-c; // Number of pixel columns remaining if (len > maxBytes) len = maxBytes; - I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns + I2CManager.write_P(m_i2cAddr, blankPixels, len+1); // Write command + 'len' blank columns } } } @@ -263,127 +261,262 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) { outputBuffer[0] = 0x40; // set SSD1306 controller to data mode uint8_t bufferPos = 1; // Copy character pixel columns - for (uint8_t i = 0; i < fontWidth; i++) - outputBuffer[bufferPos++] = GETFLASH(base++); - // Add blank pixels between letters - for (uint8_t i = 0; i < letterSpacing; i++) - outputBuffer[bufferPos++] = 0; + for (uint8_t i = 0; i < fontWidth; i++) { + if (m_col++ < m_displayWidth) + outputBuffer[bufferPos++] = GETFLASH(base++); + } // Write the data to I2C display I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock); - m_col += fontWidth + letterSpacing; return 1; } //------------------------------------------------------------------------------ -// Font characters, 5x7 pixels, 0x61 characters starting at 0x20. +// Font characters, 6x8 pixels, starting at 0x20. // Lower case characters optionally omitted. -const uint8_t FLASH SSD1306AsciiWire::System5x7[] = { +const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { // Fixed width; char width table not used !!!! - // or with lowercase character omitted. // font data - 0x00, 0x00, 0x00, 0x00, 0x00, // (space) - 0x00, 0x00, 0x5F, 0x00, 0x00, // ! - 0x00, 0x07, 0x00, 0x07, 0x00, // " - 0x14, 0x7F, 0x14, 0x7F, 0x14, // # - 0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ - 0x23, 0x13, 0x08, 0x64, 0x62, // % - 0x36, 0x49, 0x55, 0x22, 0x50, // & - 0x00, 0x05, 0x03, 0x00, 0x00, // ' - 0x00, 0x1C, 0x22, 0x41, 0x00, // ( - 0x00, 0x41, 0x22, 0x1C, 0x00, // ) - 0x08, 0x2A, 0x1C, 0x2A, 0x08, // * - 0x08, 0x08, 0x3E, 0x08, 0x08, // + - 0x00, 0x50, 0x30, 0x00, 0x00, // , - 0x08, 0x08, 0x08, 0x08, 0x08, // - - 0x00, 0x60, 0x60, 0x00, 0x00, // . - 0x20, 0x10, 0x08, 0x04, 0x02, // / - 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 - 0x00, 0x42, 0x7F, 0x40, 0x00, // 1 - 0x42, 0x61, 0x51, 0x49, 0x46, // 2 - 0x21, 0x41, 0x45, 0x4B, 0x31, // 3 - 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 - 0x27, 0x45, 0x45, 0x45, 0x39, // 5 - 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 - 0x01, 0x71, 0x09, 0x05, 0x03, // 7 - 0x36, 0x49, 0x49, 0x49, 0x36, // 8 - 0x06, 0x49, 0x49, 0x29, 0x1E, // 9 - 0x00, 0x36, 0x36, 0x00, 0x00, // : - 0x00, 0x56, 0x36, 0x00, 0x00, // ; - 0x00, 0x08, 0x14, 0x22, 0x41, // < - 0x14, 0x14, 0x14, 0x14, 0x14, // = - 0x41, 0x22, 0x14, 0x08, 0x00, // > - 0x02, 0x01, 0x51, 0x09, 0x06, // ? - 0x32, 0x49, 0x79, 0x41, 0x3E, // @ - 0x7E, 0x11, 0x11, 0x11, 0x7E, // A - 0x7F, 0x49, 0x49, 0x49, 0x36, // B - 0x3E, 0x41, 0x41, 0x41, 0x22, // C - 0x7F, 0x41, 0x41, 0x22, 0x1C, // D - 0x7F, 0x49, 0x49, 0x49, 0x41, // E - 0x7F, 0x09, 0x09, 0x01, 0x01, // F - 0x3E, 0x41, 0x41, 0x51, 0x32, // G - 0x7F, 0x08, 0x08, 0x08, 0x7F, // H - 0x00, 0x41, 0x7F, 0x41, 0x00, // I - 0x20, 0x40, 0x41, 0x3F, 0x01, // J - 0x7F, 0x08, 0x14, 0x22, 0x41, // K - 0x7F, 0x40, 0x40, 0x40, 0x40, // L - 0x7F, 0x02, 0x04, 0x02, 0x7F, // M - 0x7F, 0x04, 0x08, 0x10, 0x7F, // N - 0x3E, 0x41, 0x41, 0x41, 0x3E, // O - 0x7F, 0x09, 0x09, 0x09, 0x06, // P - 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q - 0x7F, 0x09, 0x19, 0x29, 0x46, // R - 0x46, 0x49, 0x49, 0x49, 0x31, // S - 0x01, 0x01, 0x7F, 0x01, 0x01, // T - 0x3F, 0x40, 0x40, 0x40, 0x3F, // U - 0x1F, 0x20, 0x40, 0x20, 0x1F, // V - 0x7F, 0x20, 0x18, 0x20, 0x7F, // W - 0x63, 0x14, 0x08, 0x14, 0x63, // X - 0x03, 0x04, 0x78, 0x04, 0x03, // Y - 0x61, 0x51, 0x49, 0x45, 0x43, // Z - 0x00, 0x00, 0x7F, 0x41, 0x41, // [ - 0x02, 0x04, 0x08, 0x10, 0x20, // "\" - 0x41, 0x41, 0x7F, 0x00, 0x00, // ] - 0x04, 0x02, 0x01, 0x02, 0x04, // ^ - 0x40, 0x40, 0x40, 0x40, 0x40, // _ - 0x00, 0x01, 0x02, 0x04, 0x00, // ` + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) (20) + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, // ! (21) + 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, // " + 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, // $ + 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, // % + 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, // & + 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, // ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // + + 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, // , + 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // - + 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // / (47) + 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 0 (48) + 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 1 + 0x42, 0x61, 0x51, 0x49, 0x46, 0x00, // 2 + 0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, // 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, // 4 + 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, // 5 + 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, // 6 + 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, // 7 + 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, // 8 + 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, // 9 (57) + 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ; + 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // < + 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // = + 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, // > + 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ? + 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, // @ (64) + 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, // A (65) + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, // C + 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, // E + 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, // F + 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K + 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L + 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, // M + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N + 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, // O + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, // P + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q + 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, // R + 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, // V + 0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00, // W + 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, // X + 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, // Z (90) + 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // "\" + 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, // _ + 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // ' (96) #ifndef NOLOWERCASE - 0x20, 0x54, 0x54, 0x54, 0x78, // a - 0x7F, 0x48, 0x44, 0x44, 0x38, // b - 0x38, 0x44, 0x44, 0x44, 0x20, // c - 0x38, 0x44, 0x44, 0x48, 0x7F, // d - 0x38, 0x54, 0x54, 0x54, 0x18, // e - 0x08, 0x7E, 0x09, 0x01, 0x02, // f - 0x08, 0x14, 0x54, 0x54, 0x3C, // g - 0x7F, 0x08, 0x04, 0x04, 0x78, // h - 0x00, 0x44, 0x7D, 0x40, 0x00, // i - 0x20, 0x40, 0x44, 0x3D, 0x00, // j - 0x00, 0x7F, 0x10, 0x28, 0x44, // k - 0x00, 0x41, 0x7F, 0x40, 0x00, // l - 0x7C, 0x04, 0x18, 0x04, 0x78, // m - 0x7C, 0x08, 0x04, 0x04, 0x78, // n - 0x38, 0x44, 0x44, 0x44, 0x38, // o - 0x7C, 0x14, 0x14, 0x14, 0x08, // p - 0x08, 0x14, 0x14, 0x18, 0x7C, // q - 0x7C, 0x08, 0x04, 0x04, 0x08, // r - 0x48, 0x54, 0x54, 0x54, 0x20, // s - 0x04, 0x3F, 0x44, 0x40, 0x20, // t - 0x3C, 0x40, 0x40, 0x20, 0x7C, // u - 0x1C, 0x20, 0x40, 0x20, 0x1C, // v - 0x3C, 0x40, 0x30, 0x40, 0x3C, // w - 0x44, 0x28, 0x10, 0x28, 0x44, // x - 0x0C, 0x50, 0x50, 0x50, 0x3C, // y - 0x44, 0x64, 0x54, 0x4C, 0x44, // z + 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a (97) + 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b + 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, // c + 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d + 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e + 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f + 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, // j + 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l + 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, // n + 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o + 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, // p + 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, // q + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r + 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s + 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w + 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z (122) #endif - 0x00, 0x08, 0x36, 0x41, 0x00, // { - 0x00, 0x00, 0x7F, 0x00, 0x00, // | - 0x00, 0x41, 0x36, 0x08, 0x00, // } - 0x08, 0x08, 0x2A, 0x1C, 0x08, // -> - 0x08, 0x1C, 0x2A, 0x08, 0x08, // <- - 0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, // { (123) + 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, // | + 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, // } + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, // -> + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, // <- (127) +#ifndef NO_EXTENDED_CHARACTERS +// Extended characters - based on "DOS Western Europe" characters +// International characters not yet implemented. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x80 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xa0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + // Extended characters 176-180 + 0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0 + 0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1 + 0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2 + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3 + 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4 + 0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 185-190 + 0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9 + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba + 0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb + 0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe + + // Extended characters 191-199 + 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf + 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, // Bottom left corner 0xc0 + 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, // Horizontal line with upward spur 0xc1 + 0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, // Horizontal line with downward spur 0xc2 + 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3 + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4 + 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 200-206 + 0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8 + 0x00, 0x00, 0xfc, 0x04, 0xf4, 0x14, // Double top left corner 0xc9 + 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, // Double horizontal with double upward spur 0xca + 0x14, 0x14, 0xf4, 0x04, 0xf4, 0x14, // Double horizontal with double downward spur 0xcb + 0x00, 0x00, 0xff, 0x00, 0xf7, 0x14, // Double vertical line with double right spur 0xcc + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd + 0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 217-223 + 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9 + 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, // Top left corner 0xda + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Solid block 0xdb + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // Bottom half block 0xdc + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, // Left half block 0xdd + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xf0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + // Extended character 248 + 0x00, 0x06, 0x09, 0x09, 0x06, 0x00, // degree symbol 0xf8 +#endif + 0x00 }; + +const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6; diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index d40eac6..57427a1 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -27,21 +27,24 @@ #include "I2CManager.h" #include "DIAG.h" +#include "DisplayInterface.h" // Uncomment to remove lower-case letters to save 108 bytes of flash //#define NOLOWERCASE + + //------------------------------------------------------------------------------ // Constructor -class SSD1306AsciiWire : public Display { +class SSD1306AsciiWire : public DisplayDevice { public: // Constructors SSD1306AsciiWire(int width, int height); // Auto-detects I2C address - SSD1306AsciiWire(); // Requires call to 'begin()' + SSD1306AsciiWire(I2CAddress address, int width, int height); // Initialize the display controller. - bool begin(I2CAddress address, int width, int height); + bool begin(); // Clear the display and set the cursor to (0, 0). void clearNative() override; @@ -53,6 +56,8 @@ class SSD1306AsciiWire : public Display { size_t writeNative(uint8_t c) override; bool isBusy() override { return requestBlock.isBusy(); } + uint16_t getNumCols() { return m_charsPerRow; } + uint16_t getNumRows() { return m_charsPerColumn; } private: // Cursor column. @@ -63,28 +68,31 @@ class SSD1306AsciiWire : public Display { uint8_t m_displayWidth; // Display height. uint8_t m_displayHeight; + // Display width in characters + uint8_t m_charsPerRow; + // Display height in characters + uint8_t m_charsPerColumn; // Column offset RAM to SEG. uint8_t m_colOffset = 0; // Current font. - const uint8_t* const m_font = System5x7; + const uint8_t* const m_font = System6x8; // Flag to prevent calling begin() twice uint8_t m_initialised = false; - // Only fixed size 5x7 fonts in a 6x8 cell are supported. - static const uint8_t fontWidth = 5; - static const uint8_t fontHeight = 7; - static const uint8_t letterSpacing = 1; + // Only fixed size 6x8 fonts in a 6x8 cell are supported. + static const uint8_t fontWidth = 6; + static const uint8_t fontHeight = 8; static const uint8_t m_fontFirstChar = 0x20; - static const uint8_t m_fontCharCount = 0x61; + static const uint8_t m_fontCharCount; - I2CAddress m_i2cAddr; + I2CAddress m_i2cAddr = 0; I2CRB requestBlock; - uint8_t outputBuffer[fontWidth+letterSpacing+1]; + uint8_t outputBuffer[fontWidth+1]; static const uint8_t blankPixels[]; - static const uint8_t System5x7[]; + static const uint8_t System6x8[]; static const uint8_t FLASH Adafruit128xXXinit[]; static const uint8_t FLASH SH1106_132x64init[]; }; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index a659fc4..f7d9c50 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -18,7 +18,7 @@ */ #include "StringFormatter.h" #include -#include "Display.h" +#include "DisplayInterface.h" bool Diag::ACK=false; bool Diag::CMD=false; @@ -45,19 +45,17 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!Display::displayHandler) return; - Display::displayHandler->setRow(row); + DisplayInterface::setRow(row); va_start(args, input); - send2(Display::displayHandler,input,args); + send2(DisplayInterface::getDisplayHandler(),input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!Display::displayHandler) return; - Display::displayHandler->setRow(display, row); + DisplayInterface::setRow(display, row); va_start(args, input); - send2(Display::displayHandler,input,args); + send2(DisplayInterface::getDisplayHandler(),input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/defines.h b/defines.h index e9ae631..9748824 100644 --- a/defines.h +++ b/defines.h @@ -46,9 +46,13 @@ #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_NANO) #define ARDUINO_TYPE "NANO" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_MEGA) #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_AVR_MEGA2560) From 6b863ea4834a8b6f40e07d3cd0ba8585875545a4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:41:33 +0000 Subject: [PATCH 76/95] Update I2CManager_NonBlocking.h Cosmetic changes --- I2CManager_NonBlocking.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 991b45c..322ca88 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -109,7 +109,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * starting the operation. ***************************************************************************/ void I2CManagerClass::startTransaction() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; completionStatus = I2C_STATUS_OK; @@ -174,7 +174,7 @@ void I2CManagerClass::startTransaction() { void I2CManagerClass::queueRequest(I2CRB *req) { req->status = I2C_STATUS_PENDING; req->nextRequest = NULL; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { if (!queueTail) queueHead = queueTail = req; // Only item on queue else @@ -236,7 +236,7 @@ void I2CManagerClass::setTimeout(unsigned long value) { * may be caused by an I2C wire short for example. ***************************************************************************/ void I2CManagerClass::checkForTimeout() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { I2CRB *t = queueHead; if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) { // Check for timeout From d6c8595f8a93f6106bc5ffa4be48ea9bb8b059b0 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:50:42 +0000 Subject: [PATCH 77/95] Update Display.h Get rid of compiler warning. --- Display.h | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Display.h b/Display.h index 7bbbc78..d747680 100644 --- a/Display.h +++ b/Display.h @@ -23,11 +23,8 @@ #include "DisplayInterface.h" // Allow maximum message length to be overridden from config.h -#if !defined(MAX_MSG_SIZE) -// On a screen that's 128 pixels wide, character 22 overlaps end of screen -// However, by making the line longer than the screen, we don't have to -// clear the screen, we just overwrite what was there. -#define MAX_MSG_SIZE 22 +#if !defined(MAX_MSG_SIZE) +#define MAX_MSG_SIZE 20 #endif // Set default scroll mode (overridable in config.h) @@ -48,13 +45,13 @@ private: DisplayDevice *_deviceDriver; unsigned long lastScrollTime = 0; - int8_t hotRow = 0; - int8_t hotCol = 0; + uint8_t hotRow = 0; + uint8_t hotCol = 0; int8_t topRow = 0; - int8_t slot = 0; + uint8_t slot = 0; int8_t rowFirst = -1; int8_t rowNext = 0; - int8_t charIndex = 0; + uint8_t charIndex = 0; char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; bool done = false; From 10cd5800612c737e2420801b77eb324a4eb240a2 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 22:27:23 +0000 Subject: [PATCH 78/95] Update I2CManager_NonBlocking.h Missing initialisation of read buffer pointer! --- I2CManager_NonBlocking.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 322ca88..fb5bae5 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -316,6 +316,7 @@ void I2CManagerClass::handleInterrupt() { deviceAddress = rbAddress; sendBuffer = currentRequest->writeBuffer; bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; bytesToReceive = currentRequest->readLen; operation = currentRequest->operation & OPERATION_MASK; state = I2C_STATE_ACTIVE; @@ -326,16 +327,21 @@ void I2CManagerClass::handleInterrupt() { // Application request completed, now send epilogue to mux overallStatus = completionStatus; currentRequest->nBytes = rxCount; // Save number of bytes read into rb - muxPhase = MuxPhase_EPILOG; - deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); - muxData[0] = 0x00; - sendBuffer = &muxData[0]; - bytesToSend = 1; - bytesToReceive = 0; - operation = OPERATION_SEND; - state = I2C_STATE_ACTIVE; - I2C_sendStart(); - return; + if (_muxCount == 1) { + // Only one MUX, don't need to deselect subbus + muxPhase = MuxPhase_OFF; + } else { + muxPhase = MuxPhase_EPILOG; + deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); + muxData[0] = 0x00; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } } else if (muxPhase == MuxPhase_EPILOG) { // Epilog finished, ignore completionStatus muxPhase = MuxPhase_OFF; From 9797a0fd2d08d193c8e8d51271723d8fe7dc0a39 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Feb 2023 16:04:21 +0000 Subject: [PATCH 79/95] Add high accuracy timing for STM32, on pins D3 and D6. --- DCCTimerSTM32.cpp | 52 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index df66c98..48c254f 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -1,4 +1,5 @@ /* + * © 2023 Neil McKechnie * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021 Harald Barth @@ -50,13 +51,13 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #endif INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #11 until disabused of this notion -// Timer #11 is used for "servo" library, but as DCC-EX is not using -// this libary, we should be free and clear. -HardwareTimer timer(TIM11); +// Let's use STM32's timer #2 which supports hardware pulse generation on pins D3 and D6 +// (accurate timing, independent of the latency of interrupt handling). +// Pin D3 is driven by TIM2 channel 2 and D6 is TIM2 channel 3. +HardwareTimer timer(TIM2); // Timer IRQ handler -void Timer11_Handler() { +void Timer_Handler() { interruptHandler(); } @@ -67,9 +68,8 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); timer.setPrescaleFactor(1); -// timer.setOverflow(CLOCK_CYCLES * 2); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); - timer.attachInterrupt(Timer11_Handler); + timer.attachInterrupt(Timer_Handler); timer.refresh(); timer.resume(); @@ -77,20 +77,42 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { } bool DCCTimer::isPWMPin(byte pin) { - //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, - // there's no support yet for High Accuracy, so for now return false - // return digitalPinHasPWM(pin); - return false; + // Timer 2 Channel 2 controls pin D3, and Timer2 Channel 3 controls D6. + // Enable the appropriate timer channel. + switch (pin) { + case 3: + timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, D3); + return true; + case 6: + timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, D6); + return true; + default: + return false; + } } void DCCTimer::setPWM(byte pin, bool high) { - // TODO: High Accuracy mode is not supported as yet, and may never need to be - (void) pin; - (void) high; + // Set the timer so that, at the next counter overflow, the requested + // pin state is activated automatically before the interrupt code runs. + switch (pin) { + case 3: + if (high) + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_0; + else + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_1; + break; + case 6: + if (high) + TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_0; + else + TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_1; + break; + } } void DCCTimer::clearPWM() { - return; + timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, NC); } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { From 173676287c2ddaccf93f433a658443f42a763707 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 18 Feb 2023 09:32:38 +0000 Subject: [PATCH 80/95] Allow extended I2C addresses to be specified in non-extended configuration If an extended I2C address is specified (including mux and/or subbus) then these parameters are ignored, but a warning output to the diagnostic console. --- I2CManager.cpp | 8 +++++++- I2CManager.h | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 05feb28..14e84b7 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -350,4 +350,10 @@ void I2CAddress::toHex(const uint8_t value, char *buffer) { *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; bits = value & 0xf; *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; -} \ No newline at end of file +} + +#if !defined(I2C_EXTENDED_ADDRESS) + +/* static */ bool I2CAddress::_addressWarningDone = false; + +#endif \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h index 542c92f..016874a 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -24,6 +24,7 @@ #include #include "FSH.h" #include "defines.h" +#include "DIAG.h" /* * Manager for I2C communications. For portability, it allows use @@ -144,9 +145,6 @@ //#define I2C_EXTENDED_ADDRESS -// Type to hold I2C address -#if defined(I2C_EXTENDED_ADDRESS) - ///////////////////////////////////////////////////////////////////////////////////// // Extended I2C Address type to facilitate extended I2C addresses including // I2C multiplexer support. @@ -189,6 +187,9 @@ enum I2CSubBus : uint8_t { SubBus_All = 255, // Enable all sub-buses }; +// Type to hold I2C address +#if defined(I2C_EXTENDED_ADDRESS) + // First MUX address (they range between 0x70-0x77). #define I2C_MUX_BASE_ADDRESS 0x70 @@ -315,6 +316,14 @@ public: I2CAddress(const uint8_t deviceAddress) { _deviceAddress = deviceAddress; } + I2CAddress(I2CMux, I2CSubBus, const uint8_t deviceAddress) { + addressWarning(); + _deviceAddress = deviceAddress; + } + I2CAddress(I2CSubBus, const uint8_t deviceAddress) { + addressWarning(); + _deviceAddress = deviceAddress; + } // Basic constructor I2CAddress() : I2CAddress(0) {} @@ -344,6 +353,13 @@ public: private: // Helper function for converting byte to four-character hex string (e.g. 0x23). void toHex(const uint8_t value, char *buffer); + void addressWarning() { + if (!_addressWarningDone) { + DIAG(F("WARNIING: Extended I2C address used but not supported in this configuration")); + _addressWarningDone = true; + } + } + static bool _addressWarningDone; }; #endif // I2C_EXTENDED_ADDRESS From 33229b48470ee982a1cf880c1907348b05662af3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 19 Feb 2023 19:14:15 +0000 Subject: [PATCH 81/95] Update SSD1306Ascii.cpp Bugfix: Move calculation of m_charsPerColumn and m_charsPerRow into constructors, to avoid incorrect random values being reported. --- SSD1306Ascii.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index f5dd281..911074b 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -143,18 +143,18 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { // SSD1306AsciiWire Method Definitions //------------------------------------------------------------------------------ -// Constructor -SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { - m_i2cAddr = 0; - m_displayWidth = width; - m_displayHeight = height; -} +// Auto-detect address +SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) + : SSD1306AsciiWire(0, width, height) { } -// CS auto-detect and configure constructor +// Constructor with explicit address SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) { m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; + // Set size in characters + m_charsPerColumn = m_displayHeight / fontHeight; + m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up } bool SSD1306AsciiWire::begin() { @@ -173,9 +173,6 @@ bool SSD1306AsciiWire::begin() { DIAG(F("OLED display not found")); } - // Set size in characters - m_charsPerColumn = m_displayHeight / fontHeight; - m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up m_col = 0; m_row = 0; m_colOffset = 0; From a36dccfad0ecd564c7341db96e821704a9e1097e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 10:55:37 +0000 Subject: [PATCH 82/95] Add UserAddin class to facilitate user-written cyclic functions. UserAddin allows a function to be 'plugged in' to the IODevice (HAL) framework and executed cyclically, using just one line of code in the myHal.cpp. This will facilitate functions for displaying CS state on OLEDs and LCDs, among other things. --- IODevice.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/IODevice.h b/IODevice.h index 3ff1dbe..51b5aa0 100644 --- a/IODevice.h +++ b/IODevice.h @@ -418,6 +418,55 @@ private: ///////////////////////////////////////////////////////////////////////////////////////////////////// + +// IODevice framework for invoking user-written functions. +// To use, define a function that you want to be regularly +// invoked, and then create an instance of UserAddin. +// For example, you can show the status, on screen 3, of the first eight +// locos in the speed table: +// +// void updateLocoScreen() { +// for (int i=0; i<8; i++) { +// if (DCC::speedTable[i].loco > 0) { +// int speed = DCC::speedTable[i].speedCode; +// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco, +// speed & 0x7f, speed & 0x80 ? 'R' : 'F'); +// } +// } +// } +// +// void halSetup() { +// ... +// UserAddin(updateLocoScreen, 1000); // Update every 1000ms +// ... +// } +// +class UserAddin : public IODevice { +private: + void (*_invokeUserFunction)(); + int _delay; // milliseconds +public: + UserAddin(void (*func)(), int delay) { + _invokeUserFunction = func; + _delay = delay; + addDevice(this); + } + // userFunction has no return value, no parameter. delay is in milliseconds. + static void create(void (*userFunction)(), int delay) { + new UserAddin(userFunction, delay); + } +protected: + void _begin() { _display(); } + void _loop(unsigned long currentMicros) override { + _invokeUserFunction(); + // _loop won't be called again until _delay ms have elapsed. + delayUntil(currentMicros + _delay * 1000UL); + } + void _display() override { + DIAG(F("UserAddin run every %dms"), _delay); + } +}; + #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" From 43b1e8db21aed001e586980e209670c2cc7d5471 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:00:27 +0000 Subject: [PATCH 83/95] Update LCD driver to make it more reliable Delay times at startup extended to make start-up configuration more reliable. --- LiquidCrystal_I2C.cpp | 8 ++++---- LiquidCrystal_I2C.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index 3bf98ef..b8b8e97 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -80,15 +80,15 @@ bool LiquidCrystal_I2C::begin() { // we start in 8bit mode, try to set 4 bit mode write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms + delayMicroseconds(5000); // wait min 4.1ms // second try write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms + delayMicroseconds(5000); // wait min 4.1ms // third go! write4bits(0x03); - delayMicroseconds(150); + delayMicroseconds(5000); // finally, set to 4-bit interface write4bits(0x02); @@ -112,7 +112,7 @@ bool LiquidCrystal_I2C::begin() { /********** high level commands, for the user! */ void LiquidCrystal_I2C::clearNative() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero - delayMicroseconds(1600); // this command takes 1.52ms + delayMicroseconds(2000); // this command takes 1.52ms but allow plenty } void LiquidCrystal_I2C::setRowNative(byte row) { diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index daecd61..650ad15 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -90,7 +90,7 @@ private: uint8_t _displayfunction; uint8_t _displaycontrol; uint8_t _displaymode; - uint8_t _backlightval; + uint8_t _backlightval = 0; uint8_t outputBuffer[4]; I2CRB rb; From 034bb6b675312c05ad8feda5cfacce1b0300f0ee Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:03:14 +0000 Subject: [PATCH 84/95] Update DCCTimerSTM32.cpp Enable hardware pulse generation on STM32 for pins 12 and 13 (standard motor shield pins), using timers TIM2 and TIM3. Any other pins will currently be controlled directly by the interrupt routine (in effect, by digitalWrite calls). --- DCCTimerSTM32.cpp | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 48c254f..2504a26 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -51,10 +51,13 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #endif INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #2 which supports hardware pulse generation on pins D3 and D6 -// (accurate timing, independent of the latency of interrupt handling). -// Pin D3 is driven by TIM2 channel 2 and D6 is TIM2 channel 3. +// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13. +// Also, timer #3 will do hardware pulses on pin D12. This gives +// accurate timing, independent of the latency of interrupt handling. +// We only need to interrupt on one of these (TIM2), the other will just generate +// pulses. HardwareTimer timer(TIM2); +HardwareTimer timerAux(TIM3); // Timer IRQ handler void Timer_Handler() { @@ -67,24 +70,30 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); + timerAux.pause(); timer.setPrescaleFactor(1); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); timer.attachInterrupt(Timer_Handler); timer.refresh(); + timerAux.setPrescaleFactor(1); + timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); + timerAux.refresh(); + timer.resume(); + timerAux.resume(); interrupts(); } bool DCCTimer::isPWMPin(byte pin) { - // Timer 2 Channel 2 controls pin D3, and Timer2 Channel 3 controls D6. + // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12. // Enable the appropriate timer channel. switch (pin) { - case 3: - timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, D3); + case 12: + timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12); return true; - case 6: - timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, D6); + case 13: + timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13); return true; default: return false; @@ -94,25 +103,26 @@ bool DCCTimer::isPWMPin(byte pin) { void DCCTimer::setPWM(byte pin, bool high) { // Set the timer so that, at the next counter overflow, the requested // pin state is activated automatically before the interrupt code runs. + // TIM2 is timer, TIM3 is timerAux. switch (pin) { - case 3: + case 12: if (high) - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_0; + TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; else - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_1; + TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; break; - case 6: + case 13: if (high) - TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_0; + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; else - TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_1; + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; break; } } void DCCTimer::clearPWM() { - timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, NC); - timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { From 8e90bb69967a15256cf21a95683cff57a0d9ef83 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:07:19 +0000 Subject: [PATCH 85/95] Enable extended addresses and extended OLED characters on all but Nano, Uno and Mega4809.. Define I2C_EXTENDED_ADDRESS on most platforms, and define NO_EXTENDED_CHARACTERS on Nano, Uno and Mega4809. --- defines.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/defines.h b/defines.h index 9748824..88f7fb4 100644 --- a/defines.h +++ b/defines.h @@ -43,6 +43,9 @@ #undef USB_SERIAL // Teensy has this defined by default... #define USB_SERIAL Serial +// Include extended addresses unless specifically excluded +#define I2C_EXTENDED_ADDRESS + #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY @@ -60,6 +63,8 @@ #elif defined(ARDUINO_ARCH_MEGAAVR) #define ARDUINO_TYPE "MEGAAVR" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_TEENSY31) #define ARDUINO_TYPE "TEENSY3132" #undef USB_SERIAL From c2e8557c4ce575fc4e49a2ef89024f41c72b4cba Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:12:31 +0000 Subject: [PATCH 86/95] defines.h - cosmetic change to indenting. Lay out long #if statement with indents to make it a bit easier to read. --- defines.h | 184 +++++++++++++++++++++++++++--------------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/defines.h b/defines.h index 88f7fb4..5582e8b 100644 --- a/defines.h +++ b/defines.h @@ -47,115 +47,115 @@ #define I2C_EXTENDED_ADDRESS #if defined(ARDUINO_AVR_UNO) -#define ARDUINO_TYPE "UNO" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "UNO" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_NANO) -#define ARDUINO_TYPE "NANO" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "NANO" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_MEGA) -#define ARDUINO_TYPE "MEGA" + #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_AVR_MEGA2560) -#define ARDUINO_TYPE "MEGA" + #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_ARCH_MEGAAVR) -#define ARDUINO_TYPE "MEGAAVR" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "MEGAAVR" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_TEENSY31) -#define ARDUINO_TYPE "TEENSY3132" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif -#elif defined(ARDUINO_TEENSY35) -#define ARDUINO_TYPE "TEENSY35" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -// Teensy support for I2C is awaiting development -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif -#elif defined(ARDUINO_TEENSY36) -#define ARDUINO_TYPE "TEENSY36" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif -#elif defined(ARDUINO_TEENSY40) -#define ARDUINO_TYPE "TEENSY40" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif -#elif defined(ARDUINO_TEENSY41) -#define ARDUINO_TYPE "TEENSY41" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE + #define ARDUINO_TYPE "TEENSY3132" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE #define I2C_USE_WIRE -#endif + #endif +#elif defined(ARDUINO_TEENSY35) + #define ARDUINO_TYPE "TEENSY35" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + // Teensy support for I2C is awaiting development + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif +#elif defined(ARDUINO_TEENSY36) + #define ARDUINO_TYPE "TEENSY36" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif +#elif defined(ARDUINO_TEENSY40) + #define ARDUINO_TYPE "TEENSY40" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif +#elif defined(ARDUINO_TEENSY41) + #define ARDUINO_TYPE "TEENSY41" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif #elif defined(ARDUINO_ARCH_ESP8266) -#define ARDUINO_TYPE "ESP8266" -#warning "ESP8266 platform untested, you are on your own" + #define ARDUINO_TYPE "ESP8266" + #warning "ESP8266 platform untested, you are on your own" #elif defined(ARDUINO_ARCH_ESP32) -#define ARDUINO_TYPE "ESP32" -#ifndef DISABLE_EEPROM -#define DISABLE_EEPROM -#endif + #define ARDUINO_TYPE "ESP32" + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif #elif defined(ARDUINO_ARCH_SAMD) -#define ARDUINO_TYPE "SAMD21" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -// SAMD no EEPROM by default -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif + #define ARDUINO_TYPE "SAMD21" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + // SAMD no EEPROM by default + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif #elif defined(ARDUINO_ARCH_STM32) -#define ARDUINO_TYPE "STM32" -// STM32 no EEPROM by default -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// STM32 support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif + #define ARDUINO_TYPE "STM32" + // STM32 no EEPROM by default + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // STM32 support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) -#define ARDUINO_TYPE "RP2040" + #define ARDUINO_TYPE "RP2040" */ #else -#define CPU_TYPE_ERROR + #define CPU_TYPE_ERROR #endif // replace board type if provided by compiler From 4eb277f19e2c2b821552319ab7b222a4a630a8ac Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:06:03 +0000 Subject: [PATCH 87/95] Refactor Display handler to (hopefully) improve clarity. --- Display.cpp | 107 +++++++++++++++++++++++++++------------------------- Display.h | 14 +++---- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Display.cpp b/Display.cpp index 587a107..2c44778 100644 --- a/Display.cpp +++ b/Display.cpp @@ -36,7 +36,7 @@ * not held up significantly. The exception to this is when * the loop2() function is called with force=true, where * a screen update is executed to completion. This is normally - * only done during start-up. + * only noMoreRowsToDisplay during start-up. * The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2 * in the config.h. * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), @@ -53,9 +53,9 @@ Display::Display(DisplayDevice *deviceDriver) { // Get device dimensions in characters (e.g. 16x2). numCharacterColumns = _deviceDriver->getNumCols(); numCharacterRows = _deviceDriver->getNumRows();; - for (uint8_t row=0; rowclearNative(); for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) rowBuffer[row][0] = '\0'; - topRow = -1; // loop2 will fill from row 0 + topRow = ROW_INITIAL; // loop2 will fill from row 0 } void Display::_setRow(uint8_t line) { hotRow = line; hotCol = 0; - rowBuffer[hotRow][hotCol] = 0; // Clear existing text + rowBuffer[hotRow][0] = 0; // Clear existing text } size_t Display::_write(uint8_t b) { @@ -109,63 +109,57 @@ Display *Display::loop2(bool force) { return NULL; } else { // force full screen update from the beginning. - rowFirst = -1; - rowNext = 0; + rowFirst = ROW_INITIAL; + rowNext = ROW_INITIAL; bufferPointer = 0; - done = false; + noMoreRowsToDisplay = false; slot = 0; } do { if (bufferPointer == 0) { // Find a line of data to write to the screen. - if (rowFirst < 0) rowFirst = rowNext; - skipBlankRows(); - if (!done) { - // Non-blank line found, so copy it. - for (uint8_t i = 0; i < sizeof(buffer); i++) + if (rowFirst == ROW_INITIAL) rowFirst = rowNext; + if (findNextNonBlankRow()) { + // Non-blank line found, so copy it (including terminator) + for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++) buffer[i] = rowBuffer[rowNext][i]; - } else - buffer[0] = '\0'; // Empty line + } else { + // No non-blank lines left, so draw a blank line + buffer[0] = 0; + } _deviceDriver->setRowNative(slot); // Set position for display charIndex = 0; bufferPointer = &buffer[0]; - } else { - // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { _deviceDriver->writeNative(ch); bufferPointer++; - } else + } else { _deviceDriver->writeNative(' '); + } if (++charIndex >= MAX_CHARACTER_COLS) { // Screen slot completed, move to next slot on screen - slot++; bufferPointer = 0; - if (!done) { - moveToNextRow(); - skipBlankRows(); - } - } - - if (slot >= numCharacterRows) { - // Last slot finished, reset ready for next screen update. + slot++; + if (slot >= numCharacterRows) { + // Last slot on screen written, reset ready for next screen update. #if SCROLLMODE==2 - if (!done) { - // On next refresh, restart one row on from previous start. - rowNext = rowFirst; - moveToNextRow(); - skipBlankRows(); - } + if (!noMoreRowsToDisplay) { + // On next refresh, restart one row on from previous start. + rowNext = rowFirst; + findNextNonBlankRow(); + } #endif - done = false; - slot = 0; - rowFirst = -1; - lastScrollTime = currentMillis; - return NULL; + noMoreRowsToDisplay = false; + slot = 0; + rowFirst = ROW_INITIAL; + lastScrollTime = currentMillis; + return NULL; + } } } } while (force); @@ -173,19 +167,30 @@ Display *Display::loop2(bool force) { return NULL; } -void Display::moveToNextRow() { - rowNext = rowNext + 1; - if (rowNext >= MAX_CHARACTER_ROWS) rowNext = 0; +bool Display::findNextNonBlankRow() { + while (!noMoreRowsToDisplay) { + if (rowNext == ROW_INITIAL) + rowNext = 0; + else + rowNext = rowNext + 1; + if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL; #if SCROLLMODE == 1 - // Finished if we've looped back to row 0 - if (rowNext == 0) done = true; + // Finished if we've looped back to start + if (rowNext == ROW_INITIAL) { + noMoreRowsToDisplay = true; + return false; + } #else - // Finished if we're back to the first one shown - if (rowNext == rowFirst) done = true; + // Finished if we're back to the first one shown + if (rowNext == rowFirst) { + noMoreRowsToDisplay = true; + return false; + } #endif -} - -void Display::skipBlankRows() { - while (!done && rowBuffer[rowNext][0] == 0) - moveToNextRow(); + if (rowBuffer[rowNext][0] != 0) { + // Found non-blank row + return true; + } + } + return false; } \ No newline at end of file diff --git a/Display.h b/Display.h index d747680..be2479d 100644 --- a/Display.h +++ b/Display.h @@ -40,6 +40,7 @@ public: static const int MAX_CHARACTER_ROWS = 8; static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds + static const uint8_t ROW_INITIAL = 255; private: DisplayDevice *_deviceDriver; @@ -47,18 +48,18 @@ private: unsigned long lastScrollTime = 0; uint8_t hotRow = 0; uint8_t hotCol = 0; - int8_t topRow = 0; + uint8_t topRow = 0; uint8_t slot = 0; - int8_t rowFirst = -1; - int8_t rowNext = 0; + uint8_t rowFirst = ROW_INITIAL; + uint8_t rowNext = ROW_INITIAL; uint8_t charIndex = 0; char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; - bool done = false; + bool noMoreRowsToDisplay = false; uint16_t numCharacterRows; uint16_t numCharacterColumns = MAX_CHARACTER_COLS; - char *rowBuffer[MAX_CHARACTER_ROWS]; + char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1]; public: void begin() override; @@ -68,8 +69,7 @@ public: void _refresh() override; void _displayLoop() override; Display *loop2(bool force); - void moveToNextRow(); - void skipBlankRows(); + bool findNextNonBlankRow(); }; From 4deb3238028d88325e13857b0c596022e2663713 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:06:39 +0000 Subject: [PATCH 88/95] Update LiquidCrystal_I2C.cpp Ensure that pipelined I/O requests complete before the next one is set up. --- LiquidCrystal_I2C.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index b8b8e97..1354024 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -194,6 +194,7 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode; // Send both nibbles uint8_t len = 0; + rb.wait(); outputBuffer[len++] = highnib|En; outputBuffer[len++] = highnib; outputBuffer[len++] = lownib|En; @@ -208,6 +209,7 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { // I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the // HD44780 on the trailing edge of the Enable pin. uint8_t len = 0; + rb.wait(); outputBuffer[len++] = _data|En; outputBuffer[len++] = _data; I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously @@ -216,6 +218,7 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { // write a byte to the PCF8574 I2C interface. We don't need to set // the enable pin for this. void LiquidCrystal_I2C::expanderWrite(uint8_t value) { + rb.wait(); outputBuffer[0] = value | _backlightval; I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously } \ No newline at end of file From c3675367ed62e1092925f7cfff78cb6643420311 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:08:09 +0000 Subject: [PATCH 89/95] Update IO_VL53L0X.h Configure XSHUT control so that the pull-up on the module raises the pin to +2.8V rather than trying to drive it to +5V. --- IO_VL53L0X.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 9aa3d9c..7c80518 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -177,16 +177,18 @@ protected: break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. - // On some modules, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V, but we can provide additional - // protection by enabling the pull-up resistor on the microcontroller - // instead of driving the output directly. + // I've observed that the device tends to randomly reset if the XSHUT + // pin is set high from a 5V arduino, even through a pullup resistor. + // Assume that there will be a pull-up on the XSHUT pin to +2.8V as + // recommended in the device datasheet. Then we only need to + // turn our output pin high-impedence (by making it an input) and the + // on-board pullup will do its job. // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. if (!_addressConfigInProgress) { _addressConfigInProgress = true; - // Set XSHUT pin (if connected) to bring the module out of sleep mode. - if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, true); + // Configure XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, false); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; From 8e8ae90030467b2cf5f0efc606f44a43cb59bd4a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:09:40 +0000 Subject: [PATCH 90/95] Update I2CManager.cpp Add Real-time clock as a device address category (address 0x68). --- I2CManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/I2CManager.cpp b/I2CManager.cpp index 14e84b7..6d5db41 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -60,6 +60,8 @@ static const FSH * guessI2CDeviceType(uint8_t address) { return F("PWM"); else if (address >= 0x50 && address <= 0x5f) return F("EEPROM"); + else if (address == 0x68) + return F("Real-time clock"); else if (address >= 0x70 && address <= 0x77) return F("I2C Mux"); else From a405d36523ca6ce53bf376e1479ffb9b6bbe4b93 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:11:37 +0000 Subject: [PATCH 91/95] Rename class OLEDDisplay to HALDisplay. --- IO_OLEDDisplay.h => IO_HALDisplay.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) rename IO_OLEDDisplay.h => IO_HALDisplay.h (91%) diff --git a/IO_OLEDDisplay.h b/IO_HALDisplay.h similarity index 91% rename from IO_OLEDDisplay.h rename to IO_HALDisplay.h index 26c24b3..471ba94 100644 --- a/IO_OLEDDisplay.h +++ b/IO_HALDisplay.h @@ -29,7 +29,7 @@ * * To install, use the following command in myHal.cpp: - * OLEDDisplay::create(address, width, height); + * HALDisplay::create(address, width, height); * * where address is the I2C address (0x3c or 0x3d), * width is the width in pixels of the display, and @@ -41,18 +41,20 @@ */ -#ifndef IO_OLEDDISPLAY_H -#define IO_OLEDDISPLAY_H +#ifndef IO_HALDisplay_H +#define IO_HALDisplay_H #include "IODevice.h" #include "DisplayInterface.h" #include "SSD1306Ascii.h" +#include "LiquidCrystal_I2C.h" #include "version.h" typedef SSD1306AsciiWire OLED; +typedef LiquidCrystal_I2C LiquidCrystal; template -class OLEDDisplay : public IODevice, public DisplayInterface { +class HALDisplay : public IODevice, public DisplayInterface { private: // Here we define the device-specific variables. uint8_t _height; // in pixels @@ -70,17 +72,17 @@ private: DisplayInterface *_nextDisplay = NULL; public: - // Static function to handle "OLEDDisplay::create(...)" calls. + // Static function to handle "HALDisplay::create(...)" calls. static void create(I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height); } static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor - OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { _displayDriver = new T(i2cAddress, width, height); _I2CAddress = i2cAddress; _width = width; @@ -178,7 +180,7 @@ protected: // Display information about the device. void _display() { - DIAG(F("OLEDDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); + DIAG(F("HALDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); } ///////////////////////////////////////////////// @@ -238,4 +240,4 @@ public: }; -#endif // IO_OLEDDISPLAY_H \ No newline at end of file +#endif // IO_HALDisplay_H \ No newline at end of file From ef85d5eabac3e1ec2ab63918e57700615f235856 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:28:16 +0000 Subject: [PATCH 92/95] Update version.h 4.2.18 --- I2CManager_STM32.h | 1 + IO_ExternalEEPROM.h | 141 +++++ IO_GPIOBase.h | 9 +- IO_TFTDisplay.h | 256 +++++++++ IO_Wire.h | 136 +++++ .../CommandStation-EX-Arch-v1-0.svg | 6 +- ST7735-TFT.h | 517 ++++++++++++++++++ platformio.ini | 13 +- version.h | 34 +- 9 files changed, 1084 insertions(+), 29 deletions(-) create mode 100644 IO_ExternalEEPROM.h create mode 100644 IO_TFTDisplay.h create mode 100644 IO_Wire.h create mode 100644 ST7735-TFT.h diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 79a3726..a55fd2e 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -24,6 +24,7 @@ #include #include "I2CManager.h" +#include "I2CManager_NonBlocking.h" // to satisfy intellisense //#include //#include diff --git a/IO_ExternalEEPROM.h b/IO_ExternalEEPROM.h new file mode 100644 index 0000000..d2c90e5 --- /dev/null +++ b/IO_ExternalEEPROM.h @@ -0,0 +1,141 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This device driver monitors the state of turnout objects and writes updates, + * on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte) + * EEPROM device connected via I2C. + * + * When the device is restarted, it repositions the turnouts in accordance + * with the last saved position. + * + * To create a device instance, + * IO_ExternalEEPROM::create(0, 0, i2cAddress); + * + * + */ + +#ifndef IO_EXTERNALEEPROM_H +#define IO_EXTERNALEEPROM_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "Turnouts.h" + +class ExternalEEPROM : public IODevice { +private: + // Here we define the device-specific variables. + int _sizeInKBytes = 128; + Turnout *_turnout = 0; + int _lastTurnoutHash = 0; + I2CRB _rb; + uint8_t _buffer[32]; // 32 is max for Wire write + +public: + // Static function to handle "IO_ExampleSerial::create(...)" calls. + static void create(I2CAddress i2cAddress, int sizeInKBytes) { + if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes); + } + +protected: + // Constructor. + ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) { + _I2CAddress = i2cAddress; + _sizeInKBytes = sizeInKBytes; + + // Set up I2C structures. + _rb.setWriteParams(_I2CAddress, _buffer, 32); + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Max supported speed + + if (I2CManager.exists(_I2CAddress)) { + // Initialise or read contents of EEPROM + // and set turnout states accordingly. + // Read 32 bytes from address 0x0000. + I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0); + // Dump data + DIAG(F("EEPROM First 32 bytes:")); + for (int i=0; i<32; i+=8) + DIAG(F("%d: %x %x %x %x %x %x %x %x"), + i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3], + _buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]); + +#if defined(DIAG_IO) + _display(); +#endif + } else { + DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + } + } + + // Loop function to do background scanning of the turnouts + void _loop(unsigned long currentMicros) { + (void)currentMicros; // Suppress compiler warnings + + if (_rb.isBusy()) return; // Can't do anything until previous request has completed. + if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) { + // Device not responding, probably still writing data, so requeue request + I2CManager.queueRequest(&_rb); + return; + } + + if (_lastTurnoutHash != Turnout::turnoutlistHash) { + _lastTurnoutHash = Turnout::turnoutlistHash; + // Turnout list has changed, so pointer held from last run may be invalid + _turnout = 0; // Start at the beginning of the list again. +//#if defined(DIAG_IO) + DIAG(F("Turnout Hash Changed!")); +//#endif + } + + // Locate next turnout, or first one if there is no current one. + if (_turnout) + _turnout = _turnout->next(); + else + _turnout = Turnout::first(); + + // Retrieve turnout state + int turnoutID = _turnout->getId(); + int turnoutState = _turnout->isThrown(); + (void)turnoutID; // Suppress compiler warning + (void)turnoutState; // Suppress compiler warning + + // TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed. + // TODO: If it has, then initiate a write of the updated state to EEPROM + + delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256 + } + + // Display information about the device. + void _display() { + DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(), + _deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + +}; + +#endif // IO_EXTERNALEEPROM_H \ No newline at end of file diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 0f7019c..66b9ff6 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -86,6 +86,11 @@ GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress _hasCallback = true; // Add device to list of devices. addDevice(this); + + _portMode = 0; // default to input mode + _portPullup = -1; // default to pullup enabled + _portInputState = -1; // default to all inputs high (inactive) + _portInUse = 0; // No ports in use initially. } template @@ -100,10 +105,6 @@ void GPIOBase::_begin() { #if defined(DIAG_IO) _display(); #endif - _portMode = 0; // default to input mode - _portPullup = -1; // default to pullup enabled - _portInputState = -1; - _portInUse = 0; _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { diff --git a/IO_TFTDisplay.h b/IO_TFTDisplay.h new file mode 100644 index 0000000..1de7b85 --- /dev/null +++ b/IO_TFTDisplay.h @@ -0,0 +1,256 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text"). + * If the line specified is off the screen then the text in the bottom line will be + * overwritten. There is however a special case that if line 255 is specified, + * the existing text will scroll up and the new line added to the bottom + * line of the screen. + * + * To install, use the following command in myHal.cpp: + + * TFTDisplay::create(address, width, height); + * + * where address is the I2C address (0x3c or 0x3d), + * width is the width in pixels of the display, and + * height is the height in pixels of the display. + * + */ + + +#ifndef IO_TFTDISPLAY_H +#define IO_TFTDDISPLAY_H + +#include "IODevice.h" +#include "DisplayInterface.h" +#include "version.h" + + +template +class TFTDisplay : public IODevice, public DisplayInterface { +private: + uint8_t _displayNo = 0; + // Here we define the device-specific variables. + uint8_t _height; // in pixels + uint8_t _width; // in pixels + T *_displayDriver; + uint8_t _rowNo = 0; // Row number being written by caller + uint8_t _colNo = 0; // Position in line being written by caller + uint8_t _numRows; + uint8_t _numCols; + char *_buffer = NULL; + uint8_t *_rowGeneration = NULL; + uint8_t *_lastRowGeneration = NULL; + uint8_t _rowNoToScreen = 0; + uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 0; + +public: + // Static function to handle "TFTDisplay::create(...)" calls. + static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height); + } + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height); + } + +protected: + // Constructor + TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayDriver = new T(i2cAddress, width, height); + _displayNo = displayNo; + _I2CAddress = i2cAddress; + _width = width; + _height = height; + _numCols = (_width+5) / 6; // character block 6 x 8, round up + _numRows = _height / 8; // Round down + + _charPosToScreen = _numCols; + + // Allocate arrays + _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + // Fill buffer with spaces + memset(_buffer, ' ', _numCols*_numRows); + + _displayDriver->clearNative(); + + // Is this the main display? + if (_displayNo == 0) { + // Set first two lines on screen + this->setRow(0, 0); + print(F("DCC-EX v")); + print(F(VERSION)); + setRow(0, 1); + print(F("Lic GPLv3")); + } + + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to displayHandler methods. + // Make a note of the existing display reference, to that we can + // pass on anything we're not interested in. + _nextDisplay = DisplayInterface::displayHandler; + DisplayInterface::displayHandler = this; + + addDevice(this); + } + + + void screenUpdate() { + // Loop through the buffer and if a row has changed + // (rowGeneration[row] is changed) then start writing the + // characters from the buffer, one character per entry, + // to the screen until that row has been refreshed. + + // First check if the OLED driver is still busy from a previous + // call. If so, don't to anything until the next entry. + if (!_displayDriver->isBusy()) { + // Check if we've just done the end of a row or just started + if (_charPosToScreen >= _numCols) { + // Move to next line + if (++_rowNoToScreen >= _numRows) + _rowNoToScreen = 0; // Wrap to first row + + if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { + // Row content has changed, so start outputting it + _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; + _displayDriver->setRowNative(_rowNoToScreen); + _charPosToScreen = 0; // Prepare to output first character on next entry + } else { + // Row not changed, don't bother writing it. + } + } else { + // output character at current position + _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + } + } + return; + } + + ///////////////////////////////////////////////// + // IODevice Class Member Overrides + ///////////////////////////////////////////////// + + // Device-specific initialisation + void _begin() override { + // Initialise device + if (_displayDriver->begin()) { + + DIAG(F("TFTDisplay installed on address %s as screen %d"), + _I2CAddress.toString(), _displayNo); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; + + // Start with top line (looks better) + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; + } + } + + void _loop(unsigned long) override { + screenUpdate(); + } + + ///////////////////////////////////////////////// + // DisplayInterface functions + // + ///////////////////////////////////////////////// + +public: + void loop() override { + screenUpdate(); + if (_nextDisplay) + _nextDisplay->loop(); // continue to next display + return; + } + + // Position on nominated line number (0 to number of lines -1) + // Clear the line in the buffer ready for updating + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. + _rowGeneration[_rowNo]++; + + } + if (_nextDisplay) + _nextDisplay->setRow(displayNo, line); // Pass to next display + + } + + // Write one character to the screen referenced in the last setRow() call. + size_t write(uint8_t c) override { + if (_selectedDisplayNo == _displayNo) { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + } + if (_nextDisplay) + _nextDisplay->write(c); + return 1; + } + + // Write blanks to all of the screen (blocks until complete) + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } + if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display + } + + // Display information about the device. + void _display() { + DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); + } + +}; + +#endif // IO_TFTDDISPLAY_H \ No newline at end of file diff --git a/IO_Wire.h b/IO_Wire.h new file mode 100644 index 0000000..624c911 --- /dev/null +++ b/IO_Wire.h @@ -0,0 +1,136 @@ +/* + * © 2023, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * The purpose of this module is to provide an interface to the DCC + * I2CManager that is compatible with code written for the Arduino + * 'Wire' interface. + * + * To use it, just replace + * #include "Wire.h" or #include + * with + * #include "IO_Wire.h" + * + * Note that the CS only supports I2C master mode, so the calls related to + * slave mode are not implemented here. + * + */ + +#ifndef IO_WIRE +#define IO_WIRE + +#include "IODevice.h" + +#ifndef I2C_USE_WIRE + +class IO_Wire : public IODevice, public Stream { +public: + IO_Wire() { + addDevice(this); + }; + void begin() { + I2CManager.begin(); + } + void setClock(uint32_t speed) { + I2CManager.setClock(speed); + } + void beginTransmission(uint8_t address) { + i2cAddress = address; + outputLength = 0; + } + size_t write(byte value) override { + if (outputLength < sizeof(outputBuffer)) { + outputBuffer[outputLength++] = value; + return 1; + } else + return 0; + } + size_t write(const uint8_t *buffer, size_t size) override { + for (size_t i=0; i +#endif +#endif \ No newline at end of file diff --git a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg index b99995d..481b970 100644 --- a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg +++ b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg @@ -485,10 +485,10 @@ Accessories (Output.cpp) - + Process.14 - Other Utilities (LCDDisplay.cpp) + Other Utilities (Display.cpp) @@ -522,7 +522,7 @@ Other Utilities(LCDDisplay.cpp) + x="14.29" dy="1.2em" class="st5">(Display.cpp) Dynamic connector diff --git a/ST7735-TFT.h b/ST7735-TFT.h new file mode 100644 index 0000000..5893d3c --- /dev/null +++ b/ST7735-TFT.h @@ -0,0 +1,517 @@ +/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI + David Johnson-Davies - www.technoblogy.com - 26th October 2022 + + CC BY 4.0 + Licensed under a Creative Commons Attribution 4.0 International license: + http://creativecommons.org/licenses/by/4.0/ +*/ + +#include "FSH.h" +#include "DisplayInterface.h" + + +#if defined(MEGATINYCORE) +// ATtiny402/412 PORTA positions. Change these for the chip you're using +int const dc = 7; +int const mosi = 1; +int const sck = 3; +int const cs = 6; + +// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PORTA.OUTTGL = (x) +#define PORT_LOW(x) PORTA.OUTCLR = (x) +#define PORT_HIGH(x) PORTA.OUTSET = (x) +#define PORT_OUTPUT(x) PORTA.DIRSET = (x) + +#else +// ATtiny45/85 PORTB positions. Change these for the chip you're using +int const dc = 0; +int const mosi = 1; +int const sck = 2; +int const cs = 3; + +// Classic ATtiny port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PINB = (x) +#define PORT_LOW(x) PORTB = PORTB & ~((x)); +#define PORT_HIGH(x) PORTB = PORTB | ((x)) +#define PORT_OUTPUT(x) DDRB = (x) + +#endif + +// Display parameters - uncomment the line for the one you want to use + +// Adafruit 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// AliExpress 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// Adafruit 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0; + +// AliExpress 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1; + +// Adafruit 1.8" 160x128 display +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (red PCB) +int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (blue PCB) +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0; + +// Adafruit 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.3" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// AliExpress 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.2" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1; + +// AliExpress 2.4" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1; + +// Character set for text - stored in program memory +const uint8_t CharMap[96][6] FLASH = { +{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, +{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, +{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, +{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, +{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, +{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, +{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, +{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, +{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, +{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, +{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, +{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, +{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, +{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, +{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, +{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, +{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, +{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, +{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, +{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, +{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, +{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, +{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, +{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, +{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, +{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, +{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, +{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, +{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, +{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, +{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, +{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, +{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, +{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, +{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, +{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, +{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, +{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, +{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, +{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, +{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, +{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, +{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, +{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, +{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, +{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, +{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, +{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, +{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, +{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, +{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, +{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, +{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, +{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, +{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, +{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, +{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, +{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, +{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, +{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, +{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, +{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, +{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, +{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, +{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, +{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, +{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, +{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, +{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, +{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, +{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~' +{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } +}; + +// TFT colour display ********************************************** + +int const CASET = 0x2A; // Define column address +int const RASET = 0x2B; // Define row address +int const RAMWR = 0x2C; // Write to display RAM + +int const White = 0xFFFF; +int const Black = 0; + +// Current plot position and colours +int xpos, ypos; +int fore = White; +int back = Black; +int scale = 1; // Text scale + +// Send a byte to the display + +void Data (uint8_t d) { + for (uint8_t bit = 0x80; bit; bit >>= 1) { + PORT_TOGGLE(1<>8); Data(d1); Data(d2>>8); Data(d2); +} + +void InitDisplay () { + PORT_OUTPUT(1<>3; +} + +// Move current plot position to x,y +void MoveTo (int x, int y) { + xpos = x; ypos = y; +} + +// Plot point at x,y +void PlotPoint (int x, int y) { + PORT_TOGGLE(1<>8); Data(fore & 0xff); + PORT_TOGGLE(1< -dy) { err = err - dy; xpos = xpos + sx; } + if (e2 < dx) { err = err + dx; ypos = ypos + sy; } + } +} + +void FillRect (int w, int h) { + PORT_TOGGLE(1<>8; + uint8_t lo = fore & 0xff; + for (int i=0; i= y) { + MoveTo(x1-x, y1+y); FillRect(x<<1, 1); + MoveTo(x1-y, y1+x); FillRect(y<<1, 1); + MoveTo(x1-y, y1-x); FillRect(y<<1, 1); + MoveTo(x1-x, y1-y); FillRect(x<<1, 1); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } + xpos = x1; ypos = y1; +} + +void DrawCircle (int radius) { + int x1 = xpos, y1 = ypos, dx = 1, dy = 1; + int x = radius - 1, y = 0; + int err = dx - (radius<<1); + while (x >= y) { + PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y); + PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x); + PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x); + PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } +} + +// Plot an ASCII character with bottom left corner at x,y +void PlotChar (char c) { + int colour; + PORT_TOGGLE(1<>(7-yy) & 1) colour = fore; else colour = back; + for (int yr=0; yr>8); Data(colour & 0xFF); + } + } + } + } + PORT_TOGGLE(1<0; d = d/10) { + char j = (n/d) % 10; + if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } + } +} + +void TestChart () { + DrawRect(xsize, ysize); + scale = 8; + fore = Colour(255, 0, 0); + MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); + scale = 1; +} + +// Demos ********************************************** + +void BarChart () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings")); + // Horizontal axis + int xinc = (w-x1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1); + for (int i=0; i<=20; i=i+4) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + // Draw histogram + if (i != 20) { + int bar = xinc*4/3; + for (int b=2; b>=0; b--) { + fore = Colour(255, 127*b, 0); // Red, Orange, Yellow + MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20)); + } + fore = White; + } + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } +} + +void Waterfall () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + int factor = 5160/h*10; + MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance")); + // Horizontal axis + int xinc = (w-x1-15)/30; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1); + for (int i=0; i<=20; i=i+5) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } + // Diagonal axis + yinc = xinc/2; + // MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc); + MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc); + for (int i=0; i<=20; i=i+5) { + MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2); + DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2); + MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i); + } + // Plot data + for (int y=20; y>=0; y--) { + for (int i=0; i<=20; i++) { + int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10); + int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10); + fore = Colour(255, 255, 0); + MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor); + DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor); + fore = White; + } + } +} + +// Setup ********************************************** + +void setup() { + InitDisplay(); + ClearDisplay(); + DisplayOn(); + MoveTo(0,0); + // TestChart(); +} + +void loop () { + BarChart(); + // Waterfall(); + for (;;); +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 40dc89e..1fc7291 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,7 @@ include_dir = . [env] build_flags = -Wall -Wextra monitor_filters = time +; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0 [env:samd21-dev-usb] platform = atmelsam @@ -59,7 +60,7 @@ framework = arduino lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_echo = yes -build_flags = -std=c++17 -DI2C_EXTENDED_ADDRESS ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO +build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO [env:mega2560-debug] platform = atmelavr @@ -71,7 +72,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DI2C_EXTENDED_ADDRESS -DDIAG_IO -DDIAG_LOOPTIMES +build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr @@ -83,7 +84,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DIO_NO_HAL +build_flags = -DIO_NO_HAL [env:mega2560-I2C-wire] platform = atmelavr @@ -107,7 +108,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -mcall-prologues +build_flags = ; -DDIAG_LOOPTIMES [env:mega328] platform = atmelavr @@ -143,7 +144,7 @@ lib_deps = monitor_speed = 115200 monitor_echo = yes upload_speed = 19200 -build_flags = -DDIAG_IO +build_flags = [env:uno] platform = atmelavr @@ -187,7 +188,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable +build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes diff --git a/version.h b/version.h index d790206..35058da 100644 --- a/version.h +++ b/version.h @@ -5,22 +5,24 @@ #define VERSION "4.2.18" -// 4.2.18 I2C Multiplexer support through Extended Addresses, -// added for Wire, 4209 and AVR I2C drivers. -// I2C retries when fail. -// I2C timeout handling and recovery completed. -// I2C SAMD Driver Read code completed. -// PCF8575 I2C GPIO driver added. -// EX-RAIL ANOUT function for triggering analogue -// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). -// Installable HAL OLED Display Driver, with -// support for multiple displays. -// Layered HAL Drivers PCA9685pwm and Servo added for -// (1) native PWM on PCA9685 module and -// (2) animations of servo movement via PCA9685pwm. -// This is intended to support EXIOExpander and also -// replace the existing PCA9685 driver. -// Add to reinitialise failed drivers. +// 4.2.18 - I2C Multiplexer support through Extended Addresses, +// added for Wire, 4209 and AVR I2C drivers. +// - I2C retries when an operation fails. +// - I2C timeout handling and recovery completed. +// - I2C SAMD Driver Read code completed. +// - PCF8575 I2C GPIO driver added. +// - EX-RAIL ANOUT function for triggering analogue +// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// - Installable HALDisplay Driver, with support +// for multiple displays. +// - Layered HAL Drivers PCA9685pwm and Servo added for +// native PWM on PCA9685 module and +// for animations of servo movement via PCA9685pwm. +// This is intended to support EXIOExpander and also +// replace the existing PCA9685 driver. +// - Add to reinitialise failed drivers. +// - Add UserAddin facility to allow a user-specific C++ +// function to be added in myHal.cpp. // 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander From c52b4a60a56cf84ff014b2433348ef06e8747c77 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:34:52 +0000 Subject: [PATCH 93/95] Update IO_HALDisplay.h Update comments. --- IO_HALDisplay.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/IO_HALDisplay.h b/IO_HALDisplay.h index 471ba94..3919bbf 100644 --- a/IO_HALDisplay.h +++ b/IO_HALDisplay.h @@ -28,16 +28,23 @@ * line of the screen. * * To install, use the following command in myHal.cpp: - - * HALDisplay::create(address, width, height); + * + * HALDisplay::create(address, width, height); * - * where address is the I2C address (0x3c or 0x3d), - * width is the width in pixels of the display, and - * height is the height in pixels of the display. + * where address is the I2C address of the OLED display (0x3c or 0x3d), + * width is the width in pixels, and height is the height in pixels. * * Valid width and height are 128x32 (SSD1306 controller), * 128x64 (SSD1306) and 132x64 (SH1106). The driver uses * a 5x7 character set in a 6x8 pixel cell. + * + * OR + * + * HALDisplay::create(address, width, height); + * + * where address is the I2C address of the LCD display (0x27 typically), + * width is the width in characters (16 or 20 typically), + * and height is the height in characters (2 or 4 typically). */ From ce3885b125ee8d883b334a800a0bd6214f1c71d2 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 10:36:42 +0000 Subject: [PATCH 94/95] Update DisplayInterface.h Remove compiler warning --- DisplayInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DisplayInterface.h b/DisplayInterface.h index f1d598d..c5d8e96 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -80,7 +80,7 @@ public: // The following are overridden within the specific device class virtual void begin() {}; virtual size_t _write(uint8_t c) { (void)c; return 0; }; - virtual void _setRow(uint8_t line) {} + virtual void _setRow(uint8_t line) { (void)line; } virtual void _clear() {} virtual void _refresh() {} virtual void _displayLoop() {} From b7483d99e94c5287df227281d8cee83d63ee6752 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 11:13:05 +0000 Subject: [PATCH 95/95] Update version.h --- version.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 35058da..f846e0d 100644 --- a/version.h +++ b/version.h @@ -13,6 +13,8 @@ // - PCF8575 I2C GPIO driver added. // - EX-RAIL ANOUT function for triggering analogue // HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// - EX-RAIL SCREEN function for writing to screens other +// than the primary one. // - Installable HALDisplay Driver, with support // for multiple displays. // - Layered HAL Drivers PCA9685pwm and Servo added for @@ -21,8 +23,13 @@ // This is intended to support EXIOExpander and also // replace the existing PCA9685 driver. // - Add to reinitialise failed drivers. -// - Add UserAddin facility to allow a user-specific C++ -// function to be added in myHal.cpp. +// - Add UserAddin facility to allow a user-written C++ function to be +// declared in myHal.cpp, to be called at a user-specified frequency. +// - Add ability to configure clock speed of PCA9685 drivers +// (to allow flicker-free LED control). +// - Improve stability of VL53L0X driver when XSHUT pin connected. +// - Enable DCC high accuracy mode for STM32 on standard motor shield (pins D12/D13). +// - Incorporate improvements to ADC scanning performance (courtesy of HABA). // 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander