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
- Bus Verification: Use a logic analyzer to confirm the address matches 0x76 (ADDR low) or 0x77 (ADDR high).
- Integrity Check: Verify that drift parameter T1 is never zero upon load.
- 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;
}