From 07dd91ecc22c6f53457ead050b6a75883d301c4d Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sat, 15 Oct 2022 20:49:31 +0200 Subject: [PATCH] bmp280: read out via DMA in the background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes any busy waiting on the I²C from the main loop. Initialization is still done in a blocking way. --- src/bmp280.c | 178 ++++++++++++++++++++++++++++++++++++-------------- src/bmp280.h | 3 +- src/i2c_dma.c | 148 +++++++++++++++++++++++++++++++++++++++++ src/i2c_dma.h | 21 ++++++ src/main.c | 10 +-- 5 files changed, 302 insertions(+), 58 deletions(-) create mode 100644 src/i2c_dma.c create mode 100644 src/i2c_dma.h diff --git a/src/bmp280.c b/src/bmp280.c index 1526fb8..66c1a2a 100644 --- a/src/bmp280.c +++ b/src/bmp280.c @@ -1,6 +1,8 @@ #include #include +#include "i2c_dma.h" + #include "pinout.h" #include "bmp280_comp.h" @@ -8,6 +10,17 @@ #define BMP280_7BIT_ADDR 0x76 +typedef enum { + READY, + SEND_REQUEST, + WAIT_MEASUREMENT_COMPLETE, + READ_RESULTS, + COMPENSATE_TEMPERATURE, + COMPENSATE_PRESSURE +} bmp280_readout_state_t; + +static bmp280_readout_state_t m_readout_state; + // calibration value cache, shared with bmp280_comp.c extern uint16_t dig_T1; @@ -27,6 +40,13 @@ extern int16_t dig_P9; static fxp_t m_last_temperature = 0; static fxp_t m_last_pressure = 0; +static int32_t m_temp_raw; +static int32_t m_press_raw; + + +static void cb_i2c_dma(const uint8_t *rdata, size_t rlen); + + bool bmp280_init(void) { // configure pins for I2C1 @@ -95,6 +115,8 @@ bool bmp280_init(void) dig_P8 = (((int16_t)(int8_t)rdata[21]) << 8) | rdata[20]; // 0x9C, 0x9D dig_P9 = (((int16_t)(int8_t)rdata[23]) << 8) | rdata[22]; // 0x9E, 0x9F + i2c_dma_init(cb_i2c_dma); + return true; err_out: @@ -102,63 +124,123 @@ err_out: } +static void trigger_read_status_register(void) +{ + uint8_t wdata[1]; + uint8_t wsize = 0, rsize; + + wdata[wsize++] = 0xF3; // status register + rsize = 1; + + i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rsize); +} + + +static void trigger_read_results(void) +{ + uint8_t wdata[1]; + uint8_t wsize = 0, rsize; + + wdata[wsize++] = 0xF7; + rsize = 8; + + i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rsize); +} + + +static void cb_i2c_dma(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; + + 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; + } +} + + +bool bmp280_loop(void) +{ + bool retval = false; + + switch(m_readout_state) { + case READY: + // nothing to do in this state + break; + + case SEND_REQUEST: + case WAIT_MEASUREMENT_COMPLETE: + case READ_RESULTS: + // handled in cb_i2c_dma exclusively + break; + + case COMPENSATE_TEMPERATURE: + m_last_temperature = bmp280_comp_temperature(m_temp_raw); + m_readout_state = COMPENSATE_PRESSURE; + break; + + case COMPENSATE_PRESSURE: + m_last_pressure = bmp280_comp_pressure(m_press_raw); + m_readout_state = READY; + retval = true; + break; + } + + if(m_readout_state != READY) { + i2c_dma_loop(); + } + + return retval; +} + + bool bmp280_start_measurement(void) { uint8_t wdata[2]; uint8_t wsize = 0; + if(m_readout_state != READY) { + return false; // still busy + } + wdata[wsize++] = 0xF4; // measurement control register wdata[wsize++] = (0x01 << 5) | (0x01 << 2) | 0x01; // pressure x1, temp x1, forced mode - i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, NULL, 0); + i2c_dma_start_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, 0); - return true; -} - - -bool bmp280_is_measurement_complete(void) -{ - uint8_t wdata[1]; - uint8_t rdata[1]; - uint8_t wsize = 0, rsize; - - wdata[wsize++] = 0xF3; // status register - rsize = 1; - - i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); - - if((rdata[0] & 0x09) != 0) { - // either "measuring" or "im_update" is active, so measurement is not complete yet. - return false; - } else { - return true; - } -} - - -bool bmp280_readout_and_compensate(void) -{ - uint8_t wdata[1]; - uint8_t rdata[8]; - uint8_t wsize = 0, rsize; - - wdata[wsize++] = 0xF7; - rsize = 8; - - i2c_transfer7(I2C1, BMP280_7BIT_ADDR, wdata, wsize, rdata, rsize); - - int32_t 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) - - int32_t 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_last_temperature = bmp280_comp_temperature(temp_raw); - m_last_pressure = bmp280_comp_pressure(press_raw); + m_readout_state = SEND_REQUEST; return true; } diff --git a/src/bmp280.h b/src/bmp280.h index 9837eae..17abd92 100644 --- a/src/bmp280.h +++ b/src/bmp280.h @@ -7,10 +7,9 @@ #include bool bmp280_init(void); +bool bmp280_loop(void); // returns true if measurements have been updated bool bmp280_start_measurement(void); -bool bmp280_is_measurement_complete(void); -bool bmp280_readout_and_compensate(void); fxp_t bmp280_get_temperature(void); fxp_t bmp280_get_pressure(void); diff --git a/src/i2c_dma.c b/src/i2c_dma.c new file mode 100644 index 0000000..4a06756 --- /dev/null +++ b/src/i2c_dma.c @@ -0,0 +1,148 @@ +#include + +#include +#include + +#include "i2c_dma.h" + +#include "rs485.h" + +#define DMA_CHANNEL_TX DMA_CHANNEL2 +#define DMA_CHANNEL_RX DMA_CHANNEL3 + +static uint8_t m_wdata[2]; +static uint8_t m_wlen; +static uint8_t m_rdata[32]; +static uint8_t m_rlen; + +static uint8_t m_addr; +static uint32_t m_i2c; + +typedef enum { + IDLE, + SEND_DATA, + RECEIVE_DATA +} i2c_dma_state_t; + +static i2c_dma_state_t m_state; + + +static i2c_dma_callback_t m_callback; + +static void setup_txdma(void) +{ + dma_channel_reset(DMA1, DMA_CHANNEL_TX); + + dma_set_priority(DMA1, DMA_CHANNEL_TX, DMA_CCR_PL_LOW); + dma_set_memory_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_MSIZE_8BIT); + dma_set_peripheral_size(DMA1, DMA_CHANNEL_TX, DMA_CCR_PSIZE_8BIT); + dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_TX); + dma_set_read_from_memory(DMA1, DMA_CHANNEL_TX); + dma_set_peripheral_address(DMA1, DMA_CHANNEL_TX, (uint32_t) &I2C_TXDR(m_i2c)); + + // send m_wlen bytes from m_wdata + dma_set_memory_address(DMA1, DMA_CHANNEL_TX, (uint32_t)m_wdata); + dma_set_number_of_data(DMA1, DMA_CHANNEL_TX, m_wlen); + + dma_enable_channel(DMA1, DMA_CHANNEL_TX); + + i2c_enable_txdma(m_i2c); +} + + +static void setup_rxdma(void) +{ + dma_channel_reset(DMA1, DMA_CHANNEL_RX); + + dma_set_priority(DMA1, DMA_CHANNEL_RX, DMA_CCR_PL_LOW); + dma_set_memory_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_MSIZE_8BIT); + dma_set_peripheral_size(DMA1, DMA_CHANNEL_RX, DMA_CCR_PSIZE_8BIT); + dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL_RX); + dma_set_read_from_peripheral(DMA1, DMA_CHANNEL_RX); + dma_set_peripheral_address(DMA1, DMA_CHANNEL_RX, (uint32_t) &I2C_RXDR(m_i2c)); + + // receive m_rlen bytes and store them in m_rdata + dma_set_memory_address(DMA1, DMA_CHANNEL_RX, (uint32_t)m_rdata); + dma_set_number_of_data(DMA1, DMA_CHANNEL_RX, m_rlen); + + dma_enable_channel(DMA1, DMA_CHANNEL_RX); + + i2c_enable_rxdma(m_i2c); +} + + +void i2c_dma_init(i2c_dma_callback_t callback) +{ + m_callback = callback; + + m_state = IDLE; +} + + +void i2c_dma_loop(void) +{ + switch(m_state) { + case IDLE: + // nothing to do + return; + + case SEND_DATA: + //if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :( + if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_TX, DMA_TCIF)) { + dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_TX, DMA_TCIF); + + if(m_rlen) { + m_state = RECEIVE_DATA; + + i2c_set_7bit_address(m_i2c, m_addr); + i2c_set_read_transfer_dir(m_i2c); + i2c_set_bytes_to_transfer(m_i2c, m_rlen); + i2c_send_start(m_i2c); + i2c_enable_autoend(m_i2c); // important to do it after start() for a proper repeated start! + setup_rxdma(); + } else { + // no RX requested => we are done here + m_state = IDLE; + m_callback(NULL, 0); + } + } + break; + + case RECEIVE_DATA: + //if(i2c_transfer_complete(m_i2c)) { // does not work for some reason :( + if(dma_get_interrupt_flag(DMA1, DMA_CHANNEL_RX, DMA_TCIF)) { + dma_clear_interrupt_flags(DMA1, DMA_CHANNEL_RX, DMA_TCIF); + + m_state = IDLE; + m_callback(m_rdata, m_rlen); + } + break; + } +} + + +void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_t wn, size_t rn) +{ + m_wlen = wn; + m_rlen = rn; + + memcpy(m_wdata, w, wn); + + m_state = SEND_DATA; + + m_i2c = i2c; + m_addr = addr; + + i2c_set_7bit_address(i2c, addr); + i2c_set_write_transfer_dir(i2c); + i2c_set_bytes_to_transfer(i2c, wn); + + if(rn) { + i2c_disable_autoend(i2c); // prepare repeated start + } else { + i2c_enable_autoend(i2c); // stop condition after write transfer + } + + 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 new file mode 100644 index 0000000..05ef006 --- /dev/null +++ b/src/i2c_dma.h @@ -0,0 +1,21 @@ +#ifndef I2C_DMA_H +#define I2C_DMA_H + +#include +#include + +typedef enum +{ + I2C_DMA_EVT_TRANSFER_COMPLETE, + I2C_DMA_EVT_ADDR_NAK, +} i2c_dma_evt_t; + +typedef void (*i2c_dma_callback_t)(const uint8_t *rdata, size_t rlen); + +void i2c_dma_init(i2c_dma_callback_t callback); + +void i2c_dma_loop(void); + +void i2c_dma_start_transfer7(uint32_t i2c, uint8_t addr, const uint8_t *w, size_t wn, size_t rn); + +#endif // I2C_DMA_H diff --git a/src/main.c b/src/main.c index cb5ed9e..a78122a 100644 --- a/src/main.c +++ b/src/main.c @@ -27,7 +27,6 @@ static enum { BMP280_NOT_PRESENT, BMP280_IDLE, BMP280_MEASURING, - BMP280_READOUT } bmp280_state; /* Set up systick to fire freq times per second */ @@ -280,15 +279,10 @@ static void handle_bmp280(uint64_t timebase_ms) break; case BMP280_MEASURING: - if(timebase_ms % 10 == 0 && bmp280_is_measurement_complete()) { - bmp280_state = BMP280_READOUT; + if(timebase_ms % 10 == 0 && bmp280_loop()) { + bmp280_state = BMP280_IDLE; } break; - - case BMP280_READOUT: - bmp280_readout_and_compensate(); - bmp280_state = BMP280_IDLE; - break; } }