FDC2214+STM32纸张计数方案:原理、寄存器配置与代码实现
2026/8/31 2:28:36 网站建设 项目流程

简介:这是一套基于STM32F103ZET6主控与FDC2214电容式传感模块实现的纸张计数装置完整嵌入式工程,面向电子设计竞赛学生、传感器应用开发者及嵌入式初学者,解决非接触式纸张厚度检测与数量估算的实际问题。资源包共132个文件,含34个头文件(.h)定义寄存器与接口,32个C源文件(.c)实现I²C驱动、FDC2214初始化、查表法厚度-张数映射、自校准逻辑及串口屏通信协议,另有调试配置(.dbgconf)、编译输出(.hex/.axf/.map)和Keil工程文件(.uvprojx/.uvoptx),整体压缩包大小为2.76MB。已有772人学习下载,工程结构规范,包含完整的HAL或标准外设库驱动(如stm32f10x_i2c.c、stm32f10x_tim.c等),支持一键编译烧录,附带自校准功能与无纸状态智能提示,可直接用于课程设计、毕设原型或工业轻量级计数场景。 做纸张计数那会儿,我一开始拿的是普通电容表,想着把纸往两块极板中间一夹,读个电容值就能换算张数。结果空极板和夹一张纸的读数差小到我看不出区别,波形还一直跳。后来换了FDC2214这颗片子,事情才慢慢变得可控。这篇文章把整套方案的选型、IIC通信、寄存器配置、数据换算和标定流程整理出来,用的是STM32F103ZET6做后端控制,通过IIC接口连接FDC2214电容传感模块,目标是采集极板间纸张厚度变化并换算成张数,代码结构和关键函数都会展开讲。适合正在调FDC2214驱动、想抄一套纸张计数方案的同行,也适合比赛前临时抱佛脚的电子类学生参考。

1. 为什么是FDC2214:一张纸的电容变化到底有多小

1.1 物理原理:LC谐振测电容的本质

FDC2214的核心是一个LC谐振测量电路。外接一个电感和被测电容并联,组成LC振荡回路,芯片内部通过测量这个回路的谐振频率来反向推算电容值。电容一变,谐振频率就跟着变,FDC2214把频率转换成28位数字量输出。它测电容不直接测容值,而是测频率,这是它分辨率高的关键原因。

拿纸张计数场景来说,极板间塞进纸后,等效介电常数变了,电容变大,谐振频率下降。FDC2214在一个参考时钟窗口内统计转换结果,28位分辨率,理论上能分辨到0.1fF级别的电容变化。0.1fF是什么概念?普通万用表电容档能做到1pF分辨率就不错了,差距是四五个数量级。这就是为什么FDC2214适合干这种"微小电容变化识别"的活,而普通方案做不了。

不过要注意,FDC2214虽然输出28位,但实际有效分辨率和传感器设计、PCB走线、电源噪声都强相关。如果极板引线太长、屏蔽没做好,高位数据会乱跳,分辨率优势直接被浪费掉。后面第4章会细说极板设计的注意事项。

1.2 和常见电容测量方案的对比

做这个项目前我还对比过几个方案,这里直接给结论。

方案分辨率/精度成本接口适合场景
万用表电容档1pF左右已拥有手动静态测量,完全不适合实时计数
PCap01分辨率很高中高SPI/IIC本来也是好选择,但外围电路复杂,配置繁琐
FDC221428位IIC/SPI外围简单,LC谐振直接接传感器,最适合这种小电容变化识别

实际对比下来,FDC2214最大优势在于外围电路极简。只需要一个电感和两块极板就能构成传感器,不需要额外的高精度运放或激励源,这对做项目、做比赛都是很友好的。我最后选了FDC2214,还有一个原因是它的典型应用手册里明确提到了接近检测、液位检测这类微小电容变化场景,和纸张计数的物理模型非常接近。

1.3 纸张计数场景的等效模型

极板可以看成一个平行板电容器,电容公式是 C=εS/d。空气的介电常数约1,纸张的介电常数大概是2到3。如果纸完全填满极板间隙,电容会增大到原来的两三倍。但实际装置里,纸张只占间隙的一部分,极板间距通常有几毫米,单张纸厚0.1mm量级,所以总的等效介电常数变化很小。

我粗略估算过,一张普通A4打印纸引起的电容变化在0.1pF到0.5pF之间,具体取决于极板面积和间距。这个量级对FDC2214来说刚好落在最佳测量区间。如果极板面积太小,比如硬币大小,单张纸的变化可能低于0.05pF,测量难度会明显增加;如果极板面积太大,虽然电容变化更大,但容易受到人手靠近、环境位移的干扰。后面第4章的极板设计小节再展开。

