|
原理图如下:

只使用了电阻串联分压的原理测量电压,最最简单的方法。
用了一片Tiny13V做处理器,IO口都用满了。
那个LIGHTR2不好使,大家注意一下。
随便写写的,大家不要见笑。
代码如下:
//--------------------------------
//
// AVR电量显示器
// cnmusic@163.net
//
// Version 1.0
// 第一版,使用电路1.0版本。
//
//--------------------------------
#include <avr/io.h>
#include <avr/interrupt.h>
#define FEED_DOG asm("wdr");
#define INT_ON sei();
#define INT_OFF cli();
#define TRUE 1
#define FALSE 0
typedef unsigned char BYTE;
#define GL1 PB0
#define GL2 PB1
#define RL1 PB3
#define RL2 PB5
BYTE g_nLED[5] = {0}; // 用来控制灯的闪烁
void Sleep(void)
{
asm("sleep");
while(1);
}
int abs(int n)
{
if (n > 0)
{
return n;
}
else
{
return -n;
}
}
void ADC_Initialize(void)
{
ADCSRA = 0x00; // 禁用ADC
ADMUX = (1<<ADLAR); // VCC 做参考电压. 数据左侧对齐
ADCSRA = (1<<ADEN);
}
void delay_nus(unsigned int n) //N us延时函数
{
for (;n>0;n--)
{
asm("nop");
}
}
BYTE ADC_GetChannelValue(BYTE adc_channel)
{
BYTE value;
ADC_Initialize();
ADMUX |= adc_channel;
ADCSRA |= (1<<ADSC);
delay_nus(100);
value=ADCH;
ADCSRA = 0x00;
return value;
}
// 延迟
void DoDelay()
{
int i = 16000;
for (;i>0;i--)
{
asm("nop");
}
FEED_DOG;
}
void DoDelay2()
{
DoDelay();
DoDelay();
DoDelay();
DoDelay();
DoDelay();
DoDelay();
FEED_DOG;
}
// 灯闪烁
void FlashLED(BYTE nID,BYTE nSpeed)
{
if (nSpeed <= 0)
{
nSpeed = 1;
}
if (g_nLED[nID] > nSpeed)
{
PORTB |= (1<<nID);
g_nLED[nID] = 0;
}
else
{
PORTB &= ~(1<<nID);
g_nLED[nID] ++;
}
}
int main()
{
int nTotalBatteryV = 0;
int nVBattery1 = 0;
int nVBattery2 = 0;
BYTE bLowBat = FALSE;
// PB0 GreenLight1
// PB1 GreenLight2
// PB3 RedLight1
// PB5 RedLight2
DDRB = (1<<DDB0) | (1<<DDB1) | (1<<DDB3) | (1<<DDB5);
// 设置睡眠模式为Power-down模式。
MCUCR = (1<<SE)|(1<<SM1);
// 关中断
INT_OFF;
// 打开看门狗
asm("wdr");
// 看门狗的启动顺序
WDTCR |= (1<<WDCE) | (1<<WDE); //| (1<<WDE) | (1<<WDP2) | (1<<WDP1)|(1<<WDP0)|(1<<WDTIF)|(1<<WDTIE);
// 设置0.5秒必须触动一次看门狗
WDTCR = (1<<WDCE) | (1<<WDE) | (1<<WDTIE) | (1<<WDP2); //| (1<<WDP1)|(1<<WDP0)|(1<<WDTIE);//|(1<<WDTIF)|(1<<WDTIE);
INT_ON; // 开中断
// 自检
// 灯全量
PORTB |= (1<<RL1);
PORTB |= (1<<GL1);
PORTB |= (1<<GL2);
PORTB |= (1<<RL2);
DoDelay2();
DoDelay2();
// 灯全关
PORTB = 0;
DoDelay2();
DoDelay2();
while (1)
{
//----------------------------------------------------------------------------------------------
// 10位ADC,只使用前8位,相当于0.012890625V电压 对应数据1
// 为了避免浮点运算,我们将电压值扩大1000倍。
//----------------------------------------------------------------------------------------------
// 总电压 / 11 * 1 / (3300/256) = ADC1 // 2个分压电阻的阻值比为10:1,所以电压比为1:10,测量的电压为实际的1/11
// -->
// 总电压 = ADC1 * (3300 / 256) * 11 // 电压值扩大1000倍,便于运算,3.3V变3300
// = ADC1 * 141.796875
//----------------------------------------------------------------------------------------------
// 分电压 / 2 / (3300 / 256) = ADC2 // 因为并联分压,所以实际电压应该是*2的。
// 分电压 = ADC2 * (3300 / 256) * 2
// = ADC2 * 25.78125
//----------------------------------------------------------------------------------------------
nTotalBatteryV = ADC_GetChannelValue(1);
nTotalBatteryV *= 142; // 四舍五入141.796875
nTotalBatteryV += 140; // 根据计算,测量后的电压距离实际的电池电压有0.14V的偏差
nVBattery2 = ADC_GetChannelValue(2);
nVBattery2 *= 26;
nVBattery1 = (nTotalBatteryV - (int)nVBattery2);
// 总电压监控,低于7.6V报警
if (nTotalBatteryV < 7600)
{
bLowBat = TRUE;
}
else
{
bLowBat = FALSE;
}
FEED_DOG;
if (!bLowBat)
{
// 总电压满足要求
if (abs(nVBattery1 - nVBattery2) > 100) // 0.1V压差报警
{
FlashLED(RL1,10); // 2路电压不相同,红灯开始闪烁。
}
else
{
PORTB &= ~(1<<RL1);
}
// 根据电压距离截至电压的范围,闪烁LED。
if ((nVBattery1 - 3600) < 400) // 分电芯电压低于4V
{
FlashLED(GL1,(nVBattery1 - 3600)/100); // 闪烁指示灯。TODO:闪烁的间隔计算有误
}
else
{
PORTB |= (1<<GL1); // 分电芯电压高于4V,灯常亮。
}
if ((nVBattery2 - 3600) < 400)
{
FlashLED(GL2,(nVBattery2 - 3600)/100);
}
else
{
PORTB |= (1<<GL2);
}
}
else
{
// 总电压低,绿灯全关,红灯亮
PORTB |= (1<<RL1);
PORTB |= (1<<RL2);
PORTB &= ~(1<<GL1);
PORTB &= ~(1<<GL2);
}
DoDelay();
}
} |
|