The firmware has never been flashed, and a real bug already reached the repo because of it: RD03E_FRAME_LEN was 5 for a 6-byte frame, so the footer check collided with the distance high byte and EVERY distance reading was garbage — always `lo | 0x5500`, about 218 metres, regardless of what the sensor saw. That was pure logic with no hardware dependency. It should have been catchable on a laptop, and there was simply no way to run the code. Extracted the hardware-free logic out of the three drivers — rd03e_parse, bmp280_compensate, mems_level — as moves rather than rewrites, carrying the explanatory comments along with the code they explain. The drivers now own only their bus I/O and call into the pure units, so nothing changes for the real device. `./run_tests.sh` builds them with gcc -Wall -Wextra -Werror plus a dependency-free assert harness: 175 checks, 0 failed, from a clean tree. Proven to catch the actual bug rather than assumed to: reintroducing FRAME_LEN 5 fails four checks, including one that reads "a simple-report frame is 6 bytes, not 5", plus the truncated-frame and 5-byte-window cases. Restored, green again. This does NOT make the firmware verified, and the README says so plainly — it is called a narrow exception and scoped to pure logic. Wiring, timing, real register behaviour and the reconstructed RD-03E frame format all still need the physical board. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
194 lines
6.6 KiB
C
194 lines
6.6 KiB
C
// Bosch BMP280 driver — see bmp280.h for wiring and honesty notes.
|
|
//
|
|
// UNVERIFIED AGAINST REAL HARDWARE: this has been written against the public
|
|
// BMP280 datasheet (Bosch Sensortec, document rev 1.23) and ESP-IDF's
|
|
// documented `driver/i2c_master.h` API surface, and reasoned about carefully,
|
|
// but never compiled with a real ESP-IDF toolchain nor run against a real
|
|
// sensor. Register addresses are transcribed as directly as possible from
|
|
// the datasheet's section 3.11.1 (register map) to minimize transcription
|
|
// risk, but a real bring-up should sanity-check first readings against a
|
|
// known-good reference (e.g. compare to a household thermometer/barometer).
|
|
//
|
|
// This file owns only the I2C traffic. The calibration/ADC byte decoding
|
|
// and the §3.11.3 compensation maths live in bmp280_compensate.h/.c, which
|
|
// is ESP-IDF-free and unit-tested on a host with plain gcc (see ../test/).
|
|
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <math.h>
|
|
#include "bmp280.h"
|
|
#include "bmp280_compensate.h"
|
|
#include "driver/i2c_master.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
static const char *TAG = "bmp280";
|
|
|
|
// --- Register map (BMP280 datasheet section 3.11.1) ------------------------
|
|
#define REG_CHIP_ID 0xD0
|
|
#define REG_RESET 0xE0
|
|
#define REG_STATUS 0xF3
|
|
#define REG_CTRL_MEAS 0xF4
|
|
#define REG_CONFIG 0xF5
|
|
#define REG_PRESS_MSB 0xF7 // press(3) + temp(3) = 6 bytes, burst-read from here
|
|
#define REG_CALIB00 0x88 // dig_T1..dig_P9, 24 bytes: 0x88-0x9F
|
|
|
|
#define CHIP_ID_EXPECTED 0x58 // BMP280 (vs. BME280's 0x60 — a chip-ID
|
|
// mismatch here likely means a BME280 is
|
|
// wired instead; we proceed anyway since the
|
|
// temp/pressure register map and compensation
|
|
// math is identical between the two parts)
|
|
|
|
#define RESET_MAGIC 0xB6
|
|
|
|
#define STATUS_MEASURING_BIT 0x08
|
|
|
|
static i2c_master_bus_handle_t s_bus = NULL;
|
|
static i2c_master_dev_handle_t s_dev = NULL;
|
|
static bmp280_calib_t s_calib;
|
|
static bool s_ready = false;
|
|
|
|
static esp_err_t write_reg(uint8_t reg, uint8_t val) {
|
|
uint8_t buf[2] = { reg, val };
|
|
return i2c_master_transmit(s_dev, buf, sizeof(buf), 1000 /* ms */);
|
|
}
|
|
|
|
static esp_err_t read_regs(uint8_t reg, uint8_t *out, size_t len) {
|
|
return i2c_master_transmit_receive(s_dev, ®, 1, out, len, 1000 /* ms */);
|
|
}
|
|
|
|
static esp_err_t read_calibration(void) {
|
|
uint8_t buf[BMP280_CALIB_LEN]; // 0x88..0x9F
|
|
|
|
esp_err_t err = read_regs(REG_CALIB00, buf, sizeof(buf));
|
|
if (err != ESP_OK) return err;
|
|
|
|
bmp280_calib_from_regs(buf, &s_calib);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t bmp280_init(void) {
|
|
i2c_master_bus_config_t bus_cfg = {
|
|
.i2c_port = BMP280_I2C_PORT,
|
|
.sda_io_num = BMP280_I2C_SDA_GPIO,
|
|
.scl_io_num = BMP280_I2C_SCL_GPIO,
|
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
|
.glitch_ignore_cnt = 7,
|
|
.flags.enable_internal_pullup = true,
|
|
};
|
|
esp_err_t err = i2c_new_master_bus(&bus_cfg, &s_bus);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
i2c_device_config_t dev_cfg = {
|
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
|
.device_address = BMP280_I2C_ADDR,
|
|
.scl_speed_hz = BMP280_I2C_CLK_HZ,
|
|
};
|
|
err = i2c_master_bus_add_device(s_bus, &dev_cfg, &s_dev);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "i2c_master_bus_add_device failed: %s", esp_err_to_name(err));
|
|
i2c_del_master_bus(s_bus);
|
|
s_bus = NULL;
|
|
return err;
|
|
}
|
|
|
|
uint8_t chip_id = 0;
|
|
err = read_regs(REG_CHIP_ID, &chip_id, 1);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "chip id read failed: %s", esp_err_to_name(err));
|
|
goto fail;
|
|
}
|
|
if (chip_id != CHIP_ID_EXPECTED) {
|
|
ESP_LOGW(TAG, "unexpected chip id 0x%02x (want 0x%02x) -- check wiring/address", chip_id, CHIP_ID_EXPECTED);
|
|
// Don't hard-fail: temp+pressure register map/compensation is
|
|
// identical on a BME280 too, so a mis-wired-but-present sensor of
|
|
// either part can still usefully report both readings.
|
|
}
|
|
|
|
err = write_reg(REG_RESET, RESET_MAGIC);
|
|
if (err != ESP_OK) goto fail;
|
|
vTaskDelay(pdMS_TO_TICKS(10)); // datasheet: allow >= 2ms after reset
|
|
|
|
err = read_calibration();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "calibration read failed: %s", esp_err_to_name(err));
|
|
goto fail;
|
|
}
|
|
|
|
s_ready = true;
|
|
ESP_LOGI(TAG, "BMP280 init ok (chip id 0x%02x)", chip_id);
|
|
return ESP_OK;
|
|
|
|
fail:
|
|
i2c_master_bus_rm_device(s_dev);
|
|
s_dev = NULL;
|
|
i2c_del_master_bus(s_bus);
|
|
s_bus = NULL;
|
|
return err;
|
|
}
|
|
|
|
esp_err_t bmp280_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
|
*out_count = 0;
|
|
if (!s_ready) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
if (max_out < 2) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
// Forced mode: osrs_t=1 (001), osrs_p=1 (001), mode=forced (01).
|
|
// ctrl_meas = 0b001_001_01 = 0x25
|
|
esp_err_t err = write_reg(REG_CTRL_MEAS, 0x25);
|
|
if (err != ESP_OK) return err;
|
|
|
|
// Poll status until the "measuring" bit clears, with a hard cap so a
|
|
// wedged bus/sensor can't hang the telemetry task forever.
|
|
for (int attempt = 0; attempt < 20; attempt++) {
|
|
uint8_t status = 0;
|
|
err = read_regs(REG_STATUS, &status, 1);
|
|
if (err != ESP_OK) return err;
|
|
if ((status & STATUS_MEASURING_BIT) == 0) {
|
|
break;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
if (attempt == 19) {
|
|
ESP_LOGW(TAG, "measurement did not complete in time");
|
|
return ESP_ERR_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
uint8_t raw[BMP280_RAW_LEN];
|
|
err = read_regs(REG_PRESS_MSB, raw, sizeof(raw));
|
|
if (err != ESP_OK) return err;
|
|
|
|
int32_t adc_P = 0, adc_T = 0;
|
|
bmp280_adc_from_regs(raw, &adc_P, &adc_T);
|
|
|
|
double t_fine = 0.0;
|
|
double temp_c = bmp280_compensate_temperature(&s_calib, adc_T, &t_fine);
|
|
double press_pa = bmp280_compensate_pressure(&s_calib, adc_P, t_fine);
|
|
|
|
size_t n = 0;
|
|
memset(&out[n], 0, sizeof(out[n]));
|
|
strncpy(out[n].sensor_type, "temperature", SENSOR_READING_TYPE_MAXLEN - 1);
|
|
out[n].value = temp_c;
|
|
strncpy(out[n].unit, "c", SENSOR_READING_UNIT_MAXLEN - 1);
|
|
out[n].metadata = NULL;
|
|
n++;
|
|
|
|
memset(&out[n], 0, sizeof(out[n]));
|
|
strncpy(out[n].sensor_type, "pressure", SENSOR_READING_TYPE_MAXLEN - 1);
|
|
out[n].value = press_pa / 100.0; // Pa -> hPa
|
|
strncpy(out[n].unit, "hpa", SENSOR_READING_UNIT_MAXLEN - 1);
|
|
out[n].metadata = NULL;
|
|
n++;
|
|
|
|
*out_count = n;
|
|
return ESP_OK;
|
|
}
|