压力传感器是最常用的一种传感器,其应用范围有各种工业互通环境,涉及航空,航天,军工,石化,电力等。按照不同的测试,压力类型可分表压传感器,差压传感器,绝压传感器。
表压传感器 ——> 相对于环境压力
差压传感器 ——> 两个压力间差值
绝压传感器 ——> 相对于绝对压力
绝对压力是指立体承受的实际压力是以真空状态为起点计算的压力均匀垂直作用,在物体表面上的力称为压力液体所受压力的大小与受力面积之比叫做压强
计算公式为:
新型的压力传感器特点:体积小,质量轻,在众多领域中被广泛使用。
实现原理
压力变化 —> 阻值变化 —> 阻值电压/电流 —> 采集数据
“惠斯通电桥” 它是能精确测量电阻变化的电路
“惠斯通电桥的四分之一桥”在实际情况下一般将R4变为应变电阻,其他三个电阻值相等
那么如何将电阻变化
与压力产生的应变(ε)建立联系呢?
我们用到另外一个公式
不同之处:
有两个应变未知电阻
通过简化可得出
出现了两个未知量△R2和△R1,所以方程所以方程中出现了两个未知量△R2和△R1,而一个方程不能求出两个未知量的值
可根据传感器受到F力的作用下,随之而改变的应变电阻R3会收缩 -vε,应变电阻R4拉伸的应变值为ε,公式中 V是固定值称为“泊松比”。
根据以上公式,可以将△R2用△R1表示出来,就能通过输出电压计算出应变值
还有一种全桥应变电阻方式,这三种都用各自的有缺点可根据实际情况采用哪种方式。
在实际中电桥中数值是不为“0”的,需要调零处理
输出的电压为毫伏级,单位是mV/V。
假设传感器参数为下图所示,它满载输出的电压为20mV。
单片机检测这么小的电压值,需要经过放大处理或用专用的集成芯片
ADC 模数转换部分
上面刚说了,通过检测传感器的电压,就可以计算出重量,所以就需要一个模拟电压采集电路,看上图可以知道,S+和 S-实际上是一个电桥的输出。假设,压力传感器的输出灵敏度为 1mV/V,即表示若激励电压是 5V,则输出范围±5mV。这个输出电压通常是几毫伏,不仅非常微小,而且这还是一对差分信号。比如 S+对地电压是 2.51V,S-对地电压是 2.50V,那么 S+和 S-之间的电压差就是 0.01V,这个 0.01V 的信号就叫差分信号,他们的共模电压就是 2.50V。在检测电压时,可以先检测一下 S+的对地电压,再检测一下 S-的对地电压,然后再做一个减法运算就行了,这样需要采集两次电压,不仅增加了运算量,而且每次采集都是带误差的,而且这里的有效信号仅仅是那个 0.01V 的差。所以,我们就需要一个差分 ADC 芯片,例如:CS1237。这个芯片的输入信号可以是一个差分信号,也就是那个 0.01V 的电压,而且内部还带有一个放大器,可以把这个 0.01V 的信号放大 128 倍。由于是差分输入的芯片,所以需要特别注意一下它能够承受的共模电压大小,CS1237 可以承受最大共模电压是芯片的电源电压。
还是假设 S+和 S-之间的电压差是 0.01V,那么可以经过 128 倍放大,就变成了 1.28V。使用上图所示的接线方法,使用 3.3V 的供电,由于是差分输入的结构,所以 CS1237 可以接受的信号是±3.7mV。这样有一个好处,就是当传感器安装时候,不用担心受力方向了,不管如何安装,只要经过校准,都可以检测到有效的重量。
电压转重量的算法程序
通上电之后,秤盘上面不放任何东西,此时传感器会输出一个电压,不要管具体是多少,因为每一个批次的传感器都会有微小差异,拧螺丝的力道不一样这个电压也不会一样。无论如何,此时能够采集到一个数值,想象一下,这是数轴上的一个点 A。这个时候,再在秤盘上面放一个 500 克的砝码,不管电压是往正方向走还是往负方向走,反正采集到的电压肯定会偏离 A 点一段距离的,记录这个点位 B。那么用 B-A(两个数值是带正负号的计算)得到的数值就是在这个电子秤中,500克的重量对应的那一段。此时可以计算一下称重系数 C=500/(B-A)。数值 C 是一个小数表示 CS1237 采集的数字,每一个数字对应的重量,比如可能是 C=0.01,那么如果单片机再读取到一个数据是 D,D-A 如果等于 1000,那么秤盘上放的这个物品就是 10 克。(具体程序可参考配套例程)
程序部分
CS1237.c
//作者网址: WWW.JIXIN.PRO
#include "CS1237.h" //20bit ADC
#define ADC_Bit 20 //ADC有效位数,带符号位 最高24位
#define SCK_1 SCLK = 1
#define SCK_0 SCLK = 0
#define DAT_1 DOUT = 1
#define DAT_0 DOUT = 0
#define NOP_5() _nop_();_nop_();_nop_()
#define NOP30() NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();
#define NOP40() NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();NOP_5();
#define One_CLK SCK_1;NOP40();SCK_0;NOP40();
#define CS_CON 0X0C //芯片地配置 内部REF 输出10HZ PGA=128 通道A 0X0C
#define Lv_Bo 0.00001 //滤波系数 小于1
sbit DOUT = P3^7;//数据对应IO口
sbit SCLK = P3^5;//时钟对应IO口
static long AD_Res_Last=0;//上一轮的ADC数值保存
//延时500US 5.5296MHZ
void delay_500us(volatile unsigned char a)
{
volatile unsigned char i,j,b;
for(b=0;b<a;b++)
{
i = 3;
j = 137;
do
{
while (--j);
} while (--i);
}
}
//CS1237进入低功耗模式
void CS1237_Power_Down(void)
{
SCLK = 1;
delay_500us(100);
SCLK = 1;
SCLK = 1;
delay_500us(100);
}
//配置CS1237芯片
void Con_CS1237(void)
{
unsigned char i;
unsigned char dat;
unsigned char count_i=0;//溢出计时器
dat = CS_CON;// 0100 1000
SCK_0;//时钟拉低
while(DOUT)//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237全部拉低为0才算都准备好
{
delay_500us(10);
count_i++;
if(count_i > 150)
{
SCK_1;
DAT_1;
return;//超时,则直接退出程序
}
}
for(i=0;i<29;i++)// 1 - 29
{
One_CLK;
}
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//30
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//31
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//32
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//33
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//34
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//35
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//36
One_CLK;//37 写入了0x65
for(i=0;i<8;i++)// 38 - 45个脉冲了,写8位数据
{
SCK_1;NOP40();
if(dat&0x80)
DAT_1;
else
DAT_0;
dat <<= 1;
SCK_0;NOP40();
}
One_CLK;//46个脉冲拉高数据引脚
}
//读取芯片的配置数据
unsigned char Read_CON(void)
{
unsigned char i;
unsigned char dat=0;//读取到的数据
unsigned char count_i=0;//溢出计时器
unsigned char k=0,j=0;//中间变量
SCK_0;//时钟拉低
while(DOUT)//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237全部拉低为0才算都准备好
{
delay_500us(10);
count_i++;
if(count_i > 150)
{
SCK_1;
DAT_1;
return 1;//超时,则直接退出程序
}
}
for(i=0;i<29;i++)// 1 - 29
{
One_CLK;
}
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//30
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//31
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//32
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//33
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//34
SCK_1;NOP30();DAT_1;SCK_0;NOP30();//35
SCK_1;NOP30();DAT_0;SCK_0;NOP30();//36
DAT_1;
One_CLK;//37 写入了0x56
dat=0;
for(i=0;i<8;i++)// 38 - 45个脉冲了,读取数据
{
One_CLK;
dat <<= 1;
if(DOUT)
dat++;
}
One_CLK;//46个脉冲拉高数据引脚
return dat;
}
//读取ADC数据,返回的是一个有符号数据
long Read_CS1237(void)
{
unsigned char i;
long dat=0;//读取到的数据
unsigned char count_i=0;//溢出计时器
DOUT = 1;//端口锁存1,51必备
SCK_0;//时钟拉低
while(DOUT)//芯片准备好数据输出 时钟已经为0,数据也需要等CS1237拉低为0才算都准备好
{
delay_500us(10);
count_i++;
if(count_i > 300)
{
SCK_1;
DAT_1;
return 0;//超时,则直接退出程序
}
}
DOUT = 1;//端口锁存1,51必备
dat=0;
for(i=0;i<24;i++)//获取24位有效转换
{
SCK_1;
NOP40();
dat <<= 1;
if(DOUT)
dat ++;
SCK_0;
NOP40();
}
for(i=0;i<3;i++)//一共输入27个脉冲
{
One_CLK;
}
DAT_1;
//先根据宏定义里面的有效位,丢弃一些数据
i = 24 - ADC_Bit;//i表示将要丢弃的位数
dat >>= i;//丢弃多余的位数
return dat;
}
//初始化ADC相关参数
void Init_CS1237(void)
{
Con_CS1237();//配置CS1237
if(Read_CON() != CS_CON)//如果读取的ADC配置出错,则重启
IAP_CONTR = 0x20;
AD_Res_Last = Read_CS1237();
AD_Res_Last = Read_CS1237();
AD_Res_Last = Read_CS1237();
}
//数字一阶滤波器 滤波系数A,小于1。上一次数值B,本次数值C out = b*A + C*(1-A)
//下面这个程序负责读取出最终ADC数据
long Read_18Bit_AD(void)
{
float out,c;
out = AD_Res_Last;
c = Read_CS1237();
if(c!=0) // 读到正确数据
{
out = out*Lv_Bo + c*(1-Lv_Bo);
AD_Res_Last = out;//把这次的计算结果放到全局变量里面保护
}
return AD_Res_Last;
}
//
main.c
//作者网址: WWW.JIXIN.PRO
//内部时钟 5.5296MHZ
//电池供电可用,实时显示重量,单位g
//KEY1 校准时第一步空称 ; KEY2 砝码校准500G砝码 ;KEY3 常规去皮功能
#include "STC15W.h"
#include "intrins.h"
#include "Uart_1.h"
#include "OLED_IIC.h"
#include "CS1237.h"
#include "T4_Key.h"
#include "ADC.h"
#include "IAP_EEPROM.h"
#define Tare EEROM_20Long[0] //校准用,皮重
#define BGA_EEPROM EEROM_20Long[1] //在5V供电下,校准的内部BGA参数
#define First_ON EEROM_20Long[2] //第一次开机标志位 如果不是88则表示第一次开机,用于批量生产
#define Weight_500g EEROM_20Long[3] //500G标定时候的ADC数值
extern bit Key_1,Key_2,Key_3;//三个按键的状态,全局变量 1表示按下 ,每次用过之后需要手动置零
extern unsigned int Battery_Now;//计算出来的当前电池电压
long EEROM_20Long[20];//用于掉电保存的数值,每次烧写过后可能会归零
unsigned char Work_Count=0;//放在定时器里面,每50ms进行一次显示,称重。
unsigned int Low_Power = 0;//放到定时器里面,进行关机检测,30S重量没有变化后进入低功耗模式
static float Weight_Coe=0.00000;//全局变量,称重时参考的重量系数
static float Tare_Coe=0.00000;//全局变量,皮重的重量,放大1000倍之后的数值
static unsigned int Weight_30S_1,Weight_30S_2;//30S读取一次重量,然后比较,如果两次相同则进入低功耗
static unsigned char Power_Down_F=0;//单片机掉电标志位,用于掉电重启后初始化所有设备
//获取电池电压,50次平均值
unsigned int Get_Bat(void);
//读取CS1237,去皮后的重量,精确到g
unsigned int Get_Weight(void);
//根据校准的数值,计算出称重系数
void Get_Weight_Coe(void);
void main(void)
{
unsigned int Main_Loop=0;//在主函数里面用的,循环时候控制循环次数的变量
P0M1=0;P0M0=0;P1M1=0;P1M0=0;
P2M1=0;P2M0=0;P3M1=0;P3M0=0;
P4M1=0;P4M0=0;P5M1=0;P5M0=0;//上电初始化所有IO口为普通IO
Init_Uart1();//初始化串口1,9600bps
OLED_Init(); //OLED初始化
Init_T4();//初始化T4,用于按键检测
Init_CS1237();//初始化CS1237
Init_ADC();//初始化ADC
EA = 1;//打开单片机全局中断
Re_20_Long(0XD3B8,EEROM_20Long);//读取所有的掉电保存数据到内存里
Delay1ms(10);
if(First_ON != 88)//如果检测到第一次开机情况,表示需要校准,用于批量生产时候
{
LED2 = 0;//灯亮
First_ON = 88;
Main_Loop = 10;
while(Main_Loop--)
Tare = Read_18Bit_AD();//读取出CS1237的数据,5V情况下
Main_Loop = 10;
while(Main_Loop--)
BGA_EEPROM = Get_BGA();//在5V供电情况下,保存BGA参数
Wr_20_Long(0XD3B8,EEROM_20Long);//保存数据到EEPROM
LED2 = 1;//灯灭
}
else
Get_Weight_Coe();//依据EEPROM内容,计算称重系数
Send_Data1(Read_CON());//发送CS1237配置信息到串口
//---------------------------------------------------------------//
LED2 = 0;//灯亮
Main_Loop = 10;//开机自动读取一次当前皮重。
while(Main_Loop--)//连续读取10次,是因为CS1237读取程序里有软件滤波器,这样做更接近真实值
Tare_Coe = Read_18Bit_AD();//读取出皮重的ADC数据
Tare_Coe *= Weight_Coe;
LED2 = 1;//关灯
//--------------------------------------------------------------//
while(1)
{
if(Key_1)//去皮后的值保存到EEPROM里面,必须在5V环境下 校准专用
{
Key_1 = 0;
LED2 = 0;//灯亮
Main_Loop = 10;
while(Main_Loop--)
Tare = Read_18Bit_AD();//读取出CS1237的数据,5V情况下
Main_Loop = 10;
while(Main_Loop--)
BGA_EEPROM = Get_BGA();//在5V供电情况下,保存BGA参数
Wr_20_Long(0XD3B8,EEROM_20Long);//保存数据到EEPROM
LED2 = 1;//关灯
}
if(Key_2)//放上一个500g砝码,用于校准误差,必须在5V供电环境下
{
Key_2 = 0;
LED2 = 0;//灯亮
Main_Loop = 10;
while(Main_Loop--)
Weight_500g = Read_18Bit_AD();//读取出500G的数据
Weight_Coe = Weight_500g - Tare;//除去皮重的ADC数值
Weight_Coe = 500000 / Weight_Coe;//放大1000倍的斜率
Tare_Coe = Weight_Coe * Tare;//皮重的重量,放大1000倍之后的
Wr_20_Long(0XD3B8,EEROM_20Long);//保存数据到EEPROM
LED2 = 1;
}
if(Key_3)//正常的去皮重,不保存到EEPROM里面
{
Key_3 = 0;
LED2 = 0;
Main_Loop = 10;
while(Main_Loop--)
Tare_Coe = Read_18Bit_AD();//读取出皮重的ADC数据
Tare_Coe *= Weight_Coe;
LED2 = 1;
}
//定时器控制的子程序,每150ms调用一次
if(Work_Count == 4)
{
OLED_ShowNum(7*9,2,Get_Bat(),4,16);//显示电池电压
Weight_30S_1 = Get_Weight();//每一次称重,都要更新一下用于低功耗的数据
OLED_ShowNum(7*8,4,Weight_30S_1,4,16);//OLED显示重量
Work_Count = 0;
if(Weight_30S_1 == Weight_30S_2)
{
Low_Power ++;
}
else
{
Weight_30S_2 = Weight_30S_1;
Low_Power = 0;
}
}
//
if(Low_Power > 150)
{
CS1237_Power_Down();//CS1237进入低功耗模式
OLED_Power_Down();//OLED进入低功耗模式
INT_CLKO |= 0X10;//使能INT2中断,主要用于唤醒单片机
//所有IO口设置为高阻输入
P0M1=0;P0M0=0;P1M1=0;P1M0=0;
P2M1=0;P2M0=0;P3M1=0;P3M0=0;
P4M1=0;P4M0=0;P5M1=0;P5M0=0;
P0 = 0xff;P1 = 0xff;P2 = 0xff;
P3 = 0xff;P4 = 0xff;P5 = 0xff;
Power_Down_F = 0;
PCON |= 0X02;//单片机进入停机模式
while(1)
{
if(Power_Down_F)//
IAP_CONTR = 0x20;
}
}
}
}
//获取电池电压,50次平均值
unsigned int Get_Bat(void)
{
unsigned char i=50;
unsigned long dat=0;
while(i--)
{
Get_Vol();//主要是为了获取电池电压
dat += Battery_Now;
}
dat /= 50;
dat /= 100;//特意忽略电压的最后两位 表示以V为单位的电压保留一位小数 比如3800mV,即3.8V
dat *= 100;
return dat;
}
//读取CS1237,去皮后的重量,精确到g
unsigned int Get_Weight(void)
{
float dat;
unsigned long dat2;
dat = Read_18Bit_AD();
dat *= Weight_Coe;//计算出当前重量,毛重,1000倍放大的
dat -= Tare_Coe;//减去皮重的重量
if(dat<0)
dat = 0;
dat2 = dat;
dat2 /= 100;//准备四舍五入,因为放大了100倍,所以现在保留了小数点后一位
if((dat2 % 10) > 5)
{
dat2 /= 10;
dat2 += 1;
}
else
{
dat2 /= 10;
}
return dat2;
}
//根据校准的数值,计算出称重系数
void Get_Weight_Coe(void)
{
Weight_Coe = Weight_500g - Tare;//除去皮重的ADC数值
Weight_Coe = 500000 / Weight_Coe;//放大1000倍的斜率
Tare_Coe = Weight_Coe * Tare;//皮重的重量,放大1000倍之后的
}
//
//外部中断入口,主要用于掉电唤醒
void EX_Int2(void) interrupt 10 //INT2
{
Power_Down_F = 1 ;
}
//