ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FSR薄膜压力传感器从51到STM32实战:原理、电路与代码

FSR薄膜压力传感器从51到STM32实战:原理、电路与代码 “FSR薄膜压力传感器”这名字听着唬人其实就是一层薄薄的“压敏电阻”。我最早拿它做正经东西是给朋友改一套坐姿提醒座垫当时用的还是STC89C52加外部ADC0804代码写得够呛后来项目转战STM32F103发现同样是读FSR平台不同从电路设计到代码写法完全是两种思路。这篇就把两条路都完整走一遍从传感器原理、分压电路计算到两套平台的完整代码、压力标定和调试翻车经验一次性讲透。无论你正在51入门阶段还是已经开始折腾STM32跟着文章把它做出来以后碰到任何压敏场景——智能坐垫、握力训练器、机器人电子皮肤、脚底压力检测——都能直接套用。1. 项目整体分析与核心思路1.1 FSR薄膜压力传感器到底能做什么FSRForce Sensing Resistor在中文环境里常被叫做薄膜压力传感器核心是一个压阻元件施加压力内部导电材料的接触面积和导电通路发生变化整体电阻值随之下降。它跟电阻应变片完全是两码事。应变片测的是微小形变需要配电桥和仪表放大器FSR测的是“有没有压力”和“压力大概多大”输出变化非常明显不需要那么娇贵的调理电路。典型参数大家感受一下不施压时FSR的电阻通常能到几百千欧甚至1兆欧以上用手指轻轻按电阻立刻降到几十千欧用力按可能掉到几百欧。这个跨度非常大所以不能直接拿万用表估一下就往单片机ADC上怼需要做信号调理后面我会细讲。FSR的优势是薄、软、便宜、好集成。贴在坐垫里就是坐姿检测贴在鞋底就是步态分析夹在握力器里就是握力训练贴在机械爪指上就是“电子皮肤”的初级感知。它非常适合做阈值判断、力度趋势、按压位置分布这类需求。1.2 为什么同时写STC89C52和STM32两套方案不少同学刚接触单片机是从STC89C52开始的这芯片教学意义大于工程意义。它最大的特点是没有内置ADC。想测模拟量必须外挂ADC芯片最经典的就是ADC08048位并行输出接法直观逻辑清晰。用它来读FSR能把“模拟信号→ADC→数字处理”整个链路看得明明白白。STM32F103则是工程里最常见的MCU之一内置12位ADC还带DMA性能强不少。同一个FSR在STM32上可以做得更精细多通道扫描、DMA连续采样、串口直接输出波形、配合空闲中断做低功耗。最重要的是两种平台的ADC精度不同、参考电压不同、编程习惯不同把这些差异讲清楚你就算真正吃透了传感器。所以这篇文章不挑平台两头都讲代码都给你。先用51理解原理再用STM32做真正的产品顺序刚好。1.3 整体信号链从压力到数字量整个系统的信号链路是FSR → 分压电路/放大电路 → ADC → 单片机 → 显示/串口/执行机构FSR本身是可变电阻不能直接输出电压。要通过一个固定电阻组成分压器把“电阻变化”转成“电压变化”然后把电压送进ADC。STC89C52那边用的ADC0804是并行接口8位分辨率STM32走内置ADC12位。两种方案的电路不能完全照搬尤其注意电压范围FSR电路如果用5V供电直接接到STM32的ADC引脚上一不小心就把芯片烧了。2. FSR传感器原理与信号调理电路设计2.1 压阻效应与关键性能参数FSR的工作原理可以用“导电粒子在压力下接触更多”来理解。传感器内部有导电浆料施加压力后导电通路变多电阻下降。压力和电导之间近似满足G k × F^mG是电导F是压力k和m都是跟传感器结构有关的常数。也就是说压力和电阻不是线性关系而是近似“倒数关系”。在不同压力区间灵敏度差异很大。这也是为什么FSR很难像电子秤那样直接得到高精度重量但做压力分级和趋势检测完全够用。为了后面标定时心里有数我实测过一个典型FSR的近似电阻值不同压力下大致如下施加压力FSR典型电阻输出特性无压力 1 MΩ近似断路轻触约100 g30 kΩ 左右电阻急剧下降中等按压约1 kg3 kΩ 左右灵敏度下降重压约10 kg300 Ω 左右逐渐趋于平缓这个表格非常关键。它决定了你的分压电阻怎么选。FSR还有两个“坏毛病”一是温度漂移环境温度变化会导致阻值轻微变化二是迟滞也就是说加压和减压曲线不重合。这些属性在精密称重场景是致命伤但做交互设备基本不用太操心。2.2 分压电路电阻怎么选、电压怎么算最简单的接法是这样5V或3.3V供电FSR一端接电源另一端串联一个固定电阻R_fixed到地从FSR和R_fixed的连接点引出电压到ADC。这时输出电压为Vout VCC × R_fixed / (R_fixed R_FSR)压力增大 → R_FSR变小 → Vout升高。固定的R_fixed取值直接决定了测量范围。以5V供电为例如果选R_fixed 10kΩ计算几个典型点无压力R_FSR 1MΩVout ≈ 5 × 10 / (10 1000) ≈ 0.05V轻触100gR_FSR 30kΩVout ≈ 5 × 10 / (10 30) 1.25V轻压1kgR_FSR 3kΩVout ≈ 5 × 10 / (10 3) ≈ 3.85V重压10kgR_FSR 300ΩVout ≈ 5 × 10 / (10 0.3) ≈ 4.85V从计算能看出来10kΩ这个值在小压力区间分辨得很清楚特别适合“有没有碰到”这类检测。如果你想重点分辨1kg以上的压力把R_fixed换成1kΩ在中等压力区间的电压变化会更剧烈但要付出的代价是轻触阶段的电压变化不明显。所以选电阻之前先想清楚你要把精度放在哪个压力段。注意如果直接把FSR分压电路接到STM32的ADC引脚务必确认分压电路供电不超过3.3V不然ADC引脚会过压损坏。稳妥起见量程部分直接用3.3V供电或者用电阻分压/电平转换后再进STM32。2.3 运放缓冲与滤波电路分压输出的电压能不能直接进ADC能但效果不一定好。如果供电电压低轻压时输出变化只有零点几伏12位ADC倒是能分辨但噪声也会被ADC原封不动地采进去。更常见的问题是直接接ADC时FSR的阻值会受到ADC输入电流的影响造成测量偏移。工程上常用的做法是加一级运放缓冲和放大。最便宜的做法是用LM358双运放一个做电压跟随器一个做同相放大。电压跟随器输入阻抗高不影响分压器同相放大器可以按需把信号放大到更方便测量的范围。放大倍数由两个电阻决定Gain 1 R2 / R1比如R1 10kΩR2 20kΩ放大倍数为3倍。设计时注意别把输出电压顶到运放饱和比如后面要接3.3V ADC放大后峰值电压就别超过3.0V。滤波也不能省。FSR本身面对的是“人手按压”这种机械量变化频率不高反而是电源和空间里的高频噪声很容易被ADC采到。最简单的一阶RC低通滤波就能解决在ADC输入前串一个1kΩ电阻再接100nF电容到地截止频率约1.6kHz。如果追求更干净可以把截止频率压到100Hz附近对“按压”这种物理量完全够用。截止频率公式fc 1 / (2π × R × C)比如R 10kΩ、C 100nFfc ≈ 159Hz。手部按压动作一般不会超过10Hz所以160Hz完全不会损失有用信号。2.4 两个平台对ADC需求的核心差异STC89C52没有ADC外接的ADC0804是8位分辨率参考电压通常5V理论分辨率大约是5V / 255 ≈ 19.6mV。这个精度拿来判断“有没有按”足够但想区分“500g和600g”就非常吃力。STM32F103内置ADC是12位参考电压如果用3.3V分辨率是3.3V / 4095 ≈ 0.8mV精度上高了一个数量级。再加上支持DMA和连续采样可以做多通道扫描、软件滤波、数据平滑甚至是简单的动态压力波形采集。还有一个容易被忽略的点ADC的输入建立时间。FSR输出源阻抗很高如果ADC采样时间太短采样电容还没有充满就把值锁定了读出来的数会偏低。STM32配置ADC时建议把采样周期拉长比如239.5个周期效果比什么花哨滤波都直接。3. 基于STC89C52的完整实现3.1 硬件连接方案FSR ADC0804 LCD1602STC89C52这边我的推荐连接方式如下STC89C52引脚ADC0804引脚说明P1.0 ~ P1.7DB0 ~ DB7并行数据输出P3.5CS片选低电平有效P3.6WR启动转换低脉冲P3.7RD读数据低脉冲P3.4INTR转换完成中断低电平有效FSR分压电路输出接到ADC0804的VIN引脚。VIN-接地VREF/2悬空即可这时候ADC0804的满量程是5V。如果想让量程更精准可以给VREF/2加一个2.5V参考电压但我个人建议新手先不做默认5V最简单。另外提醒一句P0口做LCD数据线时必须接上拉电阻。STC89C52的P0口是开漏输出不接上拉LCD显示全是乱码这是51新手最常见的问题。3.2 STC89C52完整工程代码下面这份代码在Keil C51下可以直接编译。功能是循环读取FSR的电压换算成压力值显示到LCD1602上。#include reg52.h #include stdio.h #include string.h #include intrins.h #define uchar unsigned char #define uint unsigned int sbit LCD_RS P2^6; sbit LCD_RW P2^5; sbit LCD_EN P2^7; sbit ADC_CS P3^5; sbit ADC_WR P3^6; sbit ADC_RD P3^7; sbit ADC_INTR P3^4; #define ADC_DATA P1 void delay_us(uint n) { while (n--); } void delay_ms(uint n) { uint i, j; for (i n; i 0; i--) for (j 120; j 0; j--); } void LCD_WriteCmd(uchar cmd) { LCD_RS 0; LCD_RW 0; P0 cmd; LCD_EN 1; delay_us(20); LCD_EN 0; delay_ms(2); } void LCD_WriteData(uchar dat) { LCD_RS 1; LCD_RW 0; P0 dat; LCD_EN 1; delay_us(20); LCD_EN 0; delay_ms(2); } void LCD_Init(void) { LCD_WriteCmd(0x38); // 8位数据双行显示 LCD_WriteCmd(0x0C); // 开显示不显示光标 LCD_WriteCmd(0x06); // 地址自动加一 LCD_WriteCmd(0x01); // 清屏 } void LCD_ShowString(uchar row, uchar col, char *str) { uchar addr row 0 ? 0x80 : 0xC0; LCD_WriteCmd(addr col); while (*str) { LCD_WriteData(*str); } } uchar ADC0804_Read(void) { uchar dat; ADC_CS 0; ADC_WR 0; _nop_(); _nop_(); ADC_WR 1; while (ADC_INTR 1); // 等待转换完成INTR变为低 ADC_RD 0; _nop_(); _nop_(); dat ADC_DATA; ADC_RD 1; ADC_CS 1; return dat; } float GetForceByVoltage(float voltage) { // 两个标定点电压0.20V对应0g电压2.80V对应500g // 线性公式 force(g) k * (voltage - v0) float force; force 500.0f * (voltage - 0.20f) / (2.80f - 0.20f); if (force 0) force 0; return force; } void main(void) { uchar adc_val; float voltage; float force; char buf[16]; LCD_Init(); while (1) { adc_val ADC0804_Read(); voltage adc_val * 5.0f / 255.0f; force GetForceByVoltage(voltage); sprintf(buf, V%.2f V , voltage); LCD_ShowString(0, 0, buf); sprintf(buf, F%.0f g , force); LCD_ShowString(0, 1, buf); delay_ms(50); } }3.3 代码逐段说明与STC89C52实测要点这段代码的精髓在ADC0804_Read函数里。启动转换时要先把CS拉低然后给WR一个低脉冲。WR从低到高的那一刻ADC开始转换。转换需要一定时间通过INTR引脚通知平时INTR是高电平转换完成后变低。代码里用while (ADC_INTR 1);死等直到INTR变低再拉低RD读取数据。如果不想用INTR也可以改成固定延时发出WR脉冲后延时个100微秒再直接读数据。这样能省一个引脚但可靠性略差因为每片ADC0804的转换速度有差异。代码里还用到浮点运算。STC89C52本身没有FPU浮点计算会消耗较多程序空间和CPU时间。实际产品里如果对速度有要求可以把标定公式改成整数计算。我这个版本图省事用了float但你要知道它慢。STC89C52的下载也要注意它是通过串口ISP烧录的不是JTAG/SWD。Keil编译生成hex文件后用STC-ISP软件选择对应型号和串口点下载然后给开发板重新上电。很多人第一次都卡在“一直提示等待上电”这一步实际上是软件已经在等了你没断电重启。经验如果读取数值乱跳先不要怀疑代码把FSR到ADC0804的线尽量缩短同时在VIN对地加一个0.1μF电容往往立刻见效。4. 基于STM32的完整实现4.1 STM32F103C8T6硬件连接与CubeMX配置STM32这边我用的是最常见的STM32F103C8T6“蓝板”。FSR分压电路直接使用3.3V供电输出接到PA0对应ADC1的通道0。分压电阻取值可以参考第2章的计算但供电从5V改成3.3V后输出电压上限也变成了3.3V比较安全。硬件连接表STM32引脚外部器件说明PA0FSR分压输出ADC1_IN012位采样PA9 / PA10USB转TTL RX / TX串口输出测量结果3.3V / GNDFSR电路电源必须与MCU共地BOOT0通过10kΩ接地正常从Flash启动在STM32CubeMX里ADC1配置为Channel 0采样时间239.5 cycles打开连续转换和扫描模式DMA选择循环模式数据宽度Half Word。USART1配置为115200-8-N-1。时钟方面外部8M晶振倍频到72MHz。把采样时间拉到最大这个细节很多人忽略。FSR电路输出阻抗高ADC内部采样电容充电需要时间如果采样周期太短ADC读到的值会比真实值偏小尤其是接长线的时候更明显。4.2 STM32完整代码示例HAL库版下面这段是基于CubeMX生成后的main.c关键部分直接在HAL库环境下使用。我加入了8次软件均值滤波效果非常明显。/* main.c 核心代码片段 */ #include main.h #include stdio.h #include string.h ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; UART_HandleTypeDef huart1; static uint16_t adc_buf[8]; static uint32_t adc_sum; static uint16_t adc_raw; static volatile uint8_t adc_ready 0; float ForceFromVoltage(float voltage) { // 标定参数需要根据实际传感器和分压电阻调整 // 本例假设轻压0.15V对应0g2.60V对应500g float force; force 500.0f * (voltage - 0.15f) / (2.60f - 0.15f); if (force 0) force 0; return force; } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { uint8_t i; uint32_t sum 0; for (i 0; i 8; i) sum adc_buf[i]; adc_raw sum / 8; adc_ready 1; } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); char buf[64]; float voltage, force; HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 8); while (1) { if (adc_ready) { adc_ready 0; voltage (float)adc_raw * 3.3f / 4095.0f; force ForceFromVoltage(voltage); snprintf(buf, sizeof(buf), ADC:%d V:%.3f F:%.1fg\r\n, (int)adc_raw, voltage, force); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 100); } } }4.3 代码运行流程与实测记录整个流程是FSR分压器输出模拟电压ADC1连续采集8次通过DMA自动写入adc_buf数组。每次转换完成后触发中断回调在回调里对8个值取平均得到adc_raw再换算成电压和压力通过串口发到电脑上。我在实际运行中把手指轻放FSR上串口输出的电压从0.1V左右跳到0.8V用力按压后电压能到2.5V以上对应压力值也在逐渐爬升。打开串口波特率115200使用串口助手或直接用“串口波形显示”工具能画出压力曲线。有个细节必须提HAL_ADC_ConvCpltCallback里不要做长时间的浮点运算和串口发送因为DMA回调是从中断上下文调用的。我在这里只做了平均值和标志位把耗时的字符串处理和串口发送放到主循环里。否则一旦回调占用时间过长会干扰ADC的DMA循环。如果你的开发板只有ST-Link别忘了一起接上串口转USB模块。实测数据通过STM32的串口出来调试效率比看寄存器高得多。5. 压力标定、滤波与数据转换5.1 为什么必须做标定FSR的个体差异非常大。同一批次的两片传感器同样的压力分压输出未必完全一致再叠加上分压电阻5%的误差、ADC参考电压误差直接拿ADC原始值当“压力值”肯定不准。所以想让测量结果有意义必须先把“电压ADC值”和“实际压力”对应起来。但也要现实一点FSR本身不是精密称重元件标定了也达不到电子秤的精度。把它用在“坐姿检测”“握力等级”“有没有踩地”这类场景一个粗略的线性映射就够用了。5.2 两点法与多段插值标定实操最简单的标定是两点法。找一样已知重量的东西比如500g砝码没有的话拿一瓶500mL矿泉水也能凑合。先在无负载状态下记录电压V0再把砝码放在FSR上记录电压V1然后就能得到线性公式force (已知重量) × (V - V0) / (V1 - V0)这个公式对应代码里的ForceFromVoltage函数。如果希望精度更高一点别用两点法用多点插值。比如分别在0g、100g、300g、500g、1000g下记录电压做成一个查表数组已知压力(g)测得电压(V)00.101000.803001.905002.6010003.10实际使用时先判断电压落在哪个区间然后做一次线性插值。这样即便FSR自身曲线是非线性的你也能用分段直线去逼近效果比单一线性拟合好很多。5.3 软件滤波均值、中值、一阶低通硬件滤波只能压住一部分噪声软件滤波才是成本最低的抗干扰手段。滑动均值适合连续采样的场景比如采8次求平均STM32代码里就是这种做法。优点是平滑效果好代价是延迟增加。中值滤波适合有脉冲干扰的场景。比如某人操作时碰到导线出现单次尖峰把窗口里的数据排序取中间值能完美滤掉这个尖峰。一阶低通滤波公式是 y[n] αx[n] (1-α)y[n-1]。α越小滤波越重反应越慢。实时取力时α取0.2到0.4比较合适做“按下/松开”判断α可以到0.1。还要考虑滞后补偿。FSR有迟滞同样压力下加压过程和减压过程读到的电压不同。你可以通过软件做个迟滞窗口要超过高阈值才判定“按下”低于低阈值才判定“松开”避免在临界位置反复抖动。这个技巧在做坐姿检测和按键替代时特别有效。6. 常见问题与排查技巧实录6.1 STC89C52侧的典型坑Keil5编译不了C51工程新装的Keil5默认只带了ARM编译器STC89C52属于C51内核需要单独安装C51编译器支持包。装完以后在Project Manager里选择芯片时才能看到STC的选项。ADC0804读数一直0xFF大概率是CS/RD/WR时序有问题。用示波器观察这三个引脚的脉冲或者改用纯延时方式读取。如果手头没有示波器可以先写个固定程序让WR产生低脉冲然后用万用表量INTR是不是在转换完成后变低。ADC值波动剧烈检查FSR分压电路和ADC0804之间的地线。单片机的数字地和模拟地如果不做单点接地ADC很容易被数字噪声干扰。先用短粗线把两点连在一起再在ADC0804电源引脚加0.1μF去耦电容。STC-ISP下载总提示等待上电先点下载按钮再给单片机断电上电这个顺序不能反。如果还不行降低波特率比如从115200降到9600很多USB转串口模块在高速下兼容性不好。6.2 STM32侧的典型坑STM32 USB设备无法识别首先要确认USB座的D和D-有没有正确地连接到STM32的PA11和PA12。很多开发板设计时D用了上拉电阻但也有部分需要手动外接1.5kΩ上拉。另外USB正常工作的前提是芯片晶振准确8MHz晶振不起振或电容配错USB也识别不了。Keil5安装了但找不到STM32芯片包打开Pack Installer去STMicroelectronics目录下安装对应F1系列包。如果在线安装总失败可以到ST官网手动下载pack文件然后双击安装或者放到Keil安装目录的ARM/PACK文件夹下。HAL_Delay卡死不执行先检查SystemClock_Config里时钟配置是否正确。常见原因是外部晶振不起振HAL_RCC_ClockConfig未成功导致SysTick频率异常。另一排常见原因是中断抢占优先级问题SysTick中断被人为屏蔽。禁用JTAG以后仿真器连不上有些同学复用了PA13/PA14/PA15然后调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)把JTAG关了只保留SWD。此时如果调试器还在用JTAG模式就会连接失败。解决办法是把BOOT0拉高重新上电进入ISP模式用STM32 ST-LINK Utility全片擦除再拉低BOOT0恢复。6.3 电源和电容选型对ADC读数的实际影响有个挺玄学但真实存在的情况AMS1117-3.3的输出电容本来用的是钽电容你把它换成陶瓷电容结果ADC读数变得不稳定甚至整个系统偶发复位。原因在于AMS1117这类LDO对输出电容的等效串联电阻ESR有要求钽电容的ESR在几十到几百毫欧而陶瓷电容ESR极低可能会导致环路不稳定输出纹波变大。这不是说陶瓷电容一定不行有些厂家把AMS1117内部环路做了调整可以稳定工作。但从工程稳妥角度如果你照着别人的电路抄却把电容换了出现异常先从这个地方查。ADC对电源纹波非常敏感万一纹波耦合到参考电压上读数就跟着抖。我自己做产品时给MCU供电的3.3V上还会再并一个1μF陶瓷电容和10μF钽电容模拟部分单独走一小段地线然后再单点接入主地。这样弄完ADC采样数据很少出现尖刺。7. 真实项目中才体会得到的几条经验如果你准备把这套方案放到实际场景里我建议你先把“精度预期”放低。FSR做成人机交互、阈值判断都非常好用但别指望它能替代称重传感器。一个坐垫检测判断“坐着/没坐/坐姿偏了”FSR绰绰有余要放在电子秤上精确到克那选型就得重新考虑。机械结构对FSR测量的影响比很多人想象中大。FSR本身很软如果底下是海绵压力一压受力范围被摊开输出跟用硬板垫着时完全不同。想稳定最好在传感器下面垫一块硬质支撑板上面压一块软垫用于接触这样既能保证受力集中又能让用户触感舒适。这个细节我调试过很久才醒悟过来。还有引脚连接。FSR的引脚是印刷银浆层很薄焊锡反复拉几次就可能撕裂。我的做法是先把一小片导电胶带贴在引脚上再焊线或者用“FFC/FPC连接器”压接。如果是做原型验证用杜邦线加夹子夹住引脚边缘也能凑合但别指望它能长期稳定。最后再分享一个实用技巧如果你要在同一块板上接多片FSR可以用“分时供电”的方式省ADC引脚——每个FSR的电源端接一个IO口轮流通电并采集这样一片MCU可以管理好几路FSR而且不会互相干扰。这个思路我用在过一个简易“压力分布坐垫”上效果非常好。STM32的ADC通道多DMA又能连续跑扩展成4路、8路都很轻松。这篇内容把FSR这条线从51到STM32都走了一遍代码可以直接拿去改。后面你也可以在这个基础上接OLED、蓝牙或WiFi做一套完整的无线压力检测系统。别怕踩坑ADC读数不稳、标定不准这些问题谁都会遇到把原理理解了坑自然就少了。
返回列表