xjw01 发表于 2011-7-22 20:43:48

LTC2400制作500万字表头实验

LTC2400芯片,我试验了。
经过连续6天的测试,非线性的变化稳稳的保持在10ppm左右,总偏移小于1ppm,这几天的总温差约5至10度。调试电路时,经常焊接电路板,电路板反反复复的升温降温几十度,也未见因此而发生芯片老化。也就是说,经过非线性改正后,非线性误差小于1ppm。更有趣的是,它的非线性误差接于抛物线,所以只需做抛物线插值,就可以使非线误差减小到1ppm

LTC2400六位表头设计安装调试

    该电路制作之后,已连续上机测试6天,稳定性良好。于是,今天把非线性改正程序及菜单功能全部加进去,使之变成完整的表头。
    1、LTC2400的电源采用5V独立供电。数据手册给的测试电路,电源与基准共用,我上机实测效果不好。共电方案,造成自检值偏小数百个字。共电时,滤波电容越大,自检值越大,使用0.4uF比0.3uF,会增加100字左右,但还是很难达到5000,000字。当电路对电容敏感,电路稳定性稍差。所以本电路采用78L05对LTC2400独立供电,自检值接近5000,000字。所谓“自检”指的是将表笔输入端接到LTC2400的Vref端。

    2、78L05接了一个1.5k的负载,其作用是:当LTC2400输入电压过高,输入电流通过内部的输入限幅二极管倒灌进78L05,会靠成LTC2400供电电压抬升。接了1.5k负载,以防输入电压抬升过多。

    3、OP07运放输出加了一个2k限流的电阻,防止对LTC2400输入过强电流、电压。

    4、缓冲设计:LTC2400的Vin端输入阻抗很低,而且是非线性的,这是内部开关电容造成的。当输入信号为0V时Vin会有平均电流输出,约0.5uA至1uA,当输入为2.5Vref时,平均电流最小,当输入为等于Vref时Vin会有0.5uA至1uA平均电流流入。这种非稳恒的平均电流,在外部输入电阻上形成压降,会造成严重非线性的误差。为此,本电路采用OP07进行缓冲放大,得到低频段极低输出电阻,驱动LTC2400,消除输入非线性。

    5、LTC2400的信号输入端的0.22u电容的作用:LTC2400内部开关电容,会在输入端形成脉冲电压、电流,频率很高。加入这个电容后,输入端的脉冲电压基本消失,确保内部输入电容得到快速充电,提高AD的稳定性。由于OP07在高频段,输出电阻很大,因此加入了这个0.22u滤波电容后,在全频段内,得到了低阻抗缓冲输出,缓冲器的增益几乎为1,不易受其它不确定因素影响。该电容还可以减小高频干扰。

    6、与LTC2400连接的电容,使用CBB,有的使用独石,以得到稳定的容量、较低的噪声。试验过程中,曾更换为耐高温的高容量的电脑主板上用的贴片电容,噪声非常大,AD转换非常不稳定,这种电容的温漂非常巨大,决不可用于这个AD转换电路。

    7、7905输出接了一个2k的电阻:op07的工作电流很小,7905不能正常工作,所以接了一个负载。如果使用79L05,估计不接上电阻也能工作。

    8、调零后的自检值,应在5000,000字左右,正负误差几十字不要紧。如果小了几百字,可能是电容失效,或不能工作于高频。如果大了几百字,可能存在严重的电容噪声。

    9、菜单使用方法:
1)K1键,切换换菜单,每按一次,会在菜单0、1、2、3、4、5之间切换。
2)菜单0是默认菜单,显示已进行非线性矫正的AD转换结果。在菜单0下,按下K2键,显示未矫正的AD转换结果(将自动在最后一位加上一个小数点,以示区别)。再次按K2,则显示已矫正的结果。
3)菜单1设置零点偏移字数;菜单2设置中点非线性误差;
4)菜单3设置满量程字数的低4位;菜单4设置满量程字数的高3位
5)菜单5,设置滤波器步长。如:设置为8,约为8点平均。如果被置为负数并保存,下次开机时所有参数将被复位
6)在菜单1至5中,K2是光标移动键,K3是保存键,K4是更改键。光标移动到第6位,更改正负号。第7位显示菜单号。


    10、零点偏移的测量:将表笔短路得到的读数V0,并把V0保存到菜单1

    11、中点非线性误差的测定:
    在被测基准中,有4个分压电阻。R1、R2看作下臂电阻,R3、R4看作上臂电阻。设下臂电阻的压降为V1,上臂电阻的压降为V2,总电压为Va
