SPL06是Sensirion公司生产的高精度数字气压传感器,测量范围300-1200hPa,精度可达±1Pa,对应高度分辨率约8.5厘米。在四旋翼无人机中,气压传感器是实现定高飞行和高度保持功能的关键器件。本章将讲解气压与高度的关系、SPL06的工作原理、校准补偿算法以及高度测量应用。

10.1 气压与高度的关系

大气压力随海拔高度升高而降低,这种关系可以用气压高度公式精确描述。在对流层(0-11km),标准大气模型下的高度计算公式为:

h = 44330 × (1 − (P/P₀)^0.1903)

其中:

h — 海拔高度(米)

P — 当前气压(Pa)

P₀ — 海平面标准大气压(101325 Pa)

这个公式表明,气压与高度呈非线性关系。在海平面附近,每升高约8.5米,气压下降约100Pa(1hPa);在高空,同样的气压变化对应更大的高度变化。

标准大气参数:

高度(km) 气压(hPa) 温度(°C) 说明
0 1013.25 15.0 海平面
1 898.76 8.5
2 795.01 2.0
3 701.21 -4.5
5 540.48 -17.5
10 265.00 -50.0 对流层顶

ℹ️**** 注意 实际应用中,由于天气变化导致的气压波动(可达数十hPa)远大于高度变化引起的气压变化,因此定高飞行需要实时校准参考气压值,或使用GPS辅助校正。

10.2 SPL06传感器特性

SPL06采用压阻式测量原理,内部集成了高灵敏度压力敏感元件和24位Σ-Δ ADC。其主要技术参数:

参数 规格 说明
压力范围 300-1200 hPa 对应海拔约9000m到-500m
绝对精度 ±1 Pa 对应高度误差约±8.5cm
相对精度 ±0.06 hPa 短期稳定性
分辨率 0.01 Pa 24位ADC
温度范围 -40°C ~ +85°C 工作温度
温度系数 ±1.5 Pa/K 温漂特性
采样率 最高128Hz 受OSR配置影响
接口 I2C/SPI 双接口支持
I2C地址 0x76或0x77 SDO引脚决定
电源电压 1.7-3.6V 低功耗设计
工作电流 3.6 μA 待机模式

SPL06的核心优势在于内置温度传感器和片上校准系数,通过补偿算法可以消除温度对压力测量的影响,获得高精度的气压值。

10.3 SPL06寄存器与校准

SPL06内部包含校准系数寄存器(C0-C11)和测量数据寄存器,主要寄存器映射如下:

寄存器名 地址 功能 说明
ID 0x0D 设备ID 值为0x10
STATUS 0x08 状态寄存器 数据就绪标志
PRES_H/M/L 0x00-0x02 压力数据 24位,大端序
TEMP_H/M/L 0x03-0x05 温度数据 24位,大端序
C0_H/L 0x0A-0x0B 校准系数C0 压力校准
C1_H/L 0x0C-0x0D 校准系数C1 压力校准
C00_H/M/L 0x13-0x15 校准系数C00 压力校准
C10_H/M/L 0x16-0x18 校准系数C10 压力校准
C01_H/L 0x19-0x1A 校准系数C01 温度补偿
C11_H/L 0x1B-0x1C 校准系数C11 温度补偿
C20_H/L 0x1D-0x1E 校准系数C20 温度补偿
C21_H/L 0x1F-0x20 校准系数C21 温度补偿
C30_H/L 0x21-0x22 校准系数C30 温度补偿
PRSR_CFG 0x06 压力配置 OSR设置
TMPR_CFG 0x07 温度配置 OSR设置
MEAS_CFG 0x08 测量配置 测量模式
CFG_REG 0x19 配置寄存器 FIFO等
RESET 0x0C 软复位 写0x09

过采样率(OSR)配置影响测量精度和噪声:

OSR 采样次数 噪声(RMS) 转换时间 应用场景
0 1 5.0 Pa 3.6 ms 快速测量
1 2 3.5 Pa 5.2 ms
2 4 2.5 Pa 8.4 ms
3 8 1.7 Pa 14.8 ms 一般应用
4 16 1.2 Pa 27.6 ms
5 32 0.9 Pa 53.2 ms 高精度
6 64 0.5 Pa 104.4 ms 无人机推荐

