SPL06气压温度传感器驱动开发:SPI通信与补偿算法实战
2026/9/19 7:58:43 网站建设 项目流程

手头这个模块项目,最后用到的就是SPL06气压温度传感器。这颗来自Sensirion的小芯片,同时输出气压和温度,支持I2C和SPI两种接口,而我最终选了SPI驱动为主,把整套驱动和补偿算法从头到尾调通。调试过程中踩了不少坑,包括SPI时序不稳、校准系数符号位搞反、补偿后数据跳变等问题,今天把这些经验一条条整理出来。

这篇东西适合正在写SPL06驱动、或者刚接触气压传感器补偿算法的嵌入式开发者参考。基于STM32F103 + HAL库 + CubeMX环境,但拆解出来的寄存器操作思路和补偿公式是芯片级的,换别的MCU一样能用。读完你会发现,SPL06的驱动核心其实就两块:一是把SPI通信搞稳,二是把补偿系数按定点分辨率还原成物理量。前者卡通信,后者卡精度,没有想象中那么玄乎。

1. SPL06这颗芯片,选它之前要搞清楚的几件事

1.1 一颗芯片两路数据:传感器整体认知

SPL06-007是一款压阻式气压传感器,内部集成了温度传感单元,量程覆盖300 hPa到1200 hPa,典型相对精度能做到±0.06 hPa左右。供电电压范围1.7V到3.6V,待机电流在微安级别,非常适合无人机定高、可穿戴设备、便携气象站这类对功耗和体积都比较敏感的场景。

我这次选型时对比过BMP280和MS5611。BMP280生态成熟、代码多,但它的压力分辨率在这个项目里不够好看,而且长期供货不太舒服;MS5611精度确实好,但价格高出不少,对成本敏感的板子不合适。SPL06刚好卡在中间,精度够用,价格能接受,还同时给了I2C和SPI,灵活性高了不少。

项目SPL06-007BMP280MS5611
压力量程300 ~ 1200 hPa300 ~ 1100 hPa10 ~ 1200 hPa
相对精度±0.06 hPa(典型)±0.12 hPa±0.012 hPa
接口I2C / SPII2C / SPII2C / SPI
温压一体
价格区间中等较低较高

从应用层面讲,SPL06还有一个优势是自带FIFO和中断输出,可以在MCU休眠时让传感器自己攒数据,唤醒后再一次性批量读取。这个特性在做低功耗数据记录仪时特别有用,后面可以单独说。

1.2 SPI和I2C怎么选:引脚配置与接口取舍

SPL06的封装上有一组引脚用于选择通信模式,具体通过PSINT和PSEN两个引脚的电平组合决定。实际模块上一般已经固定好,要么是I2C模式,要么是SPI模式。选型板卡的时候就要确认清楚,别等画完板才发现芯片工作在I2C模式,SPI怎么调都不通。

我这块板子是SPI模式,四线制,SDI用作MOSI,SDO用作MISO,CSB作为片选。选择SPI而不是I2C,主要考虑三点:SPI是全双工,调试时可以用逻辑分析仪直接看时序,比I2C那种半双工协议直观;SPI从机片选可控性强,一根CSB就能把一个传感器从总线上摘下来,不影响其他SPI设备;还有一点是SPI传输速度上限高,就算以后要做连续FIFO读取,也不会成为瓶颈。

注意:SPL06在SPI模式下通常使用Mode 3,也就是CPOL=1、CPHA=1,空闲时钟为高电平,数据在第二个边沿采样。这个模式搞错的话,读回来的数据永远是乱的,而且很难定位问题。

2. 寄存器地图与SPI驱动实现

2.1 寄存器地图:控制测量流程的关键地址

SPL06的寄存器空间不大,但每个寄存器都有讲究。驱动里经常打交道的集中在几块:测量结果寄存器、配置寄存器、校准系数寄存器。测量结果寄存器从0x00开始,压力原始值占0x00到0x02三个字节,温度原始值占0x03到0x05三个字节,都是24位数据。

配置寄存器方面,0x06是PRS_CFG,控制压力过采样率和压力测量速率;0x07是TMP_CFG,控制温度过采样率和温度测量速率;0x08是MSR_CTRL,控制测量模式,是单次测量还是连续测量、测压力还是测温度。校准系数寄存器区从0x10开始,一直延伸到0x24附近,里面藏着做补偿算法必需的系数。

