Como protetizei o indicador do no-break

durante a depuração

No final dos anos 90, recebi a UPS. Bonito, com um indicador LED e um monte de botões, ele tinha duas baterias dentro e poderia suportar a vida útil do meu computador naquele momento (junto com o monitor!) Por até 15 minutos. Os tempos em Kamchatka eram difíceis, as luzes eram apagadas regularmente, portanto esse dispositivo era muito útil. Passei por toda a crise de energia com ele e mais de uma vez ele salvou meus documentos da perda repentina de eletricidade. Além disso, você pode conectar um gravador e, à luz de uma vela, ouvir o rádio ou suas fitas favoritas, preparando um jantar em um fogão a gás portátil ...

Naturalmente, o no-break estava quebrando. A primeira vez que seu transformador queimava. Não é aquele que é grande e está no inversor, mas é pequeno, provavelmente para medir a tensão na rede. Não encontrando a mesma fábrica, montei uma de fabricação própria e o dispositivo funcionou por mais algum tempo. Então parou. Durante muito, muito tempo, não consegui encontrar o motivo. Eu tive que soldar peças diferentes, verificá-las quanto ao desempenho e soldá-las de volta. O problema não foi encontrado. O dispositivo estripado caiu embaixo da cama por alguns anos, até que, em um belo dia, surgiu a idéia de aplicar 5 volts diretamente no controlador. E eis que: houve um bipe do alto-falante embutido e os números apareceram no indicador LED. Ele estava vivo! Além disso, é uma questão de tecnologia: caminhei ao longo do circuito da fonte de alimentação com um voltímetro e descobri que um fusível havia sido soldado na placa, que parecia insolentemente um resistor!O fusível (queimado naturalmente) foi substituído e o no-break ganhou vida.

Infelizmente, meu reparo e dois anos debaixo da cama não foram embora para o dispositivo por nada. De alguma maneira incompreensível, a porta foi queimada no controlador, responsável pelo brilho do LED verde "On-Line" e pelo segmento mais baixo de todos os indicadores de segmento digital. Não há nada a ser feito sobre isso - eu tive que chegar a um acordo. Depois de algum tempo, deixei Kamchatka e nossos caminhos divergiram.

Os anos se passaram e, tendo chegado para visitar meus pais, no canto mais distante encontrei minha bateria ininterrupta favorita: abandonada, suja, sem baterias e pernas de borracha. Naquela época, eu já havia adquirido minha própria casa em outra cidade, então foi tomada a decisão de me refugiar no refúgio, restaurar sua eficiência e usá-la para a finalidade a que se destina.

Desafio

Primeiro de tudo, o no-break foi lavado e seco. Então, em uma determinada loja de peças de rádio, foram comprados pés de borracha adequados e baterias novas. Para minha grande surpresa, um transformador adequado foi encontrado na mesma loja, em troca do meu produto caseiro. Alguns dias de trabalho e a bateria ininterrupta, lavada e atualizada, guincharam alegremente e começaram a carregar suas novas baterias. Tudo estava bem, mas o indicador ainda não estava funcionando.

A idéia de consertar isso me veio antes. Tendo desenhado todos os números (e algumas letras) do indicador de sete segmentos na folha de caderno, percebi que é possível determinar o estado do segmento mais baixo pelo estado dos demais. O LED verde pode acender quando outros LEDs não estiverem acesos. Havia muitos pensamentos sobre como isso poderia ser feito: de um simples chip ROM a um simples FPGA. Mas, como eu era estudante e morava em Kamchatka, não tive a oportunidade de adquirir algo mais complicado do que lógica mesquinha. A correção do indicador foi adiada.

desenhar segmentos

Desta vez, decidi enfrentar o problema com seriedade. Tendo vasculhado os compartimentos, novamente não encontrei para mim nem ROM, nem FPGA e nenhum CPLD. Mas o Arduino Pro Mini, ou melhor, seu clone chinês barato com Ali Express, caiu nas mãos de. Comprei o Arduin para fazer um mini-computador baseado no cartão SD WiFi da Transcend. Infelizmente, o cartão morreu durante os experimentos, e a placa com o microcontrolador permaneceu inativa. Nada, achamos um novo desafio para ela!

Trabalho

A exibição dinâmica é implementada no módulo de exibição: os sinais de segmento são comuns a todos os quatro indicadores, de modo que apenas um deles acenda por vez. Além disso: como se com o quinto indicador, três LEDs também estivessem conectados. Cinco sinais de seleção permitem especificar qual indicador (ou linha de LEDs) está sendo usado agora. Esses sinais de seleção são varridos sequencialmente a uma velocidade bastante alta e, devido à inércia da visão, parece que todos os indicadores estão acesos ao mesmo tempo.

No começo, eu queria resolver a solução mais simples: um ciclo comum que verifica sinais de seis segmentos de trabalho e liga ou desliga o sétimo que não funciona. Na verdade, isso é apenas uma emulação da ROM, sobre a qual pensei no começo.

Para fazer isso, tive que conectar seis segmentos de trabalho à entrada do microcontrolador e um segmento não de trabalho à saída.