2. 硬件连接和IIC通信:为什么我强烈建议软件模拟IIC

2.1 硬件最小系统

FDC2214的外围电路很简单,核心连接就是IIC总线和LC传感器。我用的接线方式整理成表格:

FDC2214引脚连接说明
VDD3.3V必须干净,推荐LC滤波
GNDGND单点接地,避免和电机共地
SDASTM32 PB7需要上拉电阻
SCLSTM32 PB6需要上拉电阻
ADDRGNDIIC地址选择,接地后7位地址0x2A
INTB不接或接GPIO转换完成中断引脚,用轮询可以省掉
IN0A/IN0B接LC谐振回路电感并联极板电容,两端分别接这两个引脚

上拉电阻这块很多人忽略,我特意说一下。FDC2214的SDA和SCL是开漏输出,必须外部上拉,否则IIC通信时好时坏。电阻取值4.7kΩ到10kΩ都可以。我实测下来,3.3V供电、IIC速率100kHz时用4.7kΩ比较稳,波形边沿干脆;用10kΩ也能工作但上升沿稍慢,如果线材较长容易出偶发通信失败。IIC总线上并联的小电容不要超过几十pF,线越短越稳。

关于IIC地址:ADDR接GND时,7位从机地址是0x2A;接VDD时是0x2B。换算成8位读写地址:写地址分别是0x54/0x56,读地址是0x55/0x57。很多新手在这个地方搞混,导致通信不通。我建议初始化时先读一下设备ID寄存器确认地址正确。

2.2 STM32F103ZET6硬件IIC的两个坑

STM32F103系列自带硬件IIC外设,理论上可以直接用。但实际做项目时,我强烈建议用软件模拟IIC,原因有两个。

第一个坑是标准库下的硬件IIC容易卡死。F1系列的硬件IIC在事件处理不当时会进入忙状态,总线释放不掉,现象是程序跑着跑着就死等在一个事件标志上。这个问题本质上和芯片的IIC模块设计、中断优先级、事件标志清除顺序都有关系,排查起来非常费时间。

第二个坑是HAL库虽然把硬件IIC封装得好了不少,但底层用起来依然有些边界情况需要处理。比如主机接收数据时最后两字节的NACK和STOP条件时序,处理不好就会出现多读一个字节或者通信超时。

如果你就想用硬件IIC,也不是不行,但要做好心理准备:中断优先级要合理配置,错误处理要完善,最好加超时机制。我比较务实的做法是直接软件模拟IIC,一个GPIO位操作就搞定了,逻辑完全可控,跑100kHz完全没问题,对于FDC2214这种慢速设备来说绰绰有余。整个项目调下来,软件IIC一次都没出过问题。

2.3 软件模拟IIC的核心时序实现

软件模拟IIC的关键是SDA和SCL的电平切换时序。这里给出我自己用的核心函数,STM32F103ZET6标准库写法,GPIO配置为开漏输出。

#define SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_7) #define SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_7) #define SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_6) #define SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_6) #define SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) static void IIC_Delay(void) { // 100kHz下大约5us延时,可用定时器或空循环微调 delay_us(5); } void IIC_Start(void) { SDA_H(); SCL_H(); IIC_Delay(); SDA_L(); // SCL高电平时SDA拉低,产生起始信号 IIC_Delay(); SCL_L(); } void IIC_Stop(void) { SDA_L(); SCL_H(); IIC_Delay(); SDA_H(); // SCL高电平时SDA拉高,产生停止信号 IIC_Delay(); } void IIC_SendByte(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { if (data & 0x80) SDA_H(); else SDA_L(); data <<= 1; IIC_Delay(); SCL_H(); IIC_Delay(); SCL_L(); } } uint8_t IIC_ReadByte(void) { uint8_t data = 0; for (uint8_t i = 0; i < 8; i++) { SCL_H(); IIC_Delay(); data = (data << 1) | SDA_READ(); SCL_L(); IIC_Delay(); } return data; } uint8_t IIC_WaitAck(void) { SDA_H(); // 释放SDA,让从机拉低应答 IIC_Delay(); SCL_H(); IIC_Delay(); uint8_t ack = SDA_READ(); // 低电平表示ACK SCL_L(); IIC_Delay(); return ack ? 1 : 0; }

这里有几个操作细节要注意。起始信号和停止信号必须保证SCL为高时SDA发生跳变;发送字节时SDA只能在SCL低电平期间变化;等待应答前主机必须释放SDA,否则从机拉不低总线。这些细节我踩过坑,严格按照时序来写,后面基本一次性通过。