如果表头线性度理想,存在关系式Va = V1+V2,如果不理想,中点非线性误差为a = (V1+V2-Va)/2
测定非线性误差a时,把表头置为无矫正模式(按下K2键)。
无矫正模式,是未调零的,所以测量Va、V1、V2时,应做零点偏移改正。设零点为读数为V0,那么就有 a = (V1+V2+V0-Va)/2。将夹子接在“0分压”与“2分压”得到V1,将夹子接在“2分压”与“4分压”得到V2,将夹子接在“0分压”与“4分压”得到Va。
由于OP07的输入电流不可忽略,所以也须改正。OP07的输入电流比较容易测得,分别测量“分压4”端子和“内阻测量端子”的电压,得一两个电压的差值,再把差值除以100,就得到了OP07的输入电流,单位是nA。若测得的输入电流是I纳安,那么所需的改正值是Vr = (1.5+1-0.5)*I = 2I,比如,测得I=3.5nA,那么Vr = 2*3.5 = 7
最后,a = (V1+V2-Va+V0+Vr)/2,然后把a值保存到菜单2即可。
如果觉得OP07输入阻抗低,可以试试OP177

    12、测试注意事项
    1)预热20分钟,测量期间,尽量减小空气流动或电路板移动,以免造成温度不稳定。OP07需要较长时间预热才会稳定下来。
    2)夹子不能用手切换,而应使用镊子操作。5个排针及2个夹子全应等温度才行。否则会产生几个uV的热电势,影响测量精度。
    3)在菜单设置了零点偏移参数,可以大大减小偏移,读取电压时,通常无需减去零点值。如果要求测量特别精确,最好将表笔短路,测出零点偏移量,然后在测量结果中减去该偏移值。







/*************************************
6位半LTC2400驱动程序
xjw01 于莆田 2011.7
**************************************/
//====================================
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
#include <reg52.h>
#include <math.h>

void delay(uint loop) { uint i; for(i=0;i<loop;i++); } //延时函数
//void delay2(uint k){ for(;k>0;k--) delay(10000); } //长延时,k=100大约对应1秒


//============================EEPROW偏程=========================
sfr IAP_data = 0xC2;
sfr IAP_addrH = 0xC3;
sfr IAP_addrL = 0xC4;
sfr IAP_cmd = 0xC5;
sfr IAP_trig = 0xC6;
sfr IAP_contr = 0xC7;
/********************
写字节时,可以将原有数据中的1改为0,无法将0改为1,只能使用擦除命令将0改为1
应注意,擦除命令会将整个扇区擦除
*********************/
uchar readEEP(uint k){ //读取
IAP_addrL = k; //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x81; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 1; //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A; //先送5A
IAP_trig = 0xA5; //先送5A再送A5立即触发
return IAP_data;
}
void writeEEP(uint k, uchar da){ //写入
IAP_data = da; //传入数据
IAP_addrL = k; //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x81; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 2; //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A; //先送5A
IAP_trig = 0xA5; //先送5A再送A5立即触发
}
void eraseEEP(uint k){ //擦除
IAP_addrL = k; //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x81; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 3; //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A; //先送5A
IAP_trig = 0xA5; //先送5A再送A5立即触发
}

xdata struct Ida{
int xz0; //零点偏移
int xz1; //中点偏移
int FC0; //满程低4位
int FC1; //满程高4位
int en;
} cs;

void cs_RW(char rw){
uchar i,*p = &cs;
if(rw){
eraseEEP(0);
for(i=0;i<sizeof(cs);i++) writeEEP(i,p);
}else{
for(i=0;i<sizeof(cs);i++) p=readEEP(i);
}
}



/**********
字形编码图
32
-
64| | 128
- 16
1| | 8
_. 4
2
**********/
uchar code zk={235,136,179,186,216,122,123,168,251,250}; //字库

uchar disp={235,136,179,186,216,122,123};
sfr P1M1=0x91; //P1端口设置寄存器
sfr P1M0=0x92; //P1端口设置寄存器
sfr P0M1=0x93; //P0端口设置寄存器
sfr P0M0=0x94; //P0端口设置寄存器
sfr P2M1=0x95; //P2端口设置寄存器
sfr P2M0=0x96; //P2端口设置寄存器
sfr P3M1=0xB1; //P3端口设置寄存器
sfr P3M0=0xB2; //P3端口设置寄存器

sbit ds0=P2^1; //数码管扫描口
sbit ds1=P2^2; //数码管扫描口
sbit ds2=P2^3; //数码管扫描口
sbit ds3=P2^4; //数码管扫描口
sbit ds4=P2^5; //数码管扫描口
sbit ds5=P2^6; //数码管扫描口
sbit ds6=P2^7; //数码管扫描口

sbit K0=P3^4; //键盘
sbit K1=P3^5; //键盘
sbit K2=P3^6; //键盘
sbit K3=P3^7; //键盘




//功能程序开始


void cls(){ char i; for(i=0;i<7;i++) disp=0; } //清屏
void showDig(long f){ //显示数字
uchar i;
cls();
for(i=0;i<7;i++) { disp=zk, f/=10; if(!f) break; }
}


sbit P_SCK=P1^0; //时钟
sbit P_SDO=P1^1; //数据
sbit P_CS =P1^2; //片选

long pv=0; //一阶滤波的AD转换均值

long jz(long v){ //非线性改正
float n;
v -= cs.xz0;
n = v/1.0/(cs.FC0+cs.FC1*10000.0);
if(n>0&&n<1) v -= cs.xz1*n*(1-n)*4;
return v;
}
void get_adc(char zz){
char i; long v=0; //v是AD转换结果
if(P_SDO) return; //检测转换状态
for(i=0;i<32;i++){ //读取串行数据
P_SCK = 1; delay(1);
v <<= 1;
if(P_SDO) v++;
P_SCK = 0; delay(1);
}
v -= 0x20000000; //处理符号位
v = (cs.FC0+cs.FC1*10000.0)*v/0x10000000; //尺长变换

if(cs.en>0 && cs.en<=20){ //滤波
if(labs(v-pv/cs.en)>50) pv = v*cs.en; //限幅
else pv = v + pv - pv/cs.en; //一阶滤波
v = pv/cs.en; //取平均值
}

if(!zz) v = jz(v); //非线性改正
showDig(labs(v)); //显示
if(v<0) disp += 16; //显示负号
if(zz) disp+=4; //不矫正
}


void zd0(void) interrupt 0 {//int0中断(下降沿)
}



void timerInter(void) interrupt 1 {//T0中断
}

int inc_cs(int a,char d){ //a的d位加1
char i,f=1;
int v=10;
if(d<0) return a;
if(d==5) return -a;
if(a<0) a=-a,f=-1;
for(i=0;i<d;i++) v*=10;
if(a%v+v/10 < v) return f*(a+v/10);
else return f*(a+v/10-v);
}