Tendo esboçado uma pequena matriz que comparava os vários estados das entradas com a saída e o loop que contornava essa matriz, carreguei tudo no controlador e imediatamente tive um problema: o segmento inferior sempre brilhava. Primeiro pensamento: o cant no programa. No entanto, não importa o quanto olhei para o código, nenhum erro foi encontrado. No final, entendeu-se que meu ciclo não estava de forma alguma sincronizado com a troca de indicadores. Se lermos o estado dos segmentos no final do ciclo de seleção de um indicador, é provável que acendamos ou abaixemos o próximo segmento no próximo. A bagunça.

Sem pensar duas vezes, soldei cinco sinais de seleção de indicador nas entradas restantes restantes do Arduino, configurei-os para gerar uma interrupção e comecei a usar o manipulador de interrupção em vez de um loop. Tornou-se melhor, mas não resolveu o problema. Nos lugares certos, o segmento queimou como deveria, mas naqueles lugares onde deveria ser extinto, não havia um brilho residual brilhante.

Depois de pensar por mais algum tempo, decidi que esse efeito poderia aparecer se o ciclo de pesquisa na matriz do estado desejado para os segmentos demorar mais que o tempo de gravação do indicador. Nesse caso, também saímos de nossa fase e gerenciamos o segmento do próximo indicador. É necessário que o menor tempo possível passe entre o momento de receber a interrupção do sinal de seleção e o comando de controle de segmento. Isso poderia ser feito apenas de uma maneira: para remover o código que toma a decisão sobre o estado do segmento do manipulador de interrupções, execute-o no loop principal com prioridade mínima e salve o resultado em uma espécie de buffer global. O manipulador de interrupção precisará apenas ler o valor desse buffer global e extinguir ou iluminar o segmento, dependendo do seu conteúdo. No pior dos casossó podemos nos atrasar com a mudança no estado do segmento em um determinado indicador, mas não passaremos para o próximo.

Essa foi a decisão certa. Mas, finalmente, funcionou somente depois que sincronizei o ciclo de tomada de decisão com uma interrupção usando o bloqueio de rotação e proibi o processamento de interrupção durante esse ciclo. E não apenas ganhou, mas ganhou como deveria!

Havia outro problema com os indicadores: na maioria das vezes eles mostravam apenas números. No entanto, após ligar o no-break, o processo de teste foi iniciado, durante o qual, além dos números, apareceram mais duas palavras: TEST e PASS. E se as letras T, E e P pudessem ser simplesmente adicionadas à matriz de caracteres válidos e S fosse o mesmo que 5s, a letra A não seria nada, do ponto de vista do meu programa, das oito. O ciclo de decisão simplesmente encontrou o padrão apropriado na matriz e desenhou o segmento inferior. Era necessário inventar algo para evitar isso.

E eu inventei. Durante a chegada de um sinal sobre uma mudança de indicador, é necessário determinar a qual indicador pertence e salvar o estado de seus segmentos em uma variável especialmente alocada para ele. Agora, a qualquer momento, posso determinar com precisão o conteúdo atual de todos os quatro indicadores de uma só vez. E se os símbolos P, 5 e 5 são exibidos no primeiro, terceiro e quarto, respectivamente, o segundo símbolo é definitivamente A e você não precisa acender o segmento inferior. Por precaução, também adicionei o processamento da palavra FAIL, que eu nunca tinha visto, mas que esperava que aparecesse.

Tudo, com o indicador digital terminado. Resta apenas corrigir o LED verde on-line. Mas aqui uma surpresa me esperava ... A idéia era a seguinte: o LED verde (On-Line) sempre acende sozinho. Se os LEDs amarelos (Bateria) ou vermelhos (Bateria fraca) estiverem acesos, o verde não acenderá. Assim, soldamos os fios desses LEDs ao microcontrolador, colocamos um simples if () com um "OR" lógico e tudo deve funcionar. Porém, quando o LED amarelo está aceso, ele não acende constantemente, mas pisca rapidamente. Rápido, mas não o suficiente para if () pular e não acender o LED verde. Verificou-se que, ao trabalhar na rede, o LED verde acende com o brilho total, mas ao mudar para a bateria, ele queima com metade do brilho, mas ainda queima. Bem, não é um problema, pensei, vou colocar um filtro passa-baixo simples:Cortarei todos os flashes rápidos, mas deixarei apenas os lentos que correspondem à transição para a bateria e vice-versa. A análise do tempo de intermitência do LED amarelo trouxe a seguinte surpresa: o período dos pulsos que lhe são fornecidos é muito instável e pode atingir valores bastante elevados. Verificou-se que o filtro deve transmitir sinais não superiores a 0,5-1 Hz. Isso não é muito bom, pois temos um atraso bastante grande na alteração do sinal on-line, mas é bastante tolerável.pois temos um atraso bastante grande na alteração do sinal on-line, mas é bastante suportável.pois temos um atraso bastante grande na alteração do sinal on-line, mas é bastante suportável.

Eu decidi tornar o filtro muito simples. Monitoramos 50 vezes, em intervalos regulares, o status dos LEDs amarelo e vermelho. Se um deles queima, aumentamos o contador especial em um. Depois, verificamos o valor do contador e, se os LEDs de controle acenderem por 50% do tempo verificado, acreditamos que ele está ligado e, se menor, então está apagado. No processo de depuração, tive que reduzir esse número para 10%. Por que - não entendi.

montagem final

E tudo funcionou! Restava apenas montar a placa do Arduino no gabinete da UPS com fita dupla face e uma pistola adesiva.



Para os curiosos:
o código resultante
#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/pt397697/


All Articles