【飞控系列教程·⑩】SPL06气压传感器详解:气压测高原理、校准补偿与定高飞行应用
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;
}
实验步骤:
-
将程序编译下载到飞控板,打开串口助手
-
观察静止状态下的气压、温度和高度数据,评估噪声水平
-
缓慢抬高飞控板(如举过头顶),观察高度变化是否与实际相符
-
快速上下移动飞控板,观察滤波器引入的延迟
-
对比原始高度和滤波后高度的波形,理解滤波效果
-
(选做)尝试调节低通滤波系数alpha,观察响应速度和平滑度的变化
🧪**** 实验 设计一个简单的定高控制逻辑:设定目标高度为1米,当实际高度低于0.8米时增加油门,高于1.2米时减小油门,观察高度是否能稳定在目标值附近。
定高飞行原理 实际无人机的定高控制采用PID算法,将高度误差转化为油门修正量。气压高度计提供慢速但准确的高度基准,加速度计提供快速的垂直速度信息,两者融合实现平稳的定高飞行。
学习资源
🎬 B站系列视频教程:手把手教你从零组装无人机
📦 百度网盘资料包(SDK、教材PDF、完整源码):点击下载 提取码: JZS8
本系列持续更新,欢迎收藏关注。如有问题欢迎在评论区交流。
更多推荐
所有评论(0)