main(){
uchar dispN=0; char nx=0; //显示扫描索引
uchar menu=0,gb=-1,kn=0,K,zz=0;
int *p;

TCON=0, TMOD=0x09; //将T0置为16位内部计数,并由外部启动计数。
IT0=1; //使int0下降沿中断有效。
TH0=0, TL0=0;
TR0=1; //T0启动计数
EX0=1; //开int0外部中断
ET0=1; //T0开中断
EA=1; //开总中断
PT0=1; //中断优先


P2M0 = 0xFE; //P2.1234567置为推勉输出
//P1M0 = 0x05; //P1.02置为推勉输出
//P1M1 = 0x02; //P1.1置为高阻抗
//P3M0 = 0x0C; //P3.23置为推勉输出口

delay(4000);
P_CS=1; delay(1);
P_SCK=0;delay(1);
P_CS=0;

cs_RW(0);
if(cs.en<0){ cs.xz0=0, cs.xz1=0, cs.FC0=0, cs.FC1=500, cs.en=8; cs_RW(1); }
while(1){
//显示disp
dispN = (++dispN)%7; //扫描器移动
nx++; if(nx>100)nx-=200;
ds0=ds1=ds2=ds3=ds4=ds5=ds6=0;
if(dispN==0) ds0=1;
if(dispN==1) ds1=1;
if(dispN==2) ds2=1;
if(dispN==3) ds3=1;
if(dispN==4) ds4=1;
if(dispN==5) ds5=1;
if(dispN==6) ds6=1;
if(dispN==gb && nx>0) P0 = 255; //不显示
else P0 = ~disp; //显示
K = ( ~(P3>>4) ) & 15;
if(K) { if(kn<255) kn++; } else kn = 0; //判断是否有按键按下
if(kn!=20) K=0; //按下时间不够长,键值无产
if(K==1) { menu++; if(menu>5) menu=0,gb=-1; else gb=0; } //切换菜单
if(menu==0) { //读取AD电压
if(K==2) zz=(++zz)%2; //设置是否矫正
get_adc(zz);
}
if(menu>=1&&menu<=5){
if(menu==1) p = &cs.xz0;
if(menu==2) p = &cs.xz1;
if(menu==3) p = &cs.FC0;
if(menu==4) p = &cs.FC1;
if(menu==5) p = &cs.en;
if(K==2) { gb++; if(gb>5) gb=0; } //光标键
if(K==4) cs_RW(1); //保存
if(K==8) *p = inc_cs(*p,gb); //改值
showDig( abs(*p) );
if(*p<0) disp=16; //显示负号
disp = zk; //显示菜单号
disp+=4; //用小数点表示光标
}
delay(4000);
}//while end
}

xjw01 发表于 2011-7-22 20:46:51

非线性误差监测记录(程序矫正前)                              
时间    总电压    下臂电压    上臂电压    短路电压    内压降    中点误差    相对误差(ppm)    备注
7.15日07点    4998973    2497456    2501362    -41    3.7    -53.3    -10.7   电路1
7.15日12点    4998921    2497438    2501331    -40    3.7    -52.3    -10.5   电路1
7.15日13点    4998890    2497424    2501317    -44    3.4    -49.1    -9.8   电路1
7.15日14点    4998878    2497416    2501305    -43    3.4    -53.6    -10.7   电路2
7.15日15点    4998878    2497420    2501306    -40    3.4    -52.6    -10.5   电路2
7.15日16点    4998875    2497417    2501309    -41    3.4    -50.6    -10.1   电路2
7.15日19点    4998855    2497412    2501294    -41    3.4    -50.6    -10.1   电路2
7.15日21点    4998850    2497408    2501292    -40    3.4    -51.6    -10.3   电路2
7.16日06点    4998970    2497455    2501364    -41    3.5    -51.5    -10.3   电路2
7.16日07点    4998950    2497450    2501350    -41    3.5    -51    -10.2   电路2
7.16日08点    4998912    2497437    2501328    -41    3.5    -49.5    -9.9   电路2
7.16日12点    4998873    2497418    2501305    -41    3.5    -51    -10.2   电路2
7.16日14点    4998855    2497412    2501294    -41    3.5    -50.5    -10.1   电路2
7.16日15点    4998850    2497408    2501294    -41    3.5    -50    -10.0   电路2
7.16日17点    4998844    2497406    2501290    -41    3.5    -50    -10.0   电路2
7.16日20点    4998833    2497400    2501282    -41    3.5    -51.5    -10.3   电路2
7.16日21点    4998828    2497400    2501279    -41    3.5    -50.5    -10.1   电路2
7.19日07点    4999715    2497863    2501705    -40    3.5    -50    -10.0   电路3

说明:                              
表中的内压降指被测电压信号的内阻引起的压降对本次测定影响的字数。                              
对于电路3,使用了“线性度测试基准源(二)”,测上臂电压时内阻1.5k,测下臂时内阻1k,测总电压时内阻0.5k,每纳伏运放偏流引起误差为(1.5+1-0.5)/2=1字                              
7.15日14点,AD电路板上对运放加限流,电路板被烙铁加热后,未完全复恢,所以此刻测值误差偏大一些。                              
7.19日07点,更换被测基准电路板。                              
7.17日、18日,也做了测试,但没有记录在Excel中,测值与上表差不多                              


