前阵子做一个小项目,要在设备里加一个非接触式金属检测功能:金属块靠近时把设备从低功耗状态唤醒。当时第一反应是去找专用的电感数字转换芯片,算了下成本、面积和通道数,觉得不划算,就决定试试用PSoC 4 MCU直接做电感感应(Inductive Sensing)。实测下来效果相当稳,这个方案不仅能检测金属是否存在,还能测距离、识别金属旋钮角度,而且一颗MCU可以同时管多路线圈,连主控都省了。
这篇文章就把从原理、硬件、固件到产品化的完整经验梳理一遍,适合正在评估“用普通MCU直接做电感式接近/位置传感”的工程师参考。即便你之前没接触过PSoC,只要做过一点MCU开发,跟着思路走也能拿下。
1. 项目从哪来:PSoC电感感应的底层逻辑
1.1 电感感应到底在测什么
很多人一听到电感感应就以为要用专用的LDC芯片,其实核心原理不复杂:一颗线圈和一颗电容组成LC并联谐振回路,给这个回路一个激励,它会开始振荡,振荡频率由电感和电容决定。当有金属物体靠近线圈时,金属表面会产生涡流,涡流反过来改变线圈的有效电感和等效损耗,于是谐振频率发生偏移。我们只要测出频率偏移量,就能推断有没有金属靠近、离得多远。
用生活里的例子解释,LC回路就像一个秋千:你推一下,它会以固定节奏摆动;当你把手放在秋千旁边,阻力变大或者改变了秋千的“固有状态”,摆动节奏自然就变了。电感感应做的就是“听秋千节奏变了多少”这件事。
细节上要注意,不同金属对频率的影响方向不一样。对铜、铝这类非铁磁性金属,涡流效应占主导,等效电感会变小,谐振频率上升;对钢、铁这类铁磁性金属,磁导率增大又会让电感变大,频率下降。实际应用中,如果只做接近检测,不用管方向,取频率偏移的绝对值就够了;如果要做材质判断,这个方向差异反而可以作为参考特征。
1.2 为什么选择PSoC而不是专用芯片
专用电感数字转换器当然精度高,但它本质上是把“线圈激励、比较器整形、频率测量”全部固化在芯片里,通道数有限,价格也偏高。如果你的产品里已经有MCU,再外挂一颗LDC芯片,无论从BOM成本还是PCB面积看都不划算。
PSoC 4系列的优势在于它把模拟前端做得足够灵活。芯片内部有可配置的比较器、定时器、计数器、数字逻辑块,还有一堆可以直接接模拟信号的GPIO。把LC回路接到引脚上,用内部比较器把衰减振荡整形成方波,再定时器测频率,整套信号链全部在芯片内部完成。
更关键的是,PSoC的GPIO不区分严格的“模拟/数字”引脚,每个引脚都可以当模拟输入用。这意味着你可以通过内部模拟路由,把多个线圈轮流切换到同一个比较器,实现多通道检测。我之前一块板上挂了4个线圈,做线性位置传感,成本只比单通道方案多几个线圈和电容,这在传统方案里很难想象。
另外还得提一句,PSoC的电容感应(CapSense)和电感感应有很多相似的软件架构、滤波策略、基线校准思路,如果你之前用过CapSense,切到电感感应几乎是无缝衔接。官方后来的MagSense技术线也是在这个方向上的延续,说明这条路线不是歪门邪道,是有正规军背书的。
2. 硬件设计:线圈、谐振电容和PCB布局
2.1 线圈尺寸、圈数和线宽怎么定
线圈是整个传感器的探头,它的参数直接决定检测距离和灵敏度。经验上,检测距离大约等于线圈直径的三分之一到二分之一。比如要求隔着2mm塑料外壳检测到3mm外的金属块,线圈直径至少选10mm左右。留足余量很重要,因为实际装配后的距离会比理论值差一些,我一般按1.5倍余量选型。
PCB线圈的设计可以按下面的经验值起步:线宽0.2mm~0.3mm,线间距尽量小(与线宽接近),圈数20圈到40圈,具体电感量大概在10μH~50μH范围。这个范围适合PSoC的测量能力,不会超出比较器和定时器的工作区间。
想要精确确定电感量,最好用LCR表实测。一个直径15mm、30圈、线宽0.2mm的圆形螺旋线圈,实测电感大约在20μH左右。如果手头没有LCR表,也可以先用公式粗算,但PCB线圈的寄生电容和走线形状影响很大,公式只能作为数量级参考。打样时建议多留几个不同尺寸的线圈位置,一块板子上放两个不同直径的线圈,实测后再定最终方案。
圈数影响灵敏度要特别注意:圈数越多,电感越大,Q值可能会下降,共振信号衰减更快。如果发现波形幅度很小,不要只想到加大激励,先检查是不是圈数太多导致Q值太低。
2.2 谐振电容和寄生电容计算
并联谐振电容的选择直接影响谐振频率。PSoC比较器和定时器的测量频率范围虽然不是死的,但一般建议把谐振频率控制在1MHz到3MHz之间。太低,外部噪声干扰明显;太高,PSoC内部比较器响应速度跟不太上,计量误差变大。
用公式 f = 1 / (2π√(LC)) 来选电容。举个例子:线圈电感L=22μH,想让谐振频率落在1.5MHz左右,所需的电容大约为:
C = 1 / ((2π×1.5×10^6)^2 × 22×10^-6) ≈ 0.51nF
这个值包含PCB走线寄生电容和PSoC引脚电容。PSoC引脚本身有十几pF的寄生电容,走线再贡献几pF到十几pF,加起来一般不超过50pF。当计算出的电容值远大于100pF时,寄生电容还算不上大问题;但如果你算出只有几十pF,那寄生电容就会严重拉偏谐振频率,必须重新设计。
所以我的建议是:优先保证线圈电感在20μH左右,然后选一个100pF到470pF的电容,让谐振频率落在1MHz到3MHz区间。电容务必选NP0/C0G材质,温度稳定性好,X5R/Z5U这类高介电常数电容在温度变化时容值会明显漂移,直接影响频率读数。
还有一个容易忽略的点:PSoC复位期间引脚默认是高阻,LC回路没有激励不会振荡,这没问题。但要确保外部电路上不存在会让引脚电压超过电源或低于地的冲击路径,否则可能损伤芯片内部ESD结构。
2.3 PCB布局的关键几条
线圈下方的地平面是最大的坑。铜箔上感生涡流会白白消耗LC回路的能量,导致Q值下降、灵敏度锐减。正确做法是:在线圈正下方的所有层都挖空铜箔,周围至少留出1mm~2mm的净空区域。
我在第一版打样时吃了教训,线圈直接压在大面积底层地上面,同样的代码、同样的线圈,读数变化幅度比挖空后少了将近一半。当时还以为是器件问题,后来把地挖空,什么都没改,灵敏度立刻回来了。
线圈走线建议用圆弧或45度折角,避免直角,因为直角会形成局部电感突变,影响线圈的寄生参数一致性。线圈附近不要走高频信号线,特别是I2C、SPI、UART这类有沿跳变的线。如果绕不开,必须用地线把它们隔开,或者保持至少3倍线宽的距离。
线圈到PSoC引脚的连线尽量短,走线宽度可以适当放宽到0.3mm以上,减小走线寄生电容。不要在线圈和芯片之间加任何串阻或滤波电容,这些元件会成为LC回路的一部分,使谐振特性不可预测。
3. 固件实现:从原始频率到稳定输出
3.1 PSoC Creator工程怎么搭
固件部分是我的本行。在PSoC Creator 4.x里新建工程,选一颗PSoC 4(比如CY8C4245系列),然后搭建数据通路。
如果你想走最快的路线,可以去官方库找现成的电感感应组件,早期Cypress做过一版,后来并到了MagSense技术线里,在ModusToolbox中间件里也能找到类似功能。它的好处是封装了激励、采样、频率转换,省事很多。但如果你的线圈尺寸、谐振频率比较特殊,或者你想彻底搞懂工作细节,建议手动搭一条通路,这也是我推荐的做法。
手动方案的核心连接如下:
- 线圈接PSoC引脚,同时通过内部模拟路由接到比较器输入。
- 给LC回路一个激励脉冲:把同引脚或者其他GPIO先配置为输出,输出一个几微秒到几十微秒的宽脉冲,把能量灌进LC回路。
- 然后把引脚切成高阻输入,LC回路自由振荡,产生衰减正弦波。
- 比较器把这个正弦波整形成方波,输出接到定时器/计数器的捕获输入。
- 定时器用高频时钟测量方波周期,MCU读取测量结果。
在PSoC Creator里,所谓“引脚配置成高阻输入”就是给引脚加一个数字输入缓冲,并把驱动模式改成“高阻数字”。激励和采样可以先用软件控制引脚模式切换,这个代码很简单,适合原型验证;如果追求高性能,把激励和采样交给内部可编程数字逻辑块自动完成,MCU只在结束时去读结果。
3.2 原始数据采集:数边沿还是测周期
测量频率有两种常见做法:
第一种是计数法:固定一个时间窗口(比如1ms),在窗口内统计方波的上升沿个数。这个方法实现简单,但分辨率受限于窗口时间和信号频率。假设谐振频率是1MHz,1ms窗口内数到1000个边沿,频率分辨率就是1kHz,相对偏差千分之一。某些场景够用,但你要是想检测3mm外的微小金属块,这个分辨率可能不够。
第二种是周期测量法:用高频定时器测量方波N个周期的总时间,反推平均周期。比如高频时钟48MHz,测量256个周期,信号频率1MHz,总时间256μs,定时器计数约12288个时钟,理论上分辨率能到1/12288,远超计数法。周期测量法的代价是消耗一个比较器捕获输入和一个定时器,但这对PSoC来说很划算。
我强烈建议产物阶段直接用周期测量法。你在调试时看到的读数稳定度,会直接决定阈值能不能设得比较松、误触发能不能压下去。
代码层面,可以这样实现一个简单的频率采集函数:
uint32_t measurePeriodCounts(void) { Timer_Init(); Timer_Enable(); // 等待第一个捕获事件,丢弃 while (0 == Timer_GetCaptureStatus()) { } Timer_ResetCapture(); // 等待第二个捕获事件,读取周期值 while (0 == Timer_GetCaptureStatus()) { } result = Timer_GetCaptureValue(); Timer_Disable(); return result; }上面的代码是示意,实际PSoC组件接口名称根据你用的器件和组件会略有不同。核心思路是:用第一次捕获同步时间基准,用第二次捕获得到周期。如果要测平均周期,需要连续捕获多次并累加。
3.3 滤波、基线跟踪和阈值判断
原始周期读数会带噪声,尤其是线圈靠近电机、电源波动等场景。直接拿原始数据做判断,阈值设低了会误报,设高了又降低灵敏度。所以固件里必须加滤波。
我的常见组合是:先做一次5点中值滤波,把偶发的脉冲干扰干掉;再做一阶IIR低通,系数取0.1~0.2,让曲线平滑下来。中值滤波保边沿特性好,不会把真实快速变化抹平;IIR让长期读数稳定。两者搭配表现很好。
处理完滤波,接下来是基线跟踪。上电后,线圈周围没有目标物,这段时间内采集到的值作为基线存下来。每次新读数和基线做差,得到delta值。这个delta就是判断目标物的依据。
基线不是一成不变的,温度、湿度、机械应力都会让读数缓慢漂移。如果完全不更新基线,设备用一段时间后就可能误触发或者失敏。但基线更新不能太积极,否则目标物停留在传感器附近时间长了,会被当成“新的基线”,从而漏报。
我的策略是:维护一个长期平均值,只有当连续几秒没有触发目标检测,且当前读数与长期平均值的差小于漂移上限时,才以极慢的速度把基线拉向当前值。这样既能跟上环境缓慢变化,又不会把真实目标误吸收。
阈值判断建议加上滞回。比如触发使用delta大于100Hz,释放要等delta小于50Hz。滞回可以有效避免目标处在临界位置时读数的反复抖动。
一个简化示例:
#define TRIGGER_LEVEL_HZ 100 #define RELEASE_LEVEL_HZ 50 #define MAX_DRIFT_HZ 20 int32_t delta = currentHz - baselineHz; int32_t absDelta = (delta < 0) ? -delta : delta; if (targetDetected) { if (absDelta < RELEASE_LEVEL_HZ) targetDetected = false; } else { if (absDelta > TRIGGER_LEVEL_HZ) targetDetected = true; }阈值到底设多少,取决于你用真实目标物实测出的delta范围。比如目标物紧贴外壳时delta有600Hz,那100Hz触发阈值就是合理的,留足余量;如果只有200Hz,阈值就要压低到50Hz左右。
4. 实测中的精度和抗干扰问题
4.1 温度漂移怎么压
温度是电感感应最讨厌的干扰源。铜线圈的电阻温度系数约0.4%/°C,温度升高,线圈等效电阻变大,Q值下降,振荡条件变化,频率读数跟着漂。PSoC内部比较器的翻转阈值也会随温度稍有变化。两项叠加,从0°C到70°C,裸读数漂几百Hz到上千Hz很正常。如果你的目标信号本身才几百Hz,那就麻烦了。
硬件上,谐振电容一定要选NP0/C0G材质,不要用X5R/Z5U。电容温漂引起的频率偏移是系统性的,会直接叠加到目标信号上。线圈走线稍宽一些,也能减少电阻随温度变化带来的Q值变化。
固件上,基线跟踪是兜底方案。只要产品结构确定、装配后没有大幅机械位移,慢速基线跟踪基本可以把温漂补偿掉。我实际测试过,在温箱里跑温度循环,加上慢速基线跟随后,读数波动可以从上千Hz压缩到几十Hz以内,效果非常明显。
要注意的是,产品刚上电的头几分钟,芯片自身发热会让读数有较快的漂移。所以在固件里,前5分钟内基线跟踪速度可以稍微快一点,等热平衡后切到慢速模式。
4.2 多通道扫描时的串扰
如果你的设计要同时接多个线圈,通道间的串扰必须处理。串扰来源主要有两个:一个是线圈间磁耦合,一个是驱动脉冲通过公共参考地回流到邻近通道。
最直接的解决方法是拉开间距。两个相邻线圈中心距建议大于线圈直径的2倍,这样互感衰减到很低。如果结构空间不允许,可以把两个线圈改成90度正交布局,磁耦合会大幅下降,代价是感应方向受限制。
软件上的处理同样重要。扫描顺序不要按通道顺序依次来,最好把线圈分成两组,先扫奇数组,再扫偶数组。比如4个线圈,扫描顺序是1、3、2、4。这样做给相邻线圈留出了“静默时间”,振铃消退后再测下一个通道,读数更干净。
还要注意,激励脉冲结束后,LC回路的衰减振铃会持续一段时间。如果你在这个振铃还没消失时就切换线圈、立即采样,得到的频率叠加了残留振铃,误差会明显变大。建议在采样窗口前留出几十微秒的稳定时间。这个时间可以从示波器上看到振铃衰减到接近零时确定。
4.3 环境干扰的排查顺序
遇到读数异常波动时,别急着改软件参数,先按下面顺序排查:
- 供电是否干净。把示波器探头点在PSoC电源引脚上,看波纹。如果波纹大,先解决电源问题。LC回路对电源噪声的敏感度超乎想象。
- 外部电磁干扰。近距离有大电流线、电机、开关电源,它们的磁场会直接耦合进线圈。检查这些干扰源是否和线圈靠得太近,必要时调整布局或加屏蔽。
- 机械结构是否发生变化。线圈被螺丝锁紧的扭矩、外壳受压、温度变化导致的材料膨胀,都会让线圈电感量发生缓慢变化。如果读数漂移和你的结构件受热有关,单纯改滤波收效甚微。
- 最后才怀疑固件。检查是否在主循环里做了太多事导致采样窗口抖动,或者中断里耗时长导致读数延迟不规律。
我遇到过一次很典型的问题:电机一启动,读数立刻就跳。排查后发现是电机驱动的地线回流经过线圈下方,产生了一个较大的噪声压降。把地线重新规划,绕开线圈区域,再加上电机驱动端的RC吸收电路,问题就解决了。
5. 从原型到产品:标定、产测和功耗
5.1 出厂标定怎么做
原型能跑通只是第一步,产品化时每个模块的参数都有分散性:线圈电感量差异、电容容差、外壳厚度装配差,都会让基线值不一样。所以产线上必须做标定。
最简单的标定是“两点标定”:空载时测一次基线,然后在固定位置放一个标准金属块测一次delta,两者差值作为灵敏度系数存入Flash。如果产品对精度要求高,可以多做几步,目标物以0.5mm步进移动,记录一组delta-距离曲线,线上用查表加线性插值来还原距离。
标定时机的选择很关键。我建议在整机装配完成后再标定,而不是裸板阶段。因为外壳材质和厚度会影响线圈的有效电感,裸板标定和装壳后的实际参数可能差不少。如果外壳是不锈钢之类导电材质,影响更大。
标定数据建议写进PSoC内部Flash的专用区域,或者外挂EEPROM。写Flash要注意先擦后写,而且要把数据加上校验和,防止异常断电导致标定数据损坏。
如果产品有多个通道,每个通道都要独立标定。不同通道的灵敏度可能差20%,不做通道级校准的话,位置计算的线性度会受影响。
5.2 低功耗场景怎么设计
电池供电的产品用PSoC做金属检测很有竞争力,因为PSoC 4的DeepSleep电流很低,可以用周期性唤醒扫描来降低平均功耗。
典型调度:产品平时睡在DeepSleep,每10ms醒来一次,扫描线圈、滤波、判断,整个过程约3ms,然后继续睡。计算下来,MCU的平均功耗主要取决于扫描期间电流和占空比的乘积,比一直跑着省很多。
但低功耗设计有两个坑要注意。第一个是首次上电要建立基线,这个过程必须在完成标定的前提下进行,不能让首次上电的原始读数直接作为基线,否则如果线圈周围刚好有金属件,基线就偏了。第二个是基线的定期保存。不要每次唤醒都写Flash,Flash有擦写寿命,建议每隔几分钟或几十次扫描无目标时才保存一次基线。
低功耗模式下,如果MCU唤醒后第一时间就扫描,可能会读到电源稳定前的毛刺。稳妥做法是唤醒后等几百微秒,让内部LDO输出电压稳定,再启动比较器和定时器。这个几百微秒也是功耗,但不做的话读数噪声会很大。
5.3 几个让我印象深刻的经验教训
最后分享几个实际项目里踩过的坑,都是常规文档里不会写的东西。
第一个,调试时一定要留一个能实时打印原始数值的调试通道。可以是UART,也可以是I2C从机。看起来多此一举,但等到你调阈值、调滤波参数的时候,没有原始数据曲线,你就是在盲调。我一般会用UART每秒打印一次滤波前后的值,配合串口助手存成CSV,在电脑上画曲线对比。
第二个,线圈走线和焊盘的位置会影响谐振频率。一开始我焊了一颗匹配电容,频率读数比设计值偏离了100多kHz,怀疑电容参数错了,换了好几颗都一样。后来发现是线圈出线端的焊盘铜箔太长,多出来几pF寄生电容,把频率压下去了。解决方法是把焊盘尽量靠近线圈,减小附加面积。
第三个,如果检测目标很近(1mm以内),传感器读数的快速变化会比远距离时更加非线性。距离越近,delta随距离的变化越陡。做位置标定时,一定要在近端多采样几个点,否则线性插值在近端误差很大。
第四个,如果你的目标物是旋转的金属旋钮,那么旋钮的齿轮形状会让读数产生周期性波动。这时候阈值和滤波参数不能只用平均值来判断,最好在前端加一点峰值保持逻辑,把旋钮转过齿尖时的最大delta记录下来,再和阈值比较,效果会好很多。
PSoC做电感感应这条路,我用到现在最大的感受是:它的门槛不高,但上限取决于你对谐振原理和噪声来源的理解。硬件上把线圈、电容、地平面处理干净,固件上把周期测量、滤波、基线跟踪做到位,大部分项目都能稳定跑起来。如果你也在评估这个方向,建议先拿一块PSoC开发板、一个绕好的线圈和一颗电容搭个最小系统,实测一组数据,再决定要不要往产品里放。