资讯详情

SPL06气压温度传感器驱动开发:SPI通信与补偿算法实战

📅 2026/9/19 7:58:59 | 华诺云谱 👁 阅读
SPL06气压温度传感器驱动开发:SPI通信与补偿算法实战
手头这个模块项目最后用到的就是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用作MOSISDO用作MISOCSB作为片选。选择SPI而不是I2C主要考虑三点SPI是全双工调试时可以用逻辑分析仪直接看时序比I2C那种半双工协议直观SPI从机片选可控性强一根CSB就能把一个传感器从总线上摘下来不影响其他SPI设备还有一点是SPI传输速度上限高就算以后要做连续FIFO读取也不会成为瓶颈。注意SPL06在SPI模式下通常使用Mode 3也就是CPOL1、CPHA1空闲时钟为高电平数据在第二个边沿采样。这个模式搞错的话读回来的数据永远是乱的而且很难定位问题。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_reg和spl06_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这类带校准系数的传感器本质上就是把工厂标定信息以定点格式藏在寄存器里驱动干的事情就是正确取出这些信息并还原成物理世界的数据。只要顺着这个思路走遇到问题逐项排查很快就能调通。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。