Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
66 changes: 55 additions & 11 deletions src/driver_SERCOM.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -120,13 +120,15 @@ 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) |
GCLK_CLKCTRL_GEN(3u) | GCLK_CLKCTRL_CLKEN;

i2cmExtPinsSetup();
i2cmCommon(SERCOM_I2CM_EXT);
NVIC_EnableIRQ(SERCOM3_IRQn);

/*****************
* SPI Setup
Expand Down Expand Up @@ -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;
}
15 changes: 15 additions & 0 deletions src/driver_SERCOM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 4 additions & 2 deletions src/eeprom.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
19 changes: 17 additions & 2 deletions src/emon32.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down