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.
This commit is contained in:
Thomas Kolb 2022-11-09 20:47:13 +01:00
parent 4d4cc41b46
commit 5090b7de4a
4 changed files with 81 additions and 39 deletions

View file

@ -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;
}

View file

@ -7,7 +7,7 @@
#include <fxp.h>
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);

View file

@ -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();
}

View file

@ -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);