From 6d7ce155db5aa8f667baabaf60374da47bf40e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZIGETI=20J=C3=A1nos?= Date: Sun, 26 Apr 2026 17:05:39 +0200 Subject: [PATCH 1/4] [ISSUE-46]: BME280 demo application. Some features still missing. --- configure.ac | 1 + examples/1bme280/Makefile.am | 38 +++ examples/1bme280/README.md | 35 +++ examples/1bme280/bme280app.c | 377 +++++++++++++++++++++++ examples/1bme280/defines.h | 42 +++ examples/3prog1/prog.c | 14 +- examples/Makefile.am | 4 +- modules/bme280.c | 574 ++++++++++++++++++++++------------- modules/bme280.h | 70 +++-- 9 files changed, 914 insertions(+), 241 deletions(-) create mode 100644 examples/1bme280/Makefile.am create mode 100644 examples/1bme280/README.md create mode 100644 examples/1bme280/bme280app.c create mode 100644 examples/1bme280/defines.h diff --git a/configure.ac b/configure.ac index b43feee..9994cb9 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,7 @@ AC_CONFIG_FILES([ examples/0timgalarm/Makefile examples/0uart/Makefile examples/0udma/Makefile + examples/1bme280/Makefile examples/1i2cssd1306/Makefile examples/1rmtblink/Makefile examples/1rmtdht/Makefile diff --git a/examples/1bme280/Makefile.am b/examples/1bme280/Makefile.am new file mode 100644 index 0000000..c09ad06 --- /dev/null +++ b/examples/1bme280/Makefile.am @@ -0,0 +1,38 @@ +include $(top_srcdir)/scripts/elf2bin.mk +include $(top_srcdir)/ld/flags.mk + +noinst_HEADERS = defines.h + +AM_CFLAGS = -std=c11 -flto +if WITH_BINARIES +AM_LDFLAGS += \ + -T $(top_srcdir)/ld/esp32.rom.ld \ + -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \ + -T $(top_srcdir)/ld/esp32.rom.newlib-data.ld \ + -T $(top_srcdir)/ld/esp32.rom.newlib-locale.ld \ + -T $(top_srcdir)/ld/esp32.rom.newlib-nano.ld \ + -T $(top_srcdir)/ld/esp32.rom.newlib-time.ld \ + -T $(top_srcdir)/ld/esp32.rom.redefined.ld \ + -T $(top_srcdir)/ld/esp32.rom.syscalls.ld +else +AM_LDFLAGS += \ + -T $(top_srcdir)/ld/esp32.rom.ld \ + -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \ + -T $(top_srcdir)/ld/esp32.rom.redefined.ld \ + -T $(top_srcdir)/ld/esp32.rom.syscalls.ld +endif + +AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/modules +LDADD = $(top_builddir)/src/libesp32basic.a $(top_builddir)/modules/libesp32modules.a + +bin_PROGRAMS = \ + bme280app.elf + +bme280app_elf_SOURCES = bme280app.c + +if WITH_BINARIES +CLEANFILES = \ + bme280app.bin +endif + +BUILT_SOURCES = $(CLEANFILES) diff --git a/examples/1bme280/README.md b/examples/1bme280/README.md new file mode 100644 index 0000000..1b953df --- /dev/null +++ b/examples/1bme280/README.md @@ -0,0 +1,35 @@ +### Simple application to BME280 sensor + +In this small example application we show the usage of BME280. + +#### Hardware components + +BME280 -- BME280 sensor + +#### Connections + +``` +ESP32.GND -- BME280.GND +ESP32.GPIO22 -- BME280.SCL +ESP32.GPIO23 -- BME280.SDA +ESP32.3V3 -- BME280.VCC +ESP32.GPIO21 -- BME280.CSB +ESP32.GND -- BME280.SDO +``` + +#### Control + +The application accepts the following commands (on UART0): + +* `-` / `+` - T Oversampling down (off) / up; +* `[` / `]` - P Oversampling down (off) / up; +* `{` / `}` - H Oversampling down (off) / up; +* `<` / `>` - t_standby up / down; +* `,` / `.` - measurement period up / down in forced mode; +* `f` / `n` / `s` - TODO: set mode: forced / normal / sleep; +* `c` - read config/control/status registers to local mirror; +* `r` - reset; +* `i` - information; +* `I` - Toggle verbose information (initially: off). + +#### Practices diff --git a/examples/1bme280/bme280app.c b/examples/1bme280/bme280app.c new file mode 100644 index 0000000..0d17a6f --- /dev/null +++ b/examples/1bme280/bme280app.c @@ -0,0 +1,377 @@ +/* + * Copyright 2025 SZIGETI János + * + * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3. + * See LICENSE or for full license details. + */ +#include +#include +#include +#include +#include "dport.h" +#include "gpio.h" +#include "main.h" +#include "defines.h" +#include "romfunctions.h" +#include "uart.h" +#include "utils/uartutils.h" +#include "i2c.h" +#include "lockmgr.h" +#include "iomux.h" +#include "dport.h" +#include "timg.h" +#include "typeaux.h" +#include "esp_attr.h" +#include "bme280.h" +#include "utils/i2cutils.h" + +// =================== Hard constants ================= + +// #1: Timings -- 50ms: 20Hz update freq. +#define UART_FREQ_HZ 115200U +#define I2C0_FREQ_HZ 400000U +#define I2CSCAN_PERIOD_MS 5050U +#define BME280_TSTARTUP_MS 2U + +#define UARTCTRL_PERIOD_MS 100U + +// #2: Channels / wires / addresses +#define I2C0_SCL_GPIO 23U +#define I2C0_SDA_GPIO 22U +#define BME280_CSB_GPIO 21U + +#define BME280_I2C_CH I2C1 +#define BME280_I2C_SLAVEADDR 0x76 + +// ============= Local types =============== + + +// ================ Local function declarations ================= +static void _i2c_release_cycle(uint64_t u64tckNow); +static ELockmgrResource _i2c_to_lock(EI2CBus eBus); +static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface); +static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abValid); +static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); +static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280StateDesc *psState); +static void _uartctrl_cycle(uint64_t u64tckNow); + +void _i2c_error(void *pvParam); + +// =================== Global constants ================ +const bool gbStartAppCpu = START_APP_CPU; +const uint16_t gu16Tim00Divisor = TIM0_0_DIVISOR; +const uint64_t gu64tckSchedulePeriod = (CLK_FREQ_HZ / SCHEDULE_FREQ_HZ); + +const uint32_t gau32msForcedPeriod[] = { + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000 +}; + +// ==================== Local Data ================ +static SBme280StateDesc gsState; +static bool gbDoubleWait = false; // BUGFIX: apparently, after modifying osrs_x value, the first measurement takes twice as much time as expected. +static bool gbVerbose = false; +static SI2cIfaceCfg gsIface; +static uint8_t gu8ForcedPeriodIdx = 4; + +// ==================== Implementation ================ +// -------------- Internal functions -------------- + +static void _i2c_release_cycle(uint64_t u64tckNow) { + ELockmgrResource eBus = _i2c_to_lock(BME280_I2C_CH); + I2C_Type *psI2C = i2c_regs(eBus); + RegAddr prData = i2c_nonfifo(eBus); + + if (lockmgr_is_locked(eBus)) { + bool bBusIdle = !i2c_isbusy(psI2C); + if (bBusIdle) { + uint32_t u32Label = lockmgr_get_lock_owner(eBus); + AsyncResultEntry* psEntry = lockmgr_get_entry(u32Label); + psEntry->u32IntSt = psI2C->INT_ST; + if (0 < psEntry->u8RxLen) { + for (int i = 0; i < psEntry->u8RxLen; ++i) { + psEntry->pu8ReceiveBuffer[i] = (uint8_t) (prData[i] & 0xff); + } + } + psEntry->bReady = true; + lockmgr_free_lock(eBus); + } + } +} + +static ELockmgrResource _i2c_to_lock(EI2CBus eBus) { + return eBus; +} + +static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface) { + // chip select + gpio_pin_enable(BME280_CSB_GPIO); + gpio_pin_out_on(BME280_CSB_GPIO); + + // init state + *psState = bme280_init_state(); + bme280_set_osrs(psState, BME280_SEL_H, BME280_OSRS_8); + bme280_set_osrs(psState, BME280_SEL_P, BME280_OSRS_8); + bme280_set_osrs(psState, BME280_SEL_T, BME280_OSRS_8); + gbDoubleWait = true; + bme280_set_config(psState, BME280_TSB_1000MS, BME280_IIR_OFF, 0); + bme280_req_calib(psState); + bme280_req_id(psState); + + *psIface = (SI2cIfaceCfg){ + .eBus = BME280_I2C_CH, + .eLck = _i2c_to_lock(BME280_I2C_CH), + .u8SlaveAddr = BME280_I2C_SLAVEADDR + }; +} + +static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abValid) { + uart_printf(&gsUART0, "[%d]", (uint32_t)(u64tckNow / TICKS_PER_MS)); + uart_printf(&gsUART0, "\tTfine: %d", u32TFine); + if (abValid & 1) + uart_printf(&gsUART0, "\tTemp: %d.%02d", psRes->i32Temp / 100, psRes->i32Temp % 100); + if (abValid & 2) + uart_printf(&gsUART0, "\tPres: %d.%02d", psRes->i32Pres >> 8, ((psRes->i32Pres & 0xff) * 391) / 1000); + if (abValid & 4) + uart_printf(&gsUART0, "\tHum: %d.%03d", psRes->i32Hum >> 10, ((psRes->i32Hum & 0x3ff) * 97657) / 100000); + uart_printf(&gsUART0, "\r\n"); +} + +static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme280StateDesc *psState) { + static uint64_t u64tckMainNext = 0; + static uint64_t u64tckNext = MS2TICKS(BME280_TSTARTUP_MS); + static uint8_t u8Phase = 0; // 0: idle, 1: set mode, 2: wait, 3: get status, 4: get data, 5: print data + static uint32_t u32hmsMeasurementExp = 0; // expected measurement time [half ms] + static uint32_t u32ExtraWaitCnt = 0; // additional cycles after expected measurement time (status is not 0) + + if (u64tckNext < u64tckMainNext) { + u64tckNext = u64tckMainNext; + } + if (u64tckNext <= u64tckNow) { + uint32_t u32hmsWaitHint = 0; + // bool bRxRes = bme280_async_rx_cycle(psState, &u32hmsWaitHint); + bme280_async_rx_cycle(psState, &u32hmsWaitHint); + bool bOngoing = bme280_has_async_todo(psState); + + if (!bOngoing) { // the bme280 todo queue is empty (nothing to set, nothing requested) + ++u8Phase; + + if (u8Phase == 5) { + if (gbVerbose) { + uart_printf(&gsUART0, "Predicted dt: %u, status reties: %u\r\n", u32hmsMeasurementExp / 2, u32ExtraWaitCnt); + } + uint32_t u32TFine; + SBme280TPH sMeas = bme280_get_measurement(psState, &u32TFine); + _bme280_print_result(u64tckNow, &sMeas, u32TFine, + (bme280_get_osrs(psState, true, BME280_SEL_T) ? 1 : 0) | + (bme280_get_osrs(psState, true, BME280_SEL_P) ? 2 : 0) | + (bme280_get_osrs(psState, true, BME280_SEL_H) ? 4 : 0) + ); + u8Phase = 0; + } else { + if (u8Phase == 1) { + bme280_set_mode(psState, BME280_MODE_FORCED); + u32hmsMeasurementExp = (gbDoubleWait ? 2 : 1) * bme280_measurement_duration_hms(psState); + } else if (u8Phase == 2) { + u64tckNext = u64tckNow + HMS2TICKS(u32hmsMeasurementExp); + } else if (u8Phase == 3) { + gbDoubleWait = false; + bme280_req_status(psState); + u32ExtraWaitCnt = 0; + } else if (u8Phase == 4) { + uint8_t u8Status = bme280_get_status(psState) & 0x09; + if (u8Status != 0) { + gsUART0.FIFO = 'a' + u8Status; + bme280_req_status(psState); + --u8Phase; + ++u32ExtraWaitCnt; + } else { + bme280_req_data(psState); + } + } + } + } + // TX side + bool bTxRes = bme280_async_tx_cycle(psIface, psState); + bool bTodo = bme280_has_async_todo(psState); + bool bWaitForRx = bme280_is_waiting(psState); + + // calculate wait time + if (bTodo && !bTxRes) { // could not initialize TX, retry soon + // do not increase nxt timestamp + } else if (bWaitForRx) { + if (((psState->u32CommState>>24)&0x0f)==6) { + u64tckNext = u64tckNow + MS2TICKS(BME280_TSTARTUP_MS); + } + // do not increase nxt timestamp + } else { // no tx problem, not waiting for rx + if (u8Phase == 0) { // find next main cycle tick + u64tckMainNext += MS2TICKS(gau32msForcedPeriod[gu8ForcedPeriodIdx]); + } else { + // no wait + } + } + } +} + +static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280StateDesc *psState) { + EBme280Osrs eCurVal = bme280_get_osrs(psState, false, eMetric); + const char acMetric2Ch[] = {'h', 'p', 't'}; + + int8_t i8NewVal = eCurVal + i8Diff; + bool bValid = (0 <= i8NewVal && i8NewVal < 8); + if (!bValid) { + i8NewVal = eCurVal; + } + int8_t i8Mul = + i8NewVal == 0 ? 0 : + 5 <= i8NewVal ? 16 : + 1 << (i8NewVal - 1); + if (bValid) { + bme280_set_osrs(psState, eMetric, i8NewVal); + uart_printf(&gsUART0, "osrs_%c modified: %u -> %d (x%d)\r\n", acMetric2Ch[eMetric], eCurVal, i8NewVal, i8Mul); + } else { + uart_printf(&gsUART0, "osrs_%c not changed: %d (x%d)\r\n", acMetric2Ch[eMetric], i8NewVal, i8Mul); + } + return bValid; +} + +static void _modify_idx(int8_t i8IdxDiff, uint8_t *pu8Idx, uint8_t u8MaxIdx, const uint32_t *pu32Values, const char *pcParamName) { + uint8_t u8OrigIdx = *pu8Idx; + int8_t i8NewIdx = u8OrigIdx + i8IdxDiff; + bool bValid = (0 <= i8NewIdx && i8NewIdx < u8MaxIdx); + if (!bValid) { + i8NewIdx = u8OrigIdx; + } + if (bValid) { + *pu8Idx = i8NewIdx; + uart_printf(&gsUART0, "%s modified: %u -> %u (#%d)\r\n", pcParamName, pu32Values[u8OrigIdx], pu32Values[i8NewIdx], i8NewIdx); + } else { + uart_printf(&gsUART0, "%s not changed: %u (#%u)\r\n", pcParamName, pu32Values[u8OrigIdx], u8OrigIdx); + } +} + +static void _uartctrl_cycle(uint64_t u64tckNow) { + static uint64_t u64tckNext = 0; + int8_t i8OsrsTDiff = 0; + int8_t i8OsrsPDiff = 0; + int8_t i8OsrsHDiff = 0; + int8_t i8TsbDiff = 0; + int8_t i8TfpDiff = 0; // forced mode period + static bool bInReset = false; + + if (u64tckNext <= u64tckNow) { + while (0 < (gsUART0.STATUS & 0xff)) { + char cCtrl = gsUART0Mapped.FIFO & 0xff; + switch (cCtrl) { + case '-': + --i8OsrsTDiff; + break; + case '+': + ++i8OsrsTDiff; + break; + case '[': + --i8OsrsPDiff; + break; + case ']': + ++i8OsrsPDiff; + break; + case '{': + --i8OsrsHDiff; + break; + case '}': + ++i8OsrsHDiff; + break; + case '<': + --i8TsbDiff; + break; + case '>': + ++i8TsbDiff; + break; + case ',': + --i8TfpDiff; + break; + case '.': + ++i8TfpDiff; + break; + case 'c': + bme280_req_config(&gsState); + break; + case 'i': + uart_printf(&gsUART0, "ID: 0x%02X\t", bme280_get_id(&gsState)); + uart_printf(&gsUART0, "Setters: %02X\t", gsState.u32CommState&0xFF); + uart_printf(&gsUART0, "Getters: %02X\t", (gsState.u32CommState>>8)&0xFF); + uart_printf(&gsUART0, "CommFlags: %02X\r\n", (gsState.u32CommState>>16)&0xFF); + uart_printf(&gsUART0, "Config/Status: %08X\t", ((uint32_t*)gsState.au8ConfigMirror)[0]); + uart_printf(&gsUART0, "Raw Data: %08X.%08X\r\n", ((uint32_t*)gsState.au8DataMirror)[1], ((uint32_t*)gsState.au8DataMirror)[0]); + break; + case 'I': + gbVerbose=!gbVerbose; + uart_printf(&gsUART0, "Verbose mode: %u\r\n", gbVerbose); + break; + case 'r': + bme280_reset(&gsState); + bme280_req_calib(&gsState); + bInReset = true; + uart_printf(&gsUART0, "Resetting..."); + break; + default: + uart_printf(&gsUART0, "command not found\r\n"); + } + } + if (i8OsrsTDiff != 0) { + gbDoubleWait |= _modify_osrs(i8OsrsTDiff, BME280_SEL_T, &gsState); + } + if (i8OsrsPDiff != 0) { + gbDoubleWait |= _modify_osrs(i8OsrsPDiff, BME280_SEL_P, &gsState); + } + if (i8OsrsHDiff != 0) { + gbDoubleWait |= _modify_osrs(i8OsrsHDiff, BME280_SEL_H, &gsState); + } + if (i8TfpDiff != 0) { + _modify_idx(i8TfpDiff, &gu8ForcedPeriodIdx, ARRAY_SIZE(gau32msForcedPeriod), gau32msForcedPeriod, "Forced mode period (ms)"); + } + if (bInReset) { + if (bme280_is_resetting(&gsState)) { + uart_printf(&gsUART0, "."); + } else { + uart_printf(&gsUART0, " Done.\r\n"); + bInReset = false; + } + } + u64tckNext += MS2TICKS(UARTCTRL_PERIOD_MS); + } +} + + +// -------------- Interface functions -------------- + +void prog_init_pro_pre() { + // we do some logging, hence set UART0 speed + gsUART0.CLKDIV.raw = UART_HZ2CLKDIV(UART_FREQ_HZ, APB_FREQ_HZ); + + lockmgr_init(); + i2c_init_controller(BME280_I2C_CH, I2C0_SCL_GPIO, I2C0_SDA_GPIO, HZ2APBTICKS(I2C0_FREQ_HZ)); + + _bme280_init(&gsState, &gsIface); +} + +void prog_init_app() { +} + +void prog_init_pro_post() { +} + +void prog_cycle_app(uint64_t u64tckNow) { +} + +void prog_cycle_pro(uint64_t u64tckNow) { + _i2c_release_cycle(u64tckNow); + _uartctrl_cycle(u64tckNow); + _bme280_cycle(u64tckNow, &gsIface, &gsState); +} diff --git a/examples/1bme280/defines.h b/examples/1bme280/defines.h new file mode 100644 index 0000000..42421b5 --- /dev/null +++ b/examples/1bme280/defines.h @@ -0,0 +1,42 @@ +/* + * Copyright 2024 SZIGETI János + * + * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3. + * See LICENSE or for full license details. + */ +#ifndef DEFINES_H +#define DEFINES_H + +#ifdef __cplusplus +extern "C" { +#endif + + // TIMINGS + // const -- do not change this value +#define APB_FREQ_HZ 80000000U // 80 MHz + + // variables +#define TIM0_0_DIVISOR 2U // cannot be 1 +#define START_APP_CPU 0U +#define SCHEDULE_FREQ_HZ 1000U // 1KHz + + // derived invariants +#define CLK_FREQ_HZ (APB_FREQ_HZ / TIM0_0_DIVISOR) // 40 MHz +#define TICKS_PER_MS (CLK_FREQ_HZ / 1000U) // 40000 +#define TICKS_PER_HMS (CLK_FREQ_HZ / 2000U) // 20000 (ticks per half-milliseconds) +#define TICKS_PER_US (CLK_FREQ_HZ / 1000000U) // 40 +#define NS_PER_TICKS (1000000000 / CLK_FREQ_HZ) + +#define TICKS2NS(X) ((X) * NS_PER_TICKS) +#define TICKS2US(X) ((X) / TICKS_PER_US) +#define MS2TICKS(X) ((X) * TICKS_PER_MS) +#define HMS2TICKS(X) ((X) * TICKS_PER_HMS) +#define US2TICKS(X) ((X) * TICKS_PER_US) +#define HZ2APBTICKS(X) (APB_FREQ_HZ / (X)) + +#ifdef __cplusplus +} +#endif + +#endif /* DEFINES_H */ + diff --git a/examples/3prog1/prog.c b/examples/3prog1/prog.c index 9788eda..76e6e3d 100644 --- a/examples/3prog1/prog.c +++ b/examples/3prog1/prog.c @@ -336,10 +336,10 @@ static void _oled_cycle(uint64_t u64tckNow) { static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface) { *psState = bme280_init_state(); - bme280_set_osrs_h(psState, BME280_OSRS_8); - bme280_set_osrs_t(psState, BME280_OSRS_8); - bme280_set_osrs_p(psState, BME280_OSRS_8); - bme280_set_mode_forced(psState); + bme280_set_osrs(psState, BME280_SEL_T, BME280_OSRS_8); + bme280_set_osrs(psState, BME280_SEL_P, BME280_OSRS_8); + bme280_set_osrs(psState, BME280_SEL_H, BME280_OSRS_8); + bme280_set_mode(psState, BME280_MODE_FORCED); *psIface = (SI2cIfaceCfg){ .eBus = BME280_I2C_CH, .eLck = _i2c_to_lock(BME280_I2C_CH), @@ -369,12 +369,12 @@ static void _bme280_cycle(uint64_t u64tckNow) { if (u64tckNext <= u64tckNow) { uint32_t u32hmsWaitHint = 0; bme280_async_rx_cycle(&sState, &u32hmsWaitHint); - if (bme280_is_data_updated(&sState)) { + if (bme280_is_data_ready(&sState)) { uint32_t u32TFine; SBme280TPH sResult = bme280_get_measurement(&sState, &u32TFine); _bme280_print_result(u64tckNow, &sResult, u32TFine); - bme280_ack_data_updated(&sState); - bme280_set_mode_forced(&sState); + bme280_req_data(&sState); + bme280_set_mode(&sState, BME280_MODE_FORCED); u64tckNext += MS2TICKS(BME280_PERIOD_MS); } else { if (u32hmsWaitHint == 0) { diff --git a/examples/Makefile.am b/examples/Makefile.am index 3526ac6..d8ba741 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,2 +1,4 @@ AUTOMAKE_OPTIONS = -SUBDIRS=0blink 0button 0hello 0ledctrl 0timgalarm 0uart 0udma 1i2cssd1306 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 1rmttm1637 3prog1 1rmtws2812 +SUBDIRS=0blink 0button 0hello 0ledctrl 0timgalarm 0uart 0udma \ +1bme280 1i2cssd1306 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 1rmttm1637 \ +3prog1 1rmtws2812 diff --git a/modules/bme280.c b/modules/bme280.c index 8f0c564..27f486c 100644 --- a/modules/bme280.c +++ b/modules/bme280.c @@ -28,16 +28,6 @@ // ================= Internal Types ================== -/** - * Possible values of mode bits in ctrl_meas register - */ -typedef enum { - MODE_SLEEP = 0U, - MODE_FORCED = 1U, - MODE_FORCED2 = 2U, - MODE_NORMAL = 3U -} EMode; - /** * Registers at 0xf2..0xf5 store configuration in a structured way. * This type follows the structure of those registers. @@ -49,7 +39,7 @@ typedef struct { uint8_t rsvd9 : 2; bool bMeasuring : 1; uint8_t rsvd12 : 4; - EMode eMode : 2; + EBme280Mode eMode : 2; EBme280Osrs eOsrsP : 3; EBme280Osrs eOsrsT : 3; bool bSpi3wEn : 1; @@ -74,38 +64,64 @@ typedef struct { int8_t digH6; // calib[32] } SCalib; +typedef enum { + COMM_RD_CALIB0 = 0, + COMM_RD_ID, + COMM_RD_CALIB1, + COMM_RD_CFG, + COMM_RD_STATUS, + COMM_RD_DATA, + COMM_WR_RESET, + COMM_WR_CFG +} ECommAddr; + /** - * Handling of dirty/ready bits/bytes is as follows: - * 0.) if bReset is active - * 1.) write Reset, clear local config - * 1.) if bModeSet is active - * 1.) [write CtrlHum] - if dirty - * 2.) [write CtrlConfig] - if dirty - * 3.) write CtrlMeas - * 2.) if bRequestForData is active - * 1.) [read Calib0] - if not ready - * 2.) [read Calib1] - if not ready - * 3.) read Status - until updated - * 4.) read Data + * Handling of set/get bits is as follows: + * 0.) if bSetReset is active + * 1.) write Reset, then clear local memory mirror + * 1.) if bSetCtrlHum or bSetCtrlMeas or bSetConfig is active + * 1.) write the marked registers with data from ConfigOut, then copy the marked registers form ConfigOut to ConfigMirror + * 2.) if and bGetXxx flag is active, read the corresponding registers into the XxxMirror are in the following order: + * 1.) [read Calib0] - if bGetCalib0 is set, + * 2.) [read Id] - if bGetId is set, + * 3.) [read Calib1] - if bGetCalib1 is set, + * 4.) [read Data] - if bGetData is set, + * 5.) [read Config] - if any of bGetCtrlHum, bGetCtrlMeas or bGetConfig is set, but bGetStatus is not set, + * 6.) [read Status] - if bGetStatus is set. * */ typedef union { struct { - bool bDirtyCtrlHum : 1; - bool bDirtyStatus : 1; // meaning that the status bye must be read - bool bDirtyCtrlMeas : 1; // note: async comm is not triggered until bModeSet is set. - bool bDirtyConfig : 1; - bool bDataUpdated : 1; - bool bCalib0Ready : 1; - bool bCalib1Ready : 1; - bool bWaitingForRx : 1; - bool bModeSet : 1; // triggers chain of state changes until the mode bits get written - bool bRequestForData : 1; // triggers chain of state changes until data bytes are read out - bool bReset : 1; // triggers write to reset register - uint32_t rsvd11 : 5; - uint8_t u8CurAddr : 8; - uint8_t u5CurLen : 5; - }; + ///---1st byte + // WR side flags + bool bSetCtrlHum : 1; ///< ctrl_hum register should be written to peripheral + bool bSetReset : 1; ///< reset register should be written to peripheral + bool bSetCtrlMeas : 1; ///< ctrl_mes register should be written to peripheral + bool bSetConfig : 1; ///< config register should be written to peripheral + uint32_t rsvd4 : 4; + //---2nd byte + // RD side flags + bool bGetCalib0 : 1; ///< calib00..calib25 registers should be read from peripheral + bool bGetId : 1; ///< id register should be read from peripheral + bool bGetCalib1 : 1; ///< callib26..calib41 registers should be read from peripheral + bool bGetConfig : 1; ///< config register should be read from peripheral + bool bGetStatus : 1; ///< status register should be read from peripheral + bool bGetData : 1; ///< measurement data registers should be read from peripheral + uint32_t rsvd14 : 2; + //---3rd byte + bool bWaitingForRx : 1; ///< I2C R/W communication was triggered by async TX, and the RX has not / could not mark it as done. + uint32_t rsvd17 : 7; + //---4th byte + ECommAddr u4UpdAddr : 4; ///< What R/W communication is going on / vaild if bWaitingForRx is true. + uint8_t u4UpdFlag : 4; ///< The RX process requires parameters/flags for some R/W operation (e.g., ctrl/conf reg write). + } ; + + struct { + uint8_t u8Setters; + uint8_t u8Getters; + uint8_t u8CommFlags; + uint8_t u8Params; + } ; uint32_t raw; } SSyncFlags; @@ -138,6 +154,48 @@ uint8_t gau8Oversampling[] = { 16U }; +const uint8_t gau8ReadAddr[] = { + MEMADDR_CALIB0, + MEMADDR_ID, + MEMADDR_CALIB1, + MEMADDR_CTRLH, + MEMADDR_STATUS, + MEMADDR_DATA +}; + +const uint8_t gau8ReadLen[] = { + MEMLEN_CALIB0, + 1, + MEMLEN_CALIB1, + 4, + 1, + MEMLEN_DATA +}; + +const uint8_t gau8MirrorOffset[] = { + offsetof(SBme280StateDesc, au8CalibMirror), + offsetof(SBme280StateDesc, u8IdMirror), + offsetof(SBme280StateDesc, au8CalibMirror) + MEMLEN_CALIB0, + offsetof(SBme280StateDesc, au8ConfigMirror), + offsetof(SBme280StateDesc, au8ConfigMirror) + 1, + offsetof(SBme280StateDesc, au8DataMirror) +}; + +/** + * maps EBme280MetricSelector i to the config register index of osrs_i. + */ +const uint8_t gau8OsrsCfgReg[] = { + 0, 2, 2 +}; + +/** + * maps EBme280MetricSelector i to bit shift of osrs_i within the corresponding config register. + */ +const uint8_t gau8OsrsRegShift[] = { + 0, 2, 5 +}; + + // ============ Internal function declarations ================= static inline uint32_t _tmeasure_hms(const SConfigBytes *psConfig); static SBme280TPH _transform_data(const uint8_t *pu8Data); @@ -145,12 +203,12 @@ static inline int16_t _get_calib_h4(const SCalib *psCalib); static int32_t _compensate_T(int32_t i32T, const SCalib *psCalib, uint32_t *t_fine); static uint32_t _compensate_P(int32_t i32P, const SCalib *psCalib, uint32_t t_fine); static uint32_t _compensate_H(int32_t i32H, const SCalib *psCalib, uint32_t t_fine); -static void _set_mode(SBme280StateDesc *psState, EMode eMode); -static inline void _write_byte(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState, uint8_t u8MemAddr, uint8_t u8Value); -static void _write_cfgbyte(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState, uint8_t u8MemAddr); -static void _read_bytes(const SI2cIfaceCfg *psIface, AsyncResultEntry* psEntry, SBme280StateDesc *psState, uint8_t *pu8Dest, uint8_t u8MemAddr, uint8_t u8MemLen); +static void _write_reset(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); +static void _write_cfg(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); +static void _read_bytes(const SI2cIfaceCfg *psIface, AsyncResultEntry* psEntry, SBme280StateDesc *psState, uint8_t *pu8Dest, ECommAddr eCommAdd); // ============ Internal function definitions ================= + /** * Estimate the typical measurement time (t_{measure,typ}) based on oversampling settings. * @param psConfig Stores the oversampling settings. Other attributes are not used. @@ -238,39 +296,44 @@ static SBme280TPH _compensate(SBme280TPH sRaw, const SCalib *psCalib, uint32_t * _compensate_H(sRaw.i32Hum, psCalib, *pu32TFine)}; } -static void _set_mode(SBme280StateDesc *psState, EMode eMode) { - ((SConfigBytes*) psState->au8Config)->eMode = eMode; - ((SSyncFlags*) & psState->u32CommState)->bDirtyCtrlMeas = true; - ((SSyncFlags*) & psState->u32CommState)->bModeSet = true; -} - /** - * Writes a single byte into a given register on the given slave I2C device, - * and updates the internal state. - * @param psIface Interface information. - * @param psState Internal state (to update). - * @param u8MemAddr Register address. - * @param u8Value Value to write into the register. + * Makes BME280 I2C write to reset register with reset symbol. + * @param psIface I2C interface. + * @param psState State descriptor. */ -static inline void _write_byte(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState, uint8_t u8MemAddr, uint8_t u8Value) { +static void _write_reset(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState) { uint8_t au8Buf[] = { - u8MemAddr, - u8Value + MEMADDR_RESET, + SYM_RESET }; i2c_write(psIface->eBus, psIface->u8SlaveAddr, 2, au8Buf); - ((SSyncFlags*) & psState->u32CommState)->u8CurAddr = u8MemAddr; - ((SSyncFlags*) & psState->u32CommState)->u5CurLen = 1U; + ((SSyncFlags*) & psState->u32CommState)->u4UpdAddr = COMM_WR_RESET; + ((SSyncFlags*) & psState->u32CommState)->u4UpdFlag = 0U; } /** - * Writes local configuration data byte to the given register on the device. - * Wrapper to _write_byte(). - * @param psIface Interface information. - * @param psState Internal state (local configuration data is stored here). - * @param u8MemAddr Register address. + * Makes BME280 I2C writes to the config registers (ctrl_hum, ctrl_meas and config) + * with data taken from psState->au8ConfigOut. Note, if any setter + * (i.e., bSetCtrlHum, bSetCtrlMeas or bSetConfig) is unset, the + * corresponding register will not be written (it will be skipped). + * @param psIface I2C interface. + * @param psState State descriptor. */ -static void _write_cfgbyte(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState, uint8_t u8MemAddr) { - _write_byte(psIface, psState, u8MemAddr, psState->au8Config[u8MemAddr - MEMADDR_CTRLH]); +static void _write_cfg(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState) { + uint8_t au8Buf[6]; + uint8_t u8BufIdx = 0; + uint8_t u8UpdFlag = ((SSyncFlags*)(&psState->u32CommState))->u8Setters & 0x0d; + for (int i = 0; i < 4; ++i) { + if (i == 1) continue; // skipping checking of bSetReset and writing to status register + if (u8UpdFlag & (1 << i)) { + au8Buf[u8BufIdx] = MEMADDR_CTRLH + i; + au8Buf[u8BufIdx + 1] = psState->au8ConfigOut[i]; + u8BufIdx += 2; + } + } + i2c_write(psIface->eBus, psIface->u8SlaveAddr, u8BufIdx, au8Buf); + ((SSyncFlags*) & psState->u32CommState)->u4UpdAddr = COMM_WR_CFG; + ((SSyncFlags*) & psState->u32CommState)->u4UpdFlag = u8UpdFlag; } /** @@ -279,91 +342,187 @@ static void _write_cfgbyte(const SI2cIfaceCfg *psIface, SBme280StateDesc *psStat * @param psEntry Asynchronous communication entity (stores where to write to bytes read from the slave device). * @param psState Internal state (is updated). * @param pu8Dest Memory address where to write read data. - * @param u8MemAddr Register address. - * @param u8MemLen Read length. + * @param eCommAddr 'what-to-read' selector. Implicitly defines the read register area. */ -static void _read_bytes(const SI2cIfaceCfg *psIface, AsyncResultEntry* psEntry, SBme280StateDesc *psState, uint8_t *pu8Dest, uint8_t u8MemAddr, uint8_t u8MemLen) { +static void _read_bytes(const SI2cIfaceCfg *psIface, AsyncResultEntry* psEntry, SBme280StateDesc *psState, uint8_t *pu8Dest, ECommAddr eCommAddr) { + uint8_t u8MemAddr = gau8ReadAddr[eCommAddr]; + uint8_t u8MemLen = gau8ReadLen[eCommAddr]; + psEntry->pu8ReceiveBuffer = pu8Dest; psEntry->u8RxLen = u8MemLen; i2c_read_mem(psIface->eBus, psIface->u8SlaveAddr, u8MemAddr, u8MemLen); - ((SSyncFlags*) & psState->u32CommState)->u8CurAddr = u8MemAddr; - ((SSyncFlags*) & psState->u32CommState)->u5CurLen = u8MemLen; + ((SSyncFlags*) & psState->u32CommState)->u4UpdAddr = eCommAddr; + ((SSyncFlags*) & psState->u32CommState)->u4UpdFlag = 0; } +/** + * Total memory cleanup of the state descriptor, + * setting the mirrored register space to initial state. + * @param psState State descriptor to cleanup. + */ +void _clear_state(SBme280StateDesc *psState) { + memset(psState, 0, sizeof (SBme280StateDesc)); + psState->au8DataMirror[0] = 0x80; + psState->au8DataMirror[3] = 0x80; + psState->au8DataMirror[6] = 0x80; +} // ============ Interface function definitions ================= SBme280StateDesc bme280_init_state() { SBme280StateDesc sRet; - memset(&sRet, 0, sizeof (sRet)); + _clear_state(&sRet); return sRet; }; -bool bme280_set_osrs_h(SBme280StateDesc *psState, EBme280Osrs eOsrsH) { - if (bme280_get_osrs_h(psState) == eOsrsH) return false; - ((SConfigBytes*) psState->au8Config)->eOsrsH = eOsrsH; - ((SSyncFlags*) & psState->u32CommState)->bDirtyCtrlHum = true; - return true; -}; +/** + * Sets an osrs parameter in local config register and schedules write to the peripheral. + * @param psState State descriptor. + * @param eMetric Measurement metric selector. + * @param eOsrs Value to set. + */ +void bme280_set_osrs(SBme280StateDesc *psState, EBme280MetricSelector eMetric, EBme280Osrs eOsrs) { + psState->au8ConfigOut[gau8OsrsCfgReg[eMetric]] &= ~(7 << gau8OsrsRegShift[eMetric]); + psState->au8ConfigOut[gau8OsrsCfgReg[eMetric]] |= eOsrs << gau8OsrsRegShift[eMetric]; + psState->u32CommState |= 1 << gau8OsrsCfgReg[eMetric]; +} -bool bme280_set_osrs_t(SBme280StateDesc *psState, EBme280Osrs eOsrsT) { - if (bme280_get_osrs_t(psState) == eOsrsT) return false; - ((SConfigBytes*) psState->au8Config)->eOsrsT = eOsrsT; - ((SSyncFlags*) & psState->u32CommState)->bDirtyCtrlMeas = true; - return true; +/** + * Sets measurement mode in local config register and schedules write to the peripheral. + * @param psState State descriptor. + * @param eMode Value to set. + */ +void bme280_set_mode(SBme280StateDesc *psState, EBme280Mode eMode) { + ((SConfigBytes*) psState->au8ConfigOut)->eMode = eMode; + ((SSyncFlags*) & psState->u32CommState)->bSetCtrlMeas = true; } -bool bme280_set_osrs_p(SBme280StateDesc *psState, EBme280Osrs eOsrsP) { - if (bme280_get_osrs_p(psState) == eOsrsP) return false; - ((SConfigBytes*) psState->au8Config)->eOsrsP = eOsrsP; - ((SSyncFlags*) & psState->u32CommState)->bDirtyCtrlMeas = true; +/** + * Retrieves osrs value from local register. + * @param psState State descriptor. + * @param bMirror Selects mirror (received from peripheral) (true) or outgoing (to be written to peripheral) (false) local register. + * @param eMetric Selects the measurement metric. + * @return Current osrs value of the given metric. + */ +EBme280Osrs bme280_get_osrs(const SBme280StateDesc *psState, bool bMirror, EBme280MetricSelector eMetric) { + const uint8_t *pu8Regs = bMirror ? psState->au8ConfigMirror : psState->au8ConfigOut; + return (pu8Regs[gau8OsrsCfgReg[eMetric]] >> gau8OsrsRegShift[eMetric]) & 7; +} + +/** + * Retrieves mode value from local register. + * @param psState State descriptor. + * @param bMirror Selects mirror (received from peripheral) (true) or outgoing (to be written to peripheral) (false) local register. + * @return Current mode value. + */ +EBme280Mode bme280_get_mode(const SBme280StateDesc *psState, bool bMirror) { + const uint8_t *pu8Regs = bMirror ? psState->au8ConfigMirror : psState->au8ConfigOut; + return pu8Regs[2] & 3; +} + +bool bme280_set_config(SBme280StateDesc *psState, EBme280Tsb eTsb, EBme280Iir eFilter, bool bSpi3wEn) { + ((SConfigBytes*) psState->au8ConfigOut)->eTsb = eTsb; + ((SConfigBytes*) psState->au8ConfigOut)->eFilter = eFilter; + ((SConfigBytes*) psState->au8ConfigOut)->bSpi3wEn = bSpi3wEn; + ((SSyncFlags*) & psState->u32CommState)->bSetConfig = true; return true; } -EBme280Osrs bme280_get_osrs_h(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->eOsrsH; +EBme280Tsb bme280_get_tsb(const SBme280StateDesc *psState, bool bMirror) { + const uint8_t *pu8Regs = bMirror ? psState->au8ConfigMirror : psState->au8ConfigOut; + return ((const SConfigBytes*) pu8Regs)->eTsb; } -EBme280Osrs bme280_get_osrs_t(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->eOsrsT; +EBme280Iir bme280_get_filter(const SBme280StateDesc *psState, bool bMirror) { + const uint8_t *pu8Regs = bMirror ? psState->au8ConfigMirror : psState->au8ConfigOut; + return ((const SConfigBytes*) pu8Regs)->eFilter; } -EBme280Osrs bme280_get_osrs_p(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->eOsrsP; +bool bme280_get_spi3wen(const SBme280StateDesc *psState, bool bMirror) { + const uint8_t *pu8Regs = bMirror ? psState->au8ConfigMirror : psState->au8ConfigOut; + return ((const SConfigBytes*) pu8Regs)->bSpi3wEn; } -bool bme280_set_config(SBme280StateDesc *psState, EBme280Tsb eTsb, EBme280Iir eFilter, bool bSpi3wEn) { - ((SConfigBytes*) psState->au8Config)->eTsb = eTsb; - ((SConfigBytes*) psState->au8Config)->eFilter = eFilter; - ((SConfigBytes*) psState->au8Config)->bSpi3wEn = bSpi3wEn; - ((SSyncFlags*) & psState->u32CommState)->bDirtyConfig = true; - return true; +/** + * Schedules reset (write operation). + * @param psState State descriptor. + */ +void bme280_reset(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bSetReset = true; +} + +/** + * Schedules read of id register. + * @param psState State descriptor. + */ +void bme280_req_id(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bGetId = true; +} + +/** + * Schedules read of status register. + * @param psState State descriptor. + */ +void bme280_req_status(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bGetStatus = true; } -EBme280Tsb bme280_get_tsb(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->eTsb; +/** + * Schedules read of the three config and the status registers. + * @param psState State descriptor. + */ +void bme280_req_config(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bGetConfig = true; } -EBme280Iir bme280_get_filter(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->eFilter; +/** + * Schedules read of calib00..41 registers. + * @param psState State descriptor. + */ +void bme280_req_calib(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bGetCalib0 = true; + ((SSyncFlags*) & psState->u32CommState)->bGetCalib1 = true; +} + +/** + * Schedules read of data registers. + * @param psState State descriptor. + */ +void bme280_req_data(SBme280StateDesc *psState) { + ((SSyncFlags*) & psState->u32CommState)->bGetData = true; +} + +/** + * Tells whether reset is scheduled or not. + * @param psState State descriptor. + * @return Reset is scheduled. + */ +bool bme280_is_resetting(const SBme280StateDesc *psState) { + return ((const SSyncFlags*) & psState->u32CommState)->bSetReset; } -bool bme280_get_spi3wen(const SBme280StateDesc *psState) { - return ((const SConfigBytes*) psState->au8Config)->bSpi3wEn; +/** + * Gets id from mirrored register. + * Note, first, data must be read from peripheral, see bme280_req_id()! + * @param psState State descriptor. + * @return id - received from peripheral. + */ +uint8_t bme280_get_id(const SBme280StateDesc *psState) { + return psState->u8IdMirror; } -bool bme280_is_data_updated(const SBme280StateDesc *psState) { - return ((const SSyncFlags*) &psState->u32CommState)->bDataUpdated; +uint8_t bme280_get_status(const SBme280StateDesc *psState) { + return psState->au8ConfigMirror[1] & 0x09; } -void bme280_ack_data_updated(SBme280StateDesc *psState) { - ((SSyncFlags*) & psState->u32CommState)->bDataUpdated = false; +bool bme280_is_data_ready(const SBme280StateDesc *psState) { + return !((const SSyncFlags*) &psState->u32CommState)->bGetData; } SBme280TPH bme280_get_measurement(const SBme280StateDesc *psState, uint32_t *pu32TFine) { - return bme280_calc_measeurement(psState->au8Data, psState->au8Calib, pu32TFine); + return bme280_calc_measurement(psState->au8DataMirror, psState->au8CalibMirror, pu32TFine); } -SBme280TPH bme280_calc_measeurement(const uint8_t *pu8Data, const uint8_t *pu8Calib, uint32_t *pu32TFine) { +SBme280TPH bme280_calc_measurement(const uint8_t *pu8Data, const uint8_t *pu8Calib, uint32_t *pu32TFine) { uint32_t u32TFine; SBme280TPH sRaw = _transform_data(pu8Data); SBme280TPH sRet = _compensate(sRaw, (const SCalib*) pu8Calib, &u32TFine); @@ -374,138 +533,129 @@ SBme280TPH bme280_calc_measeurement(const uint8_t *pu8Data, const uint8_t *pu8Ca return sRet; } -void bme280_set_mode_forced(SBme280StateDesc *psState) { - _set_mode(psState, MODE_FORCED); +bool bme280_has_async_todo(const SBme280StateDesc *psState) { + return psState->u32CommState & 0xFFFF; } -void bme280_set_mode_normal(SBme280StateDesc *psState) { - _set_mode(psState, MODE_NORMAL); +bool bme280_is_waiting(const SBme280StateDesc *psState) { + return ((const SSyncFlags*) & psState->u32CommState)->bWaitingForRx; } -void bme280_set_mode_sleep(SBme280StateDesc *psState) { - _set_mode(psState, MODE_SLEEP); -} - -void bme280_reset(SBme280StateDesc *psState) { - ((SSyncFlags*) & psState->u32CommState)->bReset = true; -} - -bool bme280_is_resetting(const SBme280StateDesc *psState) { - return ((const SSyncFlags*) & psState->u32CommState)->bReset; +uint32_t bme280_measurement_duration_hms(const SBme280StateDesc *psState) { + return _tmeasure_hms((const SConfigBytes*)psState->au8ConfigMirror); } +/** + * Check Peripheral state. + * @param psState + * @param pu32hmsWaitHint + * @return + */ bool bme280_async_rx_cycle(SBme280StateDesc *psState, uint32_t *pu32hmsWaitHint) { SSyncFlags *psFlags = (SSyncFlags*) & psState->u32CommState; - SConfigBytes *psConf = (SConfigBytes*) psState->au8Config; bool bRet = false; *pu32hmsWaitHint = 0U; - if (psFlags->bWaitingForRx) { - AsyncResultEntry* psEntry = lockmgr_get_entry(psState->u32LastLabel); - if (psEntry) { - if (psEntry->bReady) { - if (!(psEntry->u32IntSt & I2C_INT_MASK_ERR)) { - switch (psFlags->u8CurAddr) { - case MEMADDR_RESET: // write - psFlags->bReset = false; - *(uint32_t*) psState->au8Config = 0; - break; - case MEMADDR_CTRLH: // write - psFlags->bDirtyCtrlHum = false; - break; - case MEMADDR_CONFIG: // write - psFlags->bDirtyConfig = false; - break; - case MEMADDR_CTRLM: // write - psFlags->bDirtyCtrlMeas = false; - psFlags->bModeSet = false; - if (psConf->eMode != MODE_SLEEP) { - psFlags->bDirtyStatus = true; - psFlags->bRequestForData = true; - *pu32hmsWaitHint = _tmeasure_hms(psConf); - } - break; - case MEMADDR_STATUS: // read, requires check - if (!psConf->bMeasuring) { - psFlags->bDirtyStatus = false; - } - break; - case MEMADDR_CALIB0: // read - psFlags->bCalib0Ready = true; - break; - case MEMADDR_CALIB1: // read - psFlags->bCalib1Ready = true; - break; - case MEMADDR_DATA: // read - psFlags->bDataUpdated = true; - if (psConf->eMode != MODE_NORMAL) { - psFlags->bRequestForData = false; - } else { - *pu32hmsWaitHint = gau32hmsStandbyTime[psConf->eTsb]; - } - break; - default: // unexpected address - ; // TODO - } - bRet = true; - } else { - // no state change, retry TX - // TODO: update retry counter + + // check I.) + if (!psFlags->bWaitingForRx) { + return false; + } + + // check II.) + AsyncResultEntry* psEntry = lockmgr_get_entry(psState->u32LastLabel); + if (!psEntry) { + // TODO: this is a serious error: no lockmgr entry found + return false; + } + + // check III.) + if (!psEntry->bReady) { // still waiting for i2c bus to be ready + return false; + } + + // At this point it is sure, that the communication is over (ready). + // check IV.) + if (psEntry->u32IntSt & I2C_INT_MASK_ERR) { // failure + // TODO: store error code + } else { // success + + // some local data must be update, + // e.g., unset todo flags, + // migrate data to mirror memory area + switch (psFlags->u4UpdAddr) { + // setters + case COMM_WR_RESET: + psFlags->bSetReset = false; + // store local values before cleanup + uint32_t u32TmpCommState = psState->u32CommState; + uint32_t u32TmpLastLabel = psState->u32LastLabel; + uint32_t u32TmpConfigOut = *((uint32_t*)psState->au8ConfigOut); + // cleanup + _clear_state(psState); + // restore local values after cleanup + psState->u32CommState = u32TmpCommState; + psState->u32LastLabel = u32TmpLastLabel; + *((uint32_t*)psState->au8ConfigOut) = u32TmpConfigOut; + break; + case COMM_WR_CFG: + if (psFlags->bSetCtrlHum) { + psState->au8ConfigMirror[0] = psState->au8ConfigOut[0]; + psFlags->bSetCtrlHum = false; } - lockmgr_release_entry(psState->u32LastLabel); - psFlags->bWaitingForRx = false; - } else { // still waiting for i2c bus to be ready - return false; - } - } else { - // TODO: error, no lockmgr entry found + if (psFlags->bSetCtrlMeas) { + psState->au8ConfigMirror[2] = psState->au8ConfigOut[2]; + psFlags->bSetCtrlMeas = false; + } + if (psFlags->bSetConfig) { + psState->au8ConfigMirror[3] = psState->au8ConfigOut[3]; + psFlags->bSetConfig = false; + } + break; + // getters + default: + uint8_t u8FlagMask = 1 << (psFlags->u4UpdAddr - COMM_RD_CALIB0); + psFlags->u8Getters &= ~u8FlagMask; } + psFlags->u4UpdAddr = 0; + psFlags->u4UpdFlag = 0; + bRet = true; } + lockmgr_release_entry(psState->u32LastLabel); + psFlags->bWaitingForRx = false; return bRet; } bool bme280_async_tx_cycle(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState) { SSyncFlags *psFlags = (SSyncFlags*) & psState->u32CommState; + // check I.) there is no other communication in progress if (psFlags->bWaitingForRx) return false; - bool bWrite = psFlags->bModeSet || psFlags->bReset; - bool bRead = psFlags->bRequestForData; - + // check II.) anything to do + bool bWrite = psState->u32CommState & 0x0000000F; + bool bRead = psState->u32CommState & 0x0000FF00; if (!bWrite && !bRead) return false; + + // check III.) I2C ready if (!lockmgr_acquire_lock(psIface->eLck, &psState->u32LastLabel)) return false; AsyncResultEntry* psEntry = lockmgr_get_entry(psState->u32LastLabel); bool bRet = true; if (bWrite) { - if (psFlags->bReset) { - _write_byte(psIface, psState, MEMADDR_RESET, SYM_RESET); - } else if (psFlags->bDirtyCtrlHum) { - _write_cfgbyte(psIface, psState, MEMADDR_CTRLH); - } else if (psFlags->bDirtyConfig) { - _write_cfgbyte(psIface, psState, MEMADDR_CONFIG); - } else if (psFlags->bDirtyCtrlMeas) { - _write_cfgbyte(psIface, psState, MEMADDR_CTRLM); + if (psFlags->bSetReset) { + _write_reset(psIface, psState); } else { - // what to write? - bRet = false; + _write_cfg(psIface, psState); } } else if (bRead) { - if (!psFlags->bCalib0Ready) { - _read_bytes(psIface, psEntry, psState, psState->au8Calib, MEMADDR_CALIB0, MEMLEN_CALIB0); - } else if (!psFlags->bCalib1Ready) { - _read_bytes(psIface, psEntry, psState, psState->au8Calib + MEMLEN_CALIB0, MEMADDR_CALIB1, MEMLEN_CALIB1); - } else if (psFlags->bDirtyStatus) { - _read_bytes(psIface, psEntry, psState, psState->au8Config + (MEMADDR_STATUS - MEMADDR_CTRLH), MEMADDR_STATUS, 1); - } else if (!psFlags->bDataUpdated) { - _read_bytes(psIface, psEntry, psState, psState->au8Data, MEMADDR_DATA, MEMLEN_DATA); - } else { - // what to read? - bRet = false; - } - } else { - // unexpected branch - bRet = false; + ECommAddr eCommAddr = psFlags->bGetCalib0 ? COMM_RD_CALIB0 : + psFlags->bGetId ? COMM_RD_ID : + psFlags->bGetCalib1 ? COMM_RD_CALIB1 : + psFlags->bGetData ? COMM_RD_DATA : + !psFlags->bGetStatus ? COMM_RD_CFG : + COMM_RD_STATUS; + _read_bytes(psIface, psEntry, psState, (uint8_t*) psState + gau8MirrorOffset[eCommAddr], eCommAddr); } if (bRet) { // kind of exception handling psFlags->bWaitingForRx = true; diff --git a/modules/bme280.h b/modules/bme280.h index 839b40a..e099e31 100644 --- a/modules/bme280.h +++ b/modules/bme280.h @@ -14,6 +14,14 @@ extern "C" { #include #include "utils/i2ciface.h" + // ============= Types =============== + + typedef enum { + BME280_SEL_H = 0, + BME280_SEL_P, + BME280_SEL_T + } EBme280MetricSelector; + /** * Possible values of osrs_t, osrs_p and osrs_h (oversampling). */ @@ -29,6 +37,16 @@ extern "C" { } EBme280Osrs; /** + * Possible values of mode bits in ctrl_meas register + */ + typedef enum { + BME280_MODE_SLEEP = 0U, + BME280_MODE_FORCED = 1U, + BME280_MODE_FORCED2 = 2U, + BME280_MODE_NORMAL = 3U + } EBme280Mode; + + /** * Possible values of t_sb (standby time). */ typedef enum { @@ -73,37 +91,47 @@ extern "C" { uint32_t u32LastLabel; uint32_t u32CommState; // the following attributes are storing data trasmitted to / received from the target device - uint8_t au8Calib[42]; ///< bytes at mem 0x88 .. 0xa1, 0xe1 .. 0xf0 - uint8_t au8Data[8]; ///< bytes at mem 0xf7 .. 0xfe - uint8_t au8Config[4]; ///< bytes at mem 0xf2 .. 0xf5 + // memory to send to peripheral + uint8_t au8ConfigOut[4]; ///< bytes at mem 0xf2 .. 0xf5. Note: 0xf3 (status) is RO, so it is never written + // memory mirror + uint8_t au8ConfigMirror[4]; ///< bytes at mem 0xf2 .. 0xf5 + uint8_t au8DataMirror[8]; ///< bytes at mem 0xf7 .. 0xfe + uint8_t au8CalibMirror[42]; ///< bytes at mem 0x88 .. 0xa1, 0xe1 .. 0xf0 + uint8_t u8IdMirror; ///< byte at mem 0xd0 } SBme280StateDesc; - // interface functions + // ============= Interface functions =============== SBme280StateDesc bme280_init_state(); - bool bme280_set_osrs_h(SBme280StateDesc *psState, EBme280Osrs eOsrsH); - bool bme280_set_osrs_t(SBme280StateDesc *psState, EBme280Osrs eOsrsT); - bool bme280_set_osrs_p(SBme280StateDesc *psState, EBme280Osrs eOsrsP); - EBme280Osrs bme280_get_osrs_h(const SBme280StateDesc *psState); - EBme280Osrs bme280_get_osrs_t(const SBme280StateDesc *psState); - EBme280Osrs bme280_get_osrs_p(const SBme280StateDesc *psState); + void bme280_set_osrs(SBme280StateDesc *psState, EBme280MetricSelector eMetric, EBme280Osrs eOsrs); + void bme280_set_mode(SBme280StateDesc *psState, EBme280Mode eMode); - bool bme280_set_config(SBme280StateDesc *psState, EBme280Tsb eTsb, EBme280Iir eFilter, bool bSpi3wEn); - EBme280Tsb bme280_get_tsb(const SBme280StateDesc *psState); - EBme280Iir bme280_get_filter(const SBme280StateDesc *psState); - bool bme280_get_spi3wen(const SBme280StateDesc *psState); + EBme280Osrs bme280_get_osrs(const SBme280StateDesc *psState, bool bMirror, EBme280MetricSelector eMetric); + EBme280Mode bme280_get_mode(const SBme280StateDesc *psState, bool bMirror); - bool bme280_is_data_updated(const SBme280StateDesc *psState); - void bme280_ack_data_updated(SBme280StateDesc *psState); - SBme280TPH bme280_get_measurement(const SBme280StateDesc *psState, uint32_t *pu32TFine); - SBme280TPH bme280_calc_measeurement(const uint8_t *pu8Data, const uint8_t *pu8Calib, uint32_t *pu32TFine); + bool bme280_set_config(SBme280StateDesc *psState, EBme280Tsb eTsb, EBme280Iir eFilter, bool bSpi3wEn); + EBme280Tsb bme280_get_tsb(const SBme280StateDesc *psState, bool bMirror); + EBme280Iir bme280_get_filter(const SBme280StateDesc *psState, bool bMirror); + bool bme280_get_spi3wen(const SBme280StateDesc *psState, bool bMirror); - void bme280_set_mode_forced(SBme280StateDesc *psState); - void bme280_set_mode_normal(SBme280StateDesc *psState); - void bme280_set_mode_sleep(SBme280StateDesc *psState); void bme280_reset(SBme280StateDesc *psState); + void bme280_req_id(SBme280StateDesc *psState); + void bme280_req_config(SBme280StateDesc *psState); + void bme280_req_status(SBme280StateDesc *psState); + void bme280_req_calib(SBme280StateDesc *psState); + void bme280_req_data(SBme280StateDesc *psState); + bool bme280_is_resetting(const SBme280StateDesc *psState); + uint8_t bme280_get_id(const SBme280StateDesc *psState); + uint8_t bme280_get_status(const SBme280StateDesc *psState); + bool bme280_is_data_ready(const SBme280StateDesc *psState); + + SBme280TPH bme280_get_measurement(const SBme280StateDesc *psState, uint32_t *pu32TFine); + SBme280TPH bme280_calc_measurement(const uint8_t *pu8Data, const uint8_t *pu8Calib, uint32_t *pu32TFine); + bool bme280_has_async_todo(const SBme280StateDesc *psState); + bool bme280_is_waiting(const SBme280StateDesc *psState); + uint32_t bme280_measurement_duration_hms(const SBme280StateDesc *psState); bool bme280_async_rx_cycle(SBme280StateDesc *psState, uint32_t *pu32hmsWaitHint); bool bme280_async_tx_cycle(const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); From 1bc0c48b0046a56e273d332a3dd2c6f4b8f9ce12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZIGETI=20J=C3=A1nos?= Date: Fri, 1 May 2026 16:38:51 +0200 Subject: [PATCH 2/4] [ISSUE-46]: some tests on bme280.c --- cov/coverage.mk | 14 +++++++++---- modules/bme280.c | 3 +++ src/lockmgr.h | 1 + tests/Makefile.am | 9 ++++++--- tests/bitgen_test.c | 2 +- tests/bme280_test.c | 41 ++++++++++++++++++++++++++++++++++++++ tests/mocks/i2c_mock.c | 15 ++++++++++++++ tests/mocks/lockmgr_mock.c | 20 +++++++++++++++++++ 8 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 tests/bme280_test.c create mode 100644 tests/mocks/i2c_mock.c create mode 100644 tests/mocks/lockmgr_mock.c diff --git a/cov/coverage.mk b/cov/coverage.mk index 0ba248c..3c4fcdc 100644 --- a/cov/coverage.mk +++ b/cov/coverage.mk @@ -25,7 +25,7 @@ HEADERS_MODULES=$(wildcard $(MODDIR)/*.h) OBJS_MODULES=$(addprefix modules/, $(notdir $(SOURCES_MODULES:.c=.o))) GCDA=$(OBJS:.o=.gcda) $(OBJS_UTILS:.o=.gcda) $(OBJS_MODULES:.o=.gcda) -GCNO=$(OBJS:.o=.gcno) $(OBJS_UTILS:.o=.gcno) $(OBJS_MODULES:.o=.gcda) +GCNO=$(OBJS:.o=.gcno) $(OBJS_UTILS:.o=.gcno) $(OBJS_MODULES:.o=.gcno) GCDA_EXIST := $(foreach gcda,$(GCDA),$(wildcard $(gcda))) GCNO_EXIST := $(foreach gcno,$(GCNO),$(wildcard $(gcno))) @@ -44,6 +44,10 @@ src/%.c.gcov: src/%.gcno cd src && $(GCOV) -wrabcfu -s ../.. $(<:src/%.gcno=%.o) [ -f $@ ] || mv src/$(notdir $@) $@ +modules/%.c.gcov: modules/%.gcno + cd modules && $(GCOV) -wrabcfu -s ../.. $(<:modules/%.gcno=%.o) + [ -f $@ ] || mv modules/$(notdir $@) $@ + $(HTML): $(PROJ).info genhtml -s --branch-coverage $(PROJ).info --output-directory $(dir $(HTML)) @@ -54,12 +58,12 @@ $(PROJ).pre.info: $(PROJ).base.info $(PROJ).test.info lcov --rc lcov_branch_coverage=1 -a $(PROJ).base.info -a $(PROJ).test.info -o $@ $(PROJ).base.info: testrun - lcov -z -d src - lcov --rc lcov_branch_coverage=1 -c -i -d src -o $@ + lcov -z -d src -d modules + lcov --rc lcov_branch_coverage=1 -c -i -d src -d modules -o $@ $(PROJ).test.info: $(PROJ).base.info $(MAKE) -C tests check - lcov --rc lcov_branch_coverage=1 -c -d src -o $@ + lcov --rc lcov_branch_coverage=1 -c -d src -d modules -o $@ testrun: tests/Makefile FORCE @@ -78,12 +82,14 @@ clean: rm -f $(PROJ).test.info rm -rf $(dir $(HTML)) rm -f src/*.gcda + rm -f modules/*.gcda rm -f tests/*.gcda $(MAKE) -C tests clean distclean: clean $(MAKE) -C tests distclean rm -rf src + rm -rf modules rm -rf tests .PHONY: clean distclean source testrun gcov _gcov diff --git a/modules/bme280.c b/modules/bme280.c index 27f486c..0f49e1a 100644 --- a/modules/bme280.c +++ b/modules/bme280.c @@ -5,6 +5,7 @@ * See LICENSE or for full license details. */ #include +#include #include #include "i2c.h" #include "bme280.h" @@ -614,8 +615,10 @@ bool bme280_async_rx_cycle(SBme280StateDesc *psState, uint32_t *pu32hmsWaitHint) break; // getters default: + { uint8_t u8FlagMask = 1 << (psFlags->u4UpdAddr - COMM_RD_CALIB0); psFlags->u8Getters &= ~u8FlagMask; + } } psFlags->u4UpdAddr = 0; psFlags->u4UpdFlag = 0; diff --git a/src/lockmgr.h b/src/lockmgr.h index 5f1b52d..03e85dc 100644 --- a/src/lockmgr.h +++ b/src/lockmgr.h @@ -12,6 +12,7 @@ extern "C" { #endif #include +#include // ============== Defines ============== diff --git a/tests/Makefile.am b/tests/Makefile.am index a507215..d27e37d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,11 +1,14 @@ TESTS= \ bytegen_test \ - bitgen_test + bitgen_test \ + bme280_test check_PROGRAMS = $(TESTS) -AM_CPPFLAGS = -I${top_srcdir}/../src -LDADD = gentest_common.o +AM_CPPFLAGS = -I${top_srcdir}/../src -I${top_srcdir}/../modules +bitgen_test_LDADD = gentest_common.o +bytegen_test_LDADD = gentest_common.o bitgen_test_SOURCES = bitgen_test.c ../src/utils/generators.c bytegen_test_SOURCES = bytegen_test.c ../src/utils/generators.c +bme280_test_SOURCES = bme280_test.c ../modules/bme280.c mocks/i2c_mock.c mocks/lockmgr_mock.c diff --git a/tests/bitgen_test.c b/tests/bitgen_test.c index c622fef..3baaa9d 100644 --- a/tests/bitgen_test.c +++ b/tests/bitgen_test.c @@ -110,7 +110,7 @@ bool test_bitgen_reset(SBitGenInitParam sParam, uint8_t u8Input, uint8_t u8Reset for (; i < 8; ++i) { au8Actual[i] = bitgen_next(&sGen); } - check_seq8_equal(au8Actual, pu8Exp, 8); + return check_seq8_equal(au8Actual, pu8Exp, 8); } bool test_bitseqgen_iter(SBitGenInitParam sParam, const uint8_t *pu8Input, size_t szInputLen, const uint8_t *pu8Expected) { diff --git a/tests/bme280_test.c b/tests/bme280_test.c new file mode 100644 index 0000000..5ccfac7 --- /dev/null +++ b/tests/bme280_test.c @@ -0,0 +1,41 @@ +#include +#include +#include "bme280.h" + +bool test_duration_calc_1() { + SBme280StateDesc sDesc = bme280_init_state(); + uint32_t au32Exp[] = {2, 3 + 4, 3 + 8, 3 + 16, 3 + 32, 3 + 64, 3 + 64, 3 + 64}; + bool bRet = true; + for (uint8_t i = 0; i < 8; ++i) { + sDesc.au8ConfigMirror[0] = i; + uint32_t u32Res0 = bme280_measurement_duration_hms(&sDesc); + if (au32Exp[i] != u32Res0) { + fprintf(stderr, "Measurement duration calculation failed for osrs_h=%u: %u (exp: %u)\n", i, u32Res0, au32Exp[i]); + bRet = false; + } + } + return bRet; +} + +bool test_is_waiting() { + SBme280StateDesc sDesc = bme280_init_state(); + bool bRet = true; + bool bRes0 = bme280_is_waiting(&sDesc); + if (bRes0) { + fprintf(stderr, "Is waiting failed (expected: %d vs. actual: %d)\n", false, bRes0); + bRet = false; + } + sDesc.u32CommState|=0x00010000; + bool bRes1 = bme280_is_waiting(&sDesc); + if (!bRes1) { + fprintf(stderr, "Is waiting failed (expected: %d vs. actual: %d)\n", true, bRes1); + bRet = false; + } + return bRet; +} + +int main() { + assert(test_duration_calc_1()); + assert(test_is_waiting()); + return 0; +} \ No newline at end of file diff --git a/tests/mocks/i2c_mock.c b/tests/mocks/i2c_mock.c new file mode 100644 index 0000000..8817407 --- /dev/null +++ b/tests/mocks/i2c_mock.c @@ -0,0 +1,15 @@ +#include "i2c.h" + +// void i2c_isr_init(); +// void i2c_isr_start(ECpu eCpu, EI2CBus eBus, uint8_t u8IntChannel); +// uint8_t i2c_isr_register(EI2CBus eBus, uint32_t u32IntMask, Isr fIsr, void *pvParam); +// void i2c_isr_unregister(EI2CBus eBus, uint8_t u8Idx); + +void i2c_write(EI2CBus eBus, uint8_t u8Addr, uint32_t u32Len, const uint8_t *pu8Dat) { + +} +//void i2c_read(EI2CBus eBus, uint8_t u8Addr, uint8_t u8RxLen); +void i2c_read_mem(EI2CBus eBus, uint8_t u8Addr, uint8_t u8MemAddr, uint8_t u8RxLen) { + +} +// void i2c_init_controller(EI2CBus e8Bus, uint8_t u8SclPin, uint8_t u8SdaPin, uint32_t u32tckPeriod); diff --git a/tests/mocks/lockmgr_mock.c b/tests/mocks/lockmgr_mock.c new file mode 100644 index 0000000..2dbdb2b --- /dev/null +++ b/tests/mocks/lockmgr_mock.c @@ -0,0 +1,20 @@ +#include "lockmgr.h" +#include + +//void lockmgr_init(); +bool lockmgr_acquire_lock(ELockmgrResource eBus, uint32_t *pu32Label) { + return true; +} + +//bool lockmgr_is_locked(ELockmgrResource eBus); +//uint32_t lockmgr_get_lock_owner(ELockmgrResource eBus); +void lockmgr_free_lock(ELockmgrResource eBus) { + +} + +AsyncResultEntry *lockmgr_get_entry(uint32_t u32Label) { + return NULL; +} +void lockmgr_release_entry(uint32_t u32Label) { + +} From 356243e9683c0e36947372b9af30e0263acdbd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZIGETI=20J=C3=A1nos?= Date: Sat, 9 May 2026 10:12:04 +0200 Subject: [PATCH 3/4] [ISSUE-46]: tune I2C speed, tests --- cov/coverage.mk | 37 ++++--- examples/1bme280/Makefile.am | 2 +- examples/1bme280/README.md | 14 ++- examples/1bme280/bme280app.c | 86 ++++++++++++++--- examples/1bme280/exptuner.c | 181 +++++++++++++++++++++++++++++++++++ examples/1bme280/exptuner.h | 55 +++++++++++ tests/Makefile.am | 6 +- tests/bitgen_test.c | 4 +- tests/bme280_test.c | 37 ++++++- tests/exptuner_test.c | 94 ++++++++++++++++++ 10 files changed, 483 insertions(+), 33 deletions(-) create mode 100644 examples/1bme280/exptuner.c create mode 100644 examples/1bme280/exptuner.h create mode 100644 tests/exptuner_test.c diff --git a/cov/coverage.mk b/cov/coverage.mk index 3c4fcdc..1fba3d8 100644 --- a/cov/coverage.mk +++ b/cov/coverage.mk @@ -9,23 +9,28 @@ GCOV=gcov HTML=html/index.html SRCDIR=../src MODDIR=../modules +EGDIR=../examples ## we get direct information about modification time ## note: I do not like wildcard, but could not find better solution -SOURCES=$(wildcard $(SRCDIR)/*.c) -HEADERS=$(wildcard $(SRCDIR)/*.h) -OBJS=$(addprefix src/, $(notdir $(SOURCES:.c=.o))) +SOURCES_SRC=$(wildcard $(SRCDIR)/*.c) $(wildcard $(SRCDIR)/**/*.c) +HEADERS_SRC=$(wildcard $(SRCDIR)/*.h)$(wildcard $(SRCDIR)/**/*.h) +OBJS_SRC_PRE=$(SOURCES_SRC:%.c=%.o) +OBJS_SRC=$(OBJS_SRC_PRE:../%=%) -SOURCES_UTILS=$(wildcard $(SRCDIR)/utils/*.c) -HEADERS_UTILS=$(wildcard $(SRCDIR)/utils/*.h) -OBJS_UTILS=$(addprefix src/utils/, $(notdir $(SOURCES_UTILS:.c=.o))) +SOURCES_MODULES=$(wildcard $(MODDIR)/*.c) $(wildcard $(MODDIR)/**/*.c) +HEADERS_MODULES=$(wildcard $(MODDIR)/*.h) $(wildcard $(MODDIR)/**/*.h) +OBJS_MODULES_PRE=$(SOURCES_MODULES:%.c=%.o) +OBJS_MODULES=$(OBJS_MODULES_PRE:../%=%) -SOURCES_MODULES=$(wildcard $(MODDIR)/*.c) -HEADERS_MODULES=$(wildcard $(MODDIR)/*.h) -OBJS_MODULES=$(addprefix modules/, $(notdir $(SOURCES_MODULES:.c=.o))) +SOURCES_EXAMPLES=$(wildcard $(EGDIR)/*.c) $(wildcard $(EGDIR)/**/*.c) +HEADERS_EXAMPLES=$(wildcard $(EGDIR)/*.h) $(wildcard $(EGDIR)/**/*.h) +OBJS_EXAMPLES_PRE=$(SOURCES_EXAMPLES:%.c=%.o) +OBJS_EXAMPLES=$(OBJS_EXAMPLES_PRE:../%=%) -GCDA=$(OBJS:.o=.gcda) $(OBJS_UTILS:.o=.gcda) $(OBJS_MODULES:.o=.gcda) -GCNO=$(OBJS:.o=.gcno) $(OBJS_UTILS:.o=.gcno) $(OBJS_MODULES:.o=.gcno) + +GCDA=$(OBJS_SRC:.o=.gcda) $(OBJS_MODULES:.o=.gcda) $(OBJS_EXAMPLES:.o=.gcda) +GCNO=$(OBJS_SRC:.o=.gcno) $(OBJS_MODULES:.o=.gcno) $(OBJS_EXAMPLES:.o=.gcno) GCDA_EXIST := $(foreach gcda,$(GCDA),$(wildcard $(gcda))) GCNO_EXIST := $(foreach gcno,$(GCNO),$(wildcard $(gcno))) @@ -48,6 +53,10 @@ modules/%.c.gcov: modules/%.gcno cd modules && $(GCOV) -wrabcfu -s ../.. $(<:modules/%.gcno=%.o) [ -f $@ ] || mv modules/$(notdir $@) $@ +examples/%.c.gcov: examples/%.gcno + cd examples && $(GCOV) -wrabcfu -s ../.. $(<:examples/%.gcno=%.o) + [ -f $@ ] || mv examples/$(notdir $@) $@ + $(HTML): $(PROJ).info genhtml -s --branch-coverage $(PROJ).info --output-directory $(dir $(HTML)) @@ -59,11 +68,11 @@ $(PROJ).pre.info: $(PROJ).base.info $(PROJ).test.info $(PROJ).base.info: testrun lcov -z -d src -d modules - lcov --rc lcov_branch_coverage=1 -c -i -d src -d modules -o $@ + lcov --rc lcov_branch_coverage=1 -c -i -d src -d modules -d examples -o $@ $(PROJ).test.info: $(PROJ).base.info $(MAKE) -C tests check - lcov --rc lcov_branch_coverage=1 -c -d src -d modules -o $@ + lcov --rc lcov_branch_coverage=1 -c -d src -d modules -d examples -o $@ testrun: tests/Makefile FORCE @@ -83,6 +92,7 @@ clean: rm -rf $(dir $(HTML)) rm -f src/*.gcda rm -f modules/*.gcda + rm -f examples/*.gcda rm -f tests/*.gcda $(MAKE) -C tests clean @@ -90,6 +100,7 @@ distclean: clean $(MAKE) -C tests distclean rm -rf src rm -rf modules + rm -rf examples rm -rf tests .PHONY: clean distclean source testrun gcov _gcov diff --git a/examples/1bme280/Makefile.am b/examples/1bme280/Makefile.am index c09ad06..fcedb63 100644 --- a/examples/1bme280/Makefile.am +++ b/examples/1bme280/Makefile.am @@ -28,7 +28,7 @@ LDADD = $(top_builddir)/src/libesp32basic.a $(top_builddir)/modules/libesp32modu bin_PROGRAMS = \ bme280app.elf -bme280app_elf_SOURCES = bme280app.c +bme280app_elf_SOURCES = bme280app.c exptuner.c if WITH_BINARIES CLEANFILES = \ diff --git a/examples/1bme280/README.md b/examples/1bme280/README.md index 1b953df..b3a66ea 100644 --- a/examples/1bme280/README.md +++ b/examples/1bme280/README.md @@ -1,6 +1,6 @@ ### Simple application to BME280 sensor -In this small example application we show the usage of BME280. +In this small example application we show how to use the BME280 sensor. #### Hardware components @@ -30,6 +30,16 @@ The application accepts the following commands (on UART0): * `c` - read config/control/status registers to local mirror; * `r` - reset; * `i` - information; -* `I` - Toggle verbose information (initially: off). +* `I` - Toggle verbose information (initially: off); +* `3` / `4` - Decrease / Increase communication speed (SCL frequency, default: 100KHz, multipliers: ..., 0.5, 1, 2, 5, 10, 20, ...); +* `#` / `$` - Decrease / Increase communication speed (smooth multipliers: 1.0, 1.1, 1.2, 1.3, ..., 1.9(, 2.0, 2.1, ..., 2.4)); +* `;` / `'` - Decrease / Increase IIR filter value. + + +Note: The maximal I2C SCL frequency on esp32basic is 5.5 MHz, +this is the upper bound of communication speed the application allows. +However, the BME280 Specificaton states that `Digital interface I2C (up to 3.4 MHz)` (page 2). +With the current I2C timing setting (SCL high/low period, START/STOP/SDA delays), +the maximal communication speed is 1.8 MHz. #### Practices diff --git a/examples/1bme280/bme280app.c b/examples/1bme280/bme280app.c index 0d17a6f..a482fa3 100644 --- a/examples/1bme280/bme280app.c +++ b/examples/1bme280/bme280app.c @@ -1,5 +1,5 @@ /* - * Copyright 2025 SZIGETI János + * Copyright 2026 SZIGETI János * * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3. * See LICENSE or for full license details. @@ -22,6 +22,7 @@ #include "timg.h" #include "typeaux.h" #include "esp_attr.h" +#include "exptuner.h" #include "bme280.h" #include "utils/i2cutils.h" @@ -29,8 +30,9 @@ // #1: Timings -- 50ms: 20Hz update freq. #define UART_FREQ_HZ 115200U -#define I2C0_FREQ_HZ 400000U -#define I2CSCAN_PERIOD_MS 5050U +#define I2C0_FREQ_DEFAULT_HZ 400000U +#define I2C0_FREQ_MIN_HZ 10000U +#define I2C0_FREQ_MAX_HZ 5500000U #define BME280_TSTARTUP_MS 2U #define UARTCTRL_PERIOD_MS 100U @@ -54,7 +56,6 @@ static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, ui static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280StateDesc *psState); static void _uartctrl_cycle(uint64_t u64tckNow); - void _i2c_error(void *pvParam); // =================== Global constants ================ @@ -72,13 +73,32 @@ const uint32_t gau32msForcedPeriod[] = { 10000 }; +const uint32_t gau32IirFilter[] = { + 0, + 2, + 4, + 8, + 16, + 16, + 16, + 16 +}; + // ==================== Local Data ================ static SBme280StateDesc gsState; static bool gbDoubleWait = false; // BUGFIX: apparently, after modifying osrs_x value, the first measurement takes twice as much time as expected. static bool gbVerbose = false; static SI2cIfaceCfg gsIface; static uint8_t gu8ForcedPeriodIdx = 4; - +static uint8_t gu8IirFilterIdx = 0; +static EBme280Tsb geTsb = BME280_TSB_500US; +static bool gbSPI3W = false; + +static SExpTuner2Desc gsI2CFreqTuner; +static SExpTuner2Value gsI2CFreq; +static bool gbI2CFreqDirty = false; +static SExpTuner2Value gsI2CFreqMax; +static SExpTuner2Value gsI2CFreqMin; // ==================== Implementation ================ // -------------- Internal functions -------------- @@ -196,6 +216,13 @@ static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme2 } } } + + // set i2c clock if needed + if (gbI2CFreqDirty && !bme280_is_waiting(psState)) { + i2c_settiming(i2c_regs(BME280_I2C_CH), HZ2APBTICKS(exptuner2_get(&gsI2CFreqTuner, &gsI2CFreq))); + gbI2CFreqDirty = false; + } + // TX side bool bTxRes = bme280_async_tx_cycle(psIface, psState); bool bTodo = bme280_has_async_todo(psState); @@ -205,7 +232,7 @@ static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme2 if (bTodo && !bTxRes) { // could not initialize TX, retry soon // do not increase nxt timestamp } else if (bWaitForRx) { - if (((psState->u32CommState>>24)&0x0f)==6) { + if (((psState->u32CommState >> 24) & 0x0f) == 6) { u64tckNext = u64tckNow + MS2TICKS(BME280_TSTARTUP_MS); } // do not increase nxt timestamp @@ -261,14 +288,28 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { int8_t i8OsrsTDiff = 0; int8_t i8OsrsPDiff = 0; int8_t i8OsrsHDiff = 0; + int8_t i8IirDiff = 0; int8_t i8TsbDiff = 0; int8_t i8TfpDiff = 0; // forced mode period static bool bInReset = false; + uint8_t u8I2CFreqTune = 0; if (u64tckNext <= u64tckNow) { while (0 < (gsUART0.STATUS & 0xff)) { char cCtrl = gsUART0Mapped.FIFO & 0xff; switch (cCtrl) { + case '3': + u8I2CFreqTune = 4; + break; + case '4': + u8I2CFreqTune = 5; + break; + case '#': + u8I2CFreqTune = 6; + break; + case '$': + u8I2CFreqTune = 7; + break; case '-': --i8OsrsTDiff; break; @@ -287,6 +328,12 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { case '}': ++i8OsrsHDiff; break; + case ';': + --i8IirDiff; + break; + case '\'': + ++i8IirDiff; + break; case '<': --i8TsbDiff; break; @@ -304,14 +351,14 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { break; case 'i': uart_printf(&gsUART0, "ID: 0x%02X\t", bme280_get_id(&gsState)); - uart_printf(&gsUART0, "Setters: %02X\t", gsState.u32CommState&0xFF); - uart_printf(&gsUART0, "Getters: %02X\t", (gsState.u32CommState>>8)&0xFF); - uart_printf(&gsUART0, "CommFlags: %02X\r\n", (gsState.u32CommState>>16)&0xFF); + uart_printf(&gsUART0, "Setters: %02X\t", gsState.u32CommState & 0xFF); + uart_printf(&gsUART0, "Getters: %02X\t", (gsState.u32CommState >> 8)&0xFF); + uart_printf(&gsUART0, "CommFlags: %02X\r\n", (gsState.u32CommState >> 16)&0xFF); uart_printf(&gsUART0, "Config/Status: %08X\t", ((uint32_t*)gsState.au8ConfigMirror)[0]); uart_printf(&gsUART0, "Raw Data: %08X.%08X\r\n", ((uint32_t*)gsState.au8DataMirror)[1], ((uint32_t*)gsState.au8DataMirror)[0]); break; case 'I': - gbVerbose=!gbVerbose; + gbVerbose = !gbVerbose; uart_printf(&gsUART0, "Verbose mode: %u\r\n", gbVerbose); break; case 'r': @@ -323,6 +370,14 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { default: uart_printf(&gsUART0, "command not found\r\n"); } + if (u8I2CFreqTune & 4) { + exptuner2_step(&gsI2CFreqTuner, &gsI2CFreqMin, &gsI2CFreqMax, &gsI2CFreq, u8I2CFreqTune & 1, u8I2CFreqTune & 2); + gbI2CFreqDirty = true; + uart_printf(&gsUART0, "I2C frequency set to %u Hz (period: %u APB cycles)\r\n", + exptuner2_get(&gsI2CFreqTuner, &gsI2CFreq), + HZ2APBTICKS(exptuner2_get(&gsI2CFreqTuner, &gsI2CFreq)) + ); + } } if (i8OsrsTDiff != 0) { gbDoubleWait |= _modify_osrs(i8OsrsTDiff, BME280_SEL_T, &gsState); @@ -333,6 +388,10 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { if (i8OsrsHDiff != 0) { gbDoubleWait |= _modify_osrs(i8OsrsHDiff, BME280_SEL_H, &gsState); } + if (i8IirDiff != 0) { + _modify_idx(i8IirDiff, &gu8IirFilterIdx, ARRAY_SIZE(gau32IirFilter), gau32IirFilter, "IIR filter"); + bme280_set_config(&gsState, geTsb, (EBme280Iir)gu8IirFilterIdx, gbSPI3W); + } if (i8TfpDiff != 0) { _modify_idx(i8TfpDiff, &gu8ForcedPeriodIdx, ARRAY_SIZE(gau32msForcedPeriod), gau32msForcedPeriod, "Forced mode period (ms)"); } @@ -348,15 +407,18 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { } } - // -------------- Interface functions -------------- void prog_init_pro_pre() { // we do some logging, hence set UART0 speed gsUART0.CLKDIV.raw = UART_HZ2CLKDIV(UART_FREQ_HZ, APB_FREQ_HZ); + gsI2CFreqTuner = exptuner2_init_b10(); + gsI2CFreq = exptuner2_lower_bound(&gsI2CFreqTuner, I2C0_FREQ_DEFAULT_HZ); + gsI2CFreqMin = exptuner2_lower_bound(&gsI2CFreqTuner, I2C0_FREQ_MIN_HZ); + gsI2CFreqMax = exptuner2_lower_bound(&gsI2CFreqTuner, I2C0_FREQ_MAX_HZ); lockmgr_init(); - i2c_init_controller(BME280_I2C_CH, I2C0_SCL_GPIO, I2C0_SDA_GPIO, HZ2APBTICKS(I2C0_FREQ_HZ)); + i2c_init_controller(BME280_I2C_CH, I2C0_SCL_GPIO, I2C0_SDA_GPIO, HZ2APBTICKS(exptuner2_get(&gsI2CFreqTuner, &gsI2CFreq))); _bme280_init(&gsState, &gsIface); } diff --git a/examples/1bme280/exptuner.c b/examples/1bme280/exptuner.c new file mode 100644 index 0000000..220d820 --- /dev/null +++ b/examples/1bme280/exptuner.c @@ -0,0 +1,181 @@ +/* + * Copyright 2026 SZIGETI János + * + * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3. + * See LICENSE or for full license details. + */ +#include +#include "exptuner.h" +#include "typeaux.h" + +// ============== Defines ============== + +// ============= Local types =============== + +// ==================== Local data ================ +const uint8_t gau8B10Mul1[] = {1, 2, 5}; +const uint8_t gau8B10Mul2[] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; + +// ============== Internal function declarations ============== +static inline void _step_level1(const SExpTuner2Desc *psTuner, SExpTuner2Value *psValue, bool bUp); +static inline void _step_level2(const SExpTuner2Desc *psTuner, SExpTuner2Value *psValue, bool bUp); + +// ============== Implementation ============== +// -------------- Internal functions -------------- + +static inline void _step_level1(const SExpTuner2Desc *psTuner, SExpTuner2Value *psValue, bool bUp) { + if (bUp) { + ++psValue->u8Mul1Idx; + if (psTuner->u8Mul1Size <= psValue->u8Mul1Idx) { + psValue->u8Mul1Idx = 0; + psValue->u32Base *= psTuner->u8Base; + } + } else { // down + if (0 < psValue->u8Mul2Idx) { + } else if (0 == psValue->u8Mul1Idx) { + psValue->u8Mul1Idx = psTuner->u8Mul1Size - 1; + psValue->u32Base /= psTuner->u8Base; + } else { + --psValue->u8Mul1Idx; + } + } + psValue->u8Mul2Idx = 0; +} + +static inline void _step_level2(const SExpTuner2Desc *psTuner, SExpTuner2Value *psValue, bool bUp) { + if (bUp) { + uint32_t u32L1Next = ((psValue->u8Mul1Idx == psTuner->u8Mul1Size - 1) ? psTuner->u8Base : psTuner->puMul1[psValue->u8Mul1Idx + 1]) * psTuner->puMul2[0]; + ++psValue->u8Mul2Idx; + if ((psTuner->u8Mul2Size <= psValue->u8Mul2Idx) || (u32L1Next <= psTuner->puMul1[psValue->u8Mul1Idx] * psTuner->puMul2[psValue->u8Mul2Idx])) { + _step_level1(psTuner, psValue, true); + } + } else { // down + if (psValue->u8Mul2Idx == 0) { + uint32_t u32Curr = ((psValue->u8Mul1Idx == 0) ? psTuner->u8Base : 1) * psTuner->puMul1[psValue->u8Mul1Idx] * psTuner->puMul2[0]; + _step_level1(psTuner, psValue, false); + psValue->u8Mul2Idx = psTuner->u8Mul2Size - 1; + while (psValue->u8Mul2Idx && (u32Curr <= psTuner->puMul1[psValue->u8Mul1Idx] * psTuner->puMul2[psValue->u8Mul2Idx])) { + --psValue->u8Mul2Idx; + } + } else { + --psValue->u8Mul2Idx; + } + } +} + +// -------------- Interface functions -------------- + +/** + * Get a fairly balanced tuner descriptor with base 10 and a tuning granurality of 4 -- 10 %. + * @return + */ +SExpTuner2Desc exptuner2_init_b10() { + SExpTuner2Desc sRet = { + .u8Base = 10, + .u8FinalDiv = 1, + .u8Mul1Size = ARRAY_SIZE(gau8B10Mul1), + .u8Mul2Size = ARRAY_SIZE(gau8B10Mul2), + .puMul1 = gau8B10Mul1, + .puMul2 = gau8B10Mul2 + }; + return sRet; +} + +/** + * Tune up/down an SExpTuner2Value by exactly a single step. + * @param psTuner Tuner descriptor. + * @param psLowerBound The value must not go below this limit. + * @param psUpperBound The value must not go above this limit. + * @param psValue Value to tune. + * @param bUp Tune to higher (true) or lower (false) value. + * @param bSoft Choose between level2 (true) or level1 (false) tuning. + * @return true: value modified, false: value not modified (lower or upper limit reached). + */ +bool exptuner2_step(const SExpTuner2Desc *psTuner, const SExpTuner2Value *psLowerBound, const SExpTuner2Value *psUpperBound, SExpTuner2Value *psValue, bool bUp, bool bSoft) { + bool bRet = true; + if (bSoft) { + _step_level2(psTuner, psValue, bUp); + } else { + _step_level1(psTuner, psValue, bUp); + } + if (bUp && exptuner2_get(psTuner, psUpperBound) <= exptuner2_get(psTuner, psValue)) { + *psValue = *psUpperBound; + bRet = false; + } + if (!bUp && exptuner2_get(psTuner, psValue) <= exptuner2_get(psTuner, psLowerBound)) { + *psValue = *psLowerBound; + bRet = false; + } + return bRet; +} + +/** + * Convert SExpTuner2Value to uint32_t. + * @param psTuner Tuner descriptor. + * @param psValue Value to convert. + * @return Converted value. + */ +uint32_t exptuner2_get(const SExpTuner2Desc *psTuner, const SExpTuner2Value * psValue) { + return (psValue->u32Base * psTuner->puMul1[psValue->u8Mul1Idx] * psTuner->puMul2[psValue->u8Mul2Idx]); +} + +/** + * Get the greatest SExpTuner2Value that is not greater than u32Value. + * @param psTuner Tuner descriptor. + * @param u32Value Value to approximate. + * @return SExpTuner2Value approximation of u32Value from below. + */ +SExpTuner2Value exptuner2_lower_bound(const SExpTuner2Desc *psTuner, uint32_t u32Value) { + SExpTuner2Value sRet = {.u32Base = 1, .u8Mul1Idx = 0, .u8Mul2Idx = 0}; + // tune base + while (true) { + SExpTuner2Value sCopy = sRet; + sCopy.u32Base *= psTuner->u8Base; + if (u32Value < exptuner2_get(psTuner, &sCopy)) break; + sRet.u32Base = sCopy.u32Base; + } + + // tune mul1 + while (true) { + SExpTuner2Value sCopy = sRet; + ++sCopy.u8Mul1Idx; + if (psTuner->u8Mul1Size <= sCopy.u8Mul1Idx || u32Value < exptuner2_get(psTuner, &sCopy)) break; + sRet.u8Mul1Idx = sCopy.u8Mul1Idx; + } + + // tune mul1 + while (true) { + SExpTuner2Value sCopy = sRet; + ++sCopy.u8Mul2Idx; + if (psTuner->u8Mul2Size <= sCopy.u8Mul2Idx || u32Value < exptuner2_get(psTuner, &sCopy)) break; + sRet.u8Mul2Idx = sCopy.u8Mul2Idx; + } + return sRet; +} + + +SExpTuner2Desc exptuner2_init_b10d() { + SExpTuner2Desc sRet = exptuner2_init_b10(); + sRet.u8FinalDiv = 10; + return sRet; +} + +SExpTuner2Value exptuner2d_lower_bound(const SExpTuner2Desc *psTuner, uint32_t u32Value) { + return exptuner2_lower_bound(psTuner, psTuner->u8FinalDiv * u32Value); +} + +bool exptuner2d_step(const SExpTuner2Desc *psTuner, const SExpTuner2Value *psLowerBound, const SExpTuner2Value *psUpperBound, SExpTuner2Value *psValue, bool bUp, bool bSoft) { + uint32_t u32Orig = exptuner2d_get(psTuner, psValue); + bool bRet = false; + while (exptuner2_step(psTuner, psLowerBound, psUpperBound, psValue, bUp, bSoft)) { + if (exptuner2d_get(psTuner, psValue) != u32Orig) { + bRet = true; + break; + } + } + return bRet; +} + +uint32_t exptuner2d_get(const SExpTuner2Desc *psTuner, const SExpTuner2Value * psValue) { + return exptuner2_get(psTuner, psValue) / psTuner->u8FinalDiv; +} diff --git a/examples/1bme280/exptuner.h b/examples/1bme280/exptuner.h new file mode 100644 index 0000000..6c4b057 --- /dev/null +++ b/examples/1bme280/exptuner.h @@ -0,0 +1,55 @@ +/* + * Copyright 2026 SZIGETI János + * + * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3. + * See LICENSE or for full license details. + */ + +#ifndef EXPTUNE_H +#define EXPTUNE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + // ============== Defines ============== + // ============== Types ============== + + typedef struct { + uint8_t u8Base; + uint8_t u8FinalDiv; + uint8_t u8Mul1Size; + uint8_t u8Mul2Size; + const uint8_t *puMul1; + const uint8_t *puMul2; + } SExpTuner2Desc; + + typedef struct { + uint32_t u32Base; + uint8_t u8Mul1Idx; + uint8_t u8Mul2Idx; + } SExpTuner2Value; + + // ============== Global values / References ============== + // ============== Interface functions ============== + // -------------- Inline interface functions -------------- + // -------------- Interface functions -------------- + SExpTuner2Desc exptuner2_init_b10(); + SExpTuner2Value exptuner2_lower_bound(const SExpTuner2Desc *psTuner, uint32_t u32Value); + bool exptuner2_step(const SExpTuner2Desc *psTuner, const SExpTuner2Value *psLowerBound, const SExpTuner2Value *psUpperBound, SExpTuner2Value *psValue, bool bUp, bool bSoft); + uint32_t exptuner2_get(const SExpTuner2Desc *psTuner, const SExpTuner2Value * psValue); + + SExpTuner2Desc exptuner2_init_b10d(); + SExpTuner2Value exptuner2d_lower_bound(const SExpTuner2Desc *psTuner, uint32_t u32Value); + bool exptuner2d_step(const SExpTuner2Desc *psTuner, const SExpTuner2Value *psLowerBound, const SExpTuner2Value *psUpperBound, SExpTuner2Value *psValue, bool bUp, bool bSoft); + uint32_t exptuner2d_get(const SExpTuner2Desc *psTuner, const SExpTuner2Value * psValue); + +#ifdef __cplusplus +} +#endif + +#endif /* EXPTUNE_H */ + diff --git a/tests/Makefile.am b/tests/Makefile.am index d27e37d..b09ad72 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,14 +1,16 @@ TESTS= \ bytegen_test \ bitgen_test \ - bme280_test + bme280_test \ + exptuner_test check_PROGRAMS = $(TESTS) -AM_CPPFLAGS = -I${top_srcdir}/../src -I${top_srcdir}/../modules +AM_CPPFLAGS = -I${top_srcdir}/../src -I${top_srcdir}/../modules -I${top_srcdir}/../examples bitgen_test_LDADD = gentest_common.o bytegen_test_LDADD = gentest_common.o bitgen_test_SOURCES = bitgen_test.c ../src/utils/generators.c bytegen_test_SOURCES = bytegen_test.c ../src/utils/generators.c bme280_test_SOURCES = bme280_test.c ../modules/bme280.c mocks/i2c_mock.c mocks/lockmgr_mock.c +exptuner_test_SOURCES = exptuner_test.c ../examples/1bme280/exptuner.c \ No newline at end of file diff --git a/tests/bitgen_test.c b/tests/bitgen_test.c index 3baaa9d..69e4732 100644 --- a/tests/bitgen_test.c +++ b/tests/bitgen_test.c @@ -204,8 +204,8 @@ bool test_bitpwmgen_reset(SBitPwmGenState *psBPGState, uint32_t u32ResetAt, size return bRet; } -uint8_t gau8Input2[1000]; -uint8_t gau8Exp2[8000]; +uint8_t gau8Input2[100]; +uint8_t gau8Exp2[800]; int main(int argc, char **argv) { diff --git a/tests/bme280_test.c b/tests/bme280_test.c index 5ccfac7..c433993 100644 --- a/tests/bme280_test.c +++ b/tests/bme280_test.c @@ -1,5 +1,6 @@ #include #include +#include #include "bme280.h" bool test_duration_calc_1() { @@ -17,6 +18,39 @@ bool test_duration_calc_1() { return bRet; } +bool test_duration_calc_2() { + SBme280StateDesc sDesc = bme280_init_state(); + EBme280Osrs aeInput[][3] = { + {0, 0, 1}, // H, P, T + {0, 1, 0}, + {1, 0, 0}, + {1, 1, 1}, + {1, 2, 3} + }; + uint32_t au32Exp[] = { + 2 + 4, + 2 + 5, + 2 + 5, + 2 + 5 + 5 + 4, + 2 + 5 + 9 + 16 + }; + bool bRet = true; + for (uint8_t i = 0; i < 5; ++i) { + bme280_set_osrs(&sDesc, BME280_SEL_H, aeInput[i][0]); + bme280_set_osrs(&sDesc, BME280_SEL_P, aeInput[i][1]); + bme280_set_osrs(&sDesc, BME280_SEL_T, aeInput[i][2]); + memcpy(sDesc.au8ConfigMirror,sDesc.au8ConfigOut, 4); + uint32_t u32Res = bme280_measurement_duration_hms(&sDesc); + if (au32Exp[i] != u32Res) { + fprintf(stderr, "Measurement duration calculation failed for input line #%u, osrs_hpt={%u,%u,%u}: {actual: %u, expected: %u}\n", + i, aeInput[i][0], aeInput[i][1], aeInput[i][2], + u32Res, au32Exp[i]); + bRet = false; + } + } + return bRet; +} + bool test_is_waiting() { SBme280StateDesc sDesc = bme280_init_state(); bool bRet = true; @@ -25,7 +59,7 @@ bool test_is_waiting() { fprintf(stderr, "Is waiting failed (expected: %d vs. actual: %d)\n", false, bRes0); bRet = false; } - sDesc.u32CommState|=0x00010000; + sDesc.u32CommState |= 0x00010000; bool bRes1 = bme280_is_waiting(&sDesc); if (!bRes1) { fprintf(stderr, "Is waiting failed (expected: %d vs. actual: %d)\n", true, bRes1); @@ -36,6 +70,7 @@ bool test_is_waiting() { int main() { assert(test_duration_calc_1()); + assert(test_duration_calc_2()); assert(test_is_waiting()); return 0; } \ No newline at end of file diff --git a/tests/exptuner_test.c b/tests/exptuner_test.c new file mode 100644 index 0000000..39ef3bb --- /dev/null +++ b/tests/exptuner_test.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include "typeaux.h" +#include "1bme280/exptuner.h" + +bool test_d10_step_inc_mul2() { + bool bRet = true; + SExpTuner2Desc sTunerD10 = exptuner2_init_b10d(); + SExpTuner2Value sLo = exptuner2d_lower_bound(&sTunerD10, 1); + SExpTuner2Value sHi = exptuner2d_lower_bound(&sTunerD10, 55); + SExpTuner2Value sVal = sLo; + + uint32_t au32Exp[] = {2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, + 55, 55}; + for (size_t i = 0; i < ARRAY_SIZE(au32Exp); ++i) { + exptuner2d_step(&sTunerD10, &sLo, &sHi, &sVal, true, true); + uint32_t u32Actual = exptuner2d_get(&sTunerD10, &sVal); + if (au32Exp[i] != u32Actual) { + fprintf(stderr, "d10_step_inc_mul2 failed at step#%zu: %u (expected) vs. %u (actual)\n", i, au32Exp[i], u32Actual); + bRet = false; + } + } + return bRet; +} + +bool test_d10_step_dec_mul2() { + bool bRet = true; + SExpTuner2Desc sTunerD10 = exptuner2_init_b10d(); + SExpTuner2Value sLo = exptuner2d_lower_bound(&sTunerD10, 1); + SExpTuner2Value sHi = exptuner2d_lower_bound(&sTunerD10, 55); + SExpTuner2Value sVal = sHi; + + uint32_t au32Exp[] = {50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1}; + for (size_t i = 0; i < ARRAY_SIZE(au32Exp); ++i) { + exptuner2d_step(&sTunerD10, &sLo, &sHi, &sVal, false, true); + uint32_t u32Actual = exptuner2d_get(&sTunerD10, &sVal); + if (au32Exp[i] != u32Actual) { + fprintf(stderr, "d10_step_dec_mul2 failed at step#%zu: %u (expected) vs. %u (actual)\n", i, au32Exp[i], u32Actual); + bRet = false; + } + } + return bRet; +} + +bool test_d10_step_inc_mul1() { + bool bRet = true; + SExpTuner2Desc sTunerD10 = exptuner2_init_b10d(); + SExpTuner2Value sLimitLo = exptuner2d_lower_bound(&sTunerD10, 1); + SExpTuner2Value sLimitHi = exptuner2d_lower_bound(&sTunerD10, 550); + SExpTuner2Value sVal = sLimitLo; + + uint32_t au32Exp[] = {2, 5, 10, 20, 50, 100, 200, 500, 550, 550}; + for (size_t i = 0; i < ARRAY_SIZE(au32Exp); ++i) { + exptuner2d_step(&sTunerD10, &sLimitLo, &sLimitHi, &sVal, true, false); + uint32_t u32Actual = exptuner2d_get(&sTunerD10, &sVal); + if (au32Exp[i] != u32Actual) { + fprintf(stderr, "d10_step_inc_mul1 failed at step#%zu: %u (expected) vs. %u (actual)\n", i, au32Exp[i], u32Actual); + bRet = false; + } + } + return bRet; +} + +bool test_d10_step_dec_mul1() { + bool bRet = true; + SExpTuner2Desc sTunerD10 = exptuner2_init_b10d(); + SExpTuner2Value sLimitLo = exptuner2d_lower_bound(&sTunerD10, 1); + SExpTuner2Value sLimitHi = exptuner2d_lower_bound(&sTunerD10, 600); + SExpTuner2Value sVal = sLimitHi; + + uint32_t au32Exp[] = {500, 200, 100, 50, 20, 10, 5, 2, 1, 1}; + for (size_t i = 0; i < ARRAY_SIZE(au32Exp); ++i) { + exptuner2d_step(&sTunerD10, &sLimitLo, &sLimitHi, &sVal, false, false); + uint32_t u32Actual = exptuner2d_get(&sTunerD10, &sVal); + if (au32Exp[i] != u32Actual) { + fprintf(stderr, "d10_step_dec_mul1 failed at step#%zu: %u (expected) vs. %u (actual)\n", i, au32Exp[i], u32Actual); + bRet = false; + } + } + return bRet; +} + +int main() { + assert(test_d10_step_inc_mul1()); + assert(test_d10_step_dec_mul1()); + assert(test_d10_step_inc_mul2()); + assert(test_d10_step_dec_mul2()); + return 0; +} \ No newline at end of file From 88426e7b5ee44bffd3527554aa839bec0b7740bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZIGETI=20J=C3=A1nos?= Date: Mon, 22 Jun 2026 17:15:04 +0200 Subject: [PATCH 4/4] [ISSUE-46]: some small refactoring --- .github/workflows/ccpp.yml | 2 +- .github/workflows/coverage.yml | 2 +- examples/1bme280/Makefile.am | 2 +- examples/1bme280/README.md | 1 - examples/1bme280/bme280app.c | 185 +++++++++++++++++++++++++-------- 5 files changed, 144 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index b5b6bf2..3e37a40 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: configure run: aclocal && autoconf && automake --add-missing && ./configure --host='' 'CFLAGS=-Wall' 'LDFLAGS=' --without-binaries - name: make diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3c5c625..fdb03c5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: prepare run: aclocal && autoconf && automake --add-missing working-directory: tests diff --git a/examples/1bme280/Makefile.am b/examples/1bme280/Makefile.am index fcedb63..c601e03 100644 --- a/examples/1bme280/Makefile.am +++ b/examples/1bme280/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/scripts/elf2bin.mk include $(top_srcdir)/ld/flags.mk -noinst_HEADERS = defines.h +noinst_HEADERS = defines.h exptuner.h AM_CFLAGS = -std=c11 -flto if WITH_BINARIES diff --git a/examples/1bme280/README.md b/examples/1bme280/README.md index b3a66ea..a45f8de 100644 --- a/examples/1bme280/README.md +++ b/examples/1bme280/README.md @@ -35,7 +35,6 @@ The application accepts the following commands (on UART0): * `#` / `$` - Decrease / Increase communication speed (smooth multipliers: 1.0, 1.1, 1.2, 1.3, ..., 1.9(, 2.0, 2.1, ..., 2.4)); * `;` / `'` - Decrease / Increase IIR filter value. - Note: The maximal I2C SCL frequency on esp32basic is 5.5 MHz, this is the upper bound of communication speed the application allows. However, the BME280 Specificaton states that `Digital interface I2C (up to 3.4 MHz)` (page 2). diff --git a/examples/1bme280/bme280app.c b/examples/1bme280/bme280app.c index a482fa3..05ee0ee 100644 --- a/examples/1bme280/bme280app.c +++ b/examples/1bme280/bme280app.c @@ -38,8 +38,8 @@ #define UARTCTRL_PERIOD_MS 100U // #2: Channels / wires / addresses -#define I2C0_SCL_GPIO 23U -#define I2C0_SDA_GPIO 22U +#define I2C0_SCL_GPIO 22U +#define I2C0_SDA_GPIO 23U #define BME280_CSB_GPIO 21U #define BME280_I2C_CH I2C1 @@ -47,12 +47,41 @@ // ============= Local types =============== +/// Host operational modes. +/// Note: currently only MODE_FORCED_AUTO is implemented. + +typedef enum { + MODE_SLEEP = 0, ///< Sensor set into sleep mode + MODE_NORMAL, ///< Sensor set into normal mode. + MODE_FORCED, ///< Sensor set into forced mode, no recurring data read. + MODE_FORCED_AUTO ///< Sensor set into forced mode, measurement periodically triggered. +} EOpMode; + +/// Host operational phases (states of the FSM). + +typedef enum { + PHASE_IDLE = 0, ///< Do nothing (afer last measurement is printed and next measurement is not triggered / read). + PHASE_SETMODE, ///< Set (i2c write) measurement mode. + PHASE_WAITMEAS, ///< The measurement takes time (t_{meas}) so we have to wait a certain amount of time. + PHASE_GETSTATUS, ///< Get (i2c read) status information. + PHASE_GETDATA, ///< Get (i2c read) measurement data. + PHASE_PRINTDATA ///< Print (or do any user action) with the measurement data. +} EOpPhase; + +/// Host state descriptor. + +typedef struct { + EOpMode eMode; + EOpPhase ePhase; + uint64_t u64tckMainNext; + uint64_t u64tckNext; +} SHostState; // ================ Local function declarations ================= static void _i2c_release_cycle(uint64_t u64tckNow); static ELockmgrResource _i2c_to_lock(EI2CBus eBus); static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface); -static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abValid); +static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abAvail); static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme280StateDesc *psState); static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280StateDesc *psState); static void _uartctrl_cycle(uint64_t u64tckNow); @@ -63,6 +92,7 @@ const bool gbStartAppCpu = START_APP_CPU; const uint16_t gu16Tim00Divisor = TIM0_0_DIVISOR; const uint64_t gu64tckSchedulePeriod = (CLK_FREQ_HZ / SCHEDULE_FREQ_HZ); +/// Predefine measurement period lengths in MODE_FORCED_AUTO. const uint32_t gau32msForcedPeriod[] = { 100, 200, @@ -73,6 +103,7 @@ const uint32_t gau32msForcedPeriod[] = { 10000 }; +/// IIR filter sizes as specified in BME280 Data Sheet. const uint32_t gau32IirFilter[] = { 0, 2, @@ -86,10 +117,16 @@ const uint32_t gau32IirFilter[] = { // ==================== Local Data ================ static SBme280StateDesc gsState; +static SHostState gsHostState = { + .eMode = MODE_FORCED_AUTO, + .ePhase = PHASE_IDLE, + .u64tckMainNext = 0, + .u64tckNext = MS2TICKS(BME280_TSTARTUP_MS) +}; static bool gbDoubleWait = false; // BUGFIX: apparently, after modifying osrs_x value, the first measurement takes twice as much time as expected. static bool gbVerbose = false; static SI2cIfaceCfg gsIface; -static uint8_t gu8ForcedPeriodIdx = 4; +static uint8_t gu8ForcedPeriodIdx = 4; // 2000ms static uint8_t gu8IirFilterIdx = 0; static EBme280Tsb geTsb = BME280_TSB_500US; static bool gbSPI3W = false; @@ -101,6 +138,11 @@ static SExpTuner2Value gsI2CFreqMax; static SExpTuner2Value gsI2CFreqMin; // ==================== Implementation ================ // -------------- Internal functions -------------- +/// Check whether the i2c bus is in use (locked) +/// and if possible (i2c communication is over), +/// release the lock. +/// And in case of i2c read, copy the received data to its destination. +/// \param u64tckNow Current timestamp (unused). static void _i2c_release_cycle(uint64_t u64tckNow) { ELockmgrResource eBus = _i2c_to_lock(BME280_I2C_CH); @@ -128,6 +170,10 @@ static ELockmgrResource _i2c_to_lock(EI2CBus eBus) { return eBus; } +/// Initializes the BME280 state descriptor. +/// \param psState Ptr to state descriptor to initialize. +/// \param psIface Ptr to Interface configuration to initialize. + static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface) { // chip select gpio_pin_enable(BME280_CSB_GPIO); @@ -150,70 +196,100 @@ static void _bme280_init(SBme280StateDesc *psState, SI2cIfaceCfg *psIface) { }; } -static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abValid) { +/// Formatted Print of measurement data to UART0. +/// \param u64tckNow Current timestamp. +/// \param psRes Measurement values. +/// \param u32TFine T_{Fine} basic value. +/// \param abValid Flags describing which measurement data are available: bit0: temperature, bit1: pressure, bit2: humidity. + +static void _bme280_print_result(uint64_t u64tckNow, const SBme280TPH *psRes, uint32_t u32TFine, uint8_t abAvail) { uart_printf(&gsUART0, "[%d]", (uint32_t)(u64tckNow / TICKS_PER_MS)); uart_printf(&gsUART0, "\tTfine: %d", u32TFine); - if (abValid & 1) + if (abAvail & 1) uart_printf(&gsUART0, "\tTemp: %d.%02d", psRes->i32Temp / 100, psRes->i32Temp % 100); - if (abValid & 2) + if (abAvail & 2) uart_printf(&gsUART0, "\tPres: %d.%02d", psRes->i32Pres >> 8, ((psRes->i32Pres & 0xff) * 391) / 1000); - if (abValid & 4) + if (abAvail & 4) uart_printf(&gsUART0, "\tHum: %d.%03d", psRes->i32Hum >> 10, ((psRes->i32Hum & 0x3ff) * 97657) / 100000); uart_printf(&gsUART0, "\r\n"); } +/// Sensor controlling cycle. +/// This method is responsible for managing the communication with the peripheral (i.e., with the peripheral controller). +/// \param u64tckNow Current timestamp. +/// \param psIface Interface configuration of the peripheral. +/// \param psState State descriptor of the peripheral. + static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme280StateDesc *psState) { - static uint64_t u64tckMainNext = 0; - static uint64_t u64tckNext = MS2TICKS(BME280_TSTARTUP_MS); - static uint8_t u8Phase = 0; // 0: idle, 1: set mode, 2: wait, 3: get status, 4: get data, 5: print data + // debugging variables static uint32_t u32hmsMeasurementExp = 0; // expected measurement time [half ms] static uint32_t u32ExtraWaitCnt = 0; // additional cycles after expected measurement time (status is not 0) - if (u64tckNext < u64tckMainNext) { - u64tckNext = u64tckMainNext; + // if MainNext timer has been increased, adjust Next timer to it + if (gsHostState.u64tckNext < gsHostState.u64tckMainNext) { + gsHostState.u64tckNext = gsHostState.u64tckMainNext; } - if (u64tckNext <= u64tckNow) { + + // if Now has reached Next, there is action to do + if (gsHostState.u64tckNext <= u64tckNow) { + + // first, check ongoing i2c communication + // adjust host-side state, etc. uint32_t u32hmsWaitHint = 0; - // bool bRxRes = bme280_async_rx_cycle(psState, &u32hmsWaitHint); bme280_async_rx_cycle(psState, &u32hmsWaitHint); - bool bOngoing = bme280_has_async_todo(psState); + bool bTodoQueueEmpty = !bme280_has_async_todo(psState); - if (!bOngoing) { // the bme280 todo queue is empty (nothing to set, nothing requested) - ++u8Phase; + if (bTodoQueueEmpty) { // the bme280 todo queue is empty (nothing to set, nothing requested) + ++gsHostState.ePhase; - if (u8Phase == 5) { - if (gbVerbose) { - uart_printf(&gsUART0, "Predicted dt: %u, status reties: %u\r\n", u32hmsMeasurementExp / 2, u32ExtraWaitCnt); + switch (gsHostState.ePhase) { + case PHASE_PRINTDATA: + { + if (gbVerbose) { + uart_printf(&gsUART0, "Predicted dt: %u, status reties: %u\r\n", u32hmsMeasurementExp / 2, u32ExtraWaitCnt); + } + uint32_t u32TFine; + SBme280TPH sMeas = bme280_get_measurement(psState, &u32TFine); + _bme280_print_result(u64tckNow, &sMeas, u32TFine, + (bme280_get_osrs(psState, true, BME280_SEL_T) ? 1 : 0) | + (bme280_get_osrs(psState, true, BME280_SEL_P) ? 2 : 0) | + (bme280_get_osrs(psState, true, BME280_SEL_H) ? 4 : 0) + ); + gsHostState.ePhase = PHASE_IDLE; } - uint32_t u32TFine; - SBme280TPH sMeas = bme280_get_measurement(psState, &u32TFine); - _bme280_print_result(u64tckNow, &sMeas, u32TFine, - (bme280_get_osrs(psState, true, BME280_SEL_T) ? 1 : 0) | - (bme280_get_osrs(psState, true, BME280_SEL_P) ? 2 : 0) | - (bme280_get_osrs(psState, true, BME280_SEL_H) ? 4 : 0) - ); - u8Phase = 0; - } else { - if (u8Phase == 1) { + break; + + case PHASE_SETMODE: bme280_set_mode(psState, BME280_MODE_FORCED); u32hmsMeasurementExp = (gbDoubleWait ? 2 : 1) * bme280_measurement_duration_hms(psState); - } else if (u8Phase == 2) { - u64tckNext = u64tckNow + HMS2TICKS(u32hmsMeasurementExp); - } else if (u8Phase == 3) { + break; + + case PHASE_WAITMEAS: + gsHostState.u64tckNext = u64tckNow + HMS2TICKS(u32hmsMeasurementExp); + break; + + case PHASE_GETSTATUS: gbDoubleWait = false; bme280_req_status(psState); u32ExtraWaitCnt = 0; - } else if (u8Phase == 4) { + break; + + case PHASE_GETDATA: + { uint8_t u8Status = bme280_get_status(psState) & 0x09; if (u8Status != 0) { gsUART0.FIFO = 'a' + u8Status; bme280_req_status(psState); - --u8Phase; + --gsHostState.ePhase; ++u32ExtraWaitCnt; } else { bme280_req_data(psState); } } + break; + + default: // illegal state + uart_printf(&gsUART0, "Entered illegal state (phase: %u)\r\n", gsHostState.ePhase); } } @@ -230,15 +306,15 @@ static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme2 // calculate wait time if (bTodo && !bTxRes) { // could not initialize TX, retry soon - // do not increase nxt timestamp + // do not increase Next timestamp } else if (bWaitForRx) { - if (((psState->u32CommState >> 24) & 0x0f) == 6) { - u64tckNext = u64tckNow + MS2TICKS(BME280_TSTARTUP_MS); + if (((psState->u32CommState >> 24) & 0x0f) == 6) { // COMM_WR_RESET + gsHostState.u64tckNext = u64tckNow + MS2TICKS(BME280_TSTARTUP_MS); } - // do not increase nxt timestamp + // do not increase Next timestamp } else { // no tx problem, not waiting for rx - if (u8Phase == 0) { // find next main cycle tick - u64tckMainNext += MS2TICKS(gau32msForcedPeriod[gu8ForcedPeriodIdx]); + if (gsHostState.ePhase == PHASE_IDLE) { // find next main cycle tick + gsHostState.u64tckMainNext += MS2TICKS(gau32msForcedPeriod[gu8ForcedPeriodIdx]); } else { // no wait } @@ -246,6 +322,13 @@ static void _bme280_cycle(uint64_t u64tckNow, const SI2cIfaceCfg *psIface, SBme2 } } +/// Modify the osrs value of a metric, +/// and print information about the change. +/// \param i8Diff Amount of change (steps). +/// \param eMetric Metric selector. +/// \param psState State descriptor to modify. +/// \return OSRS has been changed. + static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280StateDesc *psState) { EBme280Osrs eCurVal = bme280_get_osrs(psState, false, eMetric); const char acMetric2Ch[] = {'h', 'p', 't'}; @@ -268,6 +351,14 @@ static bool _modify_osrs(int8_t i8Diff, EBme280MetricSelector eMetric, SBme280St return bValid; } +/// Modify an index within a range, +/// and print information about the change. +/// \param i8IdxDiff Amount of change (steps). +/// \param pu8Idx Index to modify. +/// \param u8MaxIdx Upper bound (not inclusive) of index range. (Lower bound is 0 (inclusive)). +/// \param pu32Values Values corresponding to indices (for printing). +/// \param pcParamName Name of the parameter subject to change (for printing). + static void _modify_idx(int8_t i8IdxDiff, uint8_t *pu8Idx, uint8_t u8MaxIdx, const uint32_t *pu32Values, const char *pcParamName) { uint8_t u8OrigIdx = *pu8Idx; int8_t i8NewIdx = u8OrigIdx + i8IdxDiff; @@ -283,6 +374,9 @@ static void _modify_idx(int8_t i8IdxDiff, uint8_t *pu8Idx, uint8_t u8MaxIdx, con } } +/// User control parser / interpreter on UART0. +/// \param u64tckNow Current timestamp. + static void _uartctrl_cycle(uint64_t u64tckNow) { static uint64_t u64tckNext = 0; int8_t i8OsrsTDiff = 0; @@ -292,7 +386,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { int8_t i8TsbDiff = 0; int8_t i8TfpDiff = 0; // forced mode period static bool bInReset = false; - uint8_t u8I2CFreqTune = 0; + uint8_t u8I2CFreqTune = 0; // bit0: tune up, bit1: level2 tuning, bit2: tune yes/no if (u64tckNext <= u64tckNow) { while (0 < (gsUART0.STATUS & 0xff)) { @@ -310,6 +404,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { case '$': u8I2CFreqTune = 7; break; + case '-': --i8OsrsTDiff; break; @@ -340,12 +435,14 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { case '>': ++i8TsbDiff; break; + case ',': --i8TfpDiff; break; case '.': ++i8TfpDiff; break; + case 'c': bme280_req_config(&gsState); break; @@ -357,7 +454,7 @@ static void _uartctrl_cycle(uint64_t u64tckNow) { uart_printf(&gsUART0, "Config/Status: %08X\t", ((uint32_t*)gsState.au8ConfigMirror)[0]); uart_printf(&gsUART0, "Raw Data: %08X.%08X\r\n", ((uint32_t*)gsState.au8DataMirror)[1], ((uint32_t*)gsState.au8DataMirror)[0]); break; - case 'I': + case 'I': // toggle verbose mode gbVerbose = !gbVerbose; uart_printf(&gsUART0, "Verbose mode: %u\r\n", gbVerbose); break;