// 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 #include #include #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; }