ARTICLE DETAIL

资讯详情

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

FSR薄膜压力传感器实战:从STC89C52外扩ADC到STM32内置采集

FSR薄膜压力传感器实战:从STC89C52外扩ADC到STM32内置采集 玩嵌入式这些年FSR薄膜压力传感器算是我在项目里复用率最高的传感器之一。它便宜、轻薄、耐折腾STC89C52和STM32这两款玩家里最常见的单片机都能通过它感知“轻重缓急”——按压力度、触碰有无、坐姿偏移、足底步态全靠这一层薄薄的压敏膜撑起来。不过这东西有个特点原理简单用起来坑不少。同一个FSR在51单片机上没ADC你得想办法扩展采集电路换到STM32内置12位ADC直接读可不同采样时间、不同分压电阻读出来的数据又是另一副面孔。网上相关代码满天飞但大多只贴一段不说为什么这么接、为什么这么配你抄过来往往不是读数飘就是标定不准。这篇文章就把我从选型、电路、代码到标定的完整踩坑过程写出来两种平台都给出可直接复用的代码和参数选择思路适合正在做毕设、DIY或者从51过渡到32的工程师参考。1. FSR传感器原理与选型要点1.1 压阻效应先把“电阻变压力”这件事搞明白FSR全称Force Sensing Resistor中文叫力敏电阻本质上是一个阻值会随施加压力变化的可变电阻利用的是压阻效应。你可以把它理解成一个“被压得越狠电阻越小”的特殊电阻手指轻轻搭上去阻值可能还有几十千欧用力按压到满量程阻值能掉到几百欧甚至更低。关键参数是未受力时的阻值一般规格书会写“大于1MΩ”。这个特性决定了它无法直接给单片机读。MCU读不了电阻只能读电压所以必须先把电阻变化转换成电压变化。最基础也最常用的办法就是电阻分压这个后面单独讲。另外FSR不是精密器件它更适合做“有无压力”“压力大致大小”“压力趋势变化”这类场景比如触碰检测、防夹手检测、座椅占位检测、步态分析、机器人足底反馈这些场景对绝对精度要求不高但对响应速度和重复性有一定要求。选型时最容易忽略的一点是迟滞。FSR在加载和卸载过程中即使压力相同读数也会有差异典型迟滞在10%以内。也就是说你缓慢加大压力到500g读到一个值再从500g慢慢减到0同样在500g时刻读到的值会有偏差这在做阈值判断时尤其要留意后面我专门说解决办法。1.2 常见型号和参数选择量程、面积、封装哪个最重要市面上最常用的是Interlink的FSR 400系列比如FSR402和FSR406。FSR402的激活区直径是12.7mm适合做单点按压检测FSR406的激活区直径是38.1mm面积更大适合坐垫、床垫这类需要更大感应面的场景。两者的量程类似大致在100g到10kg之间响应时间在1到3毫秒级别对绝大多数嵌入式项目够用。高端一些的还有FlexiForce系列比如A401同样叫薄膜压力传感器但它内部做了信号调理和标定精度比普通FSR好不少当然价格也贵一个数量级。我的建议是做验证原型或者课程设计直接用FSR402二十块以内能搞定如果做产品或者需要相对精确的力反馈再看FlexiForce。选择时还要关注三个点。第一是量程FSR在不同力区间内电阻变化是非线性的轻压区灵敏度高、重压区容易饱和所以先确定你关心的压力范围再选对应量程的型号。第二是激活区面积点按用小的大面检测用大的面积和灵敏度有直接关系。第三是出线方式有焊脚、有带连接器的批量做样机时带连接器的省事得多手工焊FSR的银浆导线很容易脱落这点要有心理准备。1.3 分压电路接法方向决定初始输出是高还是低把FSR接入MCU之前必须设计好分压电路。我常用的接法是FSR接在电源和采样点之间固定电阻接在采样点和地之间电路结构是这样的VCC(5V或3.3V) ---- FSR -------- R_fix(固定电阻) ---- GND | ADC采样点此时采样点电压Vout VCC * R_fix / (R_fsr R_fix)。没按压力的时候R_fsr非常大分母大Vout接近0V按下后R_fsr变小Vout升高。这个接法的好处是“无压力时输出接近0”软件上天然安全不会出现悬空误触发。固定电阻R_fix的取值是整个电路设计里最关键的一步。原则很简单在你最关心的那个压力点附近让R_fix和FSR的阻值处于同一数量级这样分压中点附近的电压变化斜率最大灵敏度最高。举个例子如果项目重点监测0到1kg范围的按压实测FSR在1kg时阻值大概在几kΩ到十几kΩR_fix取10kΩ就很合适既能覆盖轻压区间又不会在重压时太快饱和。如果只做轻触检测R_fix可以取20kΩ甚至47kΩ如果做的是重压报警R_fix取1kΩ到4.7kΩ更合理。这个参数强烈建议在原型阶段用可调电阻或者多准备几个阻值实测对比别只看规格书硬算FSR个体差异比较大。2. 双平台硬件方案对比STC89C52差个ADCSTM32直接吃2.1 硬件选型与总体电路设计两款单片机的核心差异就在ADC上。STM32F103C8T6内置12位ADC直接采集分压点就完事STC89C52完全没有ADC模块必须外挂采集芯片这是整个项目最容易被新手卡住的地方。外挂ADC的方案有几种ADC0832是8位串行ADC两路输入接口简单、代码量小新手友好缺点是分辨率一般5V参考下量化步长约19.5mVPCF8591是I2C接口的8位ADC胜在I2C可以挂多个设备但读时序稍麻烦ADC0804是8位并行ADC精度一样但占IO太多不推荐在51上折腾。如果预算和供货允许也可以直接换成带ADC的国产51单片机比如STC12C5A60S2内置10位ADC省掉外挂芯片但前提是你能接受换MCU平台。从我的实测经验看STC89C52加ADC0832这个组合最稳定时序简单代码几十行就能写完不会引入I2C那种时序调试成本所以下面的51部分我主要讲这个方案。STM32这边就省心很多F103的ADC1通道0接到PA0再配合DMA或者简单轮询都可以接线一共就三根电源、地、信号线。整体电路还需要注意一点滤波电容。在ADC采样点和GND之间并联一个0.1uF的陶瓷电容能明显抑制FSR表面接触噪声和高频干扰。这个电容离单片机ADC引脚越近越好我用过加和不加两种板子不加的时候串口打印的数值肉眼可见地抖加了之后稳定很多。另外FSR的信号线不要跟电机驱动线、PWM线绑在一起走干扰会很严重。2.2 STC89C52方案外挂ADC0832的完整接法ADC0832引脚很少一共8个脚。和STC89C52连接时CS接P1^0CLK接P1^1DIO接P1^2VCC和VREF都接5V。分压电路的VCC也统一用5V这样ADC参考电压就是5V后期换算电压时直接用。一个容易踩的细节STC89C52的P1口是准双向IO读外部信号之前必须先往引脚写1否则读进来的电平可能不对。ADC0832的DIO是双向数据线在读取数据阶段会从推挽输出切换成高阻输入51侧必须先把DIO拉高再采样。不少抄来的代码没有这个细节导致读到的数据要么全0要么乱跳。我建议在ADC0832的VCC和GND之间也加一个0.1uF退耦电容这芯片虽然便宜但供电不稳会直接影响转换精度。如果项目对读数稳定性要求高还可以在DIO线上串联一个1kΩ电阻再做输入保护防止插拔瞬间的毛刺损坏IO。2.3 STM32方案内置12位ADC的接线与初始化STM32这边接线更简单。以STM32F103C8T6为例ADC1的通道0固定在PA0引脚。分压电路VCC一定接3.3V因为STM32的ADC输入范围是0到3.3V接5V分压会直接烧引脚。这是我在帮人排查问题时见过最多的事故切记。初始化时有个关键参数很多人忽略采样时间。FSR轻压时阻值很大信号源等效阻抗高如果ADC采样时间太短内部采样电容还没充好电就开始转换结果就是读数偏低而且非线性。我建议把采样时间设到最长也就是239.5个ADC时钟周期。具体到代码里就是ADC_SAMPLETIME_239CYCLES_5这个宏。实测用1.5周期采样和239.5周期采样同一块FSR在轻压区读数能差20%以上这个坑非常隐蔽。另外F103的ADC最大时钟不能超过14MHz一般主频72MHz下选6分频得到12MHz稳定可靠。HAL库配置里对应ADC_CLOCK_SYNC_PCLK_DIV6。如果你用的是F407或者H7系列ADC时钟树略有差别但原则一样先查数据手册确认ADC输入时钟上限别默认配置就完事。3. 完整代码从裸读ADC到输出压力值3.1 STC89C52 ADC0832读取FSR代码下面这段代码是在Keil C51环境下写的晶振11.0592MHz串口9600波特率。代码里包含ADC0832时序驱动、串口打印数字的函数以及一个简单的多次采样取平均实测在STC89C52RC上稳定运行。/************************************************ * STC89C52 ADC0832 读取FSR薄膜压力传感器 * 分压电路VCC(5V) - FSR - 采样点 - R_fix(10k) - GND * 采样点接ADC0832的CH0 * 串口输出ADC原始值(0~255) 和 估算电压(mV) ************************************************/ #include reg52.h #include intrins.h typedef unsigned char u8; typedef unsigned int u16; sbit ADC_CS P1^0; // 片选低有效 sbit ADC_CLK P1^1; // 时钟 sbit ADC_DIO P1^2; // 数据线双向 void Delay_Ms(u16 ms) { u16 i, j; for(i 0; i ms; i) for(j 0; j 123; j); } void Uart_Init(void) { SCON 0x50; // 模式1允许接收 TMOD 0x0F; TMOD | 0x20; // 定时器1模式2 TH1 0xFD; // 9600bps 11.0592MHz TL1 0xFD; TR1 1; } void Uart_SendByte(u8 dat) { SBUF dat; while(TI 0); TI 0; } void Uart_SendString(char *s) { while(*s) Uart_SendByte(*s); } void Uart_SendInt(u16 num) { u8 i; u8 buf[5]; for(i 0; i 5; i) { buf[i] num % 10 0; num / 10; } for(i 5; i 0; i--) Uart_SendByte(buf[i - 1]); } /************************************************ * ADC0832读取通道08位分辨率 * 读两次校验高8位和低8位相等则说明读取成功 ************************************************/ u8 ADC0832_GetValue(void) { u8 i; u8 dat_high 0; u8 dat_low 0; ADC_CS 1; ADC_CLK 0; ADC_DIO 1; ADC_CS 0; // 片选拉低启动转换 ADC_CLK 1; _nop_(); ADC_CLK 0; _nop_(); // 起始位 ADC_DIO 1; // SGL/DIF 1单端输入 ADC_CLK 1; _nop_(); ADC_CLK 0; _nop_(); ADC_DIO 0; // ODD/EVEN 0选择CH0 ADC_CLK 1; _nop_(); ADC_CLK 0; _nop_(); ADC_DIO 1; // 释放DIO准备接收数据 // 读8位高位数据MSB在前 for(i 0; i 8; i) { ADC_CLK 1; _nop_(); ADC_CLK 0; _nop_(); dat_high (dat_high 1) | (u8)ADC_DIO; } // 读8位低位数据LSB在前用于校验 for(i 0; i 8; i) { dat_low dat_low | ((u8)ADC_DIO i); ADC_CLK 1; _nop_(); ADC_CLK 0; _nop_(); } ADC_CS 1; // 释放片选 if(dat_high dat_low) return dat_high; return 0; } /************************************************ * 连续采样num次取平均抑制FSR抖动 ************************************************/ u16 ADC0832_GetAverage(u8 num) { u16 sum 0; u8 i; for(i 0; i num; i) sum ADC0832_GetValue(); return sum / num; } void main() { u16 adc_val; u16 voltage_mv; Uart_Init(); while(1) { // 连续采8次取平均效果比单次采样稳很多 adc_val ADC0832_GetAverage(8); // 5V参考电压8位ADC单位电压 5000mV / 255 voltage_mv (u16)adc_val * 5000UL / 255UL; Uart_SendString(ADC); Uart_SendInt(adc_val); Uart_SendString( Voltage); Uart_SendInt(voltage_mv); Uart_SendString(mV\r\n); Delay_Ms(50); } }这段代码直接烧进开发板就能用。串口打印的数字中ADC后面是0到255的原始值Voltage是估算电压。我这里用8次采样取平均实测能把跳变量控制在5个LSB以内。如果还是觉得抖可以加到16次或32次代价是响应变慢取舍一下。有一个细节值得说ADC0832_GetValue里读取低8位是为了校验如果高8位和低8位不一致说明时序有问题或者DIO线没释放干净这时候返回0而不是返回错误码是为了防止读数乱跳影响上层逻辑。实际调试中如果一直返回0优先检查DIO方向切换是否成功。3.2 STM32 HAL库读取FSR完整代码STM32这边我用HAL库基于F103C8T6。核心逻辑是配置ADC1通道0轮询方式读取增加多次采样平均函数再封装一个电压换算函数。这段代码可以直接移植到CubMX生成的工程里把系统时钟和串口初始化好就能跑。/* 核心驱动代码适配 STM32F103C8T6 HAL库 * 硬件连接FSR分压采样点 - PA0 (ADC1_IN0) * 分压电路VCC接3.3V */ #include main.h ADC_HandleTypeDef hadc1; static void MX_ADC1_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; // 关键1F103 ADC时钟不能超过14MHz72MHz主频用6分频 hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV6; HAL_ADC_Init(hadc1); ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_REGULAR_RANK_1; // 关键2FSR轻压时源阻抗大必须用最长采样时间 sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } /* 单次读取ADC原始值 */ uint16_t Read_FSR_ADC(void) { uint16_t adc_val 0; if(HAL_ADC_Start(hadc1) ! HAL_OK) return 0; if(HAL_ADC_PollForConversion(hadc1, 10) ! HAL_OK) return 0; adc_val HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return adc_val; } /* 多次采样取平均 */ uint16_t Read_FSR_Filtered(uint8_t times) { uint32_t sum 0; uint8_t i; for(i 0; i times; i) sum Read_FSR_ADC(); return (uint16_t)(sum / times); } /* ADC原始值转电压12位分辨率参考电压3.3V */ float ADC_To_Voltage(uint16_t adc) { return (float)adc * 3.3f / 4095.0f; }使用时在主循环里调用Read_FSR_Filtered(16)取平均再用ADC_To_Voltage转成电压。这里我没有写DMA方式是因为轮询在大多数FSR应用里完全够用采样率几十赫兹到几百赫兹没问题。如果你要同时采集多路或者做高频步态分析再考虑ADCDMA逻辑会复杂一些。有一个容易忽略的点PA0引脚必须配置成模拟模式如果保持默认的浮空输入ADC采样结果会莫名其妙地偏低或者非线性。CubMX里在GPIO设置里直接选Analog即可。3.3 数据处理三板斧滤波、标定、阈值读完ADC只是第一步。FSR毕竟是个电阻型器件原始数据噪声大、个体差异大不处理直接拿去判断压力很容易误动作。我通常做三件事滤波、标定、阈值滞回。滤波最简单就是多次采样取平均或者用滑动窗口。如果采样点数多、噪声又比较随机可以先做一次排序去掉最大最小值再平均也就是中值平均滤波对付FSR这种偶发尖峰很有效。51上算力有限用8到16次平均就够了STM32上资源宽裕可以上个简单的滑动窗口数组效果更平滑。标定的目的是把ADC值换算成有物理意义的重量。FSR不是线性元件直接乘系数误差大我建议用多点分段线性插值。操作方法是在FSR上依次放标准砝码记录每个重量对应的ADC均值形成一个映射表然后查表插值。砝码不需要太多5个点左右就能覆盖常用量程。/* 多点线性插值标定代码适配两种平台 */ typedef struct { uint16_t adc; uint16_t weight_gram; } CalPoint; /* 实测标定表务必根据自己的FSR和分压电阻重新标定 */ const CalPoint cal_table[] { {50, 0}, {200, 100}, {500, 500}, {700, 1000}, {850, 2000}, }; #define CAL_NUM (sizeof(cal_table) / sizeof(cal_table[0])) uint16_t Cal_ADC_To_Gram(uint16_t adc) { uint8_t i; if(adc cal_table[0].adc) return cal_table[0].weight_gram; for(i 0; i CAL_NUM - 1; i) { if(adc cal_table[i].adc adc cal_table[i 1].adc) { float ratio (float)(adc - cal_table[i].adc) / (float)(cal_table[i 1].adc - cal_table[i].adc); return cal_table[i].weight_gram (uint16_t)(ratio * (cal_table[i 1].weight_gram - cal_table[i].weight_gram)); } } return cal_table[CAL_NUM - 1].weight_gram; }阈值判断这里我特别推荐加“滞回”处理。很多新手只设一个阈值读数在阈值附近抖动时按键状态就会反复跳变。解决办法是设两个阈值一个按下阈值一个松开阈值松开阈值明显低于按下阈值。比如按下设80g松开设40g这样读数在40到80g之间不会改变状态天然带防抖效果。/* 滞回阈值判断防临界抖动 */ #define PRESS_ON_THRESHOLD 80 // 按下判定单位克 #define PRESS_OFF_THRESHOLD 40 // 松开判定单位克 uint8_t press_state 0; // 0松开1按下 void Update_Press_State(uint16_t gram) { if(press_state 0 gram PRESS_ON_THRESHOLD) press_state 1; else if(press_state 1 gram PRESS_OFF_THRESHOLD) press_state 0; }Update_Press_State每20ms调用一次按键状态就非常稳定了。这个思路在机械按键消抖里很常见但用在FSR这种模拟量判断上效果同样好。4. 实测踩坑记录与排查速查表4.1 读数跳变到亲妈都不认识这是我第一次用FSR时遇到的最头疼的问题。未做任何处理时ADC读数在5秒内能跳几十个LSB尤其在轻压区手指稍微抖动一下数值就像心电图一样跳。原因有两个一是FSR本身接触面上的导电粒子在压力变化时有微动输出本身就带噪声二是引线过长或者和电源线靠太近引入工频干扰。解决办法我按优先级排一下先加0.1uF硬件滤波电容再在软件里做8到16次采样平均最后注意把FSR信号线跟电源线、PWM线分开走。做完这三步读数基本稳定。如果还不够可以试试中值滤波把采样窗口里的最大最小值去掉再平均对付偶发尖峰非常有效。4.2 手没碰就有输出这是一个非常容易被忽略的问题。理论上FSR未受力时电阻大于1MΩ分压点电压接近0但实际测量会发现ADC输出不为0甚至能到几十个LSB。我遇到过的原因有三个一是固定电阻R_fix选得太大比如用了100kΩFSR未受力时虽然也有1MΩ但分压比是100/(1001000)约9%5V参考下能到0.45V这就不小了二是PCB板面受潮或者助焊剂没洗干净采样点漏电三是FSR表面沾了导电污渍等于并联了一个电阻。排查时先测FSR两端实际阻值再量采样点电压能很快定位问题。4.3 标定好了换块板子又漂了同一个FSR同一套代码从5V供电的STC89C52板子换到3.3V供电的STM32板子标定表全部作废。这不是代码问题是ADC参考电压变了。FSR分压输出的是相对电压ADC看到的是以参考电压为基准的比值参考电压不同ADC值自然不同。解决办法有两个一是重新标定这最靠谱二是在代码里用实际测量的VCC算绝对电压再通过查表映射压力。第二种方法可以减小跨平台差异但FSR个体差异依然存在所以换FSR或者换板子后我都建议至少做一次两点标定。4.4 快速踩压和缓慢按压读数差很多FSR有迟滞特性和一定的动态响应限制快速冲击和缓慢施压即使最终压力相同读数也会有差异。早期我做一个踢踏舞节奏检测快速踩踏时读数明显偏低慢踩时读数又正常一度以为是采样率不够。后来确认是迟滞和接触时间的问题FSR的导通特性决定了它更适合低频或者静态压力检测。如果项目必须检测动态力建议把采样率提到100Hz以上配合短窗口的平均动态效果会好一些。另外分压电阻越小RC时间常数越小响应越快这也是R_fix不能盲目取大的原因之一。4.5 FSR引脚读数为0或者一直满量程这类问题先别怀疑代码先用万用表量电压。我把排查流程整理成了一个表方便直接对照现象可能原因排查方向读数一直为0FSR断路或分压点被拉到地量FSR两端阻值检查接线读数一直满量程分压点被拉到VCC检查FSR和R_fix是否接反读数跳变剧烈接触噪声或电磁干扰加0.1uF电容软件多次采样无压力时读数偏高R_fix取值过大或板面漏电换小阻值R_fix清洗PCB换平台后读数全变ADC参考电压不同用实际VCC换算电压重新标定STM32读数偏低采样时间设置太短改成239.5周期采样读数对压力不敏感分压电阻与量程不匹配按目标压力范围重选R_fix另外有个小技巧调试时如果读数异常先把FSR拆下来用一个10kΩ普通电阻替代接到采样点看ADC能不能读到稳定值。能读到正常值问题大概率在FSR和焊接读不到问题就在分压电路或者ADC配置。这个方法能帮你快速缩小排查范围比盲改代码高效得多。最后分享一点我的个人习惯FSR这东西本质上是个“活电阻”别拿它当精密称重传感器用。温度、湿度、按压面积、按压速度都会影响读数我做项目时最常用的定位是“可靠的定性检测加粗略的定量测量”。量程匹配选对、做一次多点标定、再把阈值滞回做好稳定性完全够用。如果后续想扩展可以试试在同一片FSR上加载和卸载的差异做疲劳检测或者用多片FSR组成阵列做压力分布判断还能接蓝牙通过App看实时曲线。玩熟了之后你会发现这个不到二十块的传感器能做的项目比想象中多得多。
返回列表