Developing BMP280 Sensor Support on STM32 Platforms

Hardware Wiring Specifications

Connecting the sensor requires attention to power levels and interface selection. The microcontroller must operate at 3.3V logic levels.

I2C Mode Configuration

The two-wire interface offers simplicity and supoprts multiple devices on the same bus.

MCU (STM32F103)      Target Device
-------------------------------------------------
PB6 (SCL)           <-> SCL
PB7 (SDA)           <-> SDA
3.3V Voltage Line   <-> VCC
GND Reference       <-> GND

SPI Mode Configuration

A four-wire connection allows higher throughput. Ensure Chip Select logic is managed correctly.

MCU (STM32F103)      Target Device
-------------------------------------------------
PA5 (SCK)           <-> SCK
PA7 (MOSI)          <-> MOSI
PA6 (MISO)          <-> MISO
PA4 (NSS/CS)        <-> CS
3.3V Voltage Line   <-> VCC
GND Reference       <-> GND

Communication Layer Implementation

I2C Bus Initialization

Utilizing the Hardware Abstraction Layer (HAL) ensures compatibility across STM32 generations.

#include "main.h"

// I2C Peripheral Handle Definition
I2C_HandleTypeDef h_i2c_sensor = {0};

void Initialize_I2C_Bus() 
{
    h_i2c_sensor.Instance = I2C1;
    // Set Speed to High Speed Mode (400kHz)
    h_i2c_sensor.Init.ClockSpeed = 400000;  
    h_i2c_sensor.Init.DutyCycle = I2C_DUTYCYCLE_2;
    h_i2c_sensor.Init.OwnAddress1 = 0;
    h_i2c_sensor.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    
    HAL_I2C_Init(&h_i2c_sensor);
}

Low-Level Register Access

Helper functions abstract the specific HAL calls for read and write operations.

#define SENSOR_ADDRESS 0x76

// Generic Read Function
uint8_t Read_Register(uint8_t reg_addr) {
    uint8_t temp_data;
    // MemRead writes register addr internally
    HAL_I2C_Mem_Read(&h_i2c_sensor, SENSOR_ADDRESS, reg_addr, 
                     I2C_MEMADD_SIZE_8BIT, &temp_data, 1, 100);
    return temp_data;
}

// Generic Write Function
void Write_Register(uint8_t reg_addr, uint8_t val) {
    HAL_I2C_Mem_Write(&h_i2c_sensor, SENSOR_ADDRESS, reg_addr, 
                      I2C_MEMADD_SIZE_8BIT, &val, 1, 100);
}

SPI Bus Implementation (HAL)

Leveraging the peripheral hardware is preferred over manual GPIO toggling for stability.

#include "spi.h" // Generated SPI handle

// Initialize SPI Port
void Init_SPI_Port() {
    __SPI2_CLK_ENABLE();
    // Configure Pins via RCC and GPIO (Assuming CubeMX generation here)
    HAL_SPI_Init(&h_spi2); 
}

// Read via SPI Interface
uint8_t SPI_Read_Reg(uint8_t reg_idx) {
    uint8_t tx_buf = reg_idx | 0x80; // Set Read Flag
    uint8_t rx_buf;
    
    // Enable CS Low
    GPIO_ResetBits(GPIOA, GPIO_PIN_4);
    
    // Transfer 1 byte dummy for Read
    HAL_SPI_TransmitReceive(&h_spi2, &tx_buf, &rx_buf, 1, 1000);
    
    // Disable CS High
    GPIO_SetBits(GPIOA, GPIO_PIN_4);
    
    return rx_buf;
}

Calibration and Data Processing

Drift Coefficients Structure

These values are unique to each sensor unit and stored in OTP memory addresses 0x88 to 0xA1.

typedef struct {
    uint16_t cal_T1;
    int16_t  cal_T2;
    int16_t  cal_T3;
    uint16_t cal_P1;
    int16_t  cal_P2;
    int16_t  cal_P3;
    int32_t  temperature_fine;
} Sensor_Drift_Params;

Loading Parameters

Merge consecutive bytes from registers to form 16-bit integers.

void Load_Calibration(Sensor_Drift_Params *params) {
    // Temperature Coefficients (Registers 0x88-0x8D)
    params->cal_T1 = (Read_Register(0x88) << 8) | Read_Register(0x89);
    params->cal_T2 = (int16_t)((Read_Register(0x8A) << 8) | Read_Register(0x8B));
    params->cal_T3 = (int16_t)((Read_Register(0x8C) << 8) | Read_Register(0x8D));
    
    // Pressure Coefficients (Registers 0x8E-0x93)
    params->cal_P1 = (Read_Register(0x8E) << 8) | Read_Register(0x8F);
    params->cal_P2 = (int16_t)((Read_Register(0x90) << 8) | Read_Register(0x91));
    params->cal_P3 = (int16_t)((Read_Register(0x92) << 8) | Read_Register(0x93));
}

Algebraic Compensation

Apply factory calibration constants to raw ADC readings.

