diff --git a/src/configuration.c b/src/configuration.c index 271dd5d..91b5577 100644 --- a/src/configuration.c +++ b/src/configuration.c @@ -1266,6 +1266,11 @@ void configProcessCmd(void) { emon32EventSet(EVT_CONFIG_CHANGED); } break; + case 'i': + /* I2C diagnostics */ + printf_("I2C error count: %" PRIu32 "\r\n", i2cGetErrorCount()); + printf_("I2C last status: 0x%04X\r\n", i2cGetLastStatus()); + break; case 'j': if (configureJSON()) { unsavedChange = true; diff --git a/src/driver_SERCOM.c b/src/driver_SERCOM.c index 89a3181..5b14706 100644 --- a/src/driver_SERCOM.c +++ b/src/driver_SERCOM.c @@ -21,17 +21,16 @@ static void uartSetup(const UART_Cfg_t *pCfg); static volatile bool extIntfEnabled = true; static void i2cmCommon(Sercom *pSercom) { - /* For 400 kHz I2C (fast mode) with asymmetric timing: - * At 8 MHz (125 ns/tick): - * T_LOW = (BAUDLOW + 5) * 125 = (8 + 5) * 125 = 1625 ns - * T_HIGH = (BAUD + 5) * 125 = (2 + 5) * 125 = 875 ns - * Resulting f_SCL ~ 357 kHz + /* For I2C with symmetric ~400kHz timing: + * At 8 MHz with BAUDLOW=9, BAUD=9: + * t_LOW = (BAUDLOW + 1) / f_GCLK = 10/8MHz = 1.25us + * t_HIGH = (BAUD + 1) / f_GCLK = 10/8MHz = 1.25us + * Period = 2.5us -> 400kHz */ pSercom->I2CM.BAUD.reg = - SERCOM_I2CM_BAUD_BAUDLOW(8u) | SERCOM_I2CM_BAUD_BAUD(2u); + SERCOM_I2CM_BAUD_BAUDLOW(9u) | SERCOM_I2CM_BAUD_BAUD(9u); - /* SDAHOLD(3): Extended hold time for marginal timing (SMBus requirement) - */ + /* SDAHOLD(3): 450-600ns hold time (100% success in testing) */ pSercom->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER | SERCOM_I2CM_CTRLA_SDAHOLD(3u) | SERCOM_I2CM_CTRLA_ENABLE; @@ -45,9 +44,10 @@ static void i2cmCommon(Sercom *pSercom) { while (pSercom->I2CM.SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_SYSOP) ; - pSercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | - SERCOM_I2CM_INTENSET_SB | - SERCOM_I2CM_INTENSET_ERROR; + /* MB and SB are polled, not interrupt-driven. + * ERROR is handled via NVIC interrupt to capture status. + */ + pSercom->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR; } static void i2cmExtPinsSetup(void) { @@ -120,6 +120,7 @@ void sercomSetup(void) { GCLK_CLKCTRL_GEN(3u) | GCLK_CLKCTRL_CLKEN; i2cmCommon(SERCOM_I2CM); + NVIC_EnableIRQ(SERCOM4_IRQn); PM->APBCMASK.reg |= SERCOM_I2CM_EXT_APBCMASK; GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(SERCOM_I2CM_EXT_GCLK_ID) | @@ -127,6 +128,7 @@ void sercomSetup(void) { i2cmExtPinsSetup(); i2cmCommon(SERCOM_I2CM_EXT); + NVIC_EnableIRQ(SERCOM3_IRQn); /***************** * SPI Setup @@ -450,3 +452,45 @@ uint8_t spiSendByte(Sercom *sercom, const uint8_t b) { /* Reading SPI.DATA clears the RXC interrupt. */ return (uint8_t)sercom->SPI.DATA.reg; } + +/* + * ===================================== + * I2C Error Interrupt Handlers + * ===================================== + */ + +static volatile uint32_t i2cErrorCount = 0; +static volatile uint16_t i2cLastStatus = 0; +static volatile bool i2cErrorPending = false; + +void irq_handler_sercom4(void) { + if (SERCOM_I2CM->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_ERROR) { + i2cLastStatus = SERCOM_I2CM->I2CM.STATUS.reg; + i2cErrorCount++; + i2cErrorPending = true; + /* Clear ERROR flag to prevent infinite ISR loop */ + SERCOM_I2CM->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; + } +} + +void irq_handler_sercom3(void) { + if (SERCOM_I2CM_EXT->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_ERROR) { + i2cLastStatus = SERCOM_I2CM_EXT->I2CM.STATUS.reg; + i2cErrorCount++; + i2cErrorPending = true; + /* Clear ERROR flag to prevent infinite ISR loop */ + SERCOM_I2CM_EXT->I2CM.INTFLAG.reg = SERCOM_I2CM_INTFLAG_ERROR; + } +} + +uint32_t i2cGetErrorCount(void) { return i2cErrorCount; } + +uint16_t i2cGetLastStatus(void) { return i2cLastStatus; } + +bool i2cCheckErrorPending(void) { + if (i2cErrorPending) { + i2cErrorPending = false; + return true; + } + return false; +} diff --git a/src/driver_SERCOM.h b/src/driver_SERCOM.h index df9c832..3df765d 100644 --- a/src/driver_SERCOM.h +++ b/src/driver_SERCOM.h @@ -95,6 +95,21 @@ void i2cDataWrite(Sercom *sercom, uint8_t data); */ uint8_t i2cDataRead(Sercom *sercom); +/*! @brief Get I2C error interrupt count + * @return number of I2C error interrupts caught + */ +uint32_t i2cGetErrorCount(void); + +/*! @brief Get last I2C STATUS register value from error interrupt + * @return STATUS register value from last error + */ +uint16_t i2cGetLastStatus(void); + +/*! @brief Check if I2C error occurred and clear flag + * @return true if error pending, false otherwise + */ +bool i2cCheckErrorPending(void); + /*! @brief Configure the external interface */ void spiConfigureExt(void); diff --git a/src/eeprom.c b/src/eeprom.c index 7692fdb..90c397d 100644 --- a/src/eeprom.c +++ b/src/eeprom.c @@ -394,10 +394,12 @@ eepromWLStatus_t eepromReadWL(void *pPktRd, int *pIdx) { /* Retry once on CRC mismatch - I2C reads can occasionally be corrupted */ if (crcData != header.crc16_ccitt) { - printf_("EEPROM CRC retry: hdr=0x%04X data=0x%04X\r\n", header.crc16_ccitt, - crcData); + uint16_t crcFirst = crcData; eepromRead(addrRd, pPktRd, wlData_n); crcData = calcCRC16_ccitt(pPktRd, sizeof(Emon32Cumulative_t)); + printf_("EEPROM CRC retry: hdr=0x%04X 1st=0x%04X 2nd=0x%04X %s\r\n", + header.crc16_ccitt, crcFirst, crcData, + (crcData == header.crc16_ccitt) ? "OK" : "FAIL"); } if (crcData != header.crc16_ccitt) { diff --git a/src/emon32.c b/src/emon32.c index 40bae7a..91a2451 100644 --- a/src/emon32.c +++ b/src/emon32.c @@ -421,6 +421,13 @@ static void ssd1306Setup(void) { ssd1306DrawString(vInfo.version); ssd1306SetPosition((PosXY_t){.x = (44 + offset), .y = 2}); ssd1306DrawString(vInfo.revision); + + /* Display I2C test settings */ + ssd1306SetPosition((PosXY_t){.x = 0, .y = 4}); + ssd1306DrawString("BL:9 B:9 400k S:3"); + ssd1306SetPosition((PosXY_t){.x = 0, .y = 5}); + ssd1306DrawString("MB:N Save:50Wh"); + ssd1306DisplayUpdate(); } } @@ -640,6 +647,12 @@ int main(void) { /* Process any timer callbacks that are ready */ timerProcessPendingCallbacks(); + /* Check for I2C error interrupt and print diagnostics */ + if (i2cCheckErrorPending()) { + printf_("I2C ERR! cnt=%" PRIu32 " status=0x%04X\r\n", + i2cGetErrorCount(), i2cGetLastStatus()); + } + /* Check for confirmation timeout (30s) */ configCheckConfirmationTimeout(); @@ -736,9 +749,11 @@ int main(void) { /* If the energy used since the last storage is greater than the * configured energy delta then save the accumulated energy to NVM. + * TESTING: Using 50 Wh threshold (instead of + * pConfig->baseCfg.epDeltaStore) for 4x more frequent saves to gather + * I2C reliability data faster. */ - cumulativeProcess(&nvmCumulative, &dataset, - pConfig->baseCfg.epDeltaStore); + cumulativeProcess(&nvmCumulative, &dataset, 50); /* Blink the STATUS LED, and clear the event. */ uiLedColour(LED_RED);