后来感觉是思路错误了,就不该用模拟信号来传输,于是决定使用A/D模块来把重量转换成数字信号传输,A/D模块就固定在离传感器最近的地方,将输出的数字信号用导线传输给单片机,这样能很大程度减小各种干扰。
在网上搜了一下A/D模块,基本上有两种廉价成品:HX711和PCF8591,它们分别是8位转换模块和24位转换模块。
PCF8591:
功能简介: 基于I2C接口的AD/DA转换模块,8位精度,四通道AD,单通道DA,电压输出型
最大特点:
1. 支持两种接口类型接入目标板:排针或排座
2. 支持I2C总线级联(通过排针、排座对接的方法,可同时使用多个I2C模块)
典型应用: 低速AD/DA转换
主要资源: PCF8591,I2C接口排针,I2C接口排座,可调电阻,AD输入口,DA输出口,地址跳线端口
HX711:
模块工作电压:4.8-5.5v
典型电流 1.6mA
体积:长 2.9cm * 宽 1.7cm * 高 0.4cm
● 带金属屏蔽,强抗干扰,预留MCU(STC15F104)位置,可自行升级二次开发。
● 两路可选择差分输入
● 片内低噪声可编程放大器,可选增益为32、64 和128
● 片内稳压电路可直接向外部传感器和芯片内A/D 转换器提供电源
● 片内时钟振荡器无需任何外接器件,必要时也可使用外接晶振或时钟
● 上电自动复位电路
● 简单的数字控制和串口通讯:所有控制由管脚输入,芯片内寄存器无需编程
● 可选择10Hz 或80Hz 的输出数据速率
● 同步抑制50Hz 和60Hz 的电源干扰
● 耗电量(含稳压电源电路):
典型工作电流:< 1.7mA, 断电电流:< 1μA
● 工作电压范围:2.6 ~ 5.5V
● 工作温度范围:-20 ~ +85℃
===========================================================
我要使用的拉力传感器为传统的桥式传感器,量程1kg,想要达到1g的精度,这就要求模块输出位数至少为10位(2^10=1024),8位的PCF8591无法胜任,因为它的精度只有2^8=256位。所以决定使用HX711作为我的A/D模块,而且我选的HX711模块还带屏蔽壳,正符合我的要求。
先来看一下该模块的常用接法:
模拟输入
通道 A 模拟差分输入可直接与桥式传感器的差分输出相接。由于桥式传感器输出的信号较小,为了充分利用A/D 转换器的输入动态范围,该通道的可编程增益较大,为128 或64。这些增益所对应的满量程差分输入电压分别±20mV 或±40mV。
通道B 为固定的32 增益,所对应的满量程差分输入电压为±80mV。通道B 应用于包括电池在内的系统参数检测。
供电电源
数字电源(DVDD)应使用与MCU 芯片相同的的数字供电电源。HX711 芯片内的稳压电路可同时向 A/D 转换器和外部传感器提供模拟电源。稳压电源的供电电压(VSUP)可与数字电源(DVDD)相同。稳压电源的输出电压值(VAVDD)由外部分压电阻R1、R2 和芯片的输出参考电压VBG 决定(图1),VAVDD=VBG(R1+R2)/R2。应选择该输出电压比稳压电源的输入电压(VSUP)低至少100mV。
如果不使用芯片内的稳压电路,管脚VSUP应连接到DVDD 或AVDD 中电压较高的一个管脚上。管脚VBG 上不需要外接电容,管脚VFB 应接地,管脚BASE 为无连接。时钟选择如果将管脚 XI 接地,HX711 将自动选择使用内部时钟振荡器,并自动关闭外部时钟输入和晶振的相关电路。这种情况下,典型输出数据速率为10Hz 或80Hz。如果需要准确的输出数据速率,可将外部输入时钟通过一个20pF 的隔直电容连接到XI管脚上,或将晶振连接到XI 和XO 管脚上。这种情况下,芯片内的时钟振荡器电路会自动关闭,晶振时钟或外部输入时钟电路被采用。此时,若晶振频率为11.0592MHz, 输出数据速率为准确的10Hz 或80Hz。输出数据速率与晶振频率以上述关系按比例增加或减少。使用外部输入时钟时,外部时钟信号不一定需要为方波。可将MCU 芯片的晶振输出管脚上的时钟信号通过20pF 的隔直电容连接到XI管脚上,作为外部时钟输入。外部时钟输入信号的幅值可低至150mV。
串口通讯
串口通讯线由管脚PD_SCK 和DOUT 组成,用来输出数据,选择输入通道和增益。当数据输出管脚DOUT 为高电平时,表明A/D 转换器还未准备好输出数据,此时串口时钟输入信号PD_SCK 应为低电平。当DOUT 从高电平变低电平后,PD_SCK 应输入25 至27 个不等的时钟脉冲(图二)。其中第一个时钟脉冲的上升沿将读出输出24 位数据的最高位(MSB),直至第24 个时钟脉冲完成,24 位输出数据从最高位至最低位逐位输出完成。第25至27 个时钟脉冲用来选择下一次A/D 转换的输入通道和增益,参见表三。
PD_SCK 脉冲数输入通道 增益
时序图
PD_SCK 的输入时钟脉冲数不应少于25 或多于27,否则会造成串口通讯错误。当A/D 转换器的输入通道或增益改变时,A/D 转换器需要4 个数据输出周期才能稳定。DOUT 在4 个数据输出周期后才会从高电平变低电平,输出有效数据。
==============================我也是分割线================================
好了,看了这么多原理很多人一定已经晕了,下面来简单的:HX711在Arduino上的使用!
经过各种搜索,仅仅在Google Project上找到了一个 phk@FreeBSD.ORG 写的 Arduino 库(感谢啊!),功能很完善,而且支持多版本的Arduino。
备份在这里吧,以免以后找不到:
hx711-arduino-v0.01.tar.gz(1.3 KB, 下载次数: 4528)
再来看一下接线:
1. VCC 可以是 2.6-5.5 中的任意值,因为我们使用的是 Arduino ,所以直接5V供电,GND 接地。
2. SCK 接 Arduino 的 Pin 9,DT 接 Pin10,这两个接脚可以在程序中改变。
3. E+、E-、A+ 和 A- 分别接桥式传感器的:激励电压正、负,输出电压正、负
(E+ 接红线;E- 接黑线;A+ 接绿或蓝线;A- 接白线)。
4. B+ 和 B- 接通道B的传感器,也可以通过分压电路接电源,用来检测电源电压。不用的话最好接GND,不过我试验不接也没问题。
在 Arduino 中打开示例代码,可以看到非常简单:
<div id="code_E4G"><ol><li>#include <HX711.h> // 包含库的头文件</li><li> </li><li>HX711 hx(9, 10); // 数据接脚定义 </li><li> </li><li>void setup() { </li><li> Serial.begin(9600); </li><li>} </li><li> </li><li>void loop() </li><li>{ </li><li> double sum = 0; // 为了减小误差,一次取出10个值后求平均值。 </li><li> for (int i = 0; i < 10; i++) // 循环的越多精度越高,当然耗费的时间也越多 </li><li> sum += hx.read(); // 累加 </li><li> Serial.println(sum/10); // 求平均值进行均差 </li><li>}</li></ol>
复制代码<div id="code_wjf"><ol><li>HX711(byte sck, byte dout, byte amp = 128, double co = 1); // 定义 sck、dout 接脚,增益倍数(默认128)和修正系数(默认1)</li><li>void set_amp(byte amp); // 改变增益倍数和对应的通道,至少调用一次 read() 后起作用 </li><li>bool is_ready(); // 返回 hx711 是否可用,在 read() 函数中会被调用 </li><li>long read(); // 返回传感器电压值,如果 hx711 不可用则程序会暂停在此函数 </li><li>double bias_read(); // 返回:(read() - 偏移值) * 修正系数 </li><li>void tare(int t = 10); // 将皮重添加到偏移值,影响每次 read(); 的调用 </li><li>void set_co(double co = 1); // 修改修正系数(默认为1) </li><li>void set_offset(long offset = 0); // 修改偏移值(默认为0)</li></ol>
复制代码这里唯一需要解释的是第一个函数,
<div id="code_G9M"><ol><li>HX711 hx(9, 10); // 这样用说明只定义SCK和DOUT接脚,AMP默认使用A通道的128位增益,修正系数默认为1;</li><li>HX711 hx(9, 10, 64); // 这样用说明定义SCK和DOUT接脚,AMP使用A通道的64增益,修正系数默认为1; </li><li>HX711 hx(9, 10, 32, 1.4); // 这样用说明定义SCK和DOUT接脚,AMP使用B通道的32位增益,修正系数为1.4; </li><li> </li></ol>
复制代码再强调一句,如果增益倍数选择32位增益,那么读出的数据就是B通道的。
==================================================
下面写一个具体应用示例:
我选择的传感器参数如下:
满量程输出电压=激励电压x灵敏度1.0mV/V
例如:供电电压是5V乘以灵敏度1.0mV/V=满量程5mV
实际上我选用的这个模块,当电源电压是5V时,供给传感器的供电电压是4V,于是我的传感器满量程电压为4mV。
这样我就完全可以选择增益倍数最高的A通道128位增益来得到最高的精度。
电子称连接图示:
图中绿色的为HX711模块,右下角为Arduino UNO,吊臂上挂着的就是桥式传感器(我连接的方式为悬吊式测拉力值),AD模块与传感器间的电线越短越好,过长的话会受到各种干扰,AD模块与Arduino之间的连线最好也不要超过30cm,如果必须加长的话,可以考虑使用带电磁屏蔽的线以及信号放大器。
首先使用库自带的例子测试一下,可以看到悬挂上1kg砝码以及托盘等部件后得到的值为:
<div id="code_chZ"><ol><li>1315588.75</li><li>1315597.75 </li><li>1315607.37 </li><li>1315606.75 </li><li>1315604.75 </li><li>1315589.62 </li><li>1315579.62 </li><li>1315594.25 </li><li>1315588.75 </li><li>1315580.25 </li><li>1315589.00 </li><li>1315584.25 </li><li>1315605.50 </li><li>1315596.12 </li><li>1315592.25 </li><li>1315607.75 </li><li>1315585.75 </li><li>1315582.87 </li><li>1315568.75 </li><li>1315574.75 </li><li>1315578.00 </li><li>1315583.12 </li><li>1315604.62 </li><li>1315573.50 </li><li>1315584.25 </li><li> </li></ol>
复制代码<div id="code_Eoh"><ol><li>742865.50</li><li>742847.87 </li><li>742869.00 </li><li>742879.12 </li><li>742873.68 </li><li>742858.81 </li><li>742867.81 </li><li>742843.37 </li><li>742862.18 </li><li>742844.87 </li><li>742856.50 </li><li>742834.31 </li><li>742831.31 </li><li>742825.12 </li><li>742842.31 </li><li>742821.31 </li><li>742816.12 </li><li>742846.00 </li><li>742844.00 </li><li>742826.87 </li><li>742818.12 </li><li>742812.87 </li><li>742807.18 </li><li>742835.00 </li><li> </li></ol>
复制代码1315500 - 742800 = 572700
所以修正系数大概为: 500 / 572700 = 0.00087305
那么程序就可以写成(这时可以适当的减小些读取速度,增加个delay,比如让程序半分钟一读取):
<div id="code_rAc"><ol><li></li><li>#include <HX711.h> </li><li>HX711 hx(9, 10, 128, 0.00087305); </li><li>void setup() { </li><li> Serial.begin(9600); </li><li>} </li><li>void loop() { </li><li> delay(500); </li><li> double sum = 0; </li><li> for (int i = 0; i < 10; i++) </li><li> sum += hx.read(); </li><li> Serial.println(sum/10); </li><li>} </li><li> </li></ol>
复制代码<div id="code_O66"><ol><li>169640.70</li><li>169632.59 </li><li>169632.90 </li><li>169640.90 </li><li>169632.79 </li><li>169623.59 </li><li>169649.79 </li><li>169610.40 </li><li>169615.20 </li><li>169659.40 </li><li>169619.90 </li><li>169624.79 </li><li>169614.79 </li><li>169624.29 </li><li>169617.79 </li><li>169629.20 </li><li>169604.00 </li><li>169617.50 </li><li>169628.20 </li><li>169589.40 </li><li>169602.29 </li><li>169598.40 </li><li>169596.40 </li><li>169604.00 </li><li>169612.40 </li><li>169592.20 </li><li> </li></ol>
复制代码<div id="code_GoK"><ol><li>#include <HX711.h></li><li>HX711 hx(9, 10, 128, 0.00087305); </li><li>void setup() { </li><li> Serial.begin(9600); </li><li> hx.set_offset(169600); </li><li> </li><li>} </li><li>void loop() { </li><li> delay(500); </li><li> double sum0 = 0; </li><li> double sum1 = 0; </li><li> for (int i = 0; i < 10; i++) { </li><li> sum0 += hx.read(); </li><li> sum1 += hx.bias_read(); </li><li> } </li><li> Serial.print(sum0/10); </li><li> Serial.print(" "); </li><li> Serial.println(sum1/10); </li><li>}</li></ol>
复制代码<div id="code_d2Y"><ol><li></li><li>169615.20 0.01 </li><li>169616.70 0.02 </li><li>169613.40 0.02 </li><li>169636.50 0.03 </li><li>169620.70 0.02 </li><li>169638.09 0.04 </li><li>169625.29 0.02 </li><li>169620.50 0.02 </li><li>169612.29 0.01 </li><li>169609.79 0.01 </li><li>169624.40 0.02 </li><li>169625.20 0.02 </li><li>169630.00 0.03 </li><li>169628.29 0.03 </li><li>169606.29 0.00 </li><li>169617.79 0.02 </li><li>169637.90 0.03 </li><li>169603.70 0.00 </li><li>169605.29 0.01 </li><li>169636.50 0.03 </li><li> </li><li> </li></ol>
复制代码<div id="code_WWW"><ol><li></li><li>742492.68 500.16 </li><li>742499.81 500.17 </li><li>742503.81 500.17 </li><li>742505.18 500.18 </li><li>742507.18 500.17 </li><li>742499.31 500.17 </li><li>742499.50 500.17 </li><li>742512.37 500.18 </li><li>742521.87 500.19 </li><li>742497.87 500.17 </li><li>742518.12 500.18 </li><li>742518.81 500.18 </li><li>742520.87 500.19 </li><li>742520.68 500.19 </li><li>742530.18 500.20 </li><li>742532.87 500.20 </li><li>742543.00 500.20 </li><li>742540.12 500.21 </li><li>742517.00 500.19 </li><li> </li></ol>
复制代码<div id="code_aMc"><ol><li>1315286.75 1000.25</li><li>1315304.62 1000.25 </li><li>1315296.62 1000.25 </li><li>1315302.25 1000.26 </li><li>1315302.12 1000.26 </li><li>1315294.25 1000.25 </li><li>1315310.00 1000.26 </li><li>1315277.37 1000.24 </li><li>1315283.75 1000.24 </li><li>1315280.25 1000.23 </li><li>1315286.75 1000.24 </li><li>1315293.25 1000.25 </li><li>1315309.87 1000.26 </li><li>1315299.50 1000.25 </li><li>1315307.12 1000.26 </li><li>1315304.50 1000.25 </li><li>1315301.50 1000.25 </li><li>1315296.75 1000.25 </li><li>1315284.50 1000.23 </li><li>1315284.37 1000.24 </li><li>1315308.75 1000.26 </li><li>1315291.62 1000.25 </li><li>1315312.75 1000.26 </li><li>1315312.25 1000.26 </li><li> </li></ol>
复制代码接下来就是完善程序,增加去皮重的功能了,在Arduino上接一个按钮,为了阻止电磁干扰发生误判断,我采用了常输出高电平的按钮,当按下按钮的时候输出低电平,按钮接在 4 号口上:
<div id="code_J0p"><ol><li>#include <HX711.h></li><li>HX711 hx(9, 10, 128, 0.00087305); </li><li>void setup() { </li><li> Serial.begin(9600); </li><li> hx.set_offset(169600); </li><li>} </li><li>void loop() { </li><li> if(digitalRead(4) == LOW) hx.tare(); </li><li> double sum0 = 0; </li><li> double sum1 = 0; </li><li> for (int i = 0; i < 10; i++) { </li><li> sum0 += hx.read(); </li><li> sum1 += hx.bias_read(); </li><li> } </li><li> Serial.print(sum0/10); </li><li> Serial.print(" "); </li><li> Serial.println(sum1/10); </li><li>}</li></ol>
复制代码=========================================================
去皮重也实现了,可是最求完美的我发现每次掉电后都要重新设置皮重,很是麻烦,于是决定将皮重信息存到EEPROM中保存,这样每次上电后就会自动读取存储的皮重信息,从0点开始称量了!
看了一下 HX711 的库,在去皮重的时候只需调用 hx.tare();,tare() 函数内容如下:
<div id="code_pVV"><ol><li>void HX711::tare(int t) {</li><li> double sum = 0; </li><li> for (int i = 0; i < t; i++) { </li><li> sum += read(); </li><li> } </li><li> set_offset(sum / t); </li><li>}</li></ol>
复制代码<div id="code_D4C"><ol><li>void HX711::set_offset(long offset) {</li><li> OFFSET = offset; </li><li>}</li></ol>
复制代码<div id="code_T24"><ol><li>double HX711::bias_read() {</li><li> return (read() - OFFSET) * COEFFICIENT; </li><li>}</li></ol>
复制代码不幸的是,HX711库并不允许我们这么做。也许有人要说了,那就调用 tare() 后 再调用一次 read() 来获取 OFFSET呗。这样可不行,首先调用 tare() 的时候本身就会调用 10 次 read() 并求平均值作为 OFFSET 来使用,我们再调用一次 read() 读出的数既不是之前那个平均值,精度也不如前面的平均值高,就算再求一次 10个 数的平均值,也得不到之前那个 OFFSET了,精度会大打折扣的。
看来唯一的办法就是自己手动改一下 HX711 的库,让它在调用 tare() 的时候直接返回个 OFFSET,这样就解决了我们的需求。为了和官方库区分,我们改一下库的名字,将 HX711 库文件夹复制一份改名为 HX711A ,然后将 HX711.cpp 改为 HX711A.cpp,同样 HX711.h 改为 HX711A.h。然后打开 HX711A.cpp 和 HX711A.h,作出修改。改动如下:
HX711A.cpp:
<div id="code_zsN"><ol><li>#include <HX711.h> -> #include <HX711A.h></li><li>void HX711::tare(int t) { - > double HX711::tare(int t) { </li><li>并在 tare 函数结尾加入: return sum / t; </li><li> </li></ol>
复制代码<div id="code_j9i"><ol><li></li><li>#ifndef HX711_H -> #ifndef HX711A_H </li><li>#define HX711_H -> #define HX711A_H </li><li>void tare(int t = 10); -> double tare(int t = 10); </li><li> </li></ol>
复制代码可是新的问题又出现了,OFFSET 值是 double 类型的,而我们 Arduino 提供的 EEPROM 库一次仅能存储1个 char 类型数据。于是上网查资料,发现弘版有个帖子提到了多类型存储,可是相应的库编译后有点庞大,还是自己解决吧,写了个共用体实现的double类型存储,见我的帖子:http://www.geek-workshop.com/thread-2323-1-1.html,可以在程序里加入这一部分内容。
另外,经过查询资料,桥式传感器受温度影响的偏移量也不容忽视,可以在系统中加入温度传感器(例如DS18B20),并在计算重量的时候加入线性温度漂移修正,这里我就不写温度相关代码了,仅给出温度漂移修正的函数供大家参考:
<div id="code_TBd"><ol><li>#include <HX711A.h></li><li>#include <EEPROM.h> </li><li>HX711 hx(9, 10, 128, 0.00087305); </li><li>int i=0; </li><li> </li><li>// 用于在 EEPROM 中储存 double 类型数据的共用体 </li><li>unsigned char *dpointer; </li><li>union data { </li><li> double v; </li><li> unsigned char dchar[8]; </li><li>} dvalue; </li><li> </li><li>void setup() { </li><li> Serial.begin(9600); </li><li> for(i = 0; i < 8; i++) dvalue.dchar[i] = EEPROM.read(i); // 从 EEPROM 读取偏移量 </li><li> hx.set_offset(dvalue.v); // 设置已读取的偏移量 </li><li>} </li><li>void loop() { </li><li> if(digitalRead(4) == LOW) { </li><li> dvalue.v = hx.tare(); // 去皮重并读取偏移量 </li><li> hx.set_offset(dvalue.v); // 设置已读取的偏移量 </li><li> dpointer = dvalue.dchar; // 以下程序将偏移量分解并储存到 EEPROM </li><li> for(i = 0; i < 8; i++) { </li><li> EEPROM.write(i,*dpointer); </li><li> dpointer++; </li><li> } </li><li> } </li><li> double sum = 0; </li><li> for (i = 0; i < 10; i++) { </li><li> sum += hx.bias_read(); </li><li> } </li><li> Serial.println(sum/10); </li><li> //Serial.println(sum / 10 * (1 + (20 - temprature) / 10 * 0.02)); // 带线性温度补偿的输出,注意temprature单位为摄氏度。 </li><li>} </li><li> </li></ol>
复制代码此外,程序可以增加的功能还有:
1.LCD显示
2.键盘输入及语音功能
3.标准砝码校准功能(如500g),其实就是修正系数的自我修正功能。
这几个功能我就不再研究了,都不难,留给各位自己发挥的空间吧
全文完
By shenhaiyu
2012.10.29
作者:shenhaiyu 来源:极客工坊