寄存器地址名称作用
0x00 ~ 0x02PRS_OUT压力原始测量值
0x03 ~ 0x05TMP_OUT温度原始测量值
0x06PRS_CFG压力过采样率、测量速率
0x07TMP_CFG温度过采样率、测量速率
0x08MSR_CTRL测量模式控制
0x10 ~ 0x24CALIB校准系数区

读寄存器的时候有个地方容易忽略:传感器上电后默认可能处于待机模式,光读出数据全是0或者旧值。所以初始化时一定要先配置MSR_CTRL,触发连续测量模式,再等待一定时间,之后读出来的结果才是有效的。

2.2 四线SPI读写时序:Mode 3下的双字节学问

SPL06的SPI命令字节格式很简单,最高位表示读还是写,低7位是寄存器地址。写命令时最高位为0,比如写0x08寄存器,发送的字节就是0x08;读命令时最高位为1,比如读0x06寄存器,发送的字节就是0x86。也就是0x80 | reg_addr

但读操作有一个坑:发送完命令字节之后,从机需要一个额外的时钟周期才能把数据搬到MISO线上。也就是说,读一个寄存器至少要两个字节的时钟周期,第一个字节发命令,第二个字节主机发一个占位时钟,同时把从机的返回值收进来。CSN在整个过程中必须保持低电平,不能发完命令就拉高,否则数据直接断掉。

下面是HAL库环境下的单字节读函数:

