基于STM32F103与FDC2214的非接触式电容液位检测系统设计与实现 📅 2026/8/5 1:44:32 1. 项目缘起当电容传感遇上经典MCU最近在做一个需要非接触式液位检测的小项目手头正好有一块吃灰已久的STM32F103C8T6核心板也就是大家常说的“蓝桥杯”或者“最小系统板”。需求很明确要能隔着塑料容器壁检测里面液体的高度变化。传统的方案无非是超声波、压力传感器或者光电对管但要么成本高要么安装复杂要么怕污染。翻资料的时候TI的FDC2214这颗电容数字转换器CDC芯片进入了我的视线。它主打高分辨率、高抗噪的电容测量正好适合这种非接触检测的场景。于是一个“FDC2214STM32F103”的组合方案就在我脑子里成型了。这个组合听起来有点“经典MCU驱动前沿传感器”的味道。STM32F103作为一代神U资源丰富、生态成熟几乎每个搞嵌入式的人都玩过而FDC2214则代表了高精度电容测量的一个便捷解决方案。把它们俩凑在一起绝不是简单的“点亮”而已而是要解决从硬件干扰抑制、传感器设计、数据滤波到实际标定的一整套工程问题。网上关于FDC2214的应用笔记和STM32的例程都不少但真正把两者结合并深入到稳定测量和实际应用层面的分享却比较零散。这次我就把自己从电路搭建、驱动编写、数据处理到实际测试踩过的坑和总结的经验系统地梳理一遍。2. FDC2214核心原理与选型考量在动手之前我们必须先吃透FDC2214到底是干什么的以及为什么选它而不是其他类似的芯片比如FDC1004或AD7746。2.1 电容数字转换CDC与传统的区别传统的电容测量方法比如用RC振荡电路然后测频率或者用模拟前端加ADC容易受到寄生电容、环境噪声尤其是50/60Hz工频干扰的严重影响精度和稳定性很难做高。FDC2214采用了一种叫做“LC谐振”和“Σ-Δ”数字转换相结合的技术。简单来说芯片内部会驱动一个由外部电感和传感器电容C_sensor构成的LC谐振电路产生一个特定频率的信号。这个频率会随着C_sensor的变化而变化。然后芯片内部的Σ-Δ转换器并不是直接测量电容的电压或电流而是高精度地测量这个LC振荡信号的频率。因为频率测量本身具有很高的抗干扰能力和分辨率再通过芯片内部固化的算法模型将频率值换算成最终的电容值数据输出。这样做的好处非常明显高分辨率FDC2214最高能实现28位有效分辨率的电容数据这意味着它能感知极其微小的电容变化飞法级。高抗噪性其差分输入和屏蔽驱动Shield Drive功能能有效抑制来自传感器引线或环境的共模噪声比如你的手靠近引线带来的干扰。多通道单芯片支持最多4个通道可以同时或分时测量多个传感器非常适合做多点液位检测或接近感应阵列。2.2 为什么是FDC2214而不是FDC1004TI这个系列还有更便宜的FDC1004。简单对比一下FDC1004分辨率较低24位测量范围±15pF需要外部提供参考电容I2C速率较低。它更像一个“通用型”CDC适合成本敏感、测量范围固定的场景。FDC2214分辨率高28位测量范围更灵活通过配置电感值可调内置参考振荡器最高支持400kHz的快速I2C。它是为高精度、高速度、高抗干扰应用设计的。对于非接触液位检测传感器电容变化量可能很小可能只有几个pF且环境噪声复杂FDC2214的高分辨率和抗噪能力几乎是必须的。虽然贵一点但能省去后期无数调试稳定性的麻烦。2.3 关键外部元件电感L的选择这是FDC2214设计中最关键的一环也是很多新手容易栽跟头的地方。芯片的数据手册会给出一个公式用于计算传感器电容C_sensor范围与外部电感L的关系。公式的核心思想是LC谐振频率必须落在芯片内部Σ-Δ转换器的最佳工作频段内通常推荐在1MHz到10MHz之间。你需要先估算你的传感器电容大概在什么范围。比如我设计的平行板电极贴在塑料瓶外空瓶时电容约10pF满液时电容约15pF。那么我的C_sensor范围大约是10-15pF。根据公式f 1 / (2π√(L*C))反推出电感L的大致范围。通常对于pF级别的电容电感需要选择uH级别的。我最终选择了一颗精度为2%的4.7uH绕线电感。注意电感的品质因数Q值非常重要一定要选择高Q值的电感通常绕线电感优于叠层电感。低Q值电感会导致谐振信号衰减严重噪声增大甚至无法起振。这是影响测量稳定性和精度的首要硬件因素。3. 硬件电路设计与避坑指南电路是稳定性的基石。FDC2214的电路不算复杂但几个细节处理不好轻则数据跳动大重则完全无法工作。3.1 最小系统连接图与电源设计我的连接方案如下MCUSTM32F103C8T6通信FDC2214的I2C接口SDA SCL连接STM32的PB7 PB6I2C1 加上4.7kΩ上拉电阻到3.3V。传感器CH0通道连接我的液位传感器电极。传感器电极采用差分形式IN0A和IN0B接平行板的两极SHIELD0接一个包围在信号线外的屏蔽层可以用铝箔或屏蔽线并连接到芯片的屏蔽驱动引脚。外部元件在IN0A和IN0B引脚上分别串联一个0欧姆电阻预留位置可用于调试时串联小电阻抑制振铃和对地接一个2.2pF的电容用于滤波高频噪声。最重要的电感L4.7uH连接在芯片的LA和LB引脚之间。时钟FDC2214的CLKIN引脚接一个27MHz的无源晶振这是芯片内部PLL的参考源必须要有且要稳定。电源部分需要特别关注FDC2214的模拟部分AVDD和数字部分DVDD最好分开供电。虽然数据手册说可以连在一起但为了最佳性能我使用了磁珠Ferrite Bead将3.3V电源隔离后给AVDD供电。每个电源引脚附近都必须放置一个0.1uF的陶瓷去耦电容并且尽可能靠近芯片引脚。模拟地AGND和数字地DGND在芯片下方单点连接。3.2 传感器电极设计与屏蔽驱动非接触电容传感器的电极设计是门艺术。我的目标是检测圆柱形塑料瓶的液位。电极形式我采用了两个长条形的铜箔胶带平行地贴在塑料瓶的外壁两侧。这构成了一个差分电容传感器。当瓶内液位上升时介电常数发生变化导致两个电极间的电容增大。屏蔽驱动这是FDC2214的“神器”。我将信号线连接到IN0A/IN0B的导线用屏蔽线包裹屏蔽层的网线直接连接到芯片的SHIELD0引脚。芯片会输出一个与输入信号同相位的驱动信号到这个屏蔽层。这样信号线与屏蔽层之间的电位差几乎为零从而消除了信号线与地之间寄生电容的影响也极大地抑制了外部电场耦合进来的噪声。这个功能必须用上它对提升稳定性的贡献是决定性的。布局要点传感器引线要尽量短。如果引线必须很长一定要用屏蔽线并正确连接屏蔽驱动。电极表面要保持清洁干燥。3.3 常见硬件故障排查如果电路不工作可以按以下顺序排查电源和地首先用万用表测量所有电源引脚电压是否为稳定的3.3V地线连接是否可靠。I2C通信用逻辑分析仪或示波器抓取SDA和SCL波形看STM32是否发出了正确的设备地址FDC2214默认地址0x2A写地址0x54读地址0x55。波形是否干净上升沿是否缓慢可能上拉电阻过大晶振用示波器测量CLKIN引脚看27MHz晶振是否起振波形幅度是否足够。LC振荡这是最关键的。用示波器探头最好用X10档以减少负载效应触碰LA或LB引脚。如果电路正常你应该能看到一个漂亮的正弦波频率在你计算的谐振频率附近比如我的约5MHz。如果看不到波形或者波形幅度很小、畸变首先怀疑电感问题Q值太低或焊接不良其次是传感器开路或短路。寄存器配置通过I2C读取芯片的DEVICE_ID寄存器地址0x7F应该能读到0x3055。如果读不到说明通信或芯片根本就没正常工作。4. STM32F103驱动层软件实现硬件调通后我们来搞定软件。STM32的固件库Standard Peripheral Library或HAL库都可以我这里以标准库为例因为寄存器操作更直观便于理解时序。4.1 I2C底层驱动与FDC2214寄存器配置首先实现一个可靠的I2C读写函数。FDC2214的寄存器是16位的但I2C通信时地址是8位数据是16位高位在前。// 向FDC2214指定寄存器写入一个16位数据 uint8_t FDC2214_WriteReg(uint8_t reg_addr, uint16_t reg_val) { uint8_t buf[3]; buf[0] reg_addr; // 寄存器地址 buf[1] (uint8_t)(reg_val 8); // 数据高字节 buf[2] (uint8_t)(reg_val 0xFF); // 数据低字节 if(I2C_WriteBuffer(FDC2214_I2C_ADDR, buf, 3) ! SUCCESS) { return 1; // 失败 } return 0; // 成功 } // 从FDC2214指定寄存器读取一个16位数据 uint8_t FDC2214_ReadReg(uint8_t reg_addr, uint16_t *pval) { // 先发送要读的寄存器地址 if(I2C_WriteBuffer(FDC2214_I2C_ADDR, reg_addr, 1) ! SUCCESS) { return 1; } // 然后启动读操作 uint8_t buf[2]; if(I2C_ReadBuffer(FDC2214_I2C_ADDR, buf, 2) ! SUCCESS) { return 1; } *pval ((uint16_t)buf[0] 8) | buf[1]; return 0; }接下来是关键的初始化配置。我们需要配置几个核心寄存器CONFIG寄存器地址0x1A设置测量模式单通道连续测量、传感器驱动电流、是否使能屏蔽驱动等。我配置为通道0连续测量驱动电流设为最大以提高信噪比使能屏蔽驱动。MUX_CONFIG寄存器地址0x1B配置通道复用器。因为我们只用CH0所以配置为CH0连接到内部转换器。SETTLECOUNT_CH0寄存器地址0x10设置通道0的稳定计数。这个值决定了每次测量前LC振荡器稳定需要多少个时钟周期。值越大稳定时间越长数据越可靠但转换速率越慢。需要根据你的谐振频率和精度要求权衡。我从一个较大的值如0x100开始调试。RCOUNT_CH0寄存器地址0x08设置通道0的转换结果数据更新速率。这个值决定了Σ-Δ转换器进行多少次转换才输出一个新数据。值越大输出速率越慢但噪声越低。这也是一个需要权衡的参数。CLOCK_DIVIDERS_CH0寄存器地址0x14设置参考时钟分频。这个和外部晶振频率、目标谐振频率有关需要根据数据手册公式计算。配置错误会导致无法测量。初始化流程就是按照特定顺序写入这些寄存器。一个重要的步骤是在配置完成后需要给芯片一个复位脉冲通过写CONFIG寄存器的一个特定位或者等待足够长的稳定时间几十毫秒再进行第一次数据读取。4.2 数据读取与原始值处理配置完成后就可以从DATA_CH0寄存器地址0x00循环读取数据了。读出来的是一个28位的原始数据Raw Data存储在两个16位寄存器里DATA_CH0是低16位DATA_CH0_MSB是高12位。uint32_t FDC2214_ReadChannelData(uint8_t ch) { uint16_t data_low, data_high; uint32_t raw_data 0; // 假设ch0 读取DATA_CH0 (0x00) 和 DATA_CH0_MSB (0x01) if(FDC2214_ReadReg(0x00, data_low) 0 FDC2214_ReadReg(0x01, data_high) 0) { // DATA_CH0_MSB寄存器只有低12位有效 data_high 0x0FFF; raw_data ((uint32_t)data_high 16) | data_low; } return raw_data; // 这是一个28位的数 }这个raw_data并不是电容值而是一个与传感器电容成反比的数字。需要根据数据手册的公式进行转换C_sensor (L * (Fref / (2^28))^2 * (1/(raw_data/2^28)^2 - 1) ) / (4 * π^2)其中L是你选的电感值亨利。Fref是参考频率由外部晶振和CLOCK_DIVIDERS寄存器决定。raw_data就是读到的28位原始值。这个计算涉及浮点在STM32F103上如果频繁计算可能会吃力。一个实用的技巧是如果你只关心电容的相对变化比如液位变化而不是绝对电容值你可以直接使用raw_data的倒数或者甚至直接使用raw_data本身作为观测量因为它们是单调相关的。在我的液位检测中我发现1.0 / (float)raw_data与液位高度有很好的线性关系这就足够了省去了复杂的浮点运算。4.3 利用DMA和中断实现高效数据采集为了不阻塞主循环我们可以利用STM32F103的I2C DMA功能。但FDC2214的I2C读取需要先写寄存器地址再读数据这个过程用DMA实现起来稍复杂。一个更简单高效的方法是使用查询方式定时读取结合定时器中断。配置一个基本定时器如TIM2产生一个固定频率的中断例如100Hz即10ms一次。在定时器中断服务函数中设置一个标志位data_ready_flag 1。在主循环中检查这个标志位如果为1则执行一次FDC2214_ReadChannelData读取数据进行滤波处理然后清除标志位。这样数据采集的时序是严格固定的有利于后续的数字滤波处理。STM32F103在72MHz主频下处理100Hz的I2C读取和简单滤波绰绰有余。5. 数字滤波与液位标定算法原始数据一定是充满噪声的尤其是这种高精度的测量。直接使用原始值会跳动得非常厉害无法用于控制。5.1 软件滤波策略组合拳我采用了三级滤波策略效果非常好中值滤波连续采样5个原始值去掉最大值和最小值取中间3个值的平均值。这能有效滤除偶尔出现的尖峰脉冲干扰比如开关电源的瞬间干扰。#define FILTER_SIZE 5 uint32_t buffer[FILTER_SIZE]; // ... 采样并存入buffer ... uint32_t median_filtered_value median_and_average(buffer, FILTER_SIZE);一阶低通滤波指数加权平均这是最常用且计算量小的平滑滤波。filtered_data α * new_data (1-α) * filtered_data。α是滤波系数0α1越小滤波效果越强但响应越慢。我选择α0.2在平滑性和响应速度间取得平衡。float alpha 0.2; static float lpf_data 0.0; lpf_data alpha * (float)median_filtered_value (1.0 - alpha) * lpf_data;滑动平均滤波在低通滤波之后我再对最近10个低通滤波后的结果进行一次算术平均。这进一步平滑了数据得到了一个非常稳定的观测值final_data。经过这三重滤波原本跳动可能有几十个LSB的原始数据最终变得非常平稳跳动范围在±2个LSB以内。5.2 从电容数据到液位高度标定方法滤波后的数据比如我用的是1.0 / final_data与液位高度存在关系但通常不是完美的线性尤其是电极形状不规则或容器形状复杂时。我的标定步骤如下采集标定数据准备一个带刻度的容器。从空瓶开始每增加一定体积如50ml的液体记录一次稳定的滤波后数据value_i同时记录对应的液位高度height_i单位cm或mm。直到满瓶。曲线拟合将(height_i, value_i)数据点画出来。我发现我的数据近似一条直线。使用最小二乘法进行线性拟合得到公式height k * value b。在PC上用Excel或Pythonnumpy.polyfit可以轻松完成。如果想在MCU上在线计算需要实现简单的线性回归算法但STM32F103的浮点能力有限通常是在PC上算好k和b然后硬编码到MCU程序中。分段线性插值如果拟合发现线性度不好可以采用更通用的方法——分段线性插值。将标定点按value排序存储在一个数组中。测量时根据当前的value找到它落在哪两个标定点(value_low, height_low)和(value_high, height_high)之间然后用插值公式计算高度height height_low ( (value - value_low) * (height_high - height_low) / (value_high - value_low) )这种方法精度高能适应任何非线性关系且计算量可控一次查找和几次乘除。在我的项目中线性拟合已经足够好满量程误差在2%以内完全满足要求。5.3 温度补偿的考虑电容值会受温度影响。FDC2214内部有温度传感器可以通过寄存器读取地址0x20。对于高精度应用可以定期读取温度值建立温度-电容漂移的补偿模型。但在我的室内液位检测中温度变化缓慢且范围小其影响被强大的软件滤波覆盖了所以暂时没有启用温度补偿。如果你的应用环境温差大这是一个必须考虑的环节。6. 系统集成与性能优化当单个通道稳定工作后我们可以考虑扩展和优化。6.1 多通道扫描与功耗管理FDC2214有4个通道。你可以通过配置MUX_CONFIG寄存器让芯片自动按顺序扫描多个通道。在单通道连续模式下每次读取数据后需要软件切换MUX_CONFIG寄存器来测量下一个通道。注意通道切换后需要等待新的SETTLECOUNT时间让LC电路重新稳定才能读取可靠数据。这意味着多通道扫描会降低每个通道的更新率。对于电池供电设备功耗很重要。FDC2214在单次转换模式Single Conversion Mode下功耗更低。你可以让MCU进入休眠定时唤醒启动一次FDC2214测量读取数据后再让FDC2214和MCU进入休眠。通过合理配置RCOUNT和SETTLECOUNT可以在功耗和噪声之间取得最佳平衡。6.2 与STM32其他功能的联动STM32F103丰富的资源可以让我们做得更多PWM输出可以根据液位高度用PWM驱动一个LED亮度渐变或者驱动一个蜂鸣器发出不同频率的警报声。STM32F103的定时器产生指定频率和占空比的PWM非常简单。串口通信将液位数据通过USART发送给上位机电脑或手机进行显示、记录或分析。可以方便地使用printf重定向。外部存储如果标定点很多可以将标定数据存储在STM32内部的Flash或外部的EEPROM如AT24C02中实现断电保存。显示界面可以连接一个OLED屏幕如SSD1306实时显示液位高度、百分比和报警状态。6.3 抗干扰实战经验总结在整个开发过程中对抗干扰是永恒的主题。除了前面提到的硬件屏蔽、软件滤波还有几点心得电源隔离给FDC2214的模拟部分供电最好用线性稳压器LDO而不是和数字部分共用开关电源DCDC。如果必须共用磁珠和π型滤波电路是必须的。接地艺术电路板布局时模拟地区域要完整不要被数字信号线割裂。芯片下方的接地区域要多打过孔连接到地平面。软件看门狗在长时间运行的系统中一定要开启STM32的独立看门狗IWDG防止程序跑飞。数据有效性检查在读取FDC2214数据后可以检查一下数据的合理性。例如如果读到的raw_data为0或者接近最大值很可能是通信错误或传感器异常此时应丢弃该数据并尝试重新初始化芯片。这个“FDC2214STM32F103”的组合为我提供了一个高性价比、高可靠性的非接触电容检测方案。从最初的电路板滋滋作响、数据乱跳到最终输出平滑稳定的液位曲线整个过程充满了硬件调试和软件优化的挑战。最终的效果是令人满意的传感器能够稳定地分辨出毫米级的液面变化。这套框架不仅适用于液位检测稍加修改也能用于材料成分分析、接近感应、手势识别等领域。希望我的这些踩坑经验和实现细节能给正在探索电容传感技术的朋友一些实实在在的帮助。