bmp280: read out via DMA in the background

This removes any busy waiting on the I²C from the main loop.
Initialization is still done in a blocking way.
This commit is contained in:
Thomas Kolb 2022-10-15 20:49:31 +02:00
parent 055186180f
commit 07dd91ecc2
5 changed files with 302 additions and 58 deletions

View file

@ -1,6 +1,8 @@
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/gpio.h>
#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;
}

View file

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

148
src/i2c_dma.c Normal file
View file

@ -0,0 +1,148 @@
#include <string.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/dma.h>
#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();
}

21
src/i2c_dma.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef I2C_DMA_H
#define I2C_DMA_H
#include <stdint.h>
#include <stddef.h>
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

View file

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