配置建议 无人机定高应用建议使用OSR=6(64次过采样),在精度和响应速度之间取得良好平衡。转换时间约100ms,满足10Hz控制频率需求。

10.4 数据读取与补偿算法

SPL06出厂时已将校准系数写入芯片内部寄存器。上电后需要读取这些系数,然后对原始压力和温度数据进行补偿计算,获得精确的物理量。

代码清单10-1 校准系数读取

#include "SPL06.h"

#define SPL06_REG_PSR_B2      0x00
#define SPL06_REG_TMP_B2      0x03
#define SPL06_REG_PRS_CFG     0x06
#define SPL06_REG_TMP_CFG     0x07
#define SPL06_REG_MEAS_CFG    0x08
#define SPL06_REG_CFG_REG     0x09
#define SPL06_REG_RESET       0x0C
#define SPL06_REG_ID          0x0D
#define SPL06_REG_COEF        0x10
#define SPL06_REG_COEF_SRCE   0x28

#define SPL06_I2C_TIMEOUT     60000U
#define SPL06_PRS_CFG_VALUE   0x23U  /* 4 Hz, 8 samples. */
#define SPL06_TMP_CFG_BASE    0x23U  /* 4 Hz, 8 samples. Bit7 is selected after reading COEF_SRCE. */
#define SPL06_SCALE_8X        7864320.0f

typedef struct
{
    int16_t c0;
    int16_t c1;
    int32_t c00;
    int32_t c10;
    int16_t c01;
    int16_t c11;
    int16_t c20;
    int16_t c21;
    int16_t c30;
} SPL06_CalibTypeDef;

static uint8_t spl06_addr = SPL06_I2C_ADDR_DEFAULT;
static SPL06_CalibTypeDef spl06_calib;
static float spl06_pressure_scale = SPL06_SCALE_8X;
static float spl06_temperature_scale = SPL06_SCALE_8X;
static int32_t spl06_reference_pressure_pa = 101325;
static uint8_t spl06_tmp_cfg_value = SPL06_TMP_CFG_BASE;
static int32_t spl06_pressure_offset_pa = 0;
static int16_t spl06_temperature_offset_c_x100 = 1500;

static int32_t SPL06_SignExtend(uint32_t value, uint8_t bits)
{
    uint32_t mask = 1UL << (bits - 1);
    return (int32_t)((value ^ mask) - mask);
}

static uint8_t SPL06_WaitEvent(uint32_t event)
{
    uint32_t timeout = SPL06_I2C_TIMEOUT;

    while(!I2C_CheckEvent(event))
    {
        if(timeout-- == 0)
        {
            I2C_GenerateSTOP(ENABLE);
            return 0;
        }
    }

    return 1;
}

static uint8_t SPL06_WriteByte(uint8_t reg, uint8_t data)
{
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY));

    I2C_GenerateSTART(ENABLE);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) return 0;

    I2C_Send7bitAddress((uint8_t)(spl06_addr << 1), I2C_Direction_Transmitter);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) return 0;

    I2C_SendData(reg);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return 0;

    I2C_SendData(data);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return 0;

    I2C_GenerateSTOP(ENABLE);
    return 1;
}

static uint8_t SPL06_ReadBytes(uint8_t reg, uint8_t *buf, uint8_t len)
{
    uint8_t i;

    if((buf == 0) || (len == 0)) return 0;

    while(I2C_GetFlagStatus(I2C_FLAG_BUSY));

    I2C_GenerateSTART(ENABLE);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) return 0;

    I2C_Send7bitAddress((uint8_t)(spl06_addr << 1), I2C_Direction_Transmitter);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) return 0;

    I2C_SendData(reg);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return 0;

    I2C_GenerateSTART(ENABLE);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) return 0;

    I2C_Send7bitAddress((uint8_t)(spl06_addr << 1), I2C_Direction_Receiver);
    if(!SPL06_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) return 0;

    for(i = 0; i < len; i++)
    {
        if(i == (len - 1))
        {
            I2C_AcknowledgeConfig(DISABLE);
            I2C_GenerateSTOP(ENABLE);
        }
        else
        {
            I2C_AcknowledgeConfig(ENABLE);
        }

        if(!SPL06_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
        {
            I2C_AcknowledgeConfig(ENABLE);
            return 0;
        }

        buf[i] = I2C_ReceiveData();
    }

    I2C_AcknowledgeConfig(ENABLE);
    return 1;
}