软件IIC还有个好处是方便调速率。如果设备不稳定,调大延时就行,硬件IIC要改波特率配置,折腾得多。

3. FDC2214寄存器初始化:RCOUNT、SETTLECOUNT、CLOCK_DIVIDERS、MUX_CONFIG怎么配

3.1 先读设备ID验证通信

系统上电后不要急着配置,先让电源稳定。我习惯上电后延时100ms,然后复位芯片再读设备ID。FDC2214的设备ID寄存器地址是0x7E,正常读取返回0x3055。这一段代码能确认IIC通不通、地址对不对、芯片有没有正常启动。

// 复位FDC2214 FDC2214_WriteReg(0x1C, 0x8000); delay_ms(20); // 读设备ID,验证IIC通信 uint16_t dev_id = FDC2214_ReadReg(0x7E); if (dev_id == 0x3055) { // 通信正常 } else { // 检查接线、地址、上电时序 }

如果读不到0x3055,优先检查三点:ADDR引脚的地址选择是否正确、SDA/SCL上拉电阻是否漏焊、供电是否稳定。我遇到过一次是芯片供电引脚接触不良,导致读ID时好时坏,补焊后就好了。

3.2 RCOUNT和SETTLECOUNT的取舍

RCOUNT寄存器决定了单次转换的采样窗口长度,也就是内部参考时钟周期的计数个数。这个值越大,转换时间越长,分辨率越高;值越小,转换越快,但数据抖动会加大。FDC2214的RCOUNT_CH0寄存器地址是0x08,16位有效,范围1到0xFFFF。

SETTLECOUNT寄存器是沉降计数,主要给LC振荡器一个稳定时间。传感器上电或切换通道后,振荡幅度不会瞬间稳定,需要等一段时间。SETTLECOUNT越大,等待越久,适合高Q值传感器;太小可能导致转换结果还没稳定就被读出来,数据偏大或跳变。

我自己用的推荐配置组合是:

寄存器地址我用的值对应效果
RCOUNT_CH00x080x1FFF8191个参考周期,单次转换约10ms级别
SETTLECOUNT_CH00x100x0064100个周期沉降,兼顾速度和稳定

如果你的极板传感器和我的差异大,可以先按这个组合跑,然后把RCOUNT提高试试。如果数据还是很跳,优先检查电源和传感器连接,而不是无限加大RCOUNT,因为转换时间太长会造成响应迟钝,纸张插进去半天才出结果。

3.3 CLOCK_DIVIDERS和DRIVE_CURRENT

CLOCK_DIVIDERS寄存器配置的是参考时钟分频和传感器频率分频。FDC2214使用外部参考时钟或晶振,我用的模块上有晶振,参考频率固定。CLOCK_DIVIDERS_CH0地址是0x14,位13到12配置传感器频率分频,位9到8配置参考频率分频。每个分频器有1、2、4、8四档。

这里不能随便抄一个值就完事。要先估算LC传感器的谐振频率。比如我用的电感是47μH,空极板电容约15pF,谐振频率大概是 1 / (2π√(LC)) ≈ 6MHz。这个频率下传感器分频选1,参考分频选1或2都行。如果传感器频率太高超出芯片测量范围,就要提高分频。我配置成0x1001,也就是传感器分频2、参考分频2,实测数据落在合理区间。

DRIVE_CURRENT寄存器控制LC振荡器的激励电流。激励电流太小传感器可能不起振,太大则振荡幅度过高,影响稳定性和精度。DRIVE_CURRENT_CH0地址是0x1E,寄存器值越高输出电流越小,也就是反比关系。这个值一般根据传感器谐振频率查手册表格来粗选,再通过实际波形微调。

调试DRIVE_CURRENT时有个简单方法:写一个值,读DATA,看数据是否稳定。如果数据连续几十次跳变超过一定范围,就换个档位试试。我最终用的值是0x7800,对应大约几毫安的激励电流,具体以你自己的传感器实测为准。

3.4 用MUX_CONFIG选通道并启动转换

FDC2214有四个测量通道,MUX_CONFIG寄存器用来选择使能哪个通道以及是否自动切换序列。如果只用一个极板传感器,配置CH0就够了。MUX_CONFIG地址是0x1B,我用到的值是0x0201,对应使能CH0、关闭其他通道、不做自动序列切换。

