零、序言:
交流内阻测试终于搞定了,在此感谢:
1.GrandF提供原理图和相关知识
2.LTT童鞋帮我layout,和雕刻PCB
3.二代童鞋提供论坛,和手机拍照
4.CDD童鞋找料
5. ZP童鞋借我示波器
一、目前的电池测试主要有两种方法:
1 直流方法
直流方法是在电池组两端接入放电负载,根据在不同电流I1、I2下的电压变U1、U2来计算内阻值,由E-I1*r=U1、E-I2*r=U2得:r=(U1一U2)/(I2-I1)
由于内阻值很小,在一定电流下的电压变化幅值相对较小,给准确测量带来困难,由于放电过程电压的变化,需要选择稳定区域计算电压变化幅值。实际测最中,苴流方法所得数据的重复性较差,准确度很难达到10%以上。
2 交流方法
交流方法相对直流法要简单。
在电池两端加上交流电压 ,u=Umaxsinωt,测得产生的交流电流 i=Imaxsin(ωt+φ),即阻抗是与频率有关的复阻抗,其相角为φ,而其模 r=|Z|=Umax/Imax。
从理论上讲,向电池馈人一个交流电流信号,测量由此信号产生的电压变化即可测得电池的内阻。
在实际使用中,由于馈入信号的幅值有限,电池的内阻在微欧或毫欧级,因此,产生的电压变化幅值也在微伏级,信号容易受到干扰。尤其是在线测量时,受到的影响更大,采用基于数字滤波器的内阻测量技术和同步检波方法可以克服外界干扰,获得比较稳定的内阻数据。
二、GrandF版的M8电子负载:
本文介绍用AVR单片机配合外围运放电路产生交变恒流源,理论上应该使用正弦形式的恒流源,但是其产生过程和检测过程相对复杂,本文利用的是流行的M8电子负载上配套的内阻测试电路,电路完全一样,电路由GrandF设计,因为他并没有开源源代码,所以本设计的源程序由自己完成,并且配套写了一个上位机软件。
首先介绍一下M8电子负载:
基本功能
1、恒流型电子负载
2、电池容量测试
3、开关电源最大工作电流测试
4、交流内阻测试
参数:
A/D 10BIT ±1 (使用64倍取样)
电压显示:50V ±50mV (使用64倍取样 降低到±10mV)
电流显示:5A ±5mA (电流取样电阻为0.05Ω,电流=0.25V/0.05Ω);最大 30A ±30mA(电流取样电阻为0.0083Ω)
电流调节:13BIT PWM D/A 5A的最小调节度为1mA ; 30A的最小调节度为4mA
交流内阻测量范围:0 - 500mΩ(10mA 1KHz)
交流内阻量程可以调节R7。R7: 50Ω 对应 0 - 500mΩ;R7: 100Ω 对应 0 - 1000mΩ;
下面是交流内阻测试部分的着重介绍,关于m8电子负载的,网络上有很多的资料,电路图一搜就可以找到,原创的论坛是一乐论坛,网址http://bbs.yleee.net/index.php
M8交流内阻测试仪:
测量范围:0 - 500m欧(10mA 1KHz),PCB使用M8电子负载附加部分电路。过后和M8电子负载整合。
原理:
由M8产生1KHz方波,由放大器产生10mA 1KHz交变恒流输出源,经过测试电阻后,产生交变电压信号经过250倍放大,再
由 M8 的快速同步相移 ADC 检出值。电路非常简单:C2、C3 两个电容可以用一个 47uF/50V 的无极性电容代替。耐压为 50V
是为了测高电压电池内阻。 电路图和PCBlayout如下图所示(明天再传)(为了省空间,图片压缩了,将就着看吧,或者去网上找,很多的):
三,我的理解:
原文中介绍的M8快速同步相移ADC,我一直没理解意思,看起来是个很有水平的采样技术,我上网一搜,没有关于它的说法;百思不得其解,后来逛yile论坛的时候发现一个公式:Rx=Kr *(ADCx - ADC0)/2^16 - R0,再看了eeprom里的校准参数的值,我猜测了ADC值与内阻的关系,并且通过抓数据,最终证实了我的想法,花了一天时间搞定了交流内阻测试的测试程序,M8电子负载使用LCD和按键作为人机交互界面,后续版本也增加了上位机软件,由于我没有LCD,因此只写了上位机软件作为测试的交互软件。
软件操作如下:
1.校准内阻接口短路时的ADC值,得到的数据就是公式里的ADC0,不停点击Calibrate 0 moHm 按钮,知道ADC Value显示的ADC值波动不大时就可以了:
2,校准已知内阻下的,ADC值,可以计算得到比例系数Kr,首先找到一个已知阻值的小电阻,最好用伏安法测出阻值,填入cali value(moHm)的文本框,不停点击Calibrate Rx moHm按钮,知道ADC value的值波动不大时就可以了。

3,按下calibrate 0 Ok 和Calibrate Rx OK两个按钮,将校准数据写入EEPROM。
4,可以按Test进行测试了
(测试短路时候的内阻,约等于0)
(测试介入内阻为166.6moHm时的内阻)
(测试内阻为200moHm时的值)
(因为没有直接的电阻,使用多个1oHm的电阻并联)
5,下图是ADC输入端的波形(CH1)和1Khz的方波(CH2)

(内阻测试仪)
四,单片机的固件程序代码:
//批评下自己,RxFlag 一开始没有加volatile 定义,运行不正常,调了好久的。
//疑问?希望高手给个答复:开发环境AVRGCC,
rxtemp = (unsigned long int)(adcx - Adc0Val);
rxtemp *= KrVal;
//rxtemp = (unsigned long int)((adcx - Adc0Val) * KrVal);
以上两种写法,红色的是正常的,运行结果正确,而蓝色结果rxtemp不正确,高16位会被截掉,不知何解!!!!!!
#include <avr/io.h>
#include "uart.h"
#include <util/delay.h>
#include <avr/eeprom.h>
#define RX_ADC0 0x0
#define RX_KR 0x2
#define RX_R0 0x4
#define RX_CAL 0x6 //校准字节正确标志
#define Cal0oHm 0xF0
#define CalRxoHm 0xF1
#define Cal0oHm_RN 0xF2
#define CalRxoHm_RN 0xF3
#define Cal0oHm_OK 0xF4
#define CalRxoHm_OK 0xF5
#define TstRxoHm 0xF6
#define TstRxoHm_RN 0xF7
unsigned char GetRs(unsigned int *pMax, unsigned int *pMin);
void UsartDecode(unsigned char *buf);
unsigned int AdResMax = 0;
unsigned int AdResMin = 0;
volatile unsigned char RxFlag = 0;
unsigned char RxBuffer[5] = {0,};
unsigned int KrVal = 0x0;
unsigned int Adc0Val = 0x0;
unsigned int R0Val = 0x0;
unsigned char State = 0x0;
int main(void)
{
cli();
uart_init();
//DDRB = 0x0f;
//adc init
//ADMUX = 0x1e;
ADMUX = 0xc1;
ADCSRA = 0x85;
//timer0
DDRD |= (1<<DDD5);
TCCR1A = 0xc2;
TCCR1B = 0x1a; //fast PWM mode, 1 prescale
ICR1 = 0x3f6;
OCR1A = (0x3f6>>1);
sei();
//OCR1B = 0x1ff;
//while (1);
//load calibration data
if (eeprom_read_byte((const unsigned char*)RX_CAL) == 0xaa){
KrVal = eeprom_read_word((const unsigned int*)RX_KR);
Adc0Val = eeprom_read_word((const unsigned int*)RX_ADC0);
R0Val = eeprom_read_word((const unsigned int*)RX_R0);
//PORTB = 0x0f;
}
//loop forever
while (1){
if (RxFlag){
RxFlag = 0;
UsartDecode(RxBuffer);
}
//GetRs(&max, &min);
//printu((const int8_t*)"ADC value is 0x%x\r\n", max-min);
//max = 0;
//min = 0;
}
return(0);
}
void UsartDecode(unsigned char *buf)
{
unsigned char cnt = 0;
unsigned int max, min;
unsigned char casebuf = buf[0];
//
//校准时用
static unsigned int krcal = 0x0;
static unsigned int adc0cal = 0x0;
static unsigned int r0cal = 0x0;
unsigned int rx = 0;
unsigned int adcx = 0;
unsigned long int rxtemp = 0;
switch (casebuf){
case Cal0oHm:
//PORTB ^= 0x0f;
GetRs(&max, &min);
adc0cal = max - min;
buf[0] = Cal0oHm_RN;
buf[1] = 0x0;
buf[2] = 0x0;
buf[3] = (unsigned char)(adc0cal & 0x00ff);
buf[4] = (unsigned char)(adc0cal >> 8);
for (cnt = 0; cnt < 5; cnt ++){
uart_put(buf[cnt]);
}
break;
case CalRxoHm:
rx = (unsigned int)buf[2];
rx <<= 8;
rx += buf[1];
rxtemp = rx;
rxtemp <<= 16;
GetRs(&max, &min);
adcx = max - min;
krcal = (unsigned int)(rxtemp / (adcx - adc0cal));
buf[0] = CalRxoHm_RN;
buf[1] = (unsigned char)(krcal & 0x00ff);
buf[2] = (unsigned char)(krcal >> 8);
buf[3] = (unsigned char)(adcx & 0x00ff);
buf[4] = (unsigned char)(adcx >> 8);
/*
buf[1] = (unsigned char)(rxtemp & 0xff);
buf[2] = (unsigned char)((rxtemp>>8) & 0xff);
buf[3] = (unsigned char)((rxtemp>>16) & 0xff);
buf[4] = (unsigned char)((rxtemp>>24) & 0xff);
*/
for (cnt = 0; cnt < 5; cnt ++){
uart_put(buf[cnt]);
}
break;
case Cal0oHm_OK:
Adc0Val = adc0cal;
eeprom_write_byte((unsigned char*)RX_CAL, 0xff);
eeprom_write_word((unsigned int*)RX_ADC0, adc0cal);
//PORTB ^= 0x0f;
break;
case CalRxoHm_OK:
//PORTB ^= 0x0f;
KrVal = krcal;
R0Val = r0cal;
eeprom_write_word((unsigned int*)RX_KR, krcal);
eeprom_write_word((unsigned int*)RX_R0, r0cal);
eeprom_write_byte((unsigned char*)RX_CAL, 0xaa);
break;
case TstRxoHm:
if (eeprom_read_byte((const unsigned char*)RX_CAL) == 0xaa){
//printu("adcx: 0x%x, rx: 0x%x, Adc0Val: 0x%x, KrVal: 0x%x\r\n", adcx, rx, Adc0Val, KrVal);
GetRs(&max, &min);
adcx = max - min;
rxtemp = (unsigned long int)(adcx - Adc0Val);
rxtemp *= KrVal;
//rxtemp = (unsigned long int)((adcx - Adc0Val) * KrVal);
rx = (unsigned int)(rxtemp >> 16);
//printu("adcx: 0x%x, rx: 0x%x, Adc0Val: 0x%x, KrVal: 0x%x\r\n", adcx, rx, Adc0Val, KrVal);
buf[0] = TstRxoHm_RN;
/*
buf[1] = (unsigned char)(Adc0Val & 0x00ff);
buf[2] = (unsigned char)(Adc0Val >> 8);
buf[3] = (unsigned char)(KrVal & 0x00ff);
buf[4] = (unsigned char)(KrVal >> 8);
*/
buf[1] = (unsigned char)(rx & 0x00ff);
buf[2] = (unsigned char)(rx >> 8);
buf[3] = (unsigned char)(adcx & 0x00ff);
buf[4] = (unsigned char)(adcx >> 8);
/*
buf[1] = (unsigned char)(rxtemp & 0xff);
buf[2] = (unsigned char)((rxtemp>>8) & 0xff);
buf[3] = (unsigned char)((rxtemp>>16) & 0xff);
buf[4] = (unsigned char)((rxtemp>>24) & 0xff);
*/
for (cnt = 0; cnt < 5; cnt ++){
uart_put(buf[cnt]);
}
}
else{
buf[0] = TstRxoHm_RN;
buf[1] = 0xff;
buf[2] = 0xff;
buf[3] = 0xff;
buf[4] = 0xff;
for (cnt = 0; cnt < 5; cnt ++){
uart_put(buf[cnt]);
}
}
break;
default:
break;
}
//buf[0] = 0x00;
}
unsigned char GetRs(unsigned int *pMax, unsigned int *pMin)
{
unsigned char i = 0;
for (i=0; i < 64; i ++){
while (TCNT1 != (0x3f6>>2));
ADCSRA |= (0x1<<ADSC);
while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);
*pMin += ADCW;
while (TCNT1 != (0x3f6-(0x3f6>>2)));
ADCSRA |= (0x1<<ADSC);
while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);
*pMax += ADCW;
//AdRes[i] = OverSmp(14, 0);
}
return (1);
}
ISR(USART_RXC_vect)
{
static unsigned char RxPtr = 0;
if (!RxFlag){
RxBuffer[RxPtr] = UDR;
if (++RxPtr == 5){
RxFlag = 0x1;
RxPtr = 0;
}
}
}
五,Vb程序源码:
Option Explicit
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Dim ComSettings As String
Dim ComNum As Integer
Dim BaundSet As Integer
Dim ReadFlag As Boolean
Const Cal0oHm As Byte = &HF0
Const CalRxoHm As Byte = &HF1
Const Cal0oHm_RN As Byte = &HF2
Const CalRxoHm_RN As Byte = &HF3
Const Cal0oHm_OK As Byte = &HF4
Const CalRxoHm_OK As Byte = &HF5
Const TstRxoHm As Byte = &HF6
Const TstRxoHm_RN As Byte = &HF7
'frame struct
'HEAD(1byte)----Rx(2byte)----ADC(2byte)
Private Sub BrSet_Click(index As Integer)
Dim Br As String
Dim i As Integer
If BrSet(index).Checked = True Then
ComSettings = Br & ",N,8,1"
Exit Sub
Else
If MSComm1.PortOpen = True Then
Call COMCONECT_Click
End If
Br = BrSet(index).Caption
i = InStr(Br, "Bps")
Br = Mid(Br, 1, i - 1)
BrSet(0).Checked = False
BrSet(1).Checked = False
BrSet(2).Checked = False
BrSet(3).Checked = False
BrSet(index).Checked = True
ComSettings = Br & ",N,8,1"
'MSComm1.Settings = ComSettings
End If
End Sub
Private Sub COM_Click(index As Integer)
If COM(index).Checked = True Then
Exit Sub
Else
If MSComm1.PortOpen = True Then
Call COMCONECT_Click
End If
COM(0).Checked = False
COM(1).Checked = False
COM(2).Checked = False
COM(3).Checked = False
COM(4).Checked = False
COM(index).Checked = True
'MSComm1.CommPort = Index + 1
ComNum = index + 1
End If
End Sub
Private Sub COMCONECT_Click()
Dim i As Integer
Dim Br As String
If MSComm1.PortOpen = True Then
MSComm1.PortOpen = False
COMCONECT.Caption = "Connect"
Shape1.BackColor = vbRed
Else
MSComm1.Settings = ComSettings
MSComm1.InputMode = comInputModeBinary
MSComm1.InBufferSize = 1024
MSComm1.OutBufferSize = 1024
MSComm1.RThreshold = 5 'set different buffer size according to communicated data length
MSComm1.InBufferCount = 0
MSComm1.OutBufferCount = 0
MSComm1.CommPort = ComNum
MSComm1.Handshaking = comNone
MSComm1.PortOpen = True
Shape1.BackColor = vbGreen
'
i = InStr(ComSettings, ",N,8,1")
Br = Mid(ComSettings, 1, i - 1)
BaundSet = Val(Br)
COMCONECT.Caption = "DisConnect"
'start to shake hands with MCU, send &HF3 + &HAA + &H55 + &H00 + &HFF + &H05 + &H50 + checksum
'waiting for answering or timeout
'outbyte(0) = &HF3
'outbyte(1) = &HAA
'outbyte(2) = &H55
'outbyte(3) = &H0
'outbyte(4) = &HFF
'outbyte(5) = &H5
'outbyte(6) = &H50
'outbyte(7) = CalcCheckSum(outbyte)
'MSComm1.Output = outbyte
'Dim T1, T2 As Long
'ReadFlag = False
'T1 = timeGetTime
'Do
' DoEvents
' T2 = timeGetTime
'Loop Until ReadFlag = True Or T2 - T1 > 1000
' recieve the correct answer
'If ReadFlag = True Then
' CHCFG.Enabled = False
' Open3D.Enabled = False
' COMCONECT.Caption = "DisConnect"
' FrmMainChWrite.Enabled = True
'Else
' If MSComm1.PortOpen = True Then
' MSComm1.PortOpen = False
' End If
' FrmMainChWrite.Enabled = False
' MsgBox "No Answers From MCU, Please Check Config Or Cabale!!!!", vbCritical, "Error"
'End If
End If
End Sub
'calibrate 0 oHm
Private Sub Command1_Click()
Dim outbyte(4) As Byte
Label4.Caption = ""
outbyte(0) = Cal0oHm
outbyte(1) = 0
outbyte(2) = 0
outbyte(3) = 0
outbyte(4) = 0
MSComm1.Output = outbyte
If ChkCommBack = False Then
Label4.Caption = "No data"
End If
End Sub
Private Sub Command2_Click()
Dim outbyte(4) As Byte
Dim temp As Integer
If Text1.Text <> "" Then
temp = Val(Text1.Text) * 10
Else
MsgBox "Text Box can not be empty", vbCritical, "Error"
Text1.SetFocus
Exit Sub
End If
Label4.Caption = ""
outbyte(0) = CalRxoHm
outbyte(1) = CByte(temp Mod 256)
outbyte(2) = CByte((temp - outbyte(1)) / 256)
outbyte(3) = 0
outbyte(4) = 0
MSComm1.Output = outbyte
If ChkCommBack = False Then
Label4.Caption = "No data"
End If
End Sub
Private Sub Command3_Click()
Dim outbyte(4) As Byte
Label6.Caption = ""
Label7.Caption = ""
outbyte(0) = TstRxoHm
outbyte(1) = 0
outbyte(2) = 0
outbyte(3) = 0
outbyte(4) = 0
MSComm1.Output = outbyte
If ChkCommBack = False Then
Label6.Caption = "No data"
Label7.Caption = "No data"
End If
End Sub
Private Sub Command4_Click()
Dim outbyte(4) As Byte
outbyte(0) = Cal0oHm_OK
outbyte(1) = 0
outbyte(2) = 0
outbyte(3) = 0
outbyte(4) = 0
MSComm1.Output = outbyte
End Sub
Private Sub Command5_Click()
Dim outbyte(4) As Byte
outbyte(0) = CalRxoHm_OK
outbyte(1) = 0
outbyte(2) = 0
outbyte(3) = 0
outbyte(4) = 0
MSComm1.Output = outbyte
End Sub
Private Function ChkCommBack() As Boolean
Dim T1, T2 As Long
ReadFlag = False
T1 = timeGetTime
Do
DoEvents
T2 = timeGetTime
Loop Until ReadFlag = True Or T2 - T1 > 1000
ChkCommBack = ReadFlag
End Function
Private Sub Form_Load()
On Error Resume Next
'Shape1.FillColor = vbRed
Shape1.BackColor = vbRed
ComSettings = "9600,N,8,1"
ComNum = 1
End Sub
Private Sub MSComm1_OnComm()
Dim inbyte() As Byte
'Dim bytRe() As Byte
Dim sleepms As Double
Dim j As Long
Sleep (200)
Select Case MSComm1.CommEvent
Case 2
inbyte() = MSComm1.Input
Call RecieveDecode(inbyte)
Case Else
End Select
'sleepms = (11 * 1000)
'sleepms = sleepms / BaundSet * 10
'j = CLng(sleepms)
'Sleep (j * 2)
'MSComm1.InBufferCount = 0 'clear inputbuffer
End Sub
'Const Cal0oHm_RN As Byte = &HF2
'Const CalRxoHm_RN As Byte = &HF3
'Const TstRxoHm_RN As Byte = &HF7
Private Sub RecieveDecode(ByRef inbuf() As Byte)
Dim temp As Long
Dim i As Integer
Select Case inbuf(0)
Case Cal0oHm_RN
ReadFlag = True
temp = inbuf(4)
temp = temp * 256 + inbuf(3)
Label4.Caption = "0x" & CStr(Hex(temp))
' 接收到0oHm校准数据返回
Case CalRxoHm_RN
ReadFlag = True
temp = inbuf(4)
temp = temp * 256 + inbuf(3)
Label4.Caption = "0x" & CStr(Hex(temp))
' 接收到RxoHm校准数据返回
Case TstRxoHm_RN
ReadFlag = True
' 接收到测试数据返回
temp = inbuf(2)
temp = temp * 256 + inbuf(1)
i = temp Mod 10
temp = (temp - i) / 10
Label6.Caption = CStr(temp) & "." & CStr(i)
temp = inbuf(4)
temp = temp * 256 + inbuf(3)
Label7.Caption = "0x" & CStr(Hex(temp))
Case Else
ReadFlag = False
End Select
End Sub
Private Sub Form_Unload(Cancel As Integer)
Cancel = -1
End Sub
Private Sub progamend_Click()
End
End Sub