Sonntag, 20. August 2017

JouleThief XXL V2.1 - noch ein Update

der Betrieb der V2.0 ist schon sehr komfortabel. Die Spannung der Batterien wird zuverlässig angezeigt und es kommt eine Meldung, wenn deren Spannung zu weit absinkt.
Allerdings ist der Kontrast des Displays noch nicht optimal, da dessen Versorgungsspannung abhängig der Leere der Batterien im Bereich von 5V bis unter 3V schwankt. Da muss eine Kontrast-Automatik her! In Anlehnung an diese Webseite, habe ich die Schaltung erweitert: Ich habe noch 2 Analogeingänge frei, mit der ich sowohl Versorgungsspannung des Displays, als auch die Kontrastspannung messen kann. Über einen PWM-Ausgang mit nachgeschaltetem Spannungsteiler, sowie Glättungs-Schaltung kann ich nun den Kontrast immer im optimalen Bereich halten.



















In diesem Zusammenhang habe ich noch ein paar kleine Bugs im Programm beseitigt:




// ---------------------------------------------------------
// Batteriemessger�t
// es wird die Spannung von 6 Batterien gemessen und gleichzeitig
// auf einem LCD-Display dargestellt.
// ---------------------------------------------------------

#include

#define DEBUG
#include "log.h"

struct _Batterie
{
    float spannung;  // die gemessene Spannung
    bool ausgetastet;  // wird die Spannung gerade angezeigt, oder ausgetastet
    float abschaltspannung;  // darunter ist die Batterie leer
};

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // initialize the library with the numbers of the interface pins
int AnalogBatPin1 = A0;
int AnalogBatPin2 = A1;
int AnalogBatPin3 = A2;
int AnalogBatPin4 = A3;
int AnalogBatPin5 = A4;
int AnalogBatPin6 = A5;
int AnalogKontrastPin = A6;  // hier wird die Kontrastspannung gemessen
int AnalogVCCPin = A7; // hier wird die VCC gemessen (für die Kontrastspannungsberechnung)
int KontrastPWM = 6;
int TasterPin = 10;
int SummerPin = 9;
int LEDPin =13;
bool Pieps; // w�hrenddessen wird gepiepst
int Slot; // z�hlt immer durch
struct _Batterie Batterien[6];
bool SekundenBlinkbit; // toggle jede Sekunde
bool HalbsekundenBlinkbit; // toggle jede halbe Sekunde
bool ViertelsekundenBlinkbit; // toggle jede viertel Sekunde

#define NEIN 0  // Taste nicht gedr�ckt
#define KURZ 1  // Taste kurz gedr�ckt
#define LANG 2  // Taste lang gedr�ckt

// -------------------------------
void setup()
{
    SERIAL_BEGIN;
    lcd.begin(16, 2);    // set up the LCD's number of columns and rows:
    analogReference(INTERNAL);
    pinMode(TasterPin, INPUT);
    pinMode(SummerPin, OUTPUT);
    pinMode(LEDPin, OUTPUT);
    InitVariablen();
}

// -------------------------------
void InitVariablen(void)
{
    int i;

    for(i=0; i<6 br="" i="">        Batterien[i].abschaltspannung = 0.02F;
}

// -------------------------------
void MesseBatterien(void)
{
    int aval;
    float offset[6] = { 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F };
    float zaehl[6] = { 1.24F, 1.24F, 1.24F, 1.24F, 1.24F, 1.24F };
    float nenner[6] = { 0.83F, 0.83F, 0.83F, 0.83F, 0.83F, 0.83F };

    aval = analogRead(AnalogBatPin1);
    Batterien[0].spannung = (float)aval *zaehl[0] / nenner[0] / 1000.0F + offset[0];
    aval = analogRead(AnalogBatPin2);
    Batterien[1].spannung =
        (float)aval *2.0F * zaehl[1] / nenner[1] / 1000.0F + offset[1];
    Batterien[1].spannung -= Batterien[0].spannung;
    aval = analogRead(AnalogBatPin3);
    Batterien[2].spannung =
        (float)aval *3.0F * zaehl[2] / nenner[2] / 1000.0F + offset[2];
    Batterien[2].spannung -= Batterien[0].spannung;
    Batterien[2].spannung -= Batterien[1].spannung;
    aval = analogRead(AnalogBatPin4);
    Batterien[3].spannung =
        (float)aval *4.0F * zaehl[3] / nenner[3] / 1000.0F + offset[3];
    Batterien[3].spannung -= Batterien[0].spannung;
    Batterien[3].spannung -= Batterien[1].spannung;
    Batterien[3].spannung -= Batterien[2].spannung;
    aval = analogRead(AnalogBatPin5);
    Batterien[4].spannung =
        (float)aval *5.0F * zaehl[4] / nenner[4] / 1000.0F + offset[4];
    Batterien[4].spannung -= Batterien[0].spannung;
    Batterien[4].spannung -= Batterien[1].spannung;
    Batterien[4].spannung -= Batterien[2].spannung;
    Batterien[4].spannung -= Batterien[3].spannung;
    aval = analogRead(AnalogBatPin6);
    Batterien[5].spannung =
        (float)aval *6.0F * zaehl[5] / nenner[5] / 1000.0F + offset[5];
    Batterien[5].spannung -= Batterien[0].spannung;
    Batterien[5].spannung -= Batterien[1].spannung;
    Batterien[5].spannung -= Batterien[2].spannung;
    Batterien[5].spannung -= Batterien[3].spannung;
    Batterien[5].spannung -= Batterien[4].spannung;
}

// -------------------------------
void AusgabeLCD(void)
{
    char buffer1[40], buffer2[40];
    char t1[10], t2[10], t3[10], t4[10], t5[10], t6[10];

    if(Batterien[0].ausgetastet)
        strcpy(t1, "    ");
    else
        dtostrf(Batterien[0].spannung, 4, 2, t1);
    if(Batterien[1].ausgetastet)
        strcpy(t2, "    ");
    else
        dtostrf(Batterien[1].spannung, 4, 2, t2);
    if(Batterien[2].ausgetastet)
        strcpy(t3, "    ");
    else
        dtostrf(Batterien[2].spannung, 4, 2, t3);
    sprintf(buffer1, "%s %s %s  ", t1, t2, t3);

    if(Batterien[3].ausgetastet)
        strcpy(t4, "    ");
    else
        dtostrf(Batterien[3].spannung, 4, 2, t4);
    if(Batterien[4].ausgetastet)
        strcpy(t5, "    ");
    else
        dtostrf(Batterien[4].spannung, 4, 2, t5);
    if(Batterien[5].ausgetastet)
        strcpy(t6, "    ");
    else
    dtostrf(Batterien[5].spannung, 4, 2, t6);
    sprintf(buffer2, "%s %s %s  ", t4, t5, t6);

    lcd.setCursor(0, 0);
    lcd.print(buffer1);
    lcd.setCursor(0, 1);
    lcd.print(buffer2);
}

// -------------------------------
int LeseTaster(void)
{
    int tasterGedrueckt; // und hier ist die Taste
    static long int anfang; // hier merken wir uns, seit wann der Taster gedr�ckt ist
    long int jetzt=millis(); // Millisekunden
    long int drueckdauer; // so lange ist der Taster bereits gedr�ckt
    bool gedrueckt;
    static bool letztes_gedrueckt=0;

    gedrueckt = digitalRead(TasterPin);
    digitalWrite(LEDPin, gedrueckt); // visuelle R�ckmeldung

    if (gedrueckt)
    {
        drueckdauer = jetzt-anfang;
        if (drueckdauer > 1000)
            tasterGedrueckt = LANG;
        else
        {
            if (drueckdauer > 500)
                tasterGedrueckt = KURZ;
            else
                tasterGedrueckt = NEIN;
        }
        if (letztes_gedrueckt == 0)
            anfang = jetzt;
    }
    else
        anfang = jetzt; // damit die Tasten-Dr�ck-Dauer kurz ist

    letztes_gedrueckt = gedrueckt;

//    SERIAL_PRINT(anfang);
//    SERIAL_PRINT(" ");
//    SERIAL_PRINT(jetzt);
//    SERIAL_PRINT(" ");
//    SERIAL_PRINTLN(tasterGedrueckt);

    return tasterGedrueckt;
}

// -------------------------------
void PruefeBatterien(void)
{
    int i;
    int leereBatterie = -1;

    for(i=0; i<6 br="" i="">    {
        if (Batterien[i].spannung < Batterien[i].abschaltspannung)
            leereBatterie = i+1;           
    }
    // SERIAL_PRINTLN(leereBatterie);
   
    if (leereBatterie > Slot)
    {
        Pieps = true;
        //SERIAL_PRINT("Slot ");
        //SERIAL_PRINTLN(Slot);
    }
    else
        Pieps = false;
   
}

// -------------------------------
void StelleKontrast(void)
{
    int kont, vcc;
    static int kontrastwert=0;  // der PWM-Stellwert für den Kontrast
   
    kont = analogRead(AnalogKontrastPin);
    SERIAL_PRINT("Kin=");
    SERIAL_PRINT(kont);

    vcc = analogRead(AnalogVCCPin);
    SERIAL_PRINT("VCC=");
    SERIAL_PRINTLN(vcc);  // 1000bit = 5V

    kontrastwert += ((800*(vcc/1000))-kont)/7;
    if (kontrastwert<0 br="">        kontrastwert = 0;
    if (kontrastwert>255)
        kontrastwert = 255;

    SERIAL_PRINT(" Kout=");
    SERIAL_PRINTLN(kontrastwert);

    analogWrite(KontrastPWM, kontrastwert);
}

// -------------------------------
void loop()
{
    long int jetzt;
    int taste;
    static long int jedeSekunde=0; // f�r den Timer jeder Sekunde
    static long int jedeHalbeSekunde=0; // f�r den Timer jeder halben Sekunde
    static long int jedeViertelSekunde=0; // f�r den Timer jeder viertel Sekunde

    jetzt = millis();
    taste = LeseTaster();
    PruefeBatterien();

    if ((jetzt-jedeSekunde) > 1000)
    {   // was hier steht, wird zyklisch jede Sekunde ausgef�hrt
        MesseBatterien();
        StelleKontrast();

        SekundenBlinkbit = ~SekundenBlinkbit;
        jedeSekunde = jetzt;
    }

    if ((jetzt-jedeHalbeSekunde) > 500)
    {   // was hier steht, wird zyklisch jede halbe Sekunde ausgef�hrt
        AusgabeLCD();

        Slot++;
        if (Slot>5)
            Slot=0;

        SERIAL_PRINTLN(Slot);

        HalbsekundenBlinkbit = ~HalbsekundenBlinkbit;
        jedeHalbeSekunde = jetzt;
    }

    if ((jetzt-jedeViertelSekunde) > 250)
    {   // was hier steht, wird zyklisch jede viertel Sekunde ausgef�hrt
        static bool letzte; // nur maximal 1/4 Sek. piepsen
        if (Pieps & !letzte)
        {
            letzte = true;
            digitalWrite(SummerPin, HIGH);
        }
        else
        {
            letzte = false;
            digitalWrite(SummerPin, LOW);
        }
        ViertelsekundenBlinkbit = ~ViertelsekundenBlinkbit;
        jedeViertelSekunde = jetzt;
    }
}

Dienstag, 1. August 2017

Joule-Thief XXL V2.0

Ich habe nun schon einige Batterien leer gesaugt, jedoch immer wieder das Problem, dass Manche einfach auslaufen oder gar ihre ätzende Flüssigkeit versprühen. Woran liegt das?
Die Antwort ist einfach: da die Zellen in Reihe verdrahtet sind, fließt der Strom durch alle Zellen. Wenn deren Ladung unterschiedlich ist, ist deren Spannung unter Last natürlich auch unterschiedlich. Ist eine Zelle nun leer, so fließt ja weiterhin Strom durch diese Zelle und sie wird negativ aufgeladen. Das quitieren die Batterien mit Auslaufen, je nach Stromstärke mit mehr oder weniger Getöse. Was her muss, ist eine einfache Möglichkeit die Spannung jeder einzelnen Batterie zu überwachen und bevor sie Schaden anrichten kann, muss eine Meldung kommen.

Der Joule-Thief XXL V2.0 ist geboren!
Ich habe einen meiner Arduinos mit einem LCD-Display versehen und die Spannung jeder einzelnen Zelle über ein Widerstandsnetzwerk gemessen und angezeigt. Fällt nun die Spannung einer Zelle unter +0.01V, so wird ein Piepston ausgegeben, der die Position der Zelle im Verbund angibt. So ist sichergestellt, dass auch dann, wenn die Gesamtspannung zu niedrig ist um eine vernünftige Anzeige zu erzeugen, die leere Zelle gemeldet wird.


Zur Verdeutlichung habe ich die Schaltung  mal im Eagle gemalt:

Die Spannungen werden über die Potis abgegriffen und dann so eingestellt, dass die Eingangsspannung des Arduino auf dessen Analog-Pins nicht überschritten wird. Das bedeutet dann, dass die Spannung an A0 bei 0-1.5V, an A1 zwischen 0-3.0V, an A2 zwischen 0-4.5V usw. liegt. Die Messgenauigkeit wird bei jeder weiteren Zelle schlechter, aber für meine Anforderungen reicht das vollkommen.



Und so sieht das Programm aus:
// ---------------------------------------------------------
// Batteriemessgerät
// es wird die Spannung von 6 Batterien gemessen und gleichzeitig
// auf einem LCD-Display dargestellt.
// ---------------------------------------------------------

#include

#define DEBUG
#include "log.h"

struct _Batterie
{
    float spannung;  // die gemessene Spannung
    bool ausgetastet;  // wird die Spannung gerade angezeigt, oder ausgetastet
    float abschaltspannung;  // darunter ist die Batterie leer
};

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // initialize the library with the numbers of the interface pins
int AnalogBatPin1 = A0;
int AnalogBatPin2 = A1;
int AnalogBatPin3 = A2;
int AnalogBatPin4 = A3;
int AnalogBatPin5 = A4;
int AnalogBatPin6 = A5;
int TasterPin = 10;
int SummerPin = 9;
int LEDPin =13;
bool Pieps; // währenddessen wird gepiepst
int Slot; // zählt immer durch
struct _Batterie Batterien[6];
bool SekundenBlinkbit; // toggle jede Sekunde
bool HalbsekundenBlinkbit; // toggle jede halbe Sekunde
bool ViertelsekundenBlinkbit; // toggle jede viertel Sekunde

#define NEIN 0  // Taste nicht gedrückt
#define KURZ 1  // Taste kurz gedrückt
#define LANG 2  // Taste lang gedrückt

// -------------------------------
void setup()
{
    SERIAL_BEGIN;
    lcd.begin(16, 2);    // set up the LCD's number of columns and rows:
    analogReference(INTERNAL);
    pinMode(TasterPin, INPUT);
    pinMode(SummerPin, OUTPUT);
    pinMode(LEDPin, OUTPUT);
    InitVariablen();
}

// -------------------------------
void InitVariablen(void)
{
    int i;

    for(i=0; i<6 br="" i="">        Batterien[i].abschaltspannung = 0.1F;
}

// -------------------------------
void MesseBatterien(void)
{
    int aval;
    float offset[6] = { 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F };
    float zaehl[6] = { 1.24F, 1.24F, 1.24F, 1.24F, 1.24F, 1.24F };
    float nenner[6] = { 0.83F, 0.83F, 0.83F, 0.83F, 0.83F, 0.83F };

    aval = analogRead(AnalogBatPin1);
    Batterien[0].spannung = (float)aval *zaehl[0] / nenner[0] / 1000.0F + offset[0];
    aval = analogRead(AnalogBatPin2);
    Batterien[1].spannung =
        (float)aval *2.0F * zaehl[1] / nenner[1] / 1000.0F + offset[1];
    Batterien[1].spannung -= Batterien[0].spannung;
    aval = analogRead(AnalogBatPin3);
    Batterien[2].spannung =
        (float)aval *3.0F * zaehl[2] / nenner[2] / 1000.0F + offset[2];
    Batterien[2].spannung -= Batterien[0].spannung;
    Batterien[2].spannung -= Batterien[1].spannung;
    aval = analogRead(AnalogBatPin4);
    Batterien[3].spannung =
        (float)aval *4.0F * zaehl[3] / nenner[3] / 1000.0F + offset[3];
    Batterien[3].spannung -= Batterien[0].spannung;
    Batterien[3].spannung -= Batterien[1].spannung;
    Batterien[3].spannung -= Batterien[2].spannung;
    aval = analogRead(AnalogBatPin5);
    Batterien[4].spannung =
        (float)aval *5.0F * zaehl[4] / nenner[4] / 1000.0F + offset[4];
    Batterien[4].spannung -= Batterien[0].spannung;
    Batterien[4].spannung -= Batterien[1].spannung;
    Batterien[4].spannung -= Batterien[2].spannung;
    Batterien[4].spannung -= Batterien[3].spannung;
    aval = analogRead(AnalogBatPin6);
    Batterien[5].spannung =
        (float)aval *6.0F * zaehl[5] / nenner[5] / 1000.0F + offset[5];
    Batterien[5].spannung -= Batterien[0].spannung;
    Batterien[5].spannung -= Batterien[1].spannung;
    Batterien[5].spannung -= Batterien[2].spannung;
    Batterien[5].spannung -= Batterien[3].spannung;
    Batterien[5].spannung -= Batterien[4].spannung;
}

// -------------------------------
void AusgabeLCD(void)
{
    char buffer1[40], buffer2[40];
    char t1[10], t2[10], t3[10], t4[10], t5[10], t6[10];

    if(Batterien[0].ausgetastet)
        strcpy(t1, "    ");
    else
        dtostrf(Batterien[0].spannung, 4, 2, t1);
    if(Batterien[1].ausgetastet)
        strcpy(t2, "    ");
    else
        dtostrf(Batterien[1].spannung, 4, 2, t2);
    if(Batterien[2].ausgetastet)
        strcpy(t3, "    ");
    else
        dtostrf(Batterien[2].spannung, 4, 2, t3);
    sprintf(buffer1, "%s %s %s  ", t1, t2, t3);

    if(Batterien[3].ausgetastet)
        strcpy(t4, "    ");
    else
        dtostrf(Batterien[3].spannung, 4, 2, t4);
    if(Batterien[4].ausgetastet)
        strcpy(t5, "    ");
    else
        dtostrf(Batterien[4].spannung, 4, 2, t5);
    if(Batterien[5].ausgetastet)
        strcpy(t6, "    ");
    else
    dtostrf(Batterien[5].spannung, 4, 2, t6);
    sprintf(buffer2, "%s %s %s  ", t4, t5, t6);

    lcd.setCursor(0, 0);
    lcd.print(buffer1);
    lcd.setCursor(0, 1);
    lcd.print(buffer2);
}

// -------------------------------
int LeseTaster(void)
{
    int tasterGedrueckt; // und hier ist die Taste
    static long int anfang; // hier merken wir uns, seit wann der Taster gedrückt ist
    long int jetzt=millis(); // Millisekunden
    long int drueckdauer; // so lange ist der Taster bereits gedrückt
    bool gedrueckt;
    static bool letztes_gedrueckt=0;

    gedrueckt = digitalRead(TasterPin);
    digitalWrite(LEDPin, gedrueckt); // visuelle Rückmeldung

    if (gedrueckt)
    {
        drueckdauer = jetzt-anfang;
        if (drueckdauer > 1000)
            tasterGedrueckt = LANG;
        else
        {
            if (drueckdauer > 500)
                tasterGedrueckt = KURZ;
            else
                tasterGedrueckt = NEIN;
        }
        if (letztes_gedrueckt == 0)
            anfang = jetzt;
    }
    else
        anfang = jetzt; // damit die Tasten-Drück-Dauer kurz ist

    letztes_gedrueckt = gedrueckt;

//    SERIAL_PRINT(anfang);
//    SERIAL_PRINT(" ");
//    SERIAL_PRINT(jetzt);
//    SERIAL_PRINT(" ");
//    SERIAL_PRINTLN(tasterGedrueckt);

    return tasterGedrueckt;
}

// -------------------------------
void PruefeBatterien(void)
{
    int i;
    int leereBatterie = -1;

    for(i=0; i<6 br="" i="">    {
        if (Batterien[i].spannung < Batterien[i].abschaltspannung)
            leereBatterie = i+1;           
    }
    // SERIAL_PRINTLN(leereBatterie);
   
    if ((leereBatterie > Slot) && ViertelsekundenBlinkbit)
    {
        Pieps = true;
        //SERIAL_PRINT("Slot ");
        //SERIAL_PRINTLN(Slot);
    }
    else
        Pieps = false;
   
}

// -------------------------------
void loop()
{
    long int jetzt;
    int taste;
    static long int jedeSekunde=0; // für den Timer jeder Sekunde
    static long int jedeHalbeSekunde=0; // für den Timer jeder halben Sekunde
    static long int jedeViertelSekunde=0; // für den Timer jeder viertel Sekunde

    jetzt = millis();
    taste = LeseTaster();
    PruefeBatterien();

    if ((jetzt-jedeSekunde) > 1000)
    {   // was hier steht, wird zyklisch jede Sekunde ausgeführt
        MesseBatterien();

        SekundenBlinkbit = ~SekundenBlinkbit;
        jedeSekunde = jetzt;
    }

    if ((jetzt-jedeHalbeSekunde) > 500)
    {   // was hier steht, wird zyklisch jede halbe Sekunde ausgeführt
        AusgabeLCD();

        Slot++;
        if (Slot>5)
            Slot=0;

        SERIAL_PRINTLN(Slot);

        HalbsekundenBlinkbit = ~HalbsekundenBlinkbit;
        jedeHalbeSekunde = jetzt;
    }

    if ((jetzt-jedeViertelSekunde) > 250)
    {   // was hier steht, wird zyklisch jede viertel Sekunde ausgeführt
        if (Pieps)
            digitalWrite(SummerPin, HIGH);
        else
            digitalWrite(SummerPin, LOW);

        ViertelsekundenBlinkbit = ~ViertelsekundenBlinkbit;
        jedeViertelSekunde = jetzt;
    }
}

Nicht schön, aber der Code funktioniert. Wie man sieht, habe ich auch noch eine Taste mit eingebaut, damit die Maschine auch noch parametriert werden könnte. Platz für V2.1...

Zu guter Letzt habe ich noch Alles auf einer Lochrasterplatine zusammengelötet: