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/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/cov/coverage.mk b/cov/coverage.mk
index 0ba248c..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=.gcda)
+
+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)))
@@ -44,6 +49,14 @@ 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 $@) $@
+
+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))
@@ -54,12 +67,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 -d examples -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 -d examples -o $@
testrun: tests/Makefile FORCE
@@ -78,12 +91,16 @@ clean:
rm -f $(PROJ).test.info
rm -rf $(dir $(HTML))
rm -f src/*.gcda
+ rm -f modules/*.gcda
+ rm -f examples/*.gcda
rm -f tests/*.gcda
$(MAKE) -C tests clean
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
new file mode 100644
index 0000000..c601e03
--- /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 exptuner.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 exptuner.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..a45f8de
--- /dev/null
+++ b/examples/1bme280/README.md
@@ -0,0 +1,44 @@
+### Simple application to BME280 sensor
+
+In this small example application we show how to use the BME280 sensor.
+
+#### 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);
+* `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
new file mode 100644
index 0000000..05ee0ee
--- /dev/null
+++ b/examples/1bme280/bme280app.c
@@ -0,0 +1,536 @@
+/*
+ * 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
+#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 "exptuner.h"
+#include "bme280.h"
+#include "utils/i2cutils.h"
+
+// =================== Hard constants =================
+
+// #1: Timings -- 50ms: 20Hz update freq.
+#define UART_FREQ_HZ 115200U
+#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
+
+// #2: Channels / wires / addresses
+#define I2C0_SCL_GPIO 22U
+#define I2C0_SDA_GPIO 23U
+#define BME280_CSB_GPIO 21U
+
+#define BME280_I2C_CH I2C1
+#define BME280_I2C_SLAVEADDR 0x76
+
+// ============= 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 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);
+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);
+
+/// Predefine measurement period lengths in MODE_FORCED_AUTO.
+const uint32_t gau32msForcedPeriod[] = {
+ 100,
+ 200,
+ 500,
+ 1000,
+ 2000,
+ 5000,
+ 10000
+};
+
+/// IIR filter sizes as specified in BME280 Data Sheet.
+const uint32_t gau32IirFilter[] = {
+ 0,
+ 2,
+ 4,
+ 8,
+ 16,
+ 16,
+ 16,
+ 16
+};
+
+// ==================== 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; // 2000ms
+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 --------------
+/// 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);
+ 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;
+}
+
+/// 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);
+ 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
+ };
+}
+
+/// 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 (abAvail & 1)
+ uart_printf(&gsUART0, "\tTemp: %d.%02d", psRes->i32Temp / 100, psRes->i32Temp % 100);
+ if (abAvail & 2)
+ uart_printf(&gsUART0, "\tPres: %d.%02d", psRes->i32Pres >> 8, ((psRes->i32Pres & 0xff) * 391) / 1000);
+ 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) {
+ // 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 MainNext timer has been increased, adjust Next timer to it
+ if (gsHostState.u64tckNext < gsHostState.u64tckMainNext) {
+ gsHostState.u64tckNext = gsHostState.u64tckMainNext;
+ }
+
+ // 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;
+ bme280_async_rx_cycle(psState, &u32hmsWaitHint);
+ bool bTodoQueueEmpty = !bme280_has_async_todo(psState);
+
+ if (bTodoQueueEmpty) { // the bme280 todo queue is empty (nothing to set, nothing requested)
+ ++gsHostState.ePhase;
+
+ 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;
+ }
+ break;
+
+ case PHASE_SETMODE:
+ bme280_set_mode(psState, BME280_MODE_FORCED);
+ u32hmsMeasurementExp = (gbDoubleWait ? 2 : 1) * bme280_measurement_duration_hms(psState);
+ break;
+
+ case PHASE_WAITMEAS:
+ gsHostState.u64tckNext = u64tckNow + HMS2TICKS(u32hmsMeasurementExp);
+ break;
+
+ case PHASE_GETSTATUS:
+ gbDoubleWait = false;
+ bme280_req_status(psState);
+ u32ExtraWaitCnt = 0;
+ break;
+
+ case PHASE_GETDATA:
+ {
+ uint8_t u8Status = bme280_get_status(psState) & 0x09;
+ if (u8Status != 0) {
+ gsUART0.FIFO = 'a' + u8Status;
+ bme280_req_status(psState);
+ --gsHostState.ePhase;
+ ++u32ExtraWaitCnt;
+ } else {
+ bme280_req_data(psState);
+ }
+ }
+ break;
+
+ default: // illegal state
+ uart_printf(&gsUART0, "Entered illegal state (phase: %u)\r\n", gsHostState.ePhase);
+ }
+ }
+
+ // 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);
+ bool bWaitForRx = bme280_is_waiting(psState);
+
+ // calculate wait time
+ if (bTodo && !bTxRes) { // could not initialize TX, retry soon
+ // do not increase Next timestamp
+ } else if (bWaitForRx) {
+ if (((psState->u32CommState >> 24) & 0x0f) == 6) { // COMM_WR_RESET
+ gsHostState.u64tckNext = u64tckNow + MS2TICKS(BME280_TSTARTUP_MS);
+ }
+ // do not increase Next timestamp
+ } else { // no tx problem, not waiting for rx
+ if (gsHostState.ePhase == PHASE_IDLE) { // find next main cycle tick
+ gsHostState.u64tckMainNext += MS2TICKS(gau32msForcedPeriod[gu8ForcedPeriodIdx]);
+ } else {
+ // no wait
+ }
+ }
+ }
+}
+
+/// 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'};
+
+ 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;
+}
+
+/// 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;
+ 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);
+ }
+}
+
+/// 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;
+ 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; // bit0: tune up, bit1: level2 tuning, bit2: tune yes/no
+
+ 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;
+ case '+':
+ ++i8OsrsTDiff;
+ break;
+ case '[':
+ --i8OsrsPDiff;
+ break;
+ case ']':
+ ++i8OsrsPDiff;
+ break;
+ case '{':
+ --i8OsrsHDiff;
+ break;
+ case '}':
+ ++i8OsrsHDiff;
+ break;
+ case ';':
+ --i8IirDiff;
+ break;
+ case '\'':
+ ++i8IirDiff;
+ 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': // toggle verbose mode
+ 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 (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);
+ }
+ if (i8OsrsPDiff != 0) {
+ gbDoubleWait |= _modify_osrs(i8OsrsPDiff, BME280_SEL_P, &gsState);
+ }
+ 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)");
+ }
+ 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);
+ 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(exptuner2_get(&gsI2CFreqTuner, &gsI2CFreq)));
+
+ _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/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/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..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"
@@ -28,16 +29,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 +40,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 +65,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 +155,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 +204,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 +297,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 +343,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 +534,131 @@ 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;
+ }
+ if (psFlags->bSetCtrlMeas) {
+ psState->au8ConfigMirror[2] = psState->au8ConfigOut[2];
+ psFlags->bSetCtrlMeas = false;
+ }
+ if (psFlags->bSetConfig) {
+ psState->au8ConfigMirror[3] = psState->au8ConfigOut[3];
+ psFlags->bSetConfig = false;
}
- lockmgr_release_entry(psState->u32LastLabel);
- psFlags->bWaitingForRx = false;
- } else { // still waiting for i2c bus to be ready
- return false;
+ break;
+ // getters
+ default:
+ {
+ uint8_t u8FlagMask = 1 << (psFlags->u4UpdAddr - COMM_RD_CALIB0);
+ psFlags->u8Getters &= ~u8FlagMask;
}
- } else {
- // TODO: error, no lockmgr entry found
}
+ 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);
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..b09ad72 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,11 +1,16 @@
TESTS= \
bytegen_test \
- bitgen_test
+ bitgen_test \
+ bme280_test \
+ exptuner_test
check_PROGRAMS = $(TESTS)
-AM_CPPFLAGS = -I${top_srcdir}/../src
-LDADD = gentest_common.o
+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 c622fef..69e4732 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) {
@@ -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
new file mode 100644
index 0000000..c433993
--- /dev/null
+++ b/tests/bme280_test.c
@@ -0,0 +1,76 @@
+#include
+#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_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;
+ 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_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
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) {
+
+}