/*
Projekt: Einzelzellen-Überwachung und Gesamtabschaltung bei Unterspannung
für Werkzeugakku. Nur Standardbauelemente, vernachlässigbarer Ruhestrom.
Hardware: ATtiny13A mit 2×4052 zum Abgriff und Speichern der Zellenspannung
in einem Kondensator bzw. Abgabe der Ladung in eine Kapazität dreifacher Größe
zum Teilen der Spannung durch 4.
An jedem der 4052 ist ein „Trio“ von LiIon-Zellen angeschlossen.
Durch geschickte Potenzialwahl werden die Spannungsbereiche für
Mikrocontroller und 4052 nicht überschritten.
Die Schaltung zeigt undefiniertes Verhalten bei defektem Akku,
d.h. wenn die Zellenspannung unter 1,8 V sinkt und der Controller abstürzt.
4052-Adressen:
0 BA=LL Obere Zelle eines Trios
1 BA=LH Mittlere Zelle eines Trios
2 BA=HL Untere Zelle eines Trios
3 BA=HH Ladungsteiler-Kondensator; Potenzialversetzer stromfrei
Mikrocontroller-Portpins (in Klammern nur für ISP-Funktion):
5 PB0 (MOSI) 4052-Adresse A
6 PB1 (MISO) 4052-Adresse B
7 PB2 (SCK) Ausgangstransistor, L = leitend, H = gesperrt
2 PB3 ADC3 Ladungsteiler-Kondensator oberes Trio
3 PB4 ADC2 Ladungsteiler-Kondensator unteres Trio
1 PB5 !RESET Reset-Jumper
Stromverbrauch:
5 µA aus der Zelle zwischen den Netzen 3P6 und 00 durch den Watchdog: Ziemlich viel.
Zu messender Betriebsstrom.
Strom durch R2: 7 µA wenn T2 leitend.
Zum Vergleich: Selbstentladung „1%/Monat“ bedeutet bei 1,5 Ah Kapazität 20 µA.
Regime:
Der Controller prüft die Reset-Ursache.
Bei Reset vom Pin prüft er den Zählerstand der Uhr vom letzten Reset,
um ggf. in den Einspeichermodus für die Abschaltschwellen zu fallen.
Beginnend mit der oberen Zelle, d.h. mit der Zelle
die den Controller speist:
* PB3 und PB4 als L-Ausgang einstellen um C2 und C5 zu entladen
* Adresse anlegen
* Warten bis sich C1 und C4 geladen sowie C2 und C5 entladen haben
* PB3 und PB4 als hochohmigen Eingang (ohne Digitalfunktion) einstellen
* Adresse 3 anlegen
* Warten bis sich C1 in C2 und C4 in C5 entladen haben
* A/D-Wandler für C2 = ADC2 starten
* warten bis fertig
* A/D-Ergebnis mit abgespeichertem Minimalwert vergleichen
* A/D-Wandler für C5 = ADC3 starten
* warten bis fertig
* A/D-Ergebnis mit abgespeichertem Minimalwert vergleichen
Im ganzen je nach Ergebnis Ausgangstransistor aus- oder einschalten.
Danach stromsparend per Watchdog-Timer ca. 1 s warten, dann von vorn.
*/
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
EMPTY_INTERRUPT(ADC_vect);
typedef unsigned char byte;
// Adresse steht bereits in EEAR
inline byte eeread() {
while (EECR&2); // Warten bis Programmiervorgang beendet
EECR|=0x01;
return EEDR;
}
// Adresse steht bereits in EEAR
static void eewrite(byte b) {
byte a=eeread();
if (b==a) return;
EEDR = b;
EECR = 0; // Löschen und schreiben (3,4 ms)
if (b==0xFF) EECR|=0x10; // Nur löschen, nicht programmieren (1,8 ms)
if (!(b&byte(~a))) EECR|=0x20; // Nur programmieren, nicht löschen (1,8 ms)
cli();
EECR|=4;
EECR|=2;
sei();
}
ISR(WDT_vect) {
WDTCR = 0x18;
WDTCR = 0; // One-Time: Watchdog anhalten
}
static void wdt_wait(byte t) { // t = 0 (16 ms) .. 7 (2 s), 32 (4 s), 33 (8 s)
wdt_reset();
cli();
WDTCR = 0xD8 | t;
WDTCR = 0x40 | t; // Interrupt-Mode
sei();
MCUCR = 0x30; // PowerDown: Oszillator stoppt
sleep_cpu(); // Pause mit 5 µA Watchdog-Strom
}
int main() {
bool writemode=false;
PORTB = 0x07;
DDRB = 0x07;
DIDR0 = 0x3F; // Keinerlei Portpin-Digital-Lesefunktion
MCUCR = 0x30; // PowerDown
sei();
for(;;){
byte omu=0; // 0, 1 oder 2
bool okay=true;
do{
PORTB = PORTB&0x04 | omu; // Analogmultiplexer greift Zellenspannung ab
DDRB = 0x1F; // C2 und C5 entladen
wdt_wait(0); // 16 ms
DDRB = 0x07; // Entladen beenden
PORTB|= 0x03; // Analogmultiplexer auf Ladungsteilung (und Ruhelage) umschalten
ADMUX = 0x62; // 1,1-V-Referenz, ADC2 = PB4, 8 Bit reichen
ADCSRA= 0x9E; // ADC aktivieren
MCUCR = 0x28; // sleep = Noise Reduction
sleep_cpu(); // soll angeblich ADC starten!
EEARL = omu;
if (writemode) eewrite(ADCL);
else if (eeread()>ADCL) okay=false;
ADMUX|= 0x01; // ADC3 = PB3
sleep_cpu();
EEARL = omu+3;
if (writemode) eewrite(ADCL);
else if (eeread()>ADCL) okay=false;
ADCSRA= 0;
ADMUX = 0;
}while (++omu<3);
if (okay) PORTB&=~4;
else PORTB|=4;
wdt_wait(6); // 1 s
}
}
Detected encoding: UTF-8 | 0
|