Comment j'ai prothétisé l'indicateur UPS

pendant le débogage

À la fin des années 90, j'ai eu UPS. Magnifique, avec un indicateur LED et un tas de boutons, il avait deux piles à l'intérieur et pouvait soutenir la vie de mon ordinateur à ce moment-là (avec le moniteur!) Pendant aussi longtemps que 15 minutes. Les temps au Kamchatka étaient alors difficiles, les lumières étaient éteintes régulièrement, donc cet appareil était très pratique. J'ai traversé toute la crise énergétique avec lui, et plus d'une fois, il a sauvé mes documents de cours de la soudaine perte d'électricité. Et aussi, vous pouvez y connecter un magnétophone et, à la lueur d'une bougie, écouter la radio ou vos cassettes préférées, vous préparer un dîner sur une cuisinière à gaz portable ...

Naturellement, l'UPS cassait. La première fois que son transformateur a grillé. Pas celle qui est grande et qui se trouve dans l'onduleur, mais certaines petites, probablement pour mesurer la tension dans le réseau. Ne trouvant pas la même usine, j'en ai créé une faite maison et l'appareil a fonctionné encore un peu. Puis arrêté. Pendant très longtemps, je n'ai pas pu trouver la raison. J'ai dû souder différentes pièces, vérifier leur performance et les souder. Le problème est introuvable. L'appareil vidé a chuté sous le lit pendant quelques années, jusqu'à ce qu'un beau jour, l'idée m'est venue d'appliquer 5 volts directement au contrôleur. Et voilà: il y avait un bip du haut-parleur intégré et des chiffres apparaissaient sur l'indicateur LED. Il était vivant! De plus, c'est une question de technologie: j'ai parcouru le circuit d'alimentation avec un voltmètre et j'ai découvert qu'un fusible avait été soudé sur la carte, qui ressemblait insolemment à une résistance!Le fusible (naturellement grillé) a été remplacé et l'onduleur a pris vie.

Malheureusement, ma réparation et deux ans sous le lit ne sont pas partis pour l'appareil pour rien. D'une manière incompréhensible, le port a grillé dans le contrôleur, qui était responsable de la lueur de la LED verte "On-Line" et du segment le plus bas de tous les indicateurs de segment numériques. Il n'y a rien à faire - j'ai dû accepter. Après un certain temps, j'ai quitté le Kamtchatka et nos chemins ont divergé.

Les années ont passé et, étant arrivé pour rendre visite à mes parents, dans le coin le plus éloigné, j'ai trouvé ma batterie ininterrompue préférée: abandonnée, sale, sans batteries et jambes en caoutchouc. À ce moment-là, j'avais déjà acquis mon propre logement dans une autre ville, alors la décision a été prise de prendre le refuge pour moi, de restaurer son efficacité et de l'utiliser pour sa destination.

Défi

Tout d'abord, l'onduleur a été lavé et séché. Ensuite, dans un certain magasin de pièces de radio, des pieds en caoutchouc appropriés et de nouvelles piles ont été achetés. À ma grande surprise, un transformateur adapté a été trouvé dans le même magasin, en échange de mon produit fait maison. Quelques jours de travail, et la batterie ininterrompue lavée et mise à jour grinça joyeusement et commença à charger ses nouvelles batteries. Tout allait bien, mais l'indicateur ne fonctionnait toujours pas.

L'idée de le réparer m'est venue avant. Après avoir dessiné tous les chiffres (et quelques lettres) de l'indicateur à sept segments sur la feuille de cahier, j'ai réalisé qu'il était possible de déterminer l'état du segment le plus bas par l'état du reste. La LED verte peut être allumée lorsque les autres LED ne sont pas allumées. Il y avait beaucoup de réflexions sur la façon dont cela pourrait être fait: d'une simple puce ROM à un simple FPGA. Mais, étant étudiant et vivant au Kamchatka, je n'ai pas eu l'occasion d'acquérir quoi que ce soit de plus compliqué que la petite logique. La fixation de l'indicateur a été reportée.

dessiner des segments

Cette fois, j'ai décidé de m'attaquer sérieusement au problème. Ayant fouillé dans des bacs, je n'ai encore trouvé ni ROM, ni FPGA ni CPLD. Mais, Arduino Pro Mini, ou plutôt, son clone chinois bon marché avec Ali Express, est tombé entre les mains de. J'ai acheté Arduin afin de fabriquer un mini-ordinateur basé sur la carte SD WiFi de Transcend. Malheureusement, la carte est morte pendant les expériences et la carte avec le microcontrôleur est restée inactive. Rien, nous lui avons trouvé un nouveau défi!

Travail

L'affichage dynamique est implémenté dans le module d'affichage: les signaux de segment sont communs aux quatre indicateurs de sorte qu'un seul d'entre eux est allumé à la fois. De plus: comme avec le cinquième indicateur, trois LED sont également connectées. Cinq signaux de sélection vous permettent de spécifier quel indicateur (ou ligne de LED) est actuellement utilisé. Ces signaux de sélection sont balayés séquentiellement à une vitesse assez élevée et, en raison de l'inertie de la vision, il semble que tous les indicateurs soient allumés en même temps.

Au début, je voulais contourner la solution la plus simple: un cycle ordinaire qui vérifie les signaux de six segments de travail, et active ou désactive le septième qui ne fonctionne pas. En fait, ce n'est qu'une émulation de la ROM, à laquelle j'ai pensé au tout début.

Pour ce faire, j'ai dû connecter six segments de travail à l'entrée du microcontrôleur et un segment non fonctionnel à la sortie.

Après avoir esquissé un petit tableau qui comparait les différents états des entrées avec la sortie et la boucle qui contourne ce tableau, j'ai tout chargé dans le contrôleur et j'ai immédiatement eu un problème: le segment inférieur brillait toujours. Première pensée: le cant dans le programme. Cependant, peu importe combien j'ai regardé le code, aucune erreur n'a été trouvée. Au final, on a compris que mon cycle n'était en aucun cas synchronisé avec la commutation d'indicateurs. Si nous lisons l'état des segments à la fin du cycle de sélection d'un indicateur, il est probable que nous allumerons ou baisserons le segment suivant au suivant. Le désordre.

Sans réfléchir à deux fois, j'ai soudé cinq signaux de sélection d'indicateur aux entrées Arduino libres restantes, les ai configurées pour générer une interruption et j'ai commencé à utiliser un gestionnaire d'interruption au lieu d'une boucle. Cela s'est amélioré, mais n'a pas résolu le problème. Aux bons endroits, le segment a brûlé comme il se doit, mais aux endroits où il était censé s'éteindre, il n'y avait pas de lueur résiduelle brillante.

Après avoir réfléchi un peu plus longtemps, j'ai décidé que cet effet peut apparaître si le cycle de recherche dans le tableau de l'état souhaité pour les segments prend plus de temps que le temps de gravure de l'indicateur. Dans ce cas, nous sortons également de notre phase et gérons le segment du prochain indicateur. Il faut que le moins de temps possible s'écoule entre le moment de la réception de l'interruption du signal de sélection et la commande de commande de segment. Cela ne peut être fait que d'une seule manière: pour supprimer le code qui prend la décision sur l'état du segment du gestionnaire d'interruption, l'exécuter dans la boucle principale avec une priorité minimale et enregistrer le résultat dans une sorte de tampon global. Le gestionnaire d'interruption n'aura qu'à lire la valeur de ce tampon global et à éteindre ou allumer le segment, selon son contenu. Dans le pire des casnous ne pouvons être en retard qu'avec le changement de l'état du segment dans un certain indicateur, mais nous ne monterons pas dans le suivant.

C'était la bonne décision. Mais finalement, cela n'a fonctionné qu'après avoir synchronisé le cycle de prise de décision avec une interruption à l'aide de spin-lock et interdit le traitement des interruptions pendant ce cycle. Et cela a non seulement gagné, mais gagné comme il se doit!

Il y avait un autre problème avec les indicateurs: la plupart du temps, ils ne montraient que des chiffres. Cependant, après avoir allumé l'onduleur, le processus de test a commencé, au cours duquel, en plus des chiffres, deux autres mots sont apparus: TEST et PASS. Et si, les lettres T, E et P pouvaient simplement être ajoutées au tableau de caractères valides, et S était le même que 5s, alors la lettre A n'était rien, du point de vue de mon programme, de la figure huit. Le cycle de décision a simplement trouvé le motif approprié dans la matrice et a dessiné le segment inférieur. Il fallait trouver quelque chose pour éviter cela.

Et je suis venu avec. Lors de l'arrivée d'un signal sur un changement d'indicateur, il est nécessaire de déterminer à quel indicateur il appartient et d'enregistrer l'état de ses segments dans une variable spécialement allouée pour lui. Maintenant, à tout moment, je peux déterminer avec précision le contenu actuel des quatre indicateurs à la fois. Et si les symboles P, 5 et 5 sont affichés respectivement sur le premier, le troisième et le quatrième, alors le deuxième symbole est définitivement A, et vous n'avez pas besoin d'allumer le segment inférieur. Au cas où, j'ai également ajouté le traitement du mot FAIL, que je n'avais jamais vu, mais que je m'attendais à voir apparaître.

Tout, avec l'indicateur numérique terminé. Il ne reste plus qu'à fixer la LED verte On-Line. Mais là, une surprise m'attendait ... L'idée était la suivante: la LED verte (On-Line) s'allume toujours seule. Si les voyants jaune (batterie) ou rouge (batterie faible) sont allumés, le vert ne doit pas s'allumer. Ainsi, nous soudons les fils de ces LED au microcontrôleur, mettons un simple if () avec un «OU» logique et tout devrait fonctionner. Mais il s'est avéré que lorsque la LED jaune est allumée, elle ne s'allume pas en permanence, mais clignote rapidement. Rapide, mais pas suffisant pour que if () saute et n'allume pas la LED verte. Il s'est avéré que lorsque vous travaillez à partir du réseau, la LED verte s'allume à pleine luminosité, mais lors du passage à la batterie, elle a brûlé à la moitié de la luminosité, mais toujours brûlée. Bon, pas de problème, je pensais, je vais mettre un simple filtre passe-bas:Je couperai tous les flashs rapides, mais je n'en laisserai que des lents qui correspondent au passage à la batterie et vice versa. L'analyse du temps de clignotement de la LED jaune a apporté la surprise suivante: la période des impulsions qui lui sont fournies est très instable et peut atteindre des valeurs assez importantes. Il s'est avéré que le filtre devait transmettre des signaux ne dépassant pas 0,5-1 Hz. Ce n'est pas très bon, car nous obtenons un délai assez important pour changer le signal en ligne, mais c'est tout à fait tolérable.car nous obtenons un délai assez important pour changer le signal en ligne, mais c'est assez supportable.car nous obtenons un délai assez important pour changer le signal en ligne, mais c'est assez supportable.

J'ai décidé de rendre le filtre très simple. Nous contrôlons 50 fois, à intervalles réguliers, l'état des LED jaunes et rouges. Si l'un d'eux brûle, nous augmentons le compteur spécial d'une unité. Ensuite, nous vérifions la valeur du compteur, et si les LED de contrôle s'allument pendant 50% du temps vérifié, nous pensons qu'il est allumé, et si moins, il est éteint. Lors du débogage, j'ai dû réduire ce chiffre à 10%. Pourquoi - n'a pas compris.

assemblage final

Et tout a fonctionné! Il ne restait plus qu'à monter magnifiquement la carte Arduino dans le boîtier de l'onduleur avec du ruban adhésif double face et un pistolet adhésif.



Pour les curieux:
le code résultant
#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/fr397697/


All Articles