static uint8_t spl06_read_reg(uint8_t reg) { uint8_t cmd = 0x80 | (reg & 0x7F); uint8_t val = 0; HAL_GPIO_WritePin(SPL06_CS_GPIO_Port, SPL06_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, &val, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(SPL06_CS_GPIO_Port, SPL06_CS_Pin, GPIO_PIN_SET); return val; }

写操作相对简单一点,命令字节和数据字节连着发,CSN保持低电平,发完再拉高。HAL库的HAL_SPI_Transmit一次可以传多字节,直接把两个字节的数组传进去就行。

2.3 驱动代码分层:把平台相关的东西隔离出去

写传感器驱动最忌讳的就是把所有代码揉成一团,换块板子就要推倒重写。我习惯分成三层:平台层管具体的SPI读写字函数,芯片层管寄存器配置和测量结果组合,应用层管补偿算法和数据处理。平台层只暴露spl06_read_regspl06_write_reg两个接口,芯片层和上面两层完全不关心底层是STM32还是GD32,也不关心HAL库还是寄存器操作。

这样的好处很明显。后面如果要把SPL06挂到另一颗MCU上,只需要重写平台层的两个函数,芯片层和应用层原封不动搬过去。多花十分钟拆层,省下后面一天的移植时间。

初始化流程我是这样安排的:先通过SPI读取校准系数区,确认通信正常,因为通信没建立时这一片读回来往往全是0xFF或0x00;然后配置PRS_CFG和TMP_CFG,设置过采样率和测量速率;最后写MSR_CTRL进入连续测量模式。校准系数读一次就够了,存到一个结构体里,后续补偿计算直接复用,不用每次测量都去读一遍寄存器。

3. 补偿算法详解:从AD值到工程单位

3.1 校准系数读取与符号扩展

SPL06的校准系数全部是补码有符号数,但长度不统一,14位、19位、20位都有,分布在0x10到0x24这21个字节里。直接从寄存器里把三个字节拼成一个int32再拿来计算是不行的,必须先按对应位宽做符号扩展,再乘上各自的定点分辨率。

符号扩展的基本原理是:如果符号位为1,就把高位全部填1,否则高位填0。我用了一个通用函数来处理不同位宽的扩展,避免为每个系数写一套逻辑。

static int32_t sign_extend(uint32_t val, uint8_t bits) { uint32_t mask = (1UL << bits) - 1; uint32_t sign = (1UL << (bits - 1)); val &= mask; if (val & sign) { val |= ~mask; } return (int32_t)val; }

比如校准系数c00是20位有符号数,在寄存器里占三个字节,但最高字节只有低4位有效。拼出来之后用sign_extend(raw, 20),得到的就是完整的有符号整数值,然后再乘以c00对应的物理分辨率2的负16次方,才是用于补偿公式的物理量。

3.2 温度补偿计算:别忘了高位扩展那一步

SPL06的温度补偿不只是简单除一下。手册里给出的做法是先把24位温度原始值作为一个整体,根据温度过采样率对应的缩放因子kT右移,得到T_RAW_SC。这个kT和TMP_CFG里配置的过采样率一一对应,过采样率越高,kT越大。

当温度原始值有效位宽超过18位时,还需要做一次二阶修正,把温度非线性的尾巴拉回来。这个修正项在温度变化跨度大的场景下尤其明显,不做的话,高温段和低温段的温度误差可能差出好几度,压力补偿跟着也会被带偏。

温度补偿的工程实现:

static int32_t merge_raw24(uint8_t h, uint8_t m, uint8_t l) { int32_t raw = ((int32_t)h << 16) | ((int32_t)m << 8) | l; if (raw & 0x800000) { raw |= 0xFF000000; } return raw; } double spl06_calc_temp(spl06_dev_t *dev, int32_t t_raw) { double t_raw_sc = (double)t_raw / (double)(1 << dev->kT); // 高位扩展后的二阶修正 if (t_raw > 0x80000 || t_raw < -0x80000) { t_raw_sc += (t_raw_sc * (double)(2 * t_raw_sc + 1)) / 65536.0; } return t_raw_sc / 100.0; // 转换为摄氏度 }

上面代码里把二阶修正做在了24位原始值超出18位范围的条件下,实际使用中如果发现温度线性度不够,可以把条件放宽甚至无条件执行。修正项的系数在手册中有明确说明,比例因子直接抄手册换算成浮点运算即可。

3.3 压力补偿计算:双变量多项式拟合

SPL06的压力补偿是一个关于压力原始值和温度原始值的二维多项式。公式长这样:

P_COMP = c00 + P_RAW_SC * (c10 + P_RAW_SC * (c20 + P_RAW_SC * c30)) + T_RAW_SC * (c01 + P_RAW_SC * (c11 + P_RAW_SC * c21))

其中P_RAW_SC是压力原始值右移kP位之后的值,T_RAW_SC是温度补偿后的缩放值。注意这里的系数c00、c10等,都是从校准系数寄存器读出来的定点值乘上各自分辨率之后的物理值,不是寄存器里的裸整数。

我直接用double做整个多项式运算。STM32F103没有硬件浮点单元,但测量频率只有几赫兹到几十赫兹,double的软件运算开销完全在可接受范围内。用float也不是不行,但多项式里有好几项是乘加叠加,float的尾数精度在极端气压下会出现末尾偏差,既然不差这点时间,就用double求稳。

压力补偿代码:

double spl06_calc_pressure(spl06_dev_t *dev, int32_t p_raw, int32_t t_raw) { double p_raw_sc = (double)p_raw / (double)(1 << dev->kP); double t_raw_sc = (double)t_raw / (double)(1 << dev->kT); double p_comp = dev->calib.c00 + p_raw_sc * (dev->calib.c10 + p_raw_sc * (dev->calib.c20 + p_raw_sc * dev->calib.c30)) + t_raw_sc * (dev->calib.c01 + p_raw_sc * (dev->calib.c11 + p_raw_sc * dev->calib.c21)); return p_comp; // 单位是Pa }

最后输出的P_COMP单位是帕斯卡,要转成气象上常用的hPa就是除以100。如果应用里用到的是海拔高度,再拿标准大气公式换算即可。

3.4 过采样率、缩放因子和测量时间的配合

过采样率直接决定原始分辨率,也决定补偿公式中右移的位数。SPL06的压力过采样率从1倍到128倍可选,对应PRS_CFG寄存器bit6到bit4的编码0到7;温度过采样率同理,对应TMP_CFG寄存器。缩放因子kP和kT,在数值上就等于过采样率编码值,所以代码里不需要额外查表,直接取配置值用。

但过采样率不是越高越好。128倍过采样下,一次压力测量要25毫秒左右,如果系统对实时性有要求,比如无人机姿态控制里的气压定高,25毫秒的延迟会明显影响控制带宽。我在这块板子上用的压力32倍过采样、温度8倍过采样,测量速率设在16Hz,既保证短期噪声够低,又没有拖慢主控的读取节奏。

提示:过采样率改变之后,原始值右移位数也要跟着改,否则补偿结果会系统性偏移。我见过有的工程直接抄示例代码的补偿函数,但配置寄存器改成了低过采样率,导致kP不匹配,压力读数差了几十个帕斯卡还找不到原因。

4. 基于CubeMX的工程配置与移植实战

4.1 CubeMX配置SPI外设的关键参数

在CubeMX里把SPI1配置为全双工主机模式,硬件NSS信号禁用,完全由软件控制CSB引脚。这里我强烈建议用软件片选,不要偷懒用硬件片选。STM32的硬件NSS在某些库版本下会随SPI事件自动变化,一旦时序没调对,CSN提前拉高,通信被截断,排查起来很折磨人。软件片选就是一根普通GPIO,想什么时候拉高就什么时候拉高,调试时还能手动控制,逻辑清楚很多。

SPI参数设置如下:波特率预分频选择16分频,在72MHz系统时钟下就是4.5MHz,比SPL06标称的最高SPI时钟低不少,留足裕量;时钟极性CPOL设为High,时钟相位CPHA设为2 Edge,也就是Mode 3;数据宽度8位,MSB先行。

CubeMX生成工程后,CSB引脚手动配置为GPIO输出,初始电平拉高。在spl06_init函数里再把CSB拉高一次,确保上电时片选是释放状态,避免芯片因为CSN悬空进入异常状态。

4.2 初始化与读取流程:从寄存器到工程单位

初始化函数做的事情不多,但顺序不能乱。先读校准系数,确认SPI通信正常;然后配置PRS_CFG和TMP_CFG;最后写MSR_CTRL触发连续测量。我把校准系数读取放在配置之前的原因是,校准系数区在芯片内部独立存储,不受配置寄存器影响,万一SPI模式配置错了,校准系数读出来是乱值,可以提前发现通信问题。

主循环里的读取流程很简单:连续读0x00到0x02得到压力原始值,连续读0x03到0x05得到温度原始值,然后分别调用补偿函数,得到以Pa为单位的压力和以摄氏度表示的温度。

void spl06_read_data(spl06_dev_t *dev, float *pressure_hpa, float *temp_c) { uint8_t prs_buf[3]; uint8_t tmp_buf[3]; spl06_read_burst(0x00, prs_buf, 3); spl06_read_burst(0x03, tmp_buf, 3); int32_t p_raw = merge_raw24(prs_buf[0], prs_buf[1], prs_buf[2]); int32_t t_raw = merge_raw24(tmp_buf[0], tmp_buf[1], tmp_buf[2]); double temp = spl06_calc_temp(dev, t_raw); double pres = spl06_calc_pressure(dev, p_raw, t_raw); *pressure_hpa = (float)(pres / 100.0); *temp_c = (float)temp; }

这里用burst读一次拉三个字节,比每字节单独拉一次CSN快很多,也省得多次切换GPIO电平引入时序毛刺。spl06_read_burst和单字节读类似,只是发送命令后连续接收多个字节,CSN保持低电平到全部字节收完。

4.3 数据验证:和真实气压对不上的时候怎么办

驱动写完先别急着上算法,先做一个最基础的SPI回环测试,确认硬件链路没问题。可以先把MISO和MOSI短接,自发自收,如果收到的和发送的一致,说明SPI外设本身是好的。接着再读SPL06的校准系数寄存器,看看数据是否合理。

校验数据合理性有个笨但有效的办法:把传感器放在当前环境下,查一下当地气象站的气压值,再根据海拔修正一下,正常的读数应该在当地大气压上下几个hPa范围内。温度读数应该和室温接近。如果压力偏出几十甚至上百hPa,大概率不是补偿公式的问题,而是原始值组合错误或符号扩展没做对。

我在早期调试时遇到过一次压力值整体偏大的情况,后来查到是24位原始值拼接时高低字节顺序颠倒。SPL06的三字节结果是小端在前,最低字节在低地址,拼成int32时不能按习惯的高中低顺序直接移位。这个问题不看逻辑分析仪很难发现,因为数据看起来并不是完全乱掉,只是整体平移了一个量。

5. 实际调试中的排雷记录

5.1 SPI通信不生效,按这个顺序查

SPI不生效是传感器调试里最磨人的问题。我总结了一个固定排查顺序,每一步都能快速定位大概率故障方向:

先看CSN电平。用示波器或者逻辑分析仪抓CSN,确认每次通信时确实拉低了,拉低持续的时间够不够两个字节周期。软件片选尤其容易出这个问题,发完命令字节后如果有别的代码插进来,CSN拉高得太早,数据就废了。

再看SPI模式。SPL06是SPI Mode 3,空闲时钟高电平,数据在第二个边沿采样。如果配成Mode 0,时钟极性反了,读出来全是0xFF或者固定值。这个用逻辑分析仪一眼就能看出来,看SCLK空闲电平是高是低。

接着看波特率。SPI时钟太高时,如果杜邦线比较长,信号质量会明显下降,波形边沿变缓,从机采错数据。排查时可以把预分频调大,降到1MHz甚至500kHz试一下。如果低速下通信正常,高速下不稳定,优先怀疑布线寄生电容和串联电阻。

最后看命令字节最高位。读命令必须带上0x80,写命令不能带0x80。这个错误通常出现在代码复用的时候,把一个寄存器的读地址拿去写,写操作变成读操作,寄存器值怎么都改不掉。

5.2 数据跳变和偏移,问题不一定在算法

补偿算法检查了很多遍都没有问题,但读数还是跳,这种情况问题往往出在电源和布局上。SPL06是模拟传感器,对电源纹波敏感。如果芯片的VDD引脚旁边没有放一个100nF的去耦电容,或者这个电容离芯片引脚太远,气压噪声会明显变大。

我在测试时遇到过一种奇葩情况:传感器旁边有一颗DC-DC电感,开关频率正好落在测量带宽内,导致压力原始值叠加了一个周期性噪声,补偿之后压力值也来回跳。后面把传感器供电换到LDO输出,再在传感器附近补了一颗陶瓷电容,噪声一下子就压下去了。

软件层面也要注意读取时机。SPL06在每次测量完成前,结果寄存器里可能还是上一次的数据或者中间值。如果主控的轮询频率高于传感器的实际测量完成频率,就会读到重复值,看起来像是数据卡顿或跳变。要么把读取周期拉长到大于单次测量时间,要么在读取前主动等待足够时间。

5.3 常见问题速查表

现象可能原因解决方向
读回0xFF或0x00SPI模式错误、CSN没拉低进Mode 3,检查CSN电平
压力值整体偏移固定量24位原始值拼接错误核对寄存器字节序
温度、压力短期跳动大供电纹波、缺少去耦电容换LDO供电,加100nF电容
过采样率改了读数变化异常kP/kT缩放因子不同步改配置时同步修改右移位数
长时间运行后数据卡死测量完成时间不够、读取太快延长读取周期,等待测量完成
高速SPI时偶发错误信号质量差、走线过长降低SPI时钟,缩短杜邦线

5.4 关于DMA和FreeRTOS的一点建议

有读者问过SPL06读取要不要用SPI DMA,我的结论是这个场景基本没必要。一次读取就三五个字节,CPU轮询的耗时在微秒级别,DMA的优势在传输大批量数据时才明显,给SPL06上DMA属于给自己找复杂度。如果非要用DMA连续读取,记住SPI是全双工,读操作需要同时向MOSI发送占位数据,所以TX和RX各需要一条DMA通道,也就是热词里常说的“SPI需要两个DMA吗”的来源。

如果项目跑的是FreeRTOS,要注意SPI总线的互斥。多个任务不要同时调用SPL06的读写函数,否则片选信号会被任务调度打断,轻则读错数据,重则挂死总线。最省事的做法是把传感器读取集中到一个任务里,其他任务通过队列拿结果。SPI中断优先级也尽量别设得过高,保持在configMAX_SYSCALL_INTERRUPT_PRIORITY以下,避免在中断上下文里调用HAL的SPI接口造成临界区异常。

这套驱动现在跑得挺稳,补偿后的温度和压力数据和参考仪表对比,误差在手册标称范围内。回看整个调试过程,最值的经验反而是那句老话:寄存器操作和公式本身都不难,难的是把公式里的每个符号和寄存器里的每一位对上号。SPL06这类带校准系数的传感器,本质上就是把工厂标定信息以定点格式藏在寄存器里,驱动干的事情就是正确取出这些信息并还原成物理世界的数据。只要顺着这个思路走,遇到问题逐项排查,很快就能调通。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询