// FDC2214初始化函数 void FDC2214_Init(void) { // 1. 复位 FDC2214_WriteReg(0x1C, 0x8000); delay_ms(50); // 2. 配置RCOUNT_CH0 = 0x1FFF FDC2214_WriteReg(0x08, 0x1FFF); // 3. 配置SETTLECOUNT_CH0 = 0x0064 FDC2214_WriteReg(0x10, 0x0064); // 4. 配置CLOCK_DIVIDERS_CH0 = 0x1001 FDC2214_WriteReg(0x14, 0x1001); // 5. 配置DRIVE_CURRENT_CH0 = 0x7800 FDC2214_WriteReg(0x1E, 0x7800); // 6. 配置MUX_CONFIG,使能CH0,关闭其他通道 FDC2214_WriteReg(0x1B, 0x0201); }

配置完MUX_CONFIG后,芯片会按照配置自动开始连续转换。这时可以通过轮询状态寄存器或者直接读数据寄存器来判断转换是否完成。我习惯用轮询方式,简单可靠。

4. 28位数据读取与纸张厚度换算:别再拼错寄存器了

4.1 读取时序与数据拼接

FDC2214的转换结果是28位,分在两个寄存器里。DATA_CH0寄存器地址是0x00,存的是28位结果的高16位;DATA_LSB_CH0寄存器地址是0x01,存的是低12位,但这低12位放在16位寄存器的bit15到bit4上,也就是左对齐的,bit3到bit0固定为0。

所以拼接方式很关键,正确写法是:

uint32_t FDC2214_ReadDataCH0(void) { uint16_t msb = FDC2214_ReadReg(0x00); // 高16位 uint16_t lsb = FDC2214_ReadReg(0x01); // 低12位在bit15:bit4 // 正确拼接:28位结果 = (msb << 12) | (lsb >> 4) uint32_t result = ((uint32_t)msb << 12) | (lsb >> 4); return result; }

我见过有人直接写成(msb << 16) | lsb,这样低12位没有右移,数据整体偏大,而且还有可能把状态位混进去。DATA_LSB_CH0寄存器的高4位里有DRDY、DONE等状态标志,不右移的话这些标志会混入数据低位,导致结果完全不对。这是一个特别容易踩的坑。

读取时机上,我建议每次读取前先判断转换完成标志。最简单的方式是读DATA_LSB_CH0寄存器时检查bit14(DONE标志),如果为1说明本次转换已完成,可以安全读取。

uint32_t FDC2214_ReadDataWithCheck(void) { uint16_t msb, lsb; uint32_t result; // 轮询DONE标志 do { lsb = FDC2214_ReadReg(0x01); } while ((lsb & 0x4000) == 0); // bit14 = DONE msb = FDC2214_ReadReg(0x00); lsb = FDC2214_ReadReg(0x01); result = ((uint32_t)msb << 12) | (lsb >> 4); return result; }

加上这个DONE判断后,数据稳定性提升明显。如果轮询超时还是等不到DONE,大概率是传感器没起振,查一下DRIVE_CURRENT和LC回路。

4.2 从DATA到频率再到电容

拿到28位数据后,如果想精确换算成电容值,可以先算传感器谐振频率。TI手册给出的关系约为:

DATA = (2^28 × f_sensor) / (FIN_DIVIDER × f_ref)

反推就是:

f_sensor = (DATA × FIN_DIVIDER × f_ref) / 2^28

有了频率再反推电容:

C = 1 / (4π² × f_sensor² × L)

其中L是你外接的电感值。这套换算在理论上是完整的,但我实际做项目时并不直接算电容,原因有两个。第一,L本身的精度并不高,贴片电感的感值误差可能5%到10%,算出来的电容绝对值误差很大;第二,纸张计数真正关心的是相对变化量,而不是绝对容值。所以我更建议把28位DATA值直接作为原始特征量来标定和判断,省去换算环节,也少一个误差来源。

不过,理解频率和电容的关系还是有用的。我要记住一个核心方向:电容变大时,谐振频率下降,DATA值变小;纸张越多,电容越大,DATA越小。基于这个方向做标定,逻辑就清晰了。

4.3 极板设计对厚度分辨能力的影响

极板设计是决定整个装置成败的关键一环。我在调试中总结了几条实践经验。

极板面积推荐在100mm×100mm左右,太大容易受环境电容干扰,太小则信噪比不足。间距方面,极板固定间距建议控制在5mm以内,太宽了纸张引起的电容变化占比会变小。材料直接用PCB覆铜板或者铜箔胶带贴在亚克力板上就行,关键是表面平整、间距均匀。

引线是另一个大坑。极板到FDC2214传感器的引线越长,寄生电容越大,而且引线本身受人体靠近影响严重,数据会出现无规律的漂移。我的做法是极板引线不超过10cm,并且使用屏蔽线,屏蔽层单端接地。如果你发现手一靠近极板数据就疯跳,大概率就是引线屏蔽没做好。

FDC2214的传感器输入引脚IN0A和IN0B之间直接接LC并联回路,这个回路要尽量远离数字信号线和大电流走线,尤其是IIC的SCL/SDA不要贴着传感器走线走。PCB布板时最好在传感器区域周围铺一圈地,起到隔离作用。这些设计细节直接影响最终的分辨率,比代码优化更值得花时间。

5. 从原始读数到计数结果:标定与判据

5.1 标定流程:空极板、单张、批量

拿到稳定的DATA值之后,下一步是建立DATA和张数的关系。标准做法是先做标定。

我的标定流程是这样的:

  1. 极板空着,连续采样100次,取平均,记为DATA_0。
  2. 放1张纸,等数据稳定后采样100次,取平均,记为DATA_1。
  3. 依次放5张、10张、20张、50张,分别记录平均值,建立一张标定表。
  4. 每次标定时纸要放得平整,尽量贴住极板,不要悬空。

我这组实测数据(用100mm×100mm铜箔极板,间距3mm)大致长这样:

纸张数量DATA平均值与空极板的差值
092000000
19188000-12000
59140000-60000
109080000-120000
208960000-240000
508600000-600000

注意这组数据只是我那个装置的绝对值,不同板子、不同极板、不同纸张都会有差异,不要直接拿来当你的阈值。但从趋势能看出,DATA值和纸张数量近似线性,单张纸的差分信号在10000以上,远大于数据波动,分辨没问题。

5.2 计数策略:区间判断和线性拟合

标定完成之后,计数策略有两个常用方案。

方案一:区间判断。把标定表里每个张数对应的DATA值上下扩展一个容差,形成区间。比如1张纸的DATA在9180000到9196000之间,那实测数据落在这个区间就判断为1张。这个方案简单直接,代码量小,适合张数离散、数量不多的场景。

方案二:线性拟合。用最小二乘法把标定的数据点拟合成一条直线,张数 = k × ΔDATA + b。我标定的数据拟合出来大概是每张纸对应12000点左右的变化,所以判定代码可以写成:

// 假设标定得到的差值是线性的 // delta_per_sheet = (DATA_50 - DATA_0) / 50 int CountSheets(uint32_t data) { int32_t delta = (int32_t)data - (int32_t)data_base; // data_base是空极板基准 if (delta > 0) return 0; // 比基准还大,视为空 int count = (int32_t)(-delta * 50 / (data_base - data_50)); return count; }

线性拟合方案适合张数连续变化的场景,但要注意纸张较多时关系可能偏离线性,尤其超过50张以后误差会变大。如果目标就是1到100张范围,我建议分段拟合,比如0到20张用一段,21到50张用另一段,精度会好很多。

关于基准值data_base,我会在每次开机后先空极板采样几十次求平均,作为本次运行的基准,而不是把上一次保存的固定值拿来用。因为环境温度、湿度甚至极板上残留的灰尘都会让基准漂移,开机现场校准最保险。

5.3 防抖与实时性

实际使用中,纸张插进极板和抽出的过程中,DATA值会剧烈波动,不能读到一次数据就直接判定张数。我的处理方式是加滑动平均滤波和延时确认。

滑动平均窗口用10到20次采样,每采一个新值就替换掉队列里最旧的值,取平均后再判断。单次采样耗时约10ms,20次也就200ms左右,响应速度完全够用。

额外再加一个确认机制:连续3次判断的结果相同,才认为张数稳定了。这样可以过滤掉插纸瞬间的抖动。如果用户快速连续插纸,芯片也能跟上,因为有滤波和确认机制,不会在动作进行中误判。

我遇到过的情况是纸张抽出一半时,DATA值落在某个中间位置,如果此时判断张数会偏大。加了延时确认后,这种误判基本消除。

6. 实测中容易踩的坑:供电、温漂、纸张差异

6.1 供电纹波直接体现在DATA上

这是我最头疼的坑。FDC2214虽然抗干扰能力不错,但供电若是纹波大,28位数据的低位会一直跳,表现为读数像心电图一样抖动。

排查方法很简单:用示波器看FDC2214芯片VDD引脚的纹波。正常应该在几十毫伏以内。如果超过这个量级,就得处理。

处理办法有三个:

  • 芯片电源引脚附近加一个10μF钽电容和0.1μF陶瓷电容并联做去耦;
  • 如果系统里有电机、继电器这类大电流负载,FDC2214的供电要从主电源单独走线或加磁珠隔离;
  • 每次读取数据后多采几次取平均

本文还有配套的精品资源,点击获取

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

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

立即咨询