非线性误差监测记录(程序矫正后)                              
时间    总电压    下臂电压    上臂电压    短路电压    内压降    中点误差    相对误差(ppm)    备注
7.19日16点    4999654    2497904    2501743    0    3.5    0    0.0   电路3
7.19日17点    4999667    2497910    2501750    0    3.5    0    0.0   电路3
7.19日20点    4999607    2497885    2501713    -2    3.5    0    0.0   电路3
                              
说明:                              
7.19日16点,具体时间没有记录,孩子吵闹,没有及时记录时间                              
修正后,三次测量均得到0ppm误差,不排除偶然因素。                              
                              
                              
下臂非线性误差监测记录(程序矫正后)                              
时间    总电压    下臂电压    上臂电压    短路电压    内压降    中点误差    相对误差(ppm)    备注
7.19日19点    2497883    1251452    1246431    -2    0.9    1.9    0.8   电路3
7.19日20点    2497881    1251449    1246429    -2    0.9    0.4    0.2   电路3
测下臂时,相对于总量程,非线性误差减半,即0.4ppm和0.1ppm

xjw01 发表于 2011-7-22 21:05:58

开机一定要预热10分钟以上再进行各项测量。
因为OP07需要遇热才能工作。数据手册上要求顶热数分钟。为了得到更可靠的数据,最好多预热几分钟。
如果在调试过程中,对OP07高温烫过,OP07会老化,这里须要重新设置调零参数。
OP07的输入电流偏大,建议采用低失调、低温漂且低输入电流的运放。但价格不再是0.9元一个了。

xjw01 发表于 2011-7-22 21:13:32

如果使用电池作为被测信号源,要注意几点:
不可对电池充电或放电,如果测量期间,发量电池电压按1uV每秒不断上升,肯定是之前对它时行了微安级甚至毫安级充放电。
上表头上电之前,不得接入电池,否则就会造成电池充或放电。这样,电池需要经过1小时以上才能恢复电压。
要象标准电池那样保护电池才行。

不要用TL431等稳压管做为被测基准电压。稳定性不行。

xplore 发表于 2011-7-22 21:32:27

优秀文章,支持!n神马都是浮云

pipelie 发表于 2011-7-22 21:34:31

梦幻数码 发表于 2011-7-22 22:36:30

好文章

yjm2000 发表于 2011-7-22 22:47:23

又一台DIY的6位半电压表?

lilith 发表于 2011-7-22 23:04:44

引用第7楼yjm2000于2011-07-2222:47发表的:
又一台DIY的6位半电压表? images/back.gif


哦hohohoho 以后 6.5 位表头要烂大街了

裕作 发表于 2011-7-22 23:19:54

给点实物图学习学习!

xjw01 发表于 2011-7-23 07:42:51

高位表的市场需求量远远比电子称的需求小。电子称的芯片一大把,高位表少有专用芯片。所以只好用电子称的芯片来DIY表头了。
电子称的测量要求与万用表的要求不太一样。所以用这些芯片来做万用表,估个表头比较合适,要实现更多功能,看来比较困难。
电子称都用现代芯片了,高位表也应发展专用芯片了,不然有点跟不上芯片发展的步伐。

xjw01 发表于 2011-7-23 07:58:36

xjw01 发表于 2011-7-23 08:20:35

hongo 发表于 2011-7-23 09:47:37

引用第13楼xjw01于2011-07-2308:20发表的:


手工刻板很给力!

lylmy 发表于 2011-7-23 10:48:22

强帖留名

大胡子 发表于 2011-7-23 12:48:51

支持DIY

xjw01 发表于 2011-7-23 13:09:26

谢谢大家鼓励。

菜单做了一点修改,已更新。

kangxin 发表于 2011-7-23 13:17:52

高手多啊!

xjw01 发表于 2011-7-23 13:35:36

