搜索
bottom↓
回复: 6

探讨下电压测量的校准方法

[复制链接]

出0入0汤圆

发表于 2011-9-23 22:17:02 | 显示全部楼层 |阅读模式
现在做平衡充电器,需要对电池电压进行测量,而且需要达到一定的精度,废话不多说了,以下是我的一些经验和过程,写下来和大家探讨下,怎样才能做到更加精确的电压测量,只当抛砖引玉了

关于电压测量时的校准方法
在网上找了很久,没有找到一种很好的校准方法,个人摸索一一段时间,写个总结和过程,和大家分享以下,
说明:factor:比例系数,D:mcu AD采样后的数据,offset:偏移值
现在做的项目是平衡充电器,最多6节锂电池,满量程电压是25.2(LiPo),在调试过程中分别采用了一下几种方法:
1、        满量程校准
最简单的校准方法,直接用25.2v的标准来校准
Step1:接上25.2v,MCU采样结果D,factor=(25.2)/D;
Step2:计算其它点电压,Vn=Dn*factor;
此方法校准,在接近25.2时,误差会在-10mv,在9v(6节NiMH),误差会达到-0.1v,所以对于小电压,如1节、2节或3节时,误差会很大,会影响电池的充电效果,所以此方法不行
2、        零点和满量程校准
方法和1差不多,只不过多采集了零点偏置值,具体方法:
Step1:短接输出,读出MCU采样值offset
Step2:输出接入25.2v电压,读出MCU采样值D1
Step3:计算factor
                  factor=(25.2-0)/(D1-D0)
        Step4:计算其它点电压
                  Vn=(Dn-offset)*factor;
   此方法在9v时误差也在-60mv,其它点没有细测,估计也会比较大
3、        2点校准
        Step1:输出接2v,读出MCU采样值D1
        Step2:输出接25.2,读出MCU采样值D2
        Step3:计算factor和offset
                        factor=(25.2-2)/(D2-D1);
offset==D2*factor-2;
        Step4:计算其它点电压
                   Vn=Dn*factor-offset;
        此方法在误差分布:(最大值)
        0~5v        -20mv
        9v                -30mv
        21v                -50mv
        23v                -60mv
        这样的误差还是达不到要求
3、3点校准
        Step1:输出接2v,MCU读出AD值D1
        Step2:输出接12v,MCU读出AD值D2
        Step3:输出接25.2,MCU读出AD值D3
        Step4:计算factor和offset
                        factor1=(12-2)/(D2-D1),offset1=D1*factor-2
factor2=(25.2-12)/(D3-D2),offset2=D2*factor-12
Step5:计算其它点电压
                采样是要判断电压大概在哪一区间内
               
if(v<12v)
                Vn=Dn*factor1-offset1
        else
                Vn=Dn*factor2-offset2
        此方法误差基本在-20mv(最大)以内,但是在(19v~24v)时,误差会达到-50mv,于
        又拿了2块板来测试,验证这种方法是否可行,但发现另外两块办其它点的误差也会很
        大,有的会达到-60mv,在25.2v附近,竟然会达到-40mv,(另2快板现象一样),不知道什么原因,误差竟会相差这么大,于是,开始了反复的查找问题。发现了一个问题,就是第一块板输出短接时,输出的是9(MCU采样值,实际应该是9/10,为了减小误差),另外2快板有个共性,一个是-18,另一个是-20,而且在2v是,第一块板是359,另2分别是330,330
对比如下
                                0                        2
                #1                9                        350
                #2                -18                        330
                #3                -20                        330
        怀疑是零点偏移导致的误差不同,于是想到了第4种方法
4、4点校准(多了个零点)
        Step1:输出短接,MCU读取AD值offset
        Step2:输出接2v,MCU读出AD值D1
        Step3:输出接12v,MCU读出AD值D2
        Step4:输出接25.2,MCU读出AD值D3
        Step5:计算factor和offset
                        factor1=(12-2)/(D2-D1),offset1=(D1-offset)*factor-2
factor2=(25.2-12)/(D3-D2),offset2=(D2-offset)*factor-12
Step6:计算其它点电压
                采样是要判断电压大概在哪一区间内
if(v<12v)
                Vn=(Dn-offset)*factor1-offset1
        else
                Vn=(Dn-offset)*factor2-offset2
        此方法和方法3基本一样,只不过稳定性要好些,对于在(19v~24v),加了些补偿值,

以下附一段程序