// Temperature Calculation Logic
float Compute_Temperature(int32_t adc_raw_t) {
    int32_t var1, var2;
    
    // Step 1: Intermediate calculations
    var1 = (((adc_raw_t / 8) - ((sensor_params.cal_T1 << 1))) * 
            (sensor_params.cal_T2)) >> 11;
            
    var2 = (((((adc_raw_t >> 3) - (sensor_params.cal_T1 << 1)) * 
              ((adc_raw_t >> 3) - (sensor_params.cal_T1 << 1))) >> 10) * 
             sensor_params.cal_T3);
             
    sensor_params.temperature_fine = var1 + var2;
    
    // Return Celsius
    return (float)sensor_params.temperature_fine / 512.0f;
}

// Pressure Calculation Logic
float Compute_Pressure(int32_t adc_raw_p) {
    int64_t var1, var2, pressure_val;
    
    var1 = ((int64_t)sensor_params.temperature_fine) - 128000;
    var2 = var1 * var1 * (int64_t)sensor_params.cal_P6;
    var2 += (var1 * (int64_t)sensor_params.cal_P5) << 17;
    var2 += ((int64_t)sensor_params.cal_P4) << 35;
    
    var1 = ((var1 * var1 * (int64_t)sensor_params.cal_P3) >> 8) + 
           ((var1 * (int64_t)sensor_params.cal_P2) << 12);
    var1 = (((((int64_t)1) << 47) + var1) * ((int64_t)sensor_params.cal_P1)) >> 33;
    
    if (var1 == 0) return 0.0f; 
    
    pressure_val = (1048576UL - adc_raw_p) * 3125UL / var1;
    var1 = ((int64_t)sensor_params.cal_P9 * (pressure_val >> 13) * (pressure_val >> 13)) >> 25;
    var2 = ((int64_t)sensor_params.cal_P8 * pressure_val) >> 19;
    
    return (float)((pressure_val + var1 + var2) / 256) / 100.0f;
}

Data Acquisition Workflow

A single function call triggers sampling, data retrieval, and conversion.

void Get_Sensor_Values(float *out_pressure, float *out_temp) {
    uint8_t buffer[6];
    
    // Trigger Conversion (Write Reg 0xF4)
    Write_Register(0xF4, 0x27); // Force mode, Normal res
    
    HAL_Delay(10); // Wait for conversion (ms varies by settings)
    
    // Fetch 6 Bytes (Pressure MSB, Pressure LSB, Pressure XLSB, Temp MSB, etc.)
    uint8_t addr = 0xF7; 
    HAL_I2C_Mem_Read(&h_i2c_sensor, SENSOR_ADDRESS, addr, 
                     I2C_MEMADD_SIZE_8BIT, buffer, 6, 100);
                     
    // Construct Raw Integers
    int32_t raw_p = (buffer[0] << 12) | (buffer[1] << 4) | (buffer[2] >> 4);
    int32_t raw_t = (buffer[3] << 12) | (buffer[4] << 4) | (buffer[5] >> 4);
    
    *out_temp = Compute_Temperature(raw_t);
    *out_pressure = Compute_Pressure(raw_p);
}

Application Logic

The main application loop integrates initialization and preiodic sampling.

int main(void) {
    static Sensor_Drift_Params my_calib;
    float p_val, t_val;
    
    HAL_Init();
    SystemClock_Config();
    
    #ifdef USE_I2C_MODE
        Initialize_I2C_Bus();
        Load_Calibration(&my_calib);
    #else
        Init_SPI_Port();
        Write_Register(0xF4, 0x27);
    #endif
    
    while (1) {
        Get_Sensor_Values(&p_val, &t_val);
        
        printf("Temp: %.2f C, Press: %.2f hPa\r\n", t_val, p_val);
        HAL_Delay(1000);
    }
}

Tuning and Diagnostics

Configuration parameters impact accuracy and power consumption trade-offs.

Setting I2C Default SPI Optimized
Sampling Frequency 1 Hz 10 Hz
Oversampling Ratio Pt=16x, Pt=8x Pt=16x, Pt=16x
FIR Filter Level Level 2 Level 4
Power State Normal Forced

Validation Steps

  1. Bus Verification: Use a logic analyzer to confirm the address matches 0x76 (ADDR low) or 0x77 (ADDR high).
  2. Integrity Check: Verify that drift parameter T1 is never zero upon load.
  3. Power Management: Utilize sleep registers to extend battery life.
void Power_Save_Mode() {
    Write_Register(0xF4, 0x00); // Sleep Mode Enabled
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}

Extension: Altitude Derivation

Atmospheric pressure correlates linear with elevation relative to sea level standards.

float Calculate_Altitude(float current_pressure) {
    const float ref_sea_level = 1013.25f; // hPa
    return 44330.0f * (1.0f - powf(current_pressure / ref_sea_level, 0.1903f));
}

Signal Integrity Improvements

Raw sensor outputs may contain noise requiring digital smoothing.

#define QUEUE_LEN 5
static float history[QUEUE_LEN];

float Apply_MovingAverage(float new_val) {
    // Shift buffer
    for (int i = QUEUE_LEN - 1; i > 0; i--) {
        history[i] = history[i-1];
    }
    history[0] = new_val;
    
    // Sum and divide
    float sum = 0;
    for(int i=0; i<QUEUE_LEN; i++) sum += history[i];
    return sum / QUEUE_LEN;
}

Tags: STM32 BMP280 EmbeddedSystems HAL_Library SensorDrivers

Posted on Wed, 10 Jun 2026 19:02:23 +0000 by 8ennett