12、关于最后一字的跳动
在使用软件进行滤波之前,最后一字跳得利害。跳动最大范围大约是7字左右,主要跳动范围约3至5字。因此,如果两次转换结果平均,跳动范围可减小到5字左右,主要跳动范围约2至4字。如果5次平均,最大跳动范围约3字,主要跳动范围约1至2字。8次平均,会降到1至2字。当然,“跳动范围”指的是数秒以内的跳动。10至20秒或更长时间的跳动范围,还要加1字。估计这是半导体器件的极低频噪声造成的。总的说,在一阶滤波器中,参数设置为8,短期只会观察到1至2字的跳动,即±1字的跳动。如果明显超过±1字(相对于偶然超过而言)的跳动,有可能是干扰或元件质量问题。
通过更改菜单5中的滤波参数,改变平均的次数。

gaopeng31 发表于 2011-7-24 00:47:26

楼主真给力   六位半 这回表头这回可烂大街了。仪表党这回可万福了

xjw01 发表于 2011-7-24 07:01:41

刚才试了1500万字,发现最后一个字依然不会乱跳,滤波器的DEG设置为18,看来,可以做成7位半的也行,最后一位作为估读位。

xjw01 发表于 2011-7-24 07:02:59

我一直以为,数据手册所说的24bit是假的,今天一试,1600万字下,还真的不会漏码,而且稳定性还行。

src100 发表于 2011-7-24 09:20:39

这下ltc2400要涨价了。

lilith 发表于 2011-7-24 12:23:43

引用第20楼xjw01于2011-07-2407:01发表的:
刚才试了1500万字,发现最后一个字依然不会乱跳,滤波器的DEG设置为18,看来,可以做成7位半的也行,最后一位作为估读位。 images/back.gif


满度 5V 时,5 百万字,灵敏度已达 1uV,基准、ADC 本身和被测信号的噪声都已经很显著了,扩展到 1600 万字,灵敏度可达 300nV,这个实在是有一点勉强把,再说增大系数到这种程度,此时它对信号的微小变化的跟踪能力就变得很糟糕了 我想了一下,还是把它作为 Filter 算法提供在数学运算功能中,通常的输出还是用平滑算法输出吧。

lilith 发表于 2011-7-24 12:25:45

引用第21楼xjw01于2011-07-2407:02发表的:
我一直以为,数据手册所说的24bit是假的,今天一试,1600万字下,还真的不会漏码,而且稳定性还行。
images/back.gif


Ti 的 ADS1282,号称 32 位(达 31 位),0.5ppm 线性,噪声多少了?反正比 2400 还低,现代的∆-Σ 器件已经非常厉害了,不过 1282 不提供样片申请,售价高达 3、4 百元,就不好去尝试了

xjw01 发表于 2011-7-24 17:16:40

我用C51,不支持double,现在想扩展到8位遇到不少麻烦。

xjw01 发表于 2011-7-24 17:19:33

回 23楼(lilith) 的帖子

可以扩展,我采用二阶滤波,效果不错,速度下降不是很多。
主要是没有真双精度,程序不好写。晚上改一下程序再试。

xjw01 发表于 2011-7-24 22:38:39

以下是7位半的程序
不过,7位半测到了100nV,我的电路噪声大,最后一字(第8位)跳得很历害。最后第二字不会跳。
菜单5由两个参数合成,二阶滤波的两个系数,一并放在菜单5,如系数1是5,系数2是6,则应在菜单5中写入605
菜单0中,按下K2,可以在“无矫正”“有矫正7字”“有矫正8字”三种状态下显示。

/*************************************
6位半LTC2400驱动程序
xjw01 于莆田 2011.7
**************************************/
//====================================
#define uchar unsigned char
#define uintunsigned int
#define ulongunsigned long
#include <reg52.h>
#include <math.h>

void delay(uint loop) { uint i; for(i=0;i<loop;i++); } //延时函数
//void delay2(uint k){ for(;k>0;k--) delay(10000); } //长延时,k=100大约对应1秒


//============================EEPROW偏程=========================
sfr IAP_data= 0xC2;
sfr IAP_addrH = 0xC3;
sfr IAP_addrL = 0xC4;
sfr IAP_cmd   = 0xC5;
sfr IAP_trig= 0xC6;
sfr IAP_contr = 0xC7;
/********************
写字节时,可以将原有数据中的1改为0,无法将0改为1,只能使用擦除命令将0改为1
应注意,擦除命令会将整个扇区擦除
*********************/
uchar readEEP(uint k){ //读取
IAP_addrL = k;    //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x80; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 1;      //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A;//先送5A
IAP_trig = 0xA5;//先送5A再送A5立即触发
return IAP_data;
}
void writeEEP(uint k, uchar da){ //写入
IAP_data = da;    //传入数据
IAP_addrL = k;    //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x80; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 2;      //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A;//先送5A
IAP_trig = 0xA5;//先送5A再送A5立即触发
}
void eraseEEP(uint k){ //擦除
IAP_addrL = k;    //设置读取地址的低字节,地址改变才需要设置
IAP_addrH = k>>8; //设置读取地址的高字节,地址改变才需要设置
IAP_contr = 0x80; //设置等待时间,1MHz以下取7,2M以下取6,3M取5,6M取4,12M取3,20M取2,24M取1,30M取0,前导1表示许档IAP
IAP_cmd = 3;      //读取值1,写取2,擦除取3,擦除时按所在字节整个扇区撺除
IAP_trig = 0x5A;//先送5A
IAP_trig = 0xA5;//先送5A再送A5立即触发
}

xdata struct Ida{
int xz0; //零点偏移
int xz1; //中点偏移
int FC0; //满程低4位
int FC1; //满程高4位
int en;
} cs;

void cs_RW(char rw){
uchar i,*p = &cs;
if(rw){
eraseEEP(0);
for(i=0;i<sizeof(cs);i++) writeEEP(i,p);
}else{
for(i=0;i<sizeof(cs);i++) p=readEEP(i);
}
}



/**********
字形编码图
   32
   -
64| | 128
   -16
1| | 8
   _. 4
   2
**********/
uchar code zk={235,136,179,186,216,122,123,168,251,250}; //字库

uchar disp={235,136,179,186,216,122,123};
sfr P1M1=0x91; //P1端口设置寄存器
sfr P1M0=0x92; //P1端口设置寄存器
sfr P0M1=0x93; //P0端口设置寄存器
sfr P0M0=0x94; //P0端口设置寄存器
sfr P2M1=0x95; //P2端口设置寄存器
sfr P2M0=0x96; //P2端口设置寄存器
sfr P3M1=0xB1; //P3端口设置寄存器
sfr P3M0=0xB2; //P3端口设置寄存器

sbit ds0=P2^1; //数码管扫描口
sbit ds1=P2^2; //数码管扫描口
sbit ds2=P2^3; //数码管扫描口
sbit ds3=P2^4; //数码管扫描口
sbit ds4=P2^5; //数码管扫描口
sbit ds5=P2^6; //数码管扫描口
sbit ds6=P2^7; //数码管扫描口

sbit K0=P3^4; //键盘
sbit K1=P3^5; //键盘
sbit K2=P3^6; //键盘
sbit K3=P3^7; //键盘




//功能程序开始


void cls(){ char i; for(i=0;i<7;i++) disp=0; } //清屏
void showDig(long f){ //显示数字
uchar i;
cls();
for(i=0;i<7;i++) { disp=zk, f/=10; if(!f) break; }
}


sbit P_SCK=P1^0; //时钟
sbit P_SDO=P1^1; //数据
sbit P_CS =P1^2; //片选

long pv=0,pv2=0,Um=0; //滤波的积分器
long Ux=0;    //滤波输出
char ms=1;    //数据处理模式
void get_adc(){
//注意:长整形不可直接与float相加,否则损精度,必要时采用强制转换防止损度损失
char i,en,en2;
long v=0,vc,vm; //AD转换结果
float n;      //插值因子
longf = cs.FC0+cs.FC1*10000L; //满量程定标值

if(P_SDO) return;    //检测转换状态
if(ms==2) en = cs.en/100; //取模式2下的滤波参数
for(i=0;i<32;i++){      //读取串行数据
   P_SCK = 1; delay(1);
   v <<= 1;
   if(P_SDO) v++;
   P_SCK = 0; delay(1);
}
v = v/16-0x02000000; //截取24bit,并处理符号位
v*=10, vm = v/10000, vc = v%10000;

en = cs.en%100; if(!en)en=1;//一阶滤波步长
en2= cs.en/100; if(!en2) en2=1; //二阶滤波步长
if(labs(Ux-v)<1000 ){ //限幅二阶滤波
   vc += (vm-Um)*10000L;
   pv += vc - pv/en;
   pv2 += pv - pv2/en2;
}else Um=vm, pv=vc*en, pv2 = pv*en2;
Ux = Um*10000L + pv2/en2/en;

n = 0.1*Ux/0x01000000; if(n<0||n>1) n =0;   //抛物线插值因子
for(i=0,v=0;i<8;i++) v += Ux*(f&7), v>>=3, f>>=3; //尺长变换

if(ms!=0) v -= (int) ( (cs.xz0 + cs.xz1*n*(1-n)*4)*10 );//非线性改正
if(ms!=2) v /= 10; //不扩展字数

showDig(labs(v));      //显示
if(v<0) disp += 16; //显示负号
if(ms==1) disp+=4;//标识已矫正
if(ms==2) disp+=4;//标识已矫正并扩展
}


