From 5090b7de4a71b86891256f7cc479213aaa356005 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Wed, 9 Nov 2022 20:47:13 +0100 Subject: [PATCH] Try to make BMP280 communication more resilient If communication times out or a NAK is received, this is now recognized and sensor values are flagged as invalid. The communication then restarts on the next regular cycle. This is still to be tested. --- src/bmp280.c | 84 ++++++++++++++++++++++++++++++--------------------- src/bmp280.h | 2 +- src/i2c_dma.c | 29 ++++++++++++++++-- src/i2c_dma.h | 5 +-- 4 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/bmp280.c b/src/bmp280.c index 6de71e8..b33d896 100644 --- a/src/bmp280.c +++ b/src/bmp280.c @@ -16,7 +16,8 @@ typedef enum { WAIT_MEASUREMENT_COMPLETE, READ_RESULTS, COMPENSATE_TEMPERATURE, - COMPENSATE_PRESSURE + COMPENSATE_PRESSURE, + ERROR // set by the I²C callback in case of errors/timeout } bmp280_readout_state_t; static bmp280_readout_state_t m_readout_state; @@ -45,7 +46,7 @@ static int32_t m_temp_raw; static int32_t m_press_raw; -static void cb_i2c_dma(const uint8_t *rdata, size_t rlen); +static void cb_i2c_dma(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen); bool bmp280_init(void) @@ -151,44 +152,53 @@ static void trigger_read_results(void) } -static void cb_i2c_dma(const uint8_t *rdata, size_t rlen) +static void cb_i2c_dma(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen) { (void)rlen; - switch(m_readout_state) { - case SEND_REQUEST: - m_readout_state = WAIT_MEASUREMENT_COMPLETE; - trigger_read_status_register(); - break; + switch(evt) { + case I2C_DMA_EVT_TRANSFER_COMPLETE: + switch(m_readout_state) { + case SEND_REQUEST: + m_readout_state = WAIT_MEASUREMENT_COMPLETE; + trigger_read_status_register(); + break; - case WAIT_MEASUREMENT_COMPLETE: - if((rdata[0] & 0x09) != 0) { - // either "measuring" or "im_update" is active, so measurement is not complete yet. - // try again: - trigger_read_status_register(); - } else { - m_readout_state = READ_RESULTS; - trigger_read_results(); + case WAIT_MEASUREMENT_COMPLETE: + if((rdata[0] & 0x09) != 0) { + // either "measuring" or "im_update" is active, so measurement is not complete yet. + // try again: + trigger_read_status_register(); + } else { + m_readout_state = READ_RESULTS; + trigger_read_results(); + } + break; + + case READ_RESULTS: + m_press_raw = + ((int32_t)rdata[0] << 12) // press_msb (0xF7) + | ((int32_t)rdata[1] << 4) // press_lsb (0xF8) + | ((int32_t)rdata[2] >> 4); // press_xlsb (0xF9) + + m_temp_raw = + ((int32_t)rdata[3] << 12) // temp_msb (0xFA) + | ((int32_t)rdata[4] << 4) // temp_lsb (0xFB) + | ((int32_t)rdata[5] >> 4); // temp_xlsb (0xFC) + + m_readout_state = COMPENSATE_TEMPERATURE; + break; + + default: + // should never happen + while(1); + break; } break; - case READ_RESULTS: - m_press_raw = - ((int32_t)rdata[0] << 12) // press_msb (0xF7) - | ((int32_t)rdata[1] << 4) // press_lsb (0xF8) - | ((int32_t)rdata[2] >> 4); // press_xlsb (0xF9) - - m_temp_raw = - ((int32_t)rdata[3] << 12) // temp_msb (0xFA) - | ((int32_t)rdata[4] << 4) // temp_lsb (0xFB) - | ((int32_t)rdata[5] >> 4); // temp_xlsb (0xFC) - - m_readout_state = COMPENSATE_TEMPERATURE; - break; - - default: - // should never happen - while(1); + case I2C_DMA_EVT_TIMEOUT: + case I2C_DMA_EVT_NAK: + m_readout_state = ERROR; break; } } @@ -218,7 +228,13 @@ bool bmp280_loop(void) m_last_pressure = bmp280_comp_pressure(m_press_raw); m_readout_state = READY; m_measurements_valid = true; - retval = true; + retval = true; // measurement complete + break; + + case ERROR: + m_readout_state = READY; + m_measurements_valid = false; + retval = true; // measurement cycle terminated break; } diff --git a/src/bmp280.h b/src/bmp280.h index b25a2a7..f67b359 100644 --- a/src/bmp280.h +++ b/src/bmp280.h @@ -7,7 +7,7 @@ #include bool bmp280_init(void); -bool bmp280_loop(void); // returns true if measurements have been updated +bool bmp280_loop(void); // returns true when measurement cycle has terminated (successfully or with error) bool bmp280_start_measurement(void); diff --git a/src/i2c_dma.c b/src/i2c_dma.c index 3226ec8..b23bac7 100644 --- a/src/i2c_dma.c +++ b/src/i2c_dma.c @@ -8,6 +8,8 @@ #define DMA_CHANNEL_TX DMA_CHANNEL2 #define DMA_CHANNEL_RX DMA_CHANNEL3 +#define TIMEOUT_COUNTER_VALUE 100 // milliseconds + static uint8_t m_wdata[2]; static uint8_t m_wlen; static uint8_t m_rdata[32]; @@ -16,6 +18,8 @@ static uint8_t m_rlen; static uint8_t m_addr; static uint32_t m_i2c; +static uint32_t m_timeout_counter; + typedef enum { IDLE, SEND_DATA, @@ -101,9 +105,16 @@ void i2c_dma_loop(void) } else { // no RX requested => we are done here m_state = IDLE; - m_callback(NULL, 0); + m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, NULL, 0); } } + + if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) { + // there should be no NACK during the transfer => abort + I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag + m_state = IDLE; + m_callback(I2C_DMA_EVT_NAK, NULL, 0); + } break; case RECEIVE_DATA: @@ -112,10 +123,22 @@ void i2c_dma_loop(void) dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_RX, DMA_TCIF); m_state = IDLE; - m_callback(m_rdata, m_rlen); + m_callback(I2C_DMA_EVT_TRANSFER_COMPLETE, m_rdata, m_rlen); + } + + if(I2C_ISR(m_i2c) & I2C_ISR_NACKF) { + // there should be no NACK during the transfer => abort + I2C_ICR(m_i2c) |= I2C_ICR_NACKCF; // clear the interrupt flag + m_state = IDLE; + m_callback(I2C_DMA_EVT_NAK, NULL, 0); } break; } + + m_timeout_counter--; + if(m_timeout_counter == 0) { + m_callback(I2C_DMA_EVT_TIMEOUT, NULL, 0); + } } @@ -141,6 +164,8 @@ void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_ i2c_enable_autoend(i2c); // stop condition after write transfer } + m_timeout_counter = TIMEOUT_COUNTER_VALUE; + i2c_send_start(i2c); // start the transfer. The rest is handled by the DMA. setup_txdma(); } diff --git a/src/i2c_dma.h b/src/i2c_dma.h index 05ef006..041fc5a 100644 --- a/src/i2c_dma.h +++ b/src/i2c_dma.h @@ -7,10 +7,11 @@ typedef enum { I2C_DMA_EVT_TRANSFER_COMPLETE, - I2C_DMA_EVT_ADDR_NAK, + I2C_DMA_EVT_NAK, + I2C_DMA_EVT_TIMEOUT, } i2c_dma_evt_t; -typedef void (*i2c_dma_callback_t)(const uint8_t *rdata, size_t rlen); +typedef void (*i2c_dma_callback_t)(i2c_dma_evt_t evt, const uint8_t *rdata, size_t rlen); void i2c_dma_init(i2c_dma_callback_t callback);