|
现在做平衡充电器,需要对电池电压进行测量,而且需要达到一定的精度,废话不多说了,以下是我的一些经验和过程,写下来和大家探讨下,怎样才能做到更加精确的电压测量,只当抛砖引玉了
关于电压测量时的校准方法
在网上找了很久,没有找到一种很好的校准方法,个人摸索一一段时间,写个总结和过程,和大家分享以下,
说明: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
|