static uint8_t SPL06_ReadCalib(void)
{
    uint8_t coef[18];

    if(!SPL06_ReadBytes(SPL06_REG_COEF, coef, sizeof(coef))) return 0;

    spl06_calib.c0 = (int16_t)SPL06_SignExtend(((uint32_t)coef[0] << 4) | (coef[1] >> 4), 12);
    spl06_calib.c1 = (int16_t)SPL06_SignExtend(((uint32_t)(coef[1] & 0x0F) << 8) | coef[2], 12);
    spl06_calib.c00 = SPL06_SignExtend(((uint32_t)coef[3] << 12) | ((uint32_t)coef[4] << 4) | (coef[5] >> 4), 20);
    spl06_calib.c10 = SPL06_SignExtend(((uint32_t)(coef[5] & 0x0F) << 16) | ((uint32_t)coef[6] << 8) | coef[7], 20);
    spl06_calib.c01 = (int16_t)((coef[8] << 8) | coef[9]);
    spl06_calib.c11 = (int16_t)((coef[10] << 8) | coef[11]);
    spl06_calib.c20 = (int16_t)((coef[12] << 8) | coef[13]);
    spl06_calib.c21 = (int16_t)((coef[14] << 8) | coef[15]);
    spl06_calib.c30 = (int16_t)((coef[16] << 8) | coef[17]);

    return 1;
}

static uint8_t SPL06_ConfigureMeasurement(void)
{
    uint8_t coef_source = 0;
    uint8_t tmp_cfg = SPL06_TMP_CFG_BASE;

    if(SPL06_ReadBytes(SPL06_REG_COEF_SRCE, &coef_source, 1))
    {
        if(coef_source & 0x80)
        {
            tmp_cfg |= 0x80;
        }
    }

    spl06_pressure_scale = SPL06_SCALE_8X;
    spl06_temperature_scale = SPL06_SCALE_8X;

    if(!SPL06_WriteByte(SPL06_REG_PRS_CFG, SPL06_PRS_CFG_VALUE)) return 0;
    if(!SPL06_WriteByte(SPL06_REG_TMP_CFG, spl06_tmp_cfg_value)) return 0;
    if(!SPL06_WriteByte(SPL06_REG_CFG_REG, 0x00)) return 0;
    if(!SPL06_WriteByte(SPL06_REG_MEAS_CFG, 0x07)) return 0;

    return 1;
}