int inc_cs(int a,char d){ //a的d位加1
char i,f=1;
int v=10;
if(d<0) return a;
if(d==5) return -a;
if(a<0) a=-a,f=-1;
for(i=0;i<d;i++) v*=10;
if(a%v+v/10 < v) return f*(a+v/10);
else             return f*(a+v/10-v);
}

main(){
uchar dispN=0; char nx=0; //显示扫描索引
uchar menu=0,gb=-1,kn=0,K,zz=0;
int *p;


P2M0 = 0xFE;    //P2.1234567置为推勉输出
//P1M0 = 0x05;//P1.02置为推勉输出
//P1M1 = 0x02;//P1.1置为高阻抗
//P3M0 = 0x0C;//P3.23置为推勉输出口

delay(20000);
P_CS=1; delay(1);
P_SCK=0;delay(1);
P_CS=0;

cs_RW(0);
if(cs.en<0){ cs.xz0=0, cs.xz1=0, cs.FC0=0, cs.FC1=500, cs.en=406; cs_RW(1); }
while(1){
//显示disp
dispN = (++dispN)%7; //扫描器移动
nx++; if(nx>100)nx-=200;
ds0=ds1=ds2=ds3=ds4=ds5=ds6=0;
if(dispN==0) ds0=1;
if(dispN==1) ds1=1;
if(dispN==2) ds2=1;
if(dispN==3) ds3=1;
if(dispN==4) ds4=1;
if(dispN==5) ds5=1;
if(dispN==6) ds6=1;
if(dispN==gb && nx>0) P0 = 255; //不显示
else P0 = ~disp; //显示
K = ( ~(P3>>4) ) & 15;
if(K) { if(kn<255) kn++; } else kn = 0; //判断是否有按键按下
if(kn!=20) K=0;//按下时间不够长,键值无产
if(K==1) { menu++; if(menu>5) menu=0,gb=-1; else gb=0; } //切换菜单
if(menu==0) {   //读取AD电压
    if(K==2) ms=(++ms)%3; //设置显示模式
    get_adc();
}
if(menu>=1&&menu<=5){
   if(menu==1) p = &cs.xz0;
   if(menu==2) p = &cs.xz1;
   if(menu==3) p = &cs.FC0;
   if(menu==4) p = &cs.FC1;
   if(menu==5) p = &cs.en;
   if(K==2) { gb++; if(gb>5) gb=0; }//光标键
   if(K==4) cs_RW(1);            //保存
   if(K==8) *p = inc_cs(*p,gb);//改值
   showDig( abs(*p) );
   if(*p<0) disp=16; //显示负号
   disp = zk;//显示菜单号
   disp+=4; //用小数点表示光标
}
delay(4000);
}//while end
}

xjw01 发表于 2011-7-24 22:58:41

回 24楼(lilith) 的帖子

这类芯片很历害。
2400做24bit,做6位半没问题。
你说的那个芯片,31bit,估计做成7位半了。
不过,真要用那种芯片,单片机使用51单片机,力不从心。我为了给2400扩展一位,改那几行程序,调试了一下午才弄好,原因就是他不支持双精度。得用高档一些的单片机。

pu239 发表于 2011-7-25 13:16:00

支持好贴!
页: [1] 2
查看完整版本: LTC2400制作500万字表头实验