校准部分
unsigned char DoCalibrate ( PSTMSG p,unsigned char cal_type)
{
#ifdef        PGM_CALIBRATE
        U8 i=1,j=0;
        double        tmp_vol=0;
        long        tmp_adc_vol=0;
        int        max=-30000,min=30000,adc_result=0;
        if(cal_type==0)
        {
                p->Adc.short_vol_offset=p->Adc.Volt[0];
                EepromWriteBuf(BAT_VOL_OFFSET+6,(unsigned char *)&p->Adc.short_vol_offset,sizeof(p->Adc.short_vol_offset));

                return 0;
        }
        else if(cal_type==1)
        {
                if(p->Adc.Volt[0]>380 || p->Adc.Volt[0]<310)                //2v
                        return 1;
                pack_vol_1=p->Adc.Volt[0];

                return 0;

        }
        else if(cal_type==2)
        {

                if(p->Adc.Volt[0]>2150 || p->Adc.Volt[0]<2050)        //12v
                        return 1;
                pack_vol_2=p->Adc.Volt[0];

                p->Adc.pack_factorl=(double)((double)(CAL_POINT2-CAL_POINT1)/((double)(pack_vol_2-pack_vol_1)));
                p->Adc.bat_vol_offsetl=(int)((pack_vol_1-p->Adc.short_vol_offset)*p->Adc.pack_factorl*100)-CAL_POINT1*100;

        #ifdef        PGM_SHOW_CALI_DATA
                sprintf(debug_str,"PACK1:%d,%d,%d,%d\r\n",((U16)(p->Adc.pack_factorl*100000)),p->Adc.bat_vol_offsetl,pack_vol_2,pack_vol_1);
                DEBUG_PRINT_STR(debug_str);
        #endif

                EepromWriteBuf(CALIBRATE_ADD,(unsigned char *)&p->Adc.pack_factorl,sizeof(double));
                EepromWriteBuf(BAT_VOL_OFFSET,(unsigned char *)&p->Adc.bat_vol_offsetl,sizeof(p->Adc.bat_vol_offsetl));

                return 0;
               
        }
        else if(cal_type==3)
        {
//                if(p->Adc.Volt[0]>3230 || p->Adc.Volt[0]<3120)        //18v
//                        return 1;

                pack_vol_3=p->Adc.Volt[0];
                p->Adc.pack_factorm=(double)((double)(CAL_POINT3-CAL_POINT2)/((double)(pack_vol_3-pack_vol_2)));
                p->Adc.bat_vol_offsetm=(int)((pack_vol_2-p->Adc.short_vol_offset)*p->Adc.pack_factorm*100)-CAL_POINT2*100;
        //        p->Adc.pack_factor=(double)((double)252.12/((double)(p->Adc.Volt[0])));
                EepromWriteBuf(CALIBRATE_ADD+4,(unsigned char *)&p->Adc.pack_factorm,sizeof(double));
                EepromWriteBuf(BAT_VOL_OFFSET+2,(unsigned char *)&p->Adc.bat_vol_offsetm,sizeof(p->Adc.bat_vol_offsetm));
               
        #ifdef        PGM_SHOW_CALI_DATA
                sprintf(debug_str,"PACK2:%d,%d,%d,%d\r\n",((U16)(p->Adc.pack_factorm*100000)),p->Adc.bat_vol_offsetm,pack_vol_3,pack_vol_2);
                DEBUG_PRINT_STR(debug_str);
        #endif

                return 0;
        }
        else if(cal_type==4)
        {
//                if(p->Adc.Volt[0]>4500 || p->Adc.Volt[0]<4400)        //25.2v
//                        return 1;

                pack_vol_4=p->Adc.Volt[0];
                p->Adc.pack_factorh=(double)((double)(CAL_POINT4-CAL_POINT2)/((double)(pack_vol_4-pack_vol_2)));
                p->Adc.bat_vol_offseth=(int)((pack_vol_2-p->Adc.short_vol_offset)*p->Adc.pack_factorh*100)-CAL_POINT2*100;
//                p->Adc.bat_vol_offseth=(int)((pack_vol_4-p->Adc.short_vol_offset)*p->Adc.pack_factorh*100)-CAL_POINT4*100;
                EepromWriteBuf(CALIBRATE_ADD+8,(unsigned char *)&p->Adc.pack_factorh,sizeof(double));
                EepromWriteBuf(BAT_VOL_OFFSET+4,(unsigned char *)&p->Adc.bat_vol_offseth,sizeof(p->Adc.bat_vol_offseth));
               
        #ifdef        PGM_SHOW_CALI_DATA
                sprintf(debug_str,"PACK3:%d,%d,%d,%d,%d\r\n",((U16)(p->Adc.pack_factorh*100000)),p->Adc.bat_vol_offsetm,p->Adc.bat_vol_offseth,pack_vol_4,pack_vol_2);
                DEBUG_PRINT_STR(debug_str);
        #endif

                return 0;
               
        }
        if(!p->Batt.is_bal_connected)
                return 1;
        if((p->Adc.Volt[CELL(p->Adc.max_vol_cell)]-p->Adc.Volt[CELL(p->Adc.min_vol_cell)])>800)
                return 2;
        if(p->Adc.Volt[CELL(p->Adc.max_vol_cell)]>4400 ||p->Adc.Volt[CELL(p->Adc.min_vol_cell)]<4000)
                return 3;
//        if(p->Adc.Volt[0]>4470 ||p->Adc.Volt[0]<4430)
//                return 4;

//        CLI;
//        DelayMs(500);
#if 0
        tmp_adc_vol=0;
        max=-30000;
        min=30000;
        adc_result=0;

        for(j=0;j<10;j++)
        {
                adc_result=GetAdcFilter(0x10,32);
                tmp_adc_vol+=adc_result;
                if(adc_result>max)
                        max=adc_result;
                if(adc_result<min)
                        min=adc_result;
                sprintf(debug_str,"avr:%d\r\n",adc_result);
                DEBUG_PRINT_STR(debug_str);
                DelayMs(30);
        }
#endif       

#if 0
        pack_vol_3=p->Adc.Volt[0];
        p->Adc.pack_factor=(double)((double)(CAL_POINT3-CAL_POINT2)/((double)(pack_vol_3-pack_vol_2)));
        p->Adc.bat_vol_offset=(int)(pack_vol_3*p->Adc.pack_factor*100)-CAL_POINT3*100;
//        p->Adc.pack_factor=(double)((double)252.12/((double)(p->Adc.Volt[0])));
        EepromWriteBuf(CALIBRATE_ADD+4,(unsigned char *)&p->Adc.pack_factor,sizeof(double));
        EepromWriteBuf(BAT_VOL_OFFSET+2,(unsigned char *)&p->Adc.bat_vol_offset,sizeof(p->Adc.bat_vol_offset));
       
#ifdef        PGM_SHOW_CALI_DATA
        sprintf(debug_str,"PACK:%d,%d,%d,%d\r\n",((U16)(p->Adc.pack_factor*100000)),p->Adc.bat_vol_offset,pack_vol_3,pack_vol_2);
        DEBUG_PRINT_STR(debug_str);
#endif
#endif

//        DelayMs(1000);
        for(i=1;i<8;i++)
        {
                SEL_AD_CH(i);
                tmp_adc_vol=0;
                max=-30000;
                min=30000;
                adc_result=0;
                for(j=0;j<5;j++)
                {
                        adc_result=GetAdcFilter(6,32);
                        if(adc_result>max)
                        max=adc_result;
                        if(adc_result<min)
                                min=adc_result;
                        tmp_adc_vol+=adc_result;
                        DelayMs(10);

                }

                p->Adc.Volt[5+i]=(tmp_adc_vol-max-min)/3;
               
        #ifdef        PGM_SHOW_CALI_DATA
                sprintf(debug_str,"i=%d,%d",i,p->Adc.Volt[5+i]);
                DEBUG_PRINT_STR(debug_str);
        #endif
        }
       
        for(i=1;i<7;i++)
        {
               
                if(i==2)
                {
                        tmp_vol=84;
                }
                else
                {
                        tmp_vol=42;
                }
               
                p->Adc.vol_factor=(double)((double)tmp_vol/(double)p->Adc.Volt[CELL(i)]);
                EepromWriteBuf(CALIBRATE_ADD+12+(i)*4,(unsigned char *)&p->Adc.vol_factor,sizeof(double));

        #ifdef        PGM_SHOW_CALI_DATA
                sprintf(Line,"fa:%d,%d\r\n",i,((U16)(p->Adc.vol_factor*100000)));
                DEBUG_PRINT_STR(Line);
        #endif
        }

        DelayMs(100);
        SEL_AD_CH(0);
        tmp_adc_vol=0;
        max=-30000;
        min=30000;
        adc_result=0;

        for(j=0;j<5;j++)
        {
                adc_result=GetAdcFilter(0x16,32);
                if(adc_result>max)
                        max=adc_result;
                if(adc_result<min)
                        min=adc_result;
                tmp_adc_vol+=adc_result;
//                DelayMs(20);               
        }
        p->Adc.Volt[5]=(tmp_adc_vol-max-min)/3;
        SEL_AD_CH(1);
        tmp_adc_vol=0;
        max=-30000;
        min=30000;
        adc_result=0;

        for(j=0;j<5;j++)
        {
                adc_result=GetAdcFilter(0x16,32);
                if(adc_result>max)
                        max=adc_result;
                if(adc_result<min)
                        min=adc_result;
                tmp_adc_vol+=adc_result;
        }
        p->Adc.Volt[6]=(tmp_adc_vol-max-min)/3;

        p->Adc.Volt[5]=p->Adc.Volt[6]-p->Adc.Volt[5];
        p->Adc.vol_factor[0]=(double)((double)42/(double)p->Adc.Volt[5]);

        EepromWriteBuf(CALIBRATE_ADD+12+(0)*4,(unsigned char *)&p->Adc.vol_factor[0],sizeof(double));

#ifdef        PGM_SHOW_CALI_DATA
        sprintf(debug_str,"fa0:%d\r\n",((U16)(p->Adc.vol_factor[0]*100000)));
        DEBUG_PRINT_STR(debug_str);


        EepromReadBuf(CALIBRATE_ADD,(unsigned char *)&p->Adc.pack_factorl,40);
        sprintf(debug_str,"read PACK:%d,%d,%d\r\n",((U16)(p->Adc.pack_factorl*100000)),((U16)(p->Adc.pack_factorm*100000)),((U16)(p->Adc.pack_factorh*100000)));
        DEBUG_PRINT_STR(debug_str);

        for(i=0;i<7;i++)
        {
                sprintf(debug_str,"fa:%d,%d\r\n",i,((U16)(p->Adc.vol_factor*100000)));
                DEBUG_PRINT_STR(debug_str);
        }
#endif

        SEI;
#endif
        return 0;
       
}