static float SPL06_CalculateTemperatureC(void)
{
    uint8_t raw[6];
    int32_t raw_temperature;
    float t_sc;

    if(!SPL06_ReadBytes(SPL06_REG_PSR_B2, raw, sizeof(raw))) return -1000.0f;

    raw_temperature = SPL06_SignExtend(((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | raw[5], 24);
    t_sc = (float)raw_temperature / spl06_temperature_scale;

    return 0.5f * (float)spl06_calib.c0 + (float)spl06_calib.c1 * t_sc;
}

static void SPL06_AutoSelectTemperatureSource(void)
{
    float temp_a;
    float temp_b;
    uint8_t cfg_a = spl06_tmp_cfg_value;
    uint8_t cfg_b = (uint8_t)(spl06_tmp_cfg_value ^ 0x80);

    SPL06_WriteByte(SPL06_REG_TMP_CFG, cfg_a);
    DelayMs(120);
    temp_a = SPL06_CalculateTemperatureC();

    SPL06_WriteByte(SPL06_REG_TMP_CFG, cfg_b);
    DelayMs(120);
    temp_b = SPL06_CalculateTemperatureC();

    /* In an indoor project, a value near room temperature is more useful than the wrong sensor source. */
    if((temp_a < 10.0f || temp_a > 45.0f) && (temp_b > 10.0f && temp_b < 45.0f))
    {
        spl06_tmp_cfg_value = cfg_b;
    }
    else
    {
        spl06_tmp_cfg_value = cfg_a;
    }

    SPL06_WriteByte(SPL06_REG_TMP_CFG, spl06_tmp_cfg_value);
    DelayMs(120);
}
void SPL06_I2CInit(void)
{
    GPIOB_SetBits(GPIO_Pin_12 | GPIO_Pin_13);
    GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);
    GPIOPinRemap(DISABLE, RB_PIN_I2C);

    I2C_Init(I2C_Mode_I2C, 100000, I2C_DutyCycle_2, I2C_Ack_Enable, I2C_AckAddr_7bit, 0x00);
    I2C_Cmd(ENABLE);
}

uint8_t SPL06_Init(uint8_t addr)
{
    uint8_t status;
    uint16_t timeout;

    spl06_addr = addr;

    SPL06_WriteByte(SPL06_REG_RESET, 0x89);
    DelayMs(40);

    timeout = 500;
    do
    {
        if(!SPL06_ReadBytes(SPL06_REG_MEAS_CFG, &status, 1)) return 0;
        if((status & 0xC0) == 0xC0) break;
        DelayMs(2);
    } while(--timeout);

    if(timeout == 0) return 0;
    if(!SPL06_ReadCalib()) return 0;

    DelayMs(5);
    if(!SPL06_ConfigureMeasurement()) return 0;
    SPL06_AutoSelectTemperatureSource();
    return 1;
}

uint8_t SPL06_GetChipID(void)
{
    uint8_t id = 0;

    SPL06_ReadBytes(SPL06_REG_ID, &id, 1);
    return id;
}

void SPL06_SetAltitudeReference(int32_t pressure_pa)
{
    if((pressure_pa > 30000) && (pressure_pa < 120000))
    {
        spl06_reference_pressure_pa = pressure_pa;
    }
}
void SPL06_SetPressureOffset(int32_t offset_pa)
{
    if((offset_pa > -20000) && (offset_pa < 20000))
    {
        spl06_pressure_offset_pa = offset_pa;
    }
}
void SPL06_SetTemperatureOffset(int16_t offset_c_x100)
{
    if((offset_c_x100 > -5000) && (offset_c_x100 < 5000))
    {
        spl06_temperature_offset_c_x100 = offset_c_x100;
    }
}
uint8_t SPL06_ReadDebug(SPL06_DebugTypeDef *debug)
{
    uint8_t raw[6];

    if(debug == 0) return 0;
    memset(debug, 0, sizeof(SPL06_DebugTypeDef));

    debug->chip_id = SPL06_GetChipID();
    SPL06_ReadBytes(SPL06_REG_COEF_SRCE, &debug->coef_source, 1);
    SPL06_ReadBytes(SPL06_REG_PRS_CFG, &debug->prs_cfg, 1);
    SPL06_ReadBytes(SPL06_REG_TMP_CFG, &debug->tmp_cfg, 1);
    SPL06_ReadBytes(SPL06_REG_MEAS_CFG, &debug->meas_cfg, 1);

    if(!SPL06_ReadBytes(SPL06_REG_PSR_B2, raw, sizeof(raw))) return 0;

    debug->raw_pressure = SPL06_SignExtend(((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | raw[2], 24);
    debug->raw_temperature = SPL06_SignExtend(((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | raw[5], 24);

    debug->c0 = spl06_calib.c0;
    debug->c1 = spl06_calib.c1;
    debug->c00 = spl06_calib.c00;
    debug->c10 = spl06_calib.c10;
    debug->c01 = spl06_calib.c01;
    debug->c11 = spl06_calib.c11;
    debug->c20 = spl06_calib.c20;
    debug->c21 = spl06_calib.c21;
    debug->c30 = spl06_calib.c30;

    return 1;
}
uint8_t SPL06_ReadData(SPL06_DataTypeDef *data)
{
    uint8_t raw[6];
    int32_t raw_pressure;
    int32_t raw_temperature;
    float p_sc;
    float t_sc;
    float pressure;
    float temperature;
    float altitude;

    if(data == 0) return 0;
    if(!SPL06_ReadBytes(SPL06_REG_PSR_B2, raw, sizeof(raw))) return 0;

    raw_pressure = SPL06_SignExtend(((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | raw[2], 24);
    raw_temperature = SPL06_SignExtend(((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | raw[5], 24);

    p_sc = (float)raw_pressure / spl06_pressure_scale;
    t_sc = (float)raw_temperature / spl06_temperature_scale;

    temperature = 0.5f * (float)spl06_calib.c0 + (float)spl06_calib.c1 * t_sc;
    pressure = (float)spl06_calib.c00 + p_sc * ((float)spl06_calib.c10 + p_sc * ((float)spl06_calib.c20 + p_sc * (float)spl06_calib.c30)) + t_sc * (float)spl06_calib.c01 + t_sc * p_sc * ((float)spl06_calib.c11 + p_sc * (float)spl06_calib.c21);

    if((pressure < 30000.0f) || (pressure > 120000.0f) || (temperature < -40.0f) || (temperature > 85.0f))
    {
        return 0;
    }

    pressure += (float)spl06_pressure_offset_pa;
    altitude = ((float)spl06_reference_pressure_pa - pressure) * 0.083f;

    data->pressure_pa = (int32_t)pressure;
    data->temperature_c_x100 = (int32_t)(temperature * 100.0f) + spl06_temperature_offset_c_x100;
    data->altitude_m_x100 = (int32_t)(altitude * 100.0f);

    return 1;
}
#ifndef __SPL06_H__
#define __SPL06_H__

#include "CH58x_common.h"

#define SPL06_I2C_ADDR_LOW      0x76
#define SPL06_I2C_ADDR_HIGH     0x77
#define SPL06_I2C_ADDR_DEFAULT  SPL06_I2C_ADDR_LOW

typedef struct
{
    int32_t pressure_pa;
    int32_t temperature_c_x100;
    int32_t altitude_m_x100;
} SPL06_DataTypeDef;

typedef struct
{
    uint8_t chip_id;
    uint8_t coef_source;
    uint8_t prs_cfg;
    uint8_t tmp_cfg;
    uint8_t meas_cfg;
    int32_t raw_pressure;
    int32_t raw_temperature;
    int16_t c0;
    int16_t c1;
    int32_t c00;
    int32_t c10;
    int16_t c01;
    int16_t c11;
    int16_t c20;
    int16_t c21;
    int16_t c30;
} SPL06_DebugTypeDef;

void SPL06_I2CInit(void);
uint8_t SPL06_Init(uint8_t addr);
uint8_t SPL06_ReadData(SPL06_DataTypeDef *data);
uint8_t SPL06_ReadDebug(SPL06_DebugTypeDef *debug);
void SPL06_SetAltitudeReference(int32_t pressure_pa);
void SPL06_SetPressureOffset(int32_t offset_pa);
void SPL06_SetTemperatureOffset(int16_t offset_c_x100);
uint8_t SPL06_GetChipID(void);

#endif
#include "CH58x_common.h"
#include "SPL06.h"

#define FLOOR_8_HEIGHT_M_X100  2400

static void UART1_Init(void)
{
    GPIOA_SetBits(GPIO_Pin_9);
    GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
    UART1_DefInit();
}

static void UART1_Print(const char *str)
{
    UART1_SendString((uint8_t *)str, (uint16_t)strlen(str));
    DelayMs(8);
}

static long AbsLong(long value)
{
    return value < 0 ? -value : value;
}

static void PrintDebugOnce(void)
{
    SPL06_DebugTypeDef dbg;
    char text[128];

    if(!SPL06_ReadDebug(&dbg))
    {
        UART1_Print("DBG read failed\r\n");
        return;
    }

    sprintf(text, "DBG id=0x%02X coef_src=0x%02X prs_cfg=0x%02X tmp_cfg=0x%02X meas=0x%02X\r\n",
            dbg.chip_id, dbg.coef_source, dbg.prs_cfg, dbg.tmp_cfg, dbg.meas_cfg);
    UART1_Print(text);

    sprintf(text, "RAW P=%ld T=%ld\r\n", dbg.raw_pressure, dbg.raw_temperature);
    UART1_Print(text);

    sprintf(text, "COEF c0=%d c1=%d c00=%ld c10=%ld\r\n",
            dbg.c0, dbg.c1, dbg.c00, dbg.c10);
    UART1_Print(text);

    sprintf(text, "COEF c01=%d c11=%d c20=%d c21=%d c30=%d\r\n",
            dbg.c01, dbg.c11, dbg.c20, dbg.c21, dbg.c30);
    UART1_Print(text);
}

static uint8_t CalibrateAltitudeForFloor8(void)
{
    SPL06_DataTypeDef data;
    int32_t pressure_sum = 0;
    uint8_t count = 0;
    uint8_t i;
    char text[96];

    UART1_Print("Calibrating floor-8 altitude...\r\n");

    for(i = 0; i < 20; i++)
    {
        if(SPL06_ReadData(&data))
        {
            pressure_sum += data.pressure_pa;
            count++;
        }
        DelayMs(100);
    }

    if(count == 0)
    {
        UART1_Print("Altitude calibration failed\r\n");
        return 0;
    }

    data.pressure_pa = pressure_sum / count;
    SPL06_SetAltitudeReference(data.pressure_pa + (int32_t)((float)FLOOR_8_HEIGHT_M_X100 / 100.0f / 0.083f));

    sprintf(text, "Altitude calibrated: floor8=%ld.%02ld m\r\n",
            FLOOR_8_HEIGHT_M_X100 / 100,
            FLOOR_8_HEIGHT_M_X100 % 100);
    UART1_Print(text);
    return 1;
}

int main(void)
{
    SPL06_DataTypeDef data;
    char text[128];
    uint8_t chip_id;

    SetSysClock(CLK_SOURCE_PLL_60MHz);
    UART1_Init();
    SPL06_I2CInit();

    DelayMs(300);
    UART1_Print("\r\n\r\nSPL06 barometer floor-8 start\r\n");

    chip_id = SPL06_GetChipID();
    sprintf(text, "Chip ID: 0x%02X\r\n", chip_id);
    UART1_Print(text);

    if(!SPL06_Init(SPL06_I2C_ADDR_DEFAULT))
    {
        UART1_Print("SPL06 init failed, check wiring/address\r\n");
        while(1)
        {
            DelayMs(1000);
        }
    }

    PrintDebugOnce();
    CalibrateAltitudeForFloor8();

    while(1)
    {
        if(SPL06_ReadData(&data))
        {
            sprintf(text,
                    "P:%ld Pa  T:%ld.%02ld C  FloorAlt:%ld.%02ld m\r\n",
                    data.pressure_pa,
                    data.temperature_c_x100 / 100,
                    AbsLong(data.temperature_c_x100 % 100),
                    data.altitude_m_x100 / 100,
                    AbsLong(data.altitude_m_x100 % 100));
            UART1_Print(text);
        }
        else
        {
            UART1_Print("SPL06 read invalid; debug follows\r\n");
            PrintDebugOnce();
        }

        DelayMs(1000);
    }
}

typedef struct {

int16_t c0, c1;

int32_t c00, c10;

int16_t c01, c11, c20, c21, c30;

} SPL06_Calib_t;

SPL06_Calib_t calib;

void SPL06_ReadCalibration(void) {

uint8_t buf[24];

// 从0x0A连续读取24字节校准数据

I2C_ReadBytes(SPL06_ADDR, 0x0A, buf, 24);

// 解析校准系数(注意字节序和符号扩展)

calib.c0 = (int16_t)((buf[0] << 4) | (buf[1] >> 4));

if (calib.c0 & 0x0800) calib.c0 |= 0xF000; // 符号扩展

calib.c1 = (int16_t)(((buf[1] & 0x0F) << 8) | buf[2]);

if (calib.c1 & 0x0800) calib.c1 |= 0xF000;

calib.c00 = (int32_t)((buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4));

if (calib.c00 & 0x080000) calib.c00 |= 0xFFF00000;

calib.c10 = (int32_t)(((buf[5] & 0x0F) << 16) | (buf[6] << 8) | buf[7]);

if (calib.c10 & 0x080000) calib.c10 |= 0xFFF00000;

calib.c01 = (int16_t)((buf[8] << 8) | buf[9]);

calib.c11 = (int16_t)((buf[10] << 8) | buf[11]);

calib.c20 = (int16_t)((buf[12] << 8) | buf[13]);

calib.c21 = (int16_t)((buf[14] << 8) | buf[15]);

calib.c30 = (int16_t)((buf[16] << 8) | buf[17]);

}

代码清单10-2 数据补偿计算

typedef struct {

float pressure; // Pa

float temperature; // °C

} SPL06_Data_t;

// 读取原始24位数据

int32_t SPL06_ReadRawPressure(void) {

uint8_t buf[3];

I2C_ReadBytes(SPL06_ADDR, 0x00, buf, 3);

int32_t raw = (buf[0] << 16) | (buf[1] << 8) | buf[2];

if (raw & 0x800000) raw |= 0xFF000000; // 符号扩展

return raw;

}

int32_t SPL06_ReadRawTemperature(void) {

uint8_t buf[3];

I2C_ReadBytes(SPL06_ADDR, 0x03, buf, 3);

int32_t raw = (buf[0] << 16) | (buf[1] << 8) | buf[2];

if (raw & 0x800000) raw |= 0xFF000000;

return raw;

}

// 补偿计算

SPL06_Data_t SPL06_Compensate(int32_t raw_p, int32_t raw_t) {

SPL06_Data_t result;

// 缩放原始数据

float Traw_sc = (float)raw_t / 1048576.0f;

float Praw_sc = (float)raw_p / 1048576.0f;

// 温度补偿计算

result.temperature = calib.c0 * 0.5f + calib.c1 * Traw_sc;

// 压力补偿计算(多项式)

result.pressure = calib.c00

+ Praw_sc * (calib.c10

+ Praw_sc * (calib.c20

+ Praw_sc * calib.c30))

+ Traw_sc * (calib.c01

+ Praw_sc * (calib.c11

+ Praw_sc * calib.c21));

return result;

}

// 完整读取函数

SPL06_Data_t SPL06_ReadData(void) {

int32_t raw_p = SPL06_ReadRawPressure();

int32_t raw_t = SPL06_ReadRawTemperature();

return SPL06_Compensate(raw_p, raw_t);

}

ℹ️**** 注意 补偿算法中的多项式系数由厂家校准确定,直接使用即可。计算时注意使用浮点运算以保证精度。

10.5 高度计算与滤波

获得精确气压值后,通过气压高度公式可以计算相对高度。由于无人机应用中需要稳定的高度估计,还需要对高度数据进行滤波处理。

代码清单11-3 高度计算与滤波

#include <math.h>

#define SEA_LEVEL_PRESSURE 101325.0f // 海平面标准大气压(Pa)

#define ALTITUDE_FILTER_N 8 // 滑动平均窗口

// 气压转高度

float PressureToAltitude(float pressure_pa) {

return 44330.0f * (1.0f - powf(pressure_pa / SEA_LEVEL_PRESSURE, 0.1903f));

}

// 滑动平均滤波器

typedef struct {

float buffer[ALTITUDE_FILTER_N];

int index;

int count;

} MovingAvg_t;

MovingAvg_t alt_filter = {0};

void MovingAvg_Reset(MovingAvg_t *f) {

f->index = 0;

f->count = 0;

for (int i = 0; i < ALTITUDE_FILTER_N; i++) {

f->buffer[i] = 0;

}

}

float MovingAvg_Update(MovingAvg_t *f, float new_value) {

f->buffer[f->index] = new_value;

f->index = (f->index + 1) % ALTITUDE_FILTER_N;

if (f->count < ALTITUDE_FILTER_N) {

f->count++;

}

float sum = 0;

for (int i = 0; i < f->count; i++) {

sum += f->buffer[i];

}

return sum / f->count;

}

// 一阶低通滤波器

typedef struct {

float alpha; // 滤波系数 (0-1)

float last; // 上次输出

int initialized;

} LowPassFilter_t;

LowPassFilter_t alt_lpf = {.alpha = 0.1f};

float LowPass_Filter(LowPassFilter_t *f, float input) {

if (!f->initialized) {

f->last = input;

f->initialized = 1;

return input;

}

f->last = f->alpha * input + (1.0f - f->alpha) * f->last;

return f->last;

}

// 参考气压(用于相对高度测量)

float reference_pressure = 0;

void CalibrateReferencePressure(void) {

float sum = 0;

for (int i = 0; i < 16; i++) {

SPL06_Data_t data = SPL06_ReadData();

sum += data.pressure;

DelayMs(50);

}

reference_pressure = sum / 16.0f;

printf(“Reference pressure: %.2f Pa\n”, reference_pressure);

}

// 计算相对高度

float GetRelativeAltitude(void) {

SPL06_Data_t data = SPL06_ReadData();

float alt_ref = 44330.0f * (1.0f - powf(reference_pressure / SEA_LEVEL_PRESSURE, 0.1903f));

float alt_cur = 44330.0f * (1.0f - powf(data.pressure / SEA_LEVEL_PRESSURE, 0.1903f));

float relative_alt = alt_cur - alt_ref;

// 应用低通滤波

return LowPass_Filter(&alt_lpf, relative_alt);

}

高度测量技巧 起飞前校准参考气压值非常重要,可以消除天气变化带来的误差。建议取多次测量的平均值作为参考气压。

滤波器的选择需要根据应用场景权衡:滑动平均滤波器简单有效,但会引入延迟;低通滤波器响应更快,通过调节alpha系数可以控制平滑程度。在实际无人机控制中,通常将气压高度与加速度计数据进行融合(互补滤波或卡尔曼滤波),获得更准确和响应更快的高度估计。

10.6 实验:高度计与定高飞行原理

本实验将实现SPL06气压传感器的高度测量功能,验证气压高度计的精度和稳定性,并理解定高飞行的基本原理。

代码清单11-4 高度计测试程序

#include <stdio.h>

#include “i2c.h”

#include “uart.h”

#include “spl06.h”

int main(void) {

SystemInit();

UART_Init(115200);

I2C_Init(400000);

printf(“\n=== SPL06 Barometric Altimeter ===\n”);

printf(“Initializing sensor…\n”);

// 初始化SPL06

SPL06_Init();

// 校准参考气压

printf(“Calibrating reference pressure…\n”);

CalibrateReferencePressure();

printf(“Calibration complete.\n\n”);

printf(“Format: Pressure(Pa) | Temp(°C) | Altitude(m) | Alt_Filt(m)\n\n”);

// 主循环

while (1) {

SPL06_Data_t data = SPL06_ReadData();

float altitude = PressureToAltitude(data.pressure);

float rel_alt = GetRelativeAltitude();

printf(“P: %.2f | T: %.2f | Alt: %.2f | Alt_F: %.2f\n”,

data.pressure, data.temperature, altitude, rel_alt);

DelayMs(100); // 10Hz

}

return 0;

}

实验步骤:

  1. 将程序编译下载到飞控板,打开串口助手

  2. 观察静止状态下的气压、温度和高度数据,评估噪声水平

  3. 缓慢抬高飞控板(如举过头顶),观察高度变化是否与实际相符

  4. 快速上下移动飞控板,观察滤波器引入的延迟

  5. 对比原始高度和滤波后高度的波形,理解滤波效果

  6. (选做)尝试调节低通滤波系数alpha,观察响应速度和平滑度的变化

🧪**** 实验 设计一个简单的定高控制逻辑:设定目标高度为1米,当实际高度低于0.8米时增加油门,高于1.2米时减小油门,观察高度是否能稳定在目标值附近。

定高飞行原理 实际无人机的定高控制采用PID算法,将高度误差转化为油门修正量。气压高度计提供慢速但准确的高度基准,加速度计提供快速的垂直速度信息,两者融合实现平稳的定高飞行。


学习资源

🎬 B站系列视频教程手把手教你从零组装无人机

📦 百度网盘资料包(SDK、教材PDF、完整源码):点击下载 提取码: JZS8


本系列持续更新,欢迎收藏关注。如有问题欢迎在评论区交流。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