简介:面向STM32F103的电容传感器PCAP01数据读取与发送程序包,为嵌入式开发者提供了一套完整的SPI与RS-485通信参考。压缩包共154个文件,大小约2.61MB,以C源码、头文件、Keil工程文件为主,附带HEX固件、AXF调试文件及批处理脚本,可直接编译使用。程序实现了PCAP01内部寄存器读写,支持按数据手册调整刷新频率与精度,从而兼顾实时性与准确性;数据通过UART转RS-485半双工方式远距离发送,并集成SPI Flash(W25Q16)驱动,用于保存配置或缓存测量数据。工程在引脚分配、SPI时序、中断处理与错误检测上有完整示例,可帮助理解电容传感器采集链路。已有4213人学习下载,适合工业传感、物联网节点开发等场景。 第一次拿到 PCAP01 这颗芯片时,我差点被它"骗"了。表面上看,它就是个 SPI 接口的电容采集前端,写个寄存器、读个数据、串口丢出去,完事。可真把"电容传感器 PCAP01 数据读取及发送程序"完整调通,我才意识到这芯片的思路和普通 ADC 完全不一样——它给你的不是"你接了多少 pF 的电容",而是一个和放电时间强相关的比例码。不把这一点搞清楚,后面每一步都会踩坑。
这篇文章把我做这个项目的完整过程记录下来,从测量原理、硬件连接、寄存器初始化,到 SPI 读取、数据换算和串口发送,全部走一遍。适合正在用 PCAP01 做液位检测、压力传感、微电容测量,或者纯粹被这颗芯片的手册绕晕的工程师参考。我会把调试中真正卡住我的地方单独拉出来讲,这些在数据手册里可不会写。
1. PCAP01 的测量核心:为什么说它测的是时间,而不是电容
1.1 CDC 电路的工作方式
PCAP01 内部是一个完整的 CDC(Capacitance-to-Digital Converter)前端。它的测量路径不是把电容值直接送进 ADC,而是让电容通过一个已知电阻放电,用内部比较器捕捉放电过程的起始和结束时刻,再用高分辨率 TDC(Time-to-Digital Converter,时间数字转换器)把这个时间差量化成数字。
这听起来有点绕,但打个比方就好懂了:你用一个固定容量的水杯(参考电容)和一个待测水杯(传感器电容)同时放水,水通过同样的排水口排出。待测水杯的水放完需要多长时间,和它的容量直接相关。PCAP01 做的事情就是精确测量这个"放水的时间"。TDC 的时间分辨率可以做到皮秒级,所以等效出来的电容分辨率能做到几 fF 甚至更低。
这也是为什么它特别适合微小电容变化的场景,比如接近感应、薄膜厚度检测、或者高精度液位计。普通 ADC 加 RC 振荡器的方案在这些场景下,分辨率和温漂都会被甩开一个量级。
1.2 为什么参考电容模式是关键
PCAP01 可以工作在单通道或双通道模式。单通道模式下,它只测量一路传感器电容;双通道模式则同时接传感器电容和参考电容。我在项目中用的是双通道、参考电容比值模式,也就是让传感器电容 C_sensor 和参考电容 C_ref 一起参与测量,最终输出的是两者的比例关系。
这样做的直接好处是:如果供电电压波动、环境温度漂移、或者芯片本身的增益发生变化,传感器电容和参考电容会同时受到同样的影响,比例关系基本不变。这相当于给测量加了免费的"共模抑制"。实测下来,这个模式下的短时稳定性明显好于单通道模式,尤其是连续工作半小时之后,单通道方案的数据会缓慢漂移,而比值模式则稳得多。
1.3 输出到底是什么
PCAP01 最终给你的是一个 24 位的原始码,外加若干状态位。这个原始码不是直接的电容值,也不是线性的 pF 数。它内部和放电时间、参考电容、传感器电容都存在非线性关系。所以项目里真正困难的地方不在 SPI 读回那几个字节,而在你怎么把原始码换算成"人能看懂"的电容值。这个我放到后面第五部分专门讲。
2. 硬件连接与上电复位:布线、引脚和最容易翻车的细节
2.1 SPI 引脚的连接方式
PCAP01 的 SPI 接口不算复杂,SCK、SDO、SDI、CSB 四根线,加上电源和地。我用的主控是 STM32F103,3.3V 电平,可以直接连接。如果你用 5V 的单片机,务必加电平转换,PCAP01 的绝对最大额定电压扛不住 5V,我见过有人直接把 5V 怼上去,芯片当场冒烟。
引脚连接建议如下:
| PCAP01 引脚 | 功能 | MCU 引脚 |
|---|---|---|
| SCK | SPI 时钟 | SPI2_SCK |
| SDI | 数据输入(MOSI) | SPI2_MOSI |
| SDO | 数据输出(MISO) | SPI2_MISO |
| CSB | 片选,低有效 | 任意 GPIO |
| RSTB | 复位,低有效 | 任意 GPIO |
| VOCI | 输出驱动器类型配置 | 看手册配置 |
一个我踩过的坑是 SDO 和 SDI 接反。这两脚名称看起来对称,但有的开发板丝印标注是"MOSI/MISO"而不是"SDI/SDO",一粗心就接反。接反的典型症状是:写寄存器时好像写进去了,读回来的全是 0xFF 或者 0x00,非常迷惑。排查方法很简单,短接 MCU 的 MOSI 和 MISO 自发自收一次,先确认 SPI 外设本身没问题,再怀疑 PCAP01。
2.2 VOCI 引脚和 RSTB 复位时序
VOCI 引脚决定 SDO 的输出驱动类型,是推挽输出还是开漏输出。这个引脚接法不同,直接决定你能不能读回数据。如果 VOCI 悬空或者接错,SDO 可能根本拉不出有效电平,SPI 读回来的数据就是乱的。我的经验是:严格按照数据手册里推荐的接法来,手册写着接高就接高,写着接低就接低,不要想当然。
RSTB 复位引脚的时序也有讲究。上电后要给芯片一段稳定时间,再把 RSTB 拉高解除复位。我最早用 GPIO 一下就拉高了,结果读状态寄存器总是读出异常值。后来改成:上电后延时 10ms,拉低 RSTB 保持 10us 以上,再拉高,再等 1ms 让内部校准完成。按照这个时序,一次就过。
2.3 参考电容的选型和布线
既然用了比值模式,参考电容的稳定性就直接决定最终数据的稳定性。我试过 X7R 材质的电容做参考电容,数据跳得没法看;换成 C0G/NPO 材质之后,噪声水平肉眼可见地降下来。如果你要求高,可以用 0.1% 精度、温漂系数极小的薄膜电容。布线时参考电容尽量靠近芯片的 CREF 引脚,避免走线过长引入寄生电容。
另外电源退耦也很关键,PCAP01 对 VDD 上的毛刺比较敏感,我习惯在电源引脚旁边放一个 100nF 和 10uF 组合的退耦电容。做到这一条之后,读数的跳动幅度大约能减少 30% 到 50%。
3. 初始化寄存器:把 PCAP01 从"出厂状态"调到"我要的状态"
3.1 配置寄存器组的结构
PCAP01 上电后不会自动进入你想要的测量模式,必须先通过 SPI 写配置寄存器组。它内部的配置寄存器和普通芯片不一样,不是一张简单的寄存器表,而是一组按特定顺序写入的 24 位配置字,包含主配置、测量序列配置、采样率配置、中断配置等。
这里有一个重要认知:不同固件版本的 PCAP01,寄存器数量和位域定义可能有差别。我建议拿到芯片后,先读一次 ID 寄存器或者状态寄存器,确认固件版本,再翻对应的数据手册章节。不要在网上随便找一段寄存器配置就抄,我一开始就是抄了一个老版本示例,结果芯片一直在错误配置下跑,数据怎么读都不对。
3.2 典型初始化流程
我把初始化流程总结成四步:
- 复位芯片,等待内部初始化完成。
- 通过 SPI 写配置寄存器组,配置测量模式、通道选择、采样率、增益等。
- 回读配置寄存器,逐位校验写入是否成功。
- 配置 DREAD 相关寄存器,设置数据输出速率,启动测量。
写寄存器的基础函数大致长这样(示意代码,具体命令字以你的芯片版本手册为准):
#define PCAP01_CS_LOW() HAL_GPIO_WritePin(PCAP01_CS_GPIO_Port, PCAP01_CS_Pin, GPIO_PIN_RESET) #define PCAP01_CS_HIGH() HAL_GPIO_WritePin(PCAP01_CS_GPIO_Port, PCAP01_CS_Pin, GPIO_PIN_SET) // 写一个24位配置字 uint8_t pcap01_write_cfg(uint16_t addr_cmd, uint32_t cfg24) { uint8_t buf[5]; buf[0] = (uint8_t)(addr_cmd >> 8); buf[1] = (uint8_t)(addr_cmd & 0xFF); buf[2] = (uint8_t)(cfg24 >> 16); buf[3] = (uint8_t)(cfg24 >> 8); buf[4] = (uint8_t)(cfg24); PCAP01_CS_LOW(); HAL_SPI_Transmit(&hspi1, buf, 5, 100); PCAP01_CS_HIGH(); return 0; }注意每次操作 PCAP01 的 SPI 事务,都必须以 CSB 拉低开始、拉高结束,中间不能有其他 GPIO 操作干扰。只要 CSB 高电平持续时间不够,或者时序上有毛刺,芯片内部的状态机就可能错乱。
3.3 采样率的设置思路
DREAD 寄存器控制着数据的更新速率。我的项目里传感器是缓慢变化的液位,不需要高速采样,所以把数据率设在 1kSPS 左右,然后在软件里再做一次 16 次滑动平均,相当于降到 62.5SPS 输出。这样做的好处是既有足够的时间分辨率,又能把随机噪声压下去。
如果你的应用场景是快速变化的动态信号,可以把数据率调高,但要注意:数据率越高,单次测量的积分时间越短,分辨率会相应下降。这是 PCAP01 这类时间数字转换器的固有权衡,需要在分辨率和速度之间做取舍。
4. 数据读取流程:SPI 时序、DRDY 信号与连续读取实现
4.1 一次完整的读取时序
PCAP01 测量完成后,数据会锁存在内部寄存器里,等待主机来读。芯片提供了 DRDY(数据就绪)引脚,也可以查询状态寄存器的标志位。我强烈建议不要盲目地定时去读,而是用 DRDY 引脚做同步,或者至少读状态寄存器确认数据有效再读数据。
完整读取过程如下:
- 等待 DRDY 引脚拉低(表示新数据已准备好)。
- 拉低 CSB。
- 发送读数据命令和地址。
- 连续读取 32 个时钟,获取状态位和 24 位原始数据。
- 拉高 CSB。
- 处理数据,进入下一轮等待。
4.2 读取代码实现
以下是基于 STM32 HAL 库的读取示例(示意代码):
uint32_t pcap01_read_data(void) { uint8_t tx_cmd[2] = {0xC0, 0x00}; // 示意:读命令 + 地址 uint8_t rx_buf[5] = {0}; // 等待DRDY有效,超时保护 uint32_t timeout = 10000; while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) != GPIO_PIN_RESET) { if (--timeout == 0) break; } PCAP01_CS_LOW(); HAL_SPI_TransmitReceive(&hspi1, tx_cmd, rx_buf, 5, 100); PCAP01_CS_HIGH(); // 组合24位原始数据 uint32_t raw = ((uint32_t)rx_buf[2] << 16) | ((uint32_t)rx_buf[3] << 8) | (uint32_t)rx_buf[4]; return raw; }一个容易忽略的细节:很多 SPI 主设备发送命令和读取数据是分两个事务处理的,但 PCAP01 要求 CSB 在整个命令+数据读取期间保持低电平。如果中间 CSB 被拉高了一次,芯片会认为事务结束,后续读回来的数据就是错的。所以务必使用 TransmitReceive 这种半双工连续模式,而不是先 Transmit 再 Receive。
4.3 轮询和中断两种方式的选择
我用过两种读取方式,各有优劣。轮询方式代码简单,在 MCU 任务不重的时候完全够用,缺点是 CPU 会空转等待 DRDY;中断方式用 DRDY 上升沿或下降沿触发,CPU 可以去做别的事情,代码复杂一点,但实时性和资源占用都好很多。
如果你的系统里还有显示、按键、通信等任务,建议直接用中断。PCAP01 的数据率不高,中断频率完全在 MCU 的处理能力之内。我最终使用的是 DRDY 下降沿中断,在中断服务函数里只做一件事:读取原始数据并放入环形缓冲区,然后立即退出,数据处理放到主循环里做。这样可以避免在中断里做浮点运算,把中断响应时间压到最短。
5. 从 24 位原始码到真实电容值:转换与校准
5.1 为什么不能直接查表
PCAP01 的原始码和电容值之间存在非线性关系,而且这个关系会受芯片制造偏差、参考电容实际容值、温度等多方面因素影响。同一个原始码,放在两块不同的 PCAP01 上,对应的电容值不一定完全相同。这就是为什么"读原始码直接除以一个系数"的做法不靠谱。
5.2 两点标定法
我项目里用的是最经典的两点线性标定。找两个已知容值的标准电容,分别接到传感器通道上,记录对应的原始码,然后建立线性关系:
C = (D - D1) * (C2 - C1) / (D2 - D1) + C1
其中 D1、D2 是两个标准电容对应的原始码,C1、C2 是它们的已知容值。实际操作中,我拿了一个 10pF 和一个 100pF 的 C0G 电容做标定,效果良好。
需要注意的是:如果测量范围跨度很大,比如从 1pF 到 1000pF,两点线性标定可能不够,需要增加标定点,做分段线性或者多项式拟合。对于窄量程的传感器,两点标定已经能到 0.1% 级别的精度了。
5.3 用参考电容做比值校正
在双通道比值模式下,原始码本质上还包含 C_sensor 和 C_ref 的比例信息。这意味着即使参考电容本身容值有偏差,只要它稳定,标定后也能得到正确结果。我还会在每次测量时同时记录参考通道的原始码,如果发现参考通道的读数发生明显漂移,就说明硬件环境有问题(比如温漂、湿气进入、参考电容老化),这个数据可以作为系统自检的信号。
5.4 软件滤波的必要性
实测数据里总有随机噪声,我做了两级处理:
- 滑动平均:连续取 8 到 16 次原始码求平均,压制高频噪声。
- 中值滤波:在滑动平均之前,先做一次 3 点中值滤波,剔除偶发的毛刺值。
这两级滤波加在一起,数据曲线平滑很多。但要注意滤波会引入延迟,对于快速变化的场景,需要折中滤波窗口大小。我的液位传感器变化速度很慢,16 点平均完全无感,但如果你做的是高速振动检测,就要谨慎了。
实际测试数据我记录了一组对比:未滤波之前,原始码跳动幅度约 ±20 LSB;16 点滑动平均后,跳动降到 ±3 LSB 以内,换算成电容值,稳定度大约 0.01pF,对这个项目来说完全够用。
6. 串口发送程序:数据帧格式、校验与整机联调
6.1 帧格式设计
数据读出来、换算完成,最后要交给上位机。我用的是 115200 波特率串口,为了让上位机解析方便、不容易粘包,设计了一个固定帧格式:
| 字节 | 内容 | 说明 |
|---|---|---|
| 0 | 0xAA | 帧头 |
| 1 | 0x55 | 帧头验证 |
| 2 | length | 负载长度 |
| 3 | type | 0x01=原始码,0x02=电容值 |
| 4-7 | payload | 4 字节,int32 或 float |
| 8 | status | 状态位 |
| 9 | crc8 | 从帧头到 status 的 CRC 校验 |
帧头用 0xAA 0x55 双字节,基本可以避免错位。CRC8 我用的是多项式 0x31(和 Dallas/Maxim 的 1-Wire CRC 一致),代码网上很多,自己实现也不难。加上校验之后,上位机可以放心地使用数据,不用担心中间偶尔出现一帧坏数据导致曲线毛刺。
发送代码很简单,在 UART 已经初始化的前提下,拼好缓冲区直接 HAL_UART_Transmit:
void pcap_send_frame(uint8_t type, int32_t value, uint8_t status) { uint8_t buf[10]; buf[0] = 0xAA; buf[1] = 0x55; buf[2] = 5; buf[3] = type; buf[4] = (uint8_t)(value >> 24); buf[5] = (uint8_t)(value >> 16); buf[6] = (uint8_t)(value >> 8); buf[7] = (uint8_t)(value); buf[8] = status; buf[9] = crc8_buf(buf, 9); HAL_UART_Transmit(&huart1, buf, 10, 100); }6.2 上位机配合与调试验证
发送频率我设置在 20Hz,也就是每 50ms 发一帧,上位机上用 Python 的 pySerial 读取,解析成电容值后画实时曲线。这里有个小技巧:调试时先发原始码,不发换算后的电容值,等确认原始码稳定了,再做换算。因为换算公式如果写错,你看到的数据曲线会非常离谱(出现负电容、突变等),而你很难判断到底是换算的问题还是读取的问题。分步验证,能大大缩小排查范围。
6.3 读不到数据时的排查三板斧
如果按上面的流程做完,串口还是没有有效数据,我的排查顺序是:
- 量 PCAP01 的 VDD 电压是否稳定,RSTB 是否已经解除复位,DRDY 引脚是否在变化。DRDY 一点动静没有,说明芯片根本没在跑测量,先查配置和复位时序。
- 用示波器看 SPI 波形,重点看 CSB 时序是否正确、SCK 上有没有毛刺、SDO 上有没有正常返回数据。波形正常但数据不对,查命令字和寄存器地址。
- 回读配置寄存器,逐位核对写入值。回读值和写入值不一致,基本可以确定是 SPI 模式、极性、相位配置不对,或者 SDO/SDI 接反了。
我做过最蠢的一次:SPI 极性相位配置反了,但奇怪的是写寄存器偶尔能成功,数据偶尔能读出来,让我误以为是接触不良,折腾了一下午才发现是 SPI 模式配置的问题。所以如果数据"时好时坏",先检查 CPOL/CPHA,别急着怀疑芯片坏了。
6.4 最后的一点个人体会
整个项目做下来,我的最大感受是:PCAP01 这类芯片,硬件连接和 SPI 读写只占 20% 的工作量,剩下 80% 的时间都在跟"数据语义"较劲。它输出的原始码不是直观的物理量,你需要理解内部测量原理,设计校准流程,处理噪声和漂移,最后才能让数据真正可用。
如果你也在做类似的项目,我建议按这个顺序推进:先打通 SPI 读写,确认能稳定读回原始码;再做硬件稳定性测试,确保数据不跳;然后做两点标定,把电容值算准;最后才是串口发送和上位机联调。每一步都验证通过再进入下一步,看似慢,实际是最快的路径。
本文还有配套的精品资源,点击获取