电压采集部分
        if(p->Batt.is_bal_connected)
        {
                BalanceClear();
                DelayMs(5);
                tmp_time=50;
        }
        else
                tmp_time=80;
        p->Adc.Volt[0]=GetAdcFilter(0x10,tmp_time);        //bat + bat - differ

//        sprintf(debug_str,"bat + -:%d,%d,%d",p->Adc.Volt[0],p->Adc.bat_vol_offset,p->Adc.bat_vol_offsetl);
//        DEBUG_PRINT_STR(debug_str);

//        p->Pmu.Volt=(U16)((p->Adc.Volt[0]-p->Adc.bat_vol_offset)*p->Adc.pack_factor*100);
        if(p->Adc.Volt[0]<2120)        //<12v
        {
                p->Pmu.Volt=(int)((p->Adc.Volt[0]-p->Adc.short_vol_offset)*p->Adc.pack_factorl*100)-p->Adc.bat_vol_offsetl;
        }
//        else// if(p->Adc.Volt[0]<3180)        //18v
//        {
//                p->Pmu.Volt=(int)((p->Adc.Volt[0])*p->Adc.pack_factorm*100)-p->Adc.bat_vol_offsetm;
//        }
        else
        {

                p->Pmu.Volt=(int)((p->Adc.Volt[0]-p->Adc.short_vol_offset)*p->Adc.pack_factorh*100)-p->Adc.bat_vol_offseth;
        #if 1
                if(p->Pmu.Volt>=19000 && p->Pmu.Volt<=25000)        //we get that the error is about -60mv when the voltage is 21.65v,so add a compensate value
                {
                        p->Pmu.Volt+=25;
                #if 0
                        if(p->Pmu.Volt<=20000)
                                p->Pmu.Volt+=15;
                        else if(p->Pmu.Volt<=21000)
                                p->Pmu.Volt+=20;
                        else
                                p->Pmu.Volt+=30;
                #endif

                }
                else
                        p->Pmu.Volt+=8;
        #endif
        }

以上是个人在工作中的一些经验和总结,不知正确与否,仅供参考,欢迎和大家一起交流,联系方式:yinlb1987@163.com,qq:405248153



点击此处下载 ourdev_679058YLW0LU.pdf(文件大小:86K) (原文件名:探讨电压测量时的校准方法.pdf)

阿莫论坛20周年了!感谢大家的支持与爱护!!

该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981

出0入0汤圆

发表于 2011-9-23 23:25:13 | 显示全部楼层
我想,校准的方法就一个吧,2点校准。也许其他问题引起的。

出0入0汤圆

发表于 2011-9-23 23:36:33 | 显示全部楼层
对这个比较感兴趣

出0入0汤圆

发表于 2011-11-19 00:04:06 | 显示全部楼层
太强大了

出0入0汤圆

发表于 2012-1-27 21:13:11 | 显示全部楼层
程序没有看懂,还是顶下

出165入0汤圆

发表于 2012-6-22 13:24:52 | 显示全部楼层
哦,为什么不能下载附件了?

出0入0汤圆

发表于 2012-7-15 20:13:45 | 显示全部楼层
多谢分享!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-9-1 08:16

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表