Wie ich die USV-Anzeige prothetiert habe

während des Debuggens

In den späten 90ern bekam ich UPS. Wunderschön, mit einer LED-Anzeige und einer Reihe von Tasten, hatte es zwei Batterien im Inneren und konnte die Lebensdauer meines Computers zu diesem Zeitpunkt (zusammen mit dem Monitor!) Bis zu 15 Minuten lang unterstützen. Die Zeiten in Kamtschatka waren damals schwierig, die Lichter wurden regelmäßig ausgeschaltet, so dass dieses Gerät sehr praktisch war. Ich habe mit ihm die ganze Energiekrise durchgemacht, und mehr als einmal hat er meine Hausarbeiten vor dem plötzlichen Stromausfall bewahrt. Außerdem können Sie ein Tonbandgerät daran anschließen und im Kerzenlicht Radio oder Ihre Lieblingsbänder hören, um sich ein Abendessen auf einem tragbaren Gasherd vorzubereiten ...

Natürlich brach die USV. Das erste Mal brannte sein Transformator aus. Nicht derjenige, der groß ist und sich im Wechselrichter befindet, sondern ein kleiner, wahrscheinlich zum Messen der Spannung im Netzwerk. Da ich nicht die gleiche Fabrik gefunden habe, habe ich eine selbstgemachte eingerichtet, und das Gerät hat noch einige Zeit funktioniert. Dann hörte auf. Lange, sehr lange konnte ich den Grund nicht finden. Ich musste verschiedene Teile löten, auf Leistung prüfen und zurücklöten. Das Problem wurde nicht gefunden. Das entkernte Gerät fiel ein paar Jahre lang unter das Bett, bis mir eines schönen Tages die Idee kam, 5 Volt direkt an die Steuerung anzulegen. Und siehe da: Es gab einen Piepton des eingebauten Lautsprechers und Zahlen erschienen auf der LED-Anzeige. Er war am Leben! Außerdem ist es eine Frage der Technologie: Ich ging mit einem Voltmeter über den Stromversorgungskreis und stellte fest, dass eine Sicherung auf der Platine gelötet war, die unverschämt wie ein Widerstand aussah!Die Sicherung (natürlich durchgebrannt) wurde ersetzt und die USV zum Leben erweckt.

Leider sind meine Reparatur und zwei Jahre unter dem Bett nicht umsonst für das Gerät weggegangen. Auf unverständliche Weise brannte der Port in der Steuerung aus, was für das Leuchten der grünen „Online“ -LED und des niedrigsten Segments aller digitalen Segmentanzeigen verantwortlich war. Es gibt nichts zu tun - ich musste mich abfinden. Nach einiger Zeit verließ ich Kamtschatka und unsere Wege gingen auseinander.

Jahre vergingen und als ich zu meinen Eltern kam, fand ich in der hinteren Ecke meine ununterbrochene Lieblingsbatterie: verlassen, schmutzig, ohne Batterien und Gummibeine. Zu diesem Zeitpunkt hatte ich bereits meine eigene Wohnung in einer anderen Stadt erworben, daher wurde beschlossen, die Zuflucht für mich zu nehmen, ihre Effizienz wiederherzustellen und sie für den beabsichtigten Zweck zu nutzen.

Herausforderung

Zunächst wurde die USV gewaschen und getrocknet. Dann wurden in einem bestimmten Radioteilelager geeignete Gummifüße und neue Batterien gekauft. Zu meiner großen Überraschung wurde im selben Geschäft ein geeigneter Transformator gefunden, als Gegenleistung für mein hausgemachtes Produkt. Ein paar Arbeitstage, und die gewaschene, aktualisierte, ununterbrochene Batterie quietschte glücklich und begann, ihre neuen Batterien aufzuladen. Alles war in Ordnung, aber die Anzeige funktionierte immer noch nicht.

Die Idee, das Problem zu beheben, kam mir schon früher. Nachdem ich alle Zahlen (und einige Buchstaben) des Sieben-Segment-Indikators auf das Notizbuchblatt gezeichnet hatte, wurde mir klar, dass es möglich ist, den Zustand des niedrigsten Segments durch den Zustand des Restes zu bestimmen. Die grüne LED kann leuchten, wenn andere LEDs nicht leuchten. Es gab viele Gedanken darüber, wie dies getan werden könnte: von einem einfachen ROM-Chip zu einem einfachen FPGA. Aber da ich Student war und in Kamtschatka lebte, hatte ich keine Gelegenheit, etwas Komplizierteres als kleinliche Logik zu erwerben. Das Fixieren des Indikators wurde verschoben.

Segmente zeichnen

Dieses Mal habe ich beschlossen, das Problem ernsthaft anzugehen. Nachdem ich in Behältern gestöbert hatte, fand ich bei mir weder ROM noch FPGA und keine CPLD. Aber Arduino Pro Mini, oder besser gesagt, sein billiger chinesischer Klon mit Ali Express, fiel in die Hände von. Ich habe Arduin gekauft, um einen Mini-Computer auf Basis einer WiFi-SD-Karte von Transcend herzustellen. Leider starb die Karte während der Experimente und die Platine mit dem Mikrocontroller blieb im Leerlauf. Nichts, wir fanden sie eine neue Herausforderung!

Arbeit

Im Anzeigemodul ist eine dynamische Anzeige implementiert: Segmentsignale sind allen vier Anzeigen gemeinsam, so dass jeweils nur eine von ihnen leuchtet. Außerdem: Wie bei der fünften Anzeige sind auch drei LEDs angeschlossen. Mit fünf Auswahlsignalen können Sie festlegen, welche Anzeige (oder LED-Linie) gerade verwendet wird. Diese Auswahlsignale werden nacheinander mit einer ziemlich hohen Geschwindigkeit abgetastet, und aufgrund der Trägheit des Sehens scheinen alle Anzeigen gleichzeitig zu leuchten.

Zuerst wollte ich die einfachste Lösung umgehen: einen gewöhnlichen Zyklus, der Signale von sechs Arbeitssegmenten prüft und den nicht funktionierenden siebten ein- oder ausschaltet. Tatsächlich ist dies nur eine Emulation des ROM, über die ich ganz am Anfang nachgedacht habe.

Dazu musste ich sechs Arbeitssegmente an den Eingang des Mikrocontrollers und ein nicht funktionierendes Segment an den Ausgang anschließen.

Nachdem ich ein kleines Array entworfen hatte, das die verschiedenen Zustände der Eingänge mit dem Ausgang und der Schleife verglich, die dieses Array umgeht, lud ich alles in den Controller und bekam sofort ein Problem: Das untere Segment leuchtete immer. Erster Gedanke: die Neigung im Programm. Unabhängig davon, wie oft ich mir den Code angesehen habe, wurden keine Fehler gefunden. Am Ende stellte sich heraus, dass mein Zyklus in keiner Weise mit dem Umschalten der Indikatoren synchronisiert war. Wenn wir den Status der Segmente am Ende des Zyklus der Auswahl eines Indikators lesen, ist es wahrscheinlich, dass wir das nächste Segment beim nächsten aufleuchten oder absenken. Das Durcheinander.

Ohne nachzudenken, löte ich fünf Indikatorauswahlsignale an die verbleibenden freien Arduino-Eingänge, stellte sie so ein, dass sie einen Interrupt erzeugten, und begann, einen Interrupt-Handler anstelle einer Schleife zu verwenden. Es wurde besser, löste aber das Problem nicht. An den richtigen Stellen brannte das Segment so, wie es sollte, aber an den Stellen, an denen es gelöscht werden sollte, gab es kein helles Restlicht.

Nachdem ich einige Zeit darüber nachgedacht hatte, entschied ich, dass dieser Effekt auftreten kann, wenn der Suchzyklus im Array des gewünschten Zustands für die Segmente länger dauert als die Brenndauer des Indikators. In diesem Fall verlassen wir auch unsere Phase und verwalten das Segment des nächsten Indikators. Es ist notwendig, dass zwischen dem Zeitpunkt des Empfangs des Interrupts vom Auswahlsignal zum Segmentsteuerbefehl so wenig Zeit wie möglich vergeht. Dies kann nur auf eine Weise erfolgen: Um den Code, der die Entscheidung über den Status des Segments trifft, aus dem Interrupt-Handler zu entfernen, führen Sie ihn in der Hauptschleife mit minimaler Priorität aus und speichern Sie das Ergebnis in einer Art globalem Puffer. Der Interrupt-Handler muss nur den Wert dieses globalen Puffers lesen und das Segment je nach Inhalt löschen oder beleuchten. Im schlimmsten FallWir können nur mit der Änderung des Zustands des Segments in einem bestimmten Indikator zu spät kommen, aber wir werden nicht in den nächsten aufsteigen.

Das war die richtige Entscheidung. Aber schließlich funktionierte es erst, nachdem ich den Entscheidungszyklus mit Hilfe von Spin-Lock mit einem Interrupt synchronisiert und die Verarbeitung des Interrupts während dieses Zyklus verboten hatte. Und es hat nicht nur verdient, sondern auch verdient, wie es sollte!

Es gab ein weiteres Problem mit den Indikatoren: Meistens zeigten sie nur Zahlen. Nach dem Einschalten der USV wurde jedoch der Testprozess gestartet, bei dem zusätzlich zu den Zahlen zwei weitere Wörter angezeigt wurden: TEST und PASS. Und wenn die Buchstaben T, E und P einfach zu dem Array gültiger Zeichen hinzugefügt werden könnten und S gleich 5s wäre, dann wäre der Buchstabe A aus Sicht meines Programms aus Sicht der acht nichts. Der Entscheidungszyklus fand einfach das entsprechende Muster im Array und zeichnete das untere Segment. Es war notwendig, sich etwas auszudenken, um dies zu vermeiden.

Und ich habe es mir ausgedacht. Während des Eintreffens eines Signals über einen Indikatorwechsel muss bestimmt werden, zu welchem ​​Indikator er gehört, und der Status seiner Segmente in einer speziell dafür zugewiesenen Variablen gespeichert werden. Jetzt kann ich jederzeit den aktuellen Inhalt aller vier Indikatoren auf einmal ziemlich genau bestimmen. Und wenn die Symbole P, 5 und 5 auf dem ersten, dritten und vierten Symbol angezeigt werden, ist das zweite Symbol definitiv A, und Sie müssen das untere Segment nicht beleuchten. Für alle Fälle habe ich auch die Verarbeitung des Wortes FAIL hinzugefügt, die ich noch nie gesehen hatte, die ich aber erwartet hatte zu erscheinen.

Alles, mit der Digitalanzeige vorbei. Es bleibt nur die grüne Online-LED zu fixieren. Aber hier erwartete mich eine Überraschung ... Die Idee war folgende: Die grüne LED (Online) leuchtet immer alleine auf. Wenn die gelben (Batterie) oder roten (Batterie schwach) LEDs leuchten, sollte die grüne nicht leuchten. Daher löten wir Drähte von diesen LEDs an den Mikrocontroller, setzen ein einfaches if () mit einem logischen „ODER“ und alles sollte funktionieren. Es stellte sich jedoch heraus, dass die gelbe LED nicht ständig leuchtet, sondern schnell blinkt, wenn sie leuchtet. Schnell, aber nicht genug, damit if () die grüne LED überspringt und nicht leuchtet. Es stellte sich heraus, dass die grüne LED beim Arbeiten im Netzwerk bei voller Helligkeit aufleuchtet. Beim Umschalten auf den Akku brannte sie jedoch bei halber Helligkeit, brannte aber immer noch. Kein Problem, dachte ich, ich werde einen einfachen Tiefpassfilter einsetzen:Ich werde alle schnellen Blitze abschneiden, aber ich werde nur langsame Blitze hinterlassen, die dem Übergang zur Batterie entsprechen und umgekehrt. Die Analyse der Blinkzeit der gelben LED brachte folgende Überraschung: Die Periode der ihr zugeführten Impulse ist sehr instabil und kann recht große Werte erreichen. Es stellte sich heraus, dass der Filter Signale nicht höher als 0,5-1 Hz durchlassen sollte. Dies ist nicht sehr gut, da wir eine ziemlich große Verzögerung beim Ändern des Online-Signals bekommen, aber es ist ziemlich tolerierbar.da wir eine ziemlich große Verzögerung beim Ändern des Online-Signals bekommen, ist es aber ziemlich erträglich.da wir eine ziemlich große Verzögerung beim Ändern des Online-Signals bekommen, ist es aber ziemlich erträglich.

Ich habe beschlossen, den Filter sehr einfach zu machen. Wir überwachen 50 Mal in regelmäßigen Abständen den Status der gelben und roten LEDs. Wenn einer von ihnen brennt, erhöhen wir den Spezialzähler um eins. Dann überprüfen wir den Wert des Zählers. Wenn die Steuer-LEDs 50% der überprüften Zeit aufleuchten, glauben wir, dass er eingeschaltet ist, und wenn weniger, ist er ausgeschaltet. Beim Debuggen musste ich diese Zahl auf 10% reduzieren. Warum - habe es nicht herausgefunden.

Endmontage

Und alles hat funktioniert! Es blieb nur, das Arduino-Board mit doppelseitigem Klebeband und einer Klebepistole wunderschön im USV-Gehäuse zu montieren.



Für Neugierige:
der resultierende Code
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>



#define AVG_TIME    50
#define THS_TIME    45


#define SD_SEG_A    _BV(0)
#define SD_SEG_B    _BV(1)
#define SD_SEG_C    _BV(2)
#define SD_SEG_D    _BV(3)
#define SD_SEG_E    _BV(4)
#define SD_SEG_F    _BV(5)
#define SD_SEG_G    _BV(6)
#define SD_LED_RED  SD_SEG_A
#define SD_LED_YLW  SD_SEG_C

#define LD_SEL_LED  _BV(0)
#define LD_SEL_1    _BV(1)
#define LD_SEL_2    _BV(2)
#define LD_SEL_3    _BV(3)
#define LD_SEL_4    _BV(4)

#define GET_SEL     (PINC & 0x1f)


#define SD_SYM_NONE (0)
#define SD_SYM_0    (SD_SEG_A | SD_SEG_B | SD_SEG_C | SD_SEG_D | SD_SEG_E | SD_SEG_F)
#define SD_SYM_1    (SD_SEG_B | SD_SEG_C)
#define SD_SYM_2    (SD_SEG_A | SD_SEG_B | SD_SEG_D | SD_SEG_E | SD_SEG_G)
#define SD_SYM_3    (SD_SEG_A | SD_SEG_B | SD_SEG_C | SD_SEG_D | SD_SEG_G)
#define SD_SYM_4    (SD_SEG_B | SD_SEG_C | SD_SEG_F | SD_SEG_G)
#define SD_SYM_5    (SD_SEG_A | SD_SEG_C | SD_SEG_D | SD_SEG_F | SD_SEG_G)
#define SD_SYM_6    (SD_SEG_A | SD_SEG_C | SD_SEG_D | SD_SEG_E | SD_SEG_F | SD_SEG_G)
#define SD_SYM_7    (SD_SEG_A | SD_SEG_B | SD_SEG_C)
#define SD_SYM_8    (SD_SEG_A | SD_SEG_B | SD_SEG_C | SD_SEG_D | SD_SEG_E | SD_SEG_F | SD_SEG_G)
#define SD_SYM_9    (SD_SEG_A | SD_SEG_B | SD_SEG_C | SD_SEG_D | SD_SEG_F | SD_SEG_G)
#define SD_SYM_E    (SD_SEG_A | SD_SEG_D | SD_SEG_E | SD_SEG_F | SD_SEG_G)
#define SD_SYM_P    (SD_SEG_A | SD_SEG_B | SD_SEG_E | SD_SEG_F | SD_SEG_G)
#define SD_SYM_T    (SD_SEG_D | SD_SEG_E | SD_SEG_F | SD_SEG_G)

#define GET_SYM     (~PIND & 0x7f)


#define BROKEN_SEG  (SD_SEG_D)



static uint8_t sd_symbols[] = {                 // list of known symbols
    SD_SYM_NONE,
    SD_SYM_0, SD_SYM_1, SD_SYM_2, SD_SYM_3, SD_SYM_4,
    SD_SYM_5, SD_SYM_6, SD_SYM_7, SD_SYM_8, SD_SYM_9,
    SD_SYM_E, SD_SYM_P, SD_SYM_T
};
volatile static uint8_t sel, symbol;            // current input signals
volatile static short fb0, fb1, fb2, fb3, fb4;  // display frame buffer


// display routine
ISR(PCINT1_vect) {
    sel = GET_SEL;
    symbol = GET_SYM;

    if (((sel & LD_SEL_LED) && fb0) ||
            ((sel & LD_SEL_1) && fb1) ||
            ((sel & LD_SEL_2) && fb2) ||
            ((sel & LD_SEL_3) && fb3) ||
            ((sel & LD_SEL_4) && fb4)){
        PORTD &= ~BROKEN_SEG;
    }
    else {
        PORTD |= BROKEN_SEG;
    }
}


//
// entry point
//

int main(void)
{
    int cur_time;
    int led_on_time;
    uint8_t last_symbol_1, last_symbol_2, last_symbol_3, last_symbol_4;
    int i;

    // setup GPIO ports
    DDRC = 0;
    DDRD = BROKEN_SEG;

    // setup pin change interrupt
    PCICR |= _BV(PCIE1);
    PCMSK1 |= _BV(PCINT8) | _BV(PCINT9) | _BV(PCINT10) | _BV(PCINT11) | _BV(PCINT12);

    cur_time = 0;
    led_on_time = 0;
    last_symbol_1 = last_symbol_2 = last_symbol_3 = last_symbol_4 = 0;
    fb0 = fb1 = fb2 = fb3 = fb4 = 0;

    while(1) {
        // sync with display strobe
        sei();
        while (sel == 0) {}
        cli();

        // if select one of segment indicator
        if (sel & (LD_SEL_1 | LD_SEL_2 | LD_SEL_3 | LD_SEL_4)) {
            // looking for displayed symbol
            for (i = 0; i < 14; i++) {
                uint8_t sd_symbol = sd_symbols[i];
                if ((symbol & ~BROKEN_SEG) == (sd_symbol & ~BROKEN_SEG)) {
                    short val;
                    if (sd_symbol & BROKEN_SEG) {
                        val = 1;
                    } else {
                        val = 0;
                    }

                    if (sel & LD_SEL_1) {
                        last_symbol_1 = sd_symbol;
                        fb1 = val;
                    } else if (sel & LD_SEL_2) {
                        last_symbol_2 = sd_symbol;
                        fb2 = val;
                    } else if (sel & LD_SEL_3) {
                        last_symbol_3 = sd_symbol;
                        fb3 = val;
                    } else if (sel & LD_SEL_4) {
                        last_symbol_4 = sd_symbol;
                        fb4 = val;
                    }

                    // PASS workaround
                    if ((last_symbol_1 == SD_SYM_P) &&(last_symbol_2 == SD_SYM_8) &&
                            (last_symbol_3 == SD_SYM_5) && (last_symbol_4 == SD_SYM_5)) {
                        fb2 = 0;
                    }
                    // FAIL workaround
                    else if ((last_symbol_1 == SD_SYM_E) &&(last_symbol_2 == SD_SYM_8) &&
                            (last_symbol_3 == SD_SYM_1) && (last_symbol_4 == SD_SYM_1)) {
                        fb1 = 0;
                        fb2 = 0;
                        fb4 = 1;
                    }

                    break;
                }
            }
        }
        // if select LED line
        else if (sel & LD_SEL_LED) {
            if (cur_time++ > AVG_TIME) {
                if (led_on_time < THS_TIME) {
                    fb0 = 0;
                } else {
                    fb0 = 1;
                }
                cur_time = 0;
                led_on_time = 0;
            } else {
                if ((symbol & (SD_LED_RED | SD_LED_YLW)) == 0) {
                    led_on_time++;
                }
            }
        }

        // reset sync flag
        sel = 0;
    }

    return 0;
}

Source: https://habr.com/ru/post/de397697/


All Articles