3.7 MPPT (Maximum Power Point Tracking) bei Gartenleuchten-Zeug? (🚧)

Ein Paket mit Werbegeschenken von Würth-Solar: Solar-Ladegerät für 2 NiMH Zellen brachte mich auf die (Schnaps-) Idee.
Für viele (IoT-) Projekte brauche ich eine leistungsstarke Spannungsversorgung im Aussenbereich mit ordentlicher Gangreserve. LiIon-Akkus aus Mobiltelefonen oder Akkupacks ohne Balancer usw. liegen in der Bastelkiste. Solarmodule aus defekten Gartenleuchten ebenso. Könnte man diese Reste nicht sinnvoll weiter verwenden?

Solarzellenspannung für LiIon-Zelle erhöhen

Gute Gartenleuchten und auch das Würth-Solar-Ladegerät laden 2 NiMH Zellen und haben 8 Solarzellen in Reihe, was max. ca. 4V ergibt, ist zu wenig einen LiIon-Akku zu laden. Die Spannung muss erhöht werden mit einem Schaltwandler. Dabei könnte auch gleich der beste Arbeitspunkt für einen maximalen Ladestrom automatisch eingestellt werden: Maximum Power Point Tracking📖

Testaufbau mit ATtiny84

Völlig bekloppt diesen veralteten Chip zu verwenden?

  • Lagen noch 10 Stück im Regal rum
  • Funktioniert schmerzfrei von 2,7..5,5V
  • Hat genug freie Anschlüsse mit 10 Bit AD-Wandlung für Messungen
  • Hat einen AD-Wandler, der auch Spannungsdifferenzen kann und dabei 20 fach verstärken mag!
  • Hat keinen USART!

Mit Arduino programmieren

Library laden: github.com/SpenceKonde/ATTinyCore🔗
Programmer z.B. AVRISP mkII verwenden

MPPT mit ATtiny84 Schaltung
MPPT mit ATtiny84 Schaltung
BeschaltungFunktionArduinoBezPinPinBezArduinoFunktionBeschaltung
VCC114GND
P_LCDXTAL1D10PB0213PA0D0ADC0/AREFP_VCC
P_TASTERXTAL2D9PB1312PA1D1ADC1/AIN0P_UBATT
RESETRESETD11PB3411PA2D2ADC2/AIN1P_POTI
P_POTI_LEDINT0/OC0AD8PB2510PA3D3ADC3/T0P_SOLAR
P_LEDOC0B/ADC7D7PA769PA4D4ADC4/USCK/SCL/T1USCK / SCL
SDA / MOSIOC1A/SDA/MOSI/ADC6D6PA678PA5D5ADC5/DO/MISO/OC1BMISO / P_PWM
Pinbelegung des ATtiny84

Schaltplan

MPPT mit ATtiny84 Schaltplan
MPPT mit ATtiny84 Schaltplan

Die Solarzellenspannung (nur Solarzellen zur Landung von 2 NiMH-Akkus mit 8 Streifen, Zellen verwenden) wird mit einem Aufwärtswandler (10 kHz) an die Batteriespannung angepasst. Der Akkustrom wird über einen 2,2Ω Widerstand gemessen. Zum Einstellen bei der Entwicklung wurde ein Poti verbaut, seine Referenzspannung wird mit einer roten LED (D3) erzeugt. Nachts sollen LEDs aus einer Weihnachsbeleuchtung leuchten, dafür wird ein Abwärtswandler (497 Hz) verwendet.
Eine prima Dimensionierungshilfe für die Induktionen: Dimensionierung von Schaltnetzteilen🔗
Für die Spannungmessungen wird der 10Bit-AD-Wandler mit der internen Referenzspannung von 1,1V betrieben, daher müssen die zu messenden Spannungen geteilt werden. Zur Messglättung dienen 100nF Keramikkondensatoren.

MPPT mit ATtiny84 LCD-Anzeige
MPPT mit ATtiny84 LCD-Anzeige
3890B 394V 397
Solarspannung in mV: 3,890VBatteriespannung in cV: 3,94VVCC in cV: 3,97V
PW 111174%
Pulsweite 11µsStrom in Akku 11mALadezustand Akku 74%
Bedeutung der Werte

Probleme mit AD-Differenzmessung bei ATtiny84

Diesen µC habe ich u.a. gewählt weil er die Möglichkeit bieten soll die Differenzspannung von zwei AD-Eingängen zu messen und diese sogar 20fach verstärken kann.
Ich wollte den Strom-Messwiderstand R6 möglichst klein machen (zunächst 1Ω) um keine Energie zu verschwenden. In der aktuellen ATTinyCore🔗-Lib 1.5.2 von SpenceKonde kann noch nicht differnzielle Messung eingestellt werden. Auf seiner Github-Seite stellt er eine Beta-Lib 2.0 zur Diskussion: Für ATtiny84 lesen🔗

Ich habe zunächst versucht mit der alten Lib und eigenem Code die Strommessung durch zu führen und bin gescheitert. Mit seiner neuen Lib hat es nicht besser funktioniert: Beim ATtiny84 kann bei Differenzmessung wohl nur VCC als Referenzspannung eingestellt werden, weder die interne 1,1V Referenz noch eine externe Referenz sind dafür verwendbar. Auch der Bipolar-Modus hat bei mir nicht sinnvoll funktioniert.

Bin letztlich von der Differenzmessung abgerückt und habe R6 durch 2,2Ω ersetzt um aussagekräftige Spannungsdifferenzen ermitteln zu können. Auch in Hinblick auf ESP32, dessen AD-Wandler keine Differenz-Messung hat?

sbi(ADCSRB,7); // Bipolarmode im Setup setzen
int readIbatt(){ // Differenzwert von PA0 und PA1
  ADMUX = 0b00001001; // siehe S129 Datenblatt
  delay(2);
  ADCSRA |= (1 << ADSC);
  while(ADCSRA & (1<<ADSC)); // warten Messung fertig
  return ADCW;
}

Stromsparorgie

Letztlich soll die Schaltung mehr einbringen als sie verbraucht.

  • LCD Hintergrundbeuchtung ausschalten
  • LCD Power-LED abklemmen
  • LCD Spannungsversorgung abschalten
  • µC schlafen schicken, er wacht allerdings ca. 494 mal in der Sekunde durch den Timer0 Overflow-Interrupt wieder auf, dieser ist für millis() verantwortlich.

Bin von ca. 7mA bei 0,7mA bei ausgeschalteter LCD und 2,7mA mit LCD-Anzeige gelandet.

MPPT mit ATtiny84 LCD-Modifikation
MPPT mit ATtiny84 LCD-Modifikation R10 entfernen damit Power-LED nicht mehr leuchtet

Software (🚧)

Ist noch nicht ordentlich getestet, das “Nachtlicht” ist langweilig.

#include <Wire.h> // Wire Bibliothek einbinden
#include "wiring_private.h"
#include <LiquidCrystal_PCF8574.h>
#include <avr/sleep.h>
#define P_VCC 0      // D0 Spannungsteiler Spannung VCC
#define P_UBATT 1    // D1 Spannungsteiler Spannung Akku
#define P_POTI 2     // D2 Einstell-Poti
#define P_SOLAR 3    // D3 Spannungsteiler Spannung Solarzelle
#define P_PWM 5      // D5 PWM-Signal für Wandler
#define P_LED 7      // D7 LED
#define P_POTI_LED 8 // D8 Spannung für Poti
#define P_TASTER 9   // D9 Taster gegen GND zur Steuerung
#define P_LCD 10     // D10 LCD-Spannung schalten
#define PWM_MIN 6    // minimale Pulsbreite in us
#define PWM_MAX 40   // maximale Pulsbreite in us
#define ANZ_MESSUNG 10 // Anzahl Messungen fürs Mitteln
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27 für 16 Zeichen und 2 Zeilen ein
enum zustandstyp {RUHE_ENTRY,RUHE,LADEN_ENTRY,LADEN,VOLL,NACHT};  // Aufzaehlungstyp
enum zustandstyp zustand = RUHE_ENTRY; // Startzustand
void setup() {
  pinMode(P_LCD,OUTPUT);
  pinMode(P_LED,OUTPUT);
  pinMode(P_POTI_LED,OUTPUT);
  pinMode(P_TASTER,INPUT_PULLUP);
  analogReference(INTERNAL); // 1,1V intern
  //digitalWrite(P_POTI_LED,1); // Poti für Einstellungen einschalten
  TCCR1A = 0b00100011; // Ausgang OC1B bei 0 setzen und bei OCR1B loeschen
  TCCR1B = 0b00011001; // Waveform Generation Mode: Fast PWM it OCR1A als Top, Timer mit CPU-CLK 
  OCR1A = 100;   // Timer 10kHz
  set_sleep_mode(SLEEP_MODE_IDLE);
}
int a,ladung;
uint8_t pwm=0,messungen=0,nachtlicht=0; 
int8_t richtung = 1,nlRichtung=2; // Suchrichtung für PWM
int32_t uSolar,uSolarGlatt,uBatt,vcc,iBatt,iBattSum=0,iBattSumAkt=0,iBattSumAlt=0;
uint32_t oldmillis=0;
bool messungFertig=false;
bool oldTaster=false;
bool lcdAn=false;
bool taster(){          // Taste gedrückt?
  bool ausgabe = false;
  bool test = !digitalRead(P_TASTER); // Taster ist low aktiv
  if (oldTaster != test){ // hat sich was getan?
    delay(10); // 10 ms warten
    test=!digitalRead(P_TASTER); // noch mal einlesen
    if (oldTaster != test){  // immer noch anders?
      ausgabe = !oldTaster & test; // steigende:fallende Flanke
      oldTaster = test;
    }
  }
  return ausgabe;
} 
void einschaltenLCD(){
  digitalWrite(P_LCD,1);
  delay(100);
  lcd.begin(16, 2);      // LCD initialisieren
  lcd.clear();           // Displaypuffer löschen
  lcd.setBacklight(0); // Hintergrundbeleuchtung aus
  lcd.setCursor(0,0);    // Cursor auf erstes Zeichen, erste Zeile setzen
}
void lcdAusgabe(){
  lcd.setCursor(0,0);
  uSolarGlatt=(uSolarGlatt*9+uSolar)/10;
  lcd.printf("%4d ",uSolarGlatt); // mehr als 1 Parameter scheint nicht zu funktionieren
  lcd.printf("B %3d ",uBatt/10);
  lcd.printf("V %3d",vcc/10);
  ladung=(uBatt-3200)/10;
  lcd.setCursor(0,1);
  lcd.printf("PW %2d ",pwm); //pwm
  lcd.printf("%4d ",iBatt);
  lcd.printf(" %3d%%",ladung);
}
void messung(){
  //digitalWrite(P_LED,!digitalRead(P_LED));
  uSolar=analogRead(P_SOLAR);
  uSolar=uSolar*3933/928;
  uBatt=analogRead(P_UBATT);
  uBatt=uBatt*3820/902;
  vcc=analogRead(P_VCC);
  vcc=vcc*3783/888;
  //a=analogRead(P_POTI)/4;
  iBattSum+=vcc-uBatt;
  messungen++;
  if(messungen>=ANZ_MESSUNG){
    messungFertig=true;
    iBattSumAkt=iBattSum;
    iBatt=iBattSum*28/58/ANZ_MESSUNG;
    messungen=0;
    iBattSum=0;
  }
  if(lcdAn){
    lcdAusgabe();
    if(taster()){
      lcdAn=false;
      digitalWrite(P_LCD,0);
    }
  }
  else{
    if(taster()){
      lcdAn=true;
      einschaltenLCD();
    }
  }
  switch(zustand){
    case RUHE_ENTRY:
      pinMode(P_PWM,INPUT); // PWM abschalten
      pwm=0;
      analogWrite(P_LED,0);
      nachtlicht=10;
      zustand = RUHE;
      break;
    case RUHE:
      if(uSolar>3000) zustand = LADEN_ENTRY;
      if(uSolar<1000&&uBatt>3500) zustand = NACHT;
      break;
    case LADEN_ENTRY:
      pwm=PWM_MIN;
      OCR1B = pwm;
      pinMode(P_PWM,OUTPUT); // PWM einschalten
      richtung=1; // nach oben
      messungen = 0;
      iBattSum=0;
      iBattSumAlt=0;
      zustand = LADEN;
      break;
    case LADEN:
      if(messungFertig){
        if(iBattSumAkt>iBattSumAlt){
          pwm+=richtung;
        }
        else{
          richtung = (richtung>0)?-1:1;
          pwm+=richtung;
        }
        messungFertig=false;
        iBattSumAlt=iBattSumAkt;
        if(pwm<PWM_MIN)pwm=PWM_MIN;
        if(pwm>PWM_MAX)pwm=PWM_MAX;
        OCR1B = pwm;
      }
      if(uSolar<2500) zustand=RUHE_ENTRY; // wenn es dunkel wird
      if(uBatt>4100){ // bei 90%
        zustand=VOLL;
        OCR1B = pwm = 1;
      } 
      break;
    case VOLL:
      if(uSolar<2500) zustand=RUHE_ENTRY; // wenn es dunkel wird
      if(uBatt<4000) zustand=LADEN_ENTRY;
      break;
    case NACHT:
      if(uBatt<3600||uSolar>1000) zustand=RUHE_ENTRY;
      if(nachtlicht>220) nlRichtung=-rand()%4-2;
      else if(nachtlicht<10) nlRichtung=rand()%32+3;
      nachtlicht+=nlRichtung;
      analogWrite(P_LED,nachtlicht);
      break;  
  }
}
void loop() {
  if(millis()>oldmillis+500){ 
    oldmillis=millis();
    messung();
  }
  sleep_mode();  // enter sleep, wakes on interrupt
}

Fazit

Immense Materialschlacht für zwar großen Erkenntnis- aber kleinen Praxisgewinn? Problem war die Strom-Messung.
Ein Vergleich der Ladeleistung mit Würth-Solarladegerät für 2 NiHM-Zellen: NiHM: 2,5V * 50mA = 140mW. LiIon: 4,0V * 33mA = 132mW
Sieht doch gut aus! Betrachtet man den wesentlich besseren Wirkungsgrad beim LiIon-Laden holt die Schaltung wirklich viel Energie aus den ollen Solar-Zellen raus.

Lohnt es sich?

Wenn die Teile rumliegen, ein Aufwärtswandler notwendig ist und Entwicklungs- bzw. Bastel-Zeit keine Rolle spielen ist es eine prima Reste-Verwertung.
Wenn neu beschafft wird, können auch gleich passende Solarmodule besorgt werden.
Wie viele µC Port-Pins? Usolar, Uvcc, Ubatt, PWM -> 4

Ausblick ohne MPPT

Bei Ziel IoT mit ESP32 sind die freien Ports beschränkt.
Für 1,70 pro Stück habe ich 5V 60mA Solarmodule (68mmx37mm) bestellt🔗. Der Ladestrom eines LiIon-Akkus war heute 21mA, der Zellen-Fläche angemessen.
Wie viele µC Port-Pins ohne MPPT? Usolar, Uvcc, Laden -> 3