Source file: /~heha/basteln/Konsumgüter/Ladegeräte/b6s-abschalt.zip/b6s-abschalt.cpp

/*
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-80