Как я протезировал индикатор UPS

    в процессе отладки

    В конце 90-х появился у меня UPS. Красивый, с LED индикатором и кучей кнопок, имел внутри два аккумулятора и мог поддерживать жизнь моего тогдашнего компьютера (вместе с монитором!) аж целых 15 минут. Времена на Камчатке тогда были тяжелые, свет выключали регулярно, поэтому данный прибор был весьма кстати. Я прошел вместе с ним весь энергетический кризис, и не раз он спасал мои курсовые от внезапного пропадания электричества. А еще, к нему можно было подключить магнитофон и при свете свечи слушать радио или любимые кассеты, готовя себе ужин на портативной газовой плитке…

    Естественно, UPS ломался. В первый раз у него сгорел трансформатор. Не тот, который большой и стоит в инверторе, а какой-то маленький, наверное, для измерения напряжения в сети. Не найдя такой же заводской, я поставил самопальный, и девайс еще какое-то время поработал. Затем перестал. Долго, очень долго, я не мог найти причину. Приходилось выпаивать разные детали, проверять их на работоспособность и впаивать обратно. Проблема не находилась. Распотрошенный прибор пару лет провалялся под кроватью, пока, в один прекрасный день, мне не пришла в голову идея подать 5 вольт непосредственно на контроллер. И о чудо: раздался писк встроенного динамика и на LED индикаторе появились цифры. Он был жив! Дальше — дело техники: прошелся по цепи питания вольтметром и выяснил, что на плате был распаян плавкий предохранитель, наглым образом выглядевший как резистор! Предохранитель (естественно, сгоревший), был заменен и UPS ожил.

    К сожалению, мой ремонт и два года под кроватью не прошли для прибора даром. Каким-то непостижимым образом в контроллере сгорел порт, который отвечал за свечение зеленого светодиода «On-Line» и самого нижнего сегмента всех цифровых сегментных индикаторов. Тут уж ничего не поделаешь — пришлось смириться. Через некоторое время я уехал с Камчатки и наши пути разошлись.

    Прошли годы и, приехав в гости к родителям, в дальнем углу я обнаружил свой любимый бесперебойник: заброшенный, грязный, без аккумуляторов и резиновых ножек. К тому времени я уже обзавелся собственным жильем в другом городе, поэтому было принято решение забрать приборище к себе, восстановить его работоспособность и использовать по прямому назначению.

    Задача

    В первую очередь, UPS’ник был вымыт и высушен. Затем, в неком магазине радиодеталей были куплены подходящие резиновые ножки и новые аккумуляторы. К большому своему удивлению, в этом же магазине был найден подходящий трансформатор, взамен моей самоделки. Пару дней работы, и отмытый, обновленный бесперебойник радостно пискнул и начал заряжать свои новые аккумуляторы. Все было хорошо, но индикатор по прежнему оставался не рабочим.

    Идея починить его приходила мне и раньше. Нарисовав на тетрадном листке все цифры (и кое-какие буквы) семи сегментного индикатора, я понял, что определить состояние самого нижнего сегмента возможно по состоянием остальных. Зеленый же светодиод можно зажигать тогда, когда не горят другие светодиоды. Мыслей, как это можно было бы сделать имелось много: от простой микросхемы ПЗУ до простенькой FPGA. Но, поскольку я был студентом и жил на Камчатке, то не имел возможности приобрести что-нибудь сложнее мелкой логики. Починка индикатора откладывалась.

    рисуем сегменты

    В этот раз я решил заняться проблемой всерьез. Порывшись в закромах, я снова не нашел у себя ни ПЗУ, ни FPGA и какого-нибудь CPLD. Зато, под руки попала Arduino Pro Mini, вернее, его дешевый китайский клон с Ali Express. Ардуину я купил для того, чтобы сделать мини-компьютер на базе WiFi SD карточки от Transcend. К сожалению, карточка померла во время экспериментов, и плата с микроконтроллером осталась лежать без дела. Ничего, мы нашли ей новую задачу!

    Работа

    В дисплейном модуле реализована динамическая индикация: сигналы сегментов общие для всех четырех индикаторов так, что в один момент времени светится только один из них. Более того: как-бы пятым индикатором подключены еще и три светодиода. Пять сигналов выбора позволяют указать, какой именно индикатор (или линейка светодиодов) используется сейчас. Эти сигналы выбора последовательно сканируются с довольно высокой скоростью и, благодаря инертности зрения, кажется, что горят все индикаторы одновременно.

    Вначале я хотел обойтись самым простым решением: обыкновенный цикл, который проверяет сигналы c шести рабочих сегментов, и включает или выключает не рабочий седьмой. Фактически это просто эмуляция ПЗУ, про которую я думал в самом начале.

    Для этого мне пришлось подключить шесть рабочих сегментом ко входу микроконтроллера, а нерабочий сегмент – к выходу.

    Набросав небольшой массивчик, который сопоставлял различные состояния входов с выходом и цикл, который обходит это массив, я загрузил все в контроллер и сразу же получил проблему: нижний сегмент светился всегда. Первая мысль: косяк в программе. Однако, сколько я ни смотрел на код, никаких ошибок не находилось. В конце концов пришло понимание, что мой цикл никак не синхронизирован с переключением индикаторов. Если мы прочитаем состояние сегментов в конце цикла выбора одного индикатора, то велика вероятность, что нижний сегмент мы зажжем или погасим уже у следующего. Непорядок.

    Недолго думая я припаял пять сигналов выбора индикатора к оставшимся свободным входам Arduino, настроил их на генерацию прерывания и стал использовать обработчик прерывания вместо цикла. Лучше стало, но проблему не решило. В нужных местах сегмент горел как надо, но в тех местах, где он должен был погашен, оставалось не яркое остаточное свечение.

    Подумав еще какое-то время, я решил, что данный эффект может появляться, если цикл поиска в массиве нужного состояния для сегментов занимает больше времени, чем время горения индикатора. В этом случае мы тоже вылезаем из своей фазы и управляем сегментом уже следующего индикатора. Необходимо чтобы между моментом получения прерывания от сигнала выбора до команды управления сегментом прошло как можно меньше времени. Сделать это можно было только единственным образом: вынести код, принимающий решение о состоянии сегмента из обработчика прерывания, запустить его в основном цикле с минимальным приоритетом, а результат сохранять в неком глобальном буфере. Обработчику прерывания останется только прочитать значение их этого глобального буфера и погасить или зажечь сегмент, в зависимости от его содержимого. В самом плохом случае, мы можем только опоздать с изменением состояния сегмента в определенном индикаторе, но уже не залезем на соседний.

    Это было правильным решением. Но окончательно все заработало только после того, как я синхронизировал цикл принятия решения с прерыванием с помощью spin-блокировки и запретил обработку прерывания во время работы этого цикла. И оно не просто заработало, а заработало так как надо!

    С индикаторами оставалась еще одна проблема: большую часть времени они показывали только цифры. Однако, после включения UPS запускался процесс тестирования, во время которого, помимо цифр, появлялись еще и два слова: TEST и PASS. И если, буквы T, E и P можно было просто добавить в массив допустимых символов, а S – была аналогична 5-ки, то буква A ничем, с точки зрения моей программы, не отличалась от восьмерки. Цикл принятия решений просто находил соответствующий шаблон в массиве и дорисовывал нижний сегмент. Нужно было что-то придумать, чтобы этого избежать.

    И я придумал. Во время прихода сигнала о смене индикатора, нужно определять, к какому индикатору он относится и сохранять состояния его сегментов в специально отведенную для него переменную. Теперь, в любой момент времени я достаточно точно могу определить текущее содержимое сразу всех четырех индикаторов. И если на первом, третьем и четвертом отображаются символы P, 5 и 5 соответственно, то вторым символом однозначно является A, и нижний сегмент зажигать не нужно. На всякий случай, я еще добавил обработку слова FAIL, которое я ни разу не видел, но возможность появления которого предполагал.

    Все, с цифровым индикатором покончено. Осталось только починить зеленый светодиод «On-Line». Но тут меня ожидал сюрприз… Идея была такая: зеленый светодиод (On-Line) всегда горит один. Если горят желтый (Battery) или красный (Low Battery) светодиоды, то зеленый гореть не должен. Таким образом, подпаиваем к микроконтроллеру провода от этих светодиодов, ставим простой if() с логическим “OR” и все должно заработать. Но выяснилось, что, когда горит желтый светодиод, он, на самом деле, не горит постоянно, а быстро мигает. Быстро, но недостаточно, чтобы if() это пропустил и не зажег зеленый светодиод. Получалось, что при работе от сети, зеленый светодиод горит в полую яркость, но при переключении на батарею, он горел в половину яркости, но все-равно горел. Ну, не проблема, подумал я, поставлю простенький низкочастотный фильтр: буду отсекать все быстрые мигания, но оставлю только медленные, которые соответствуют переходу на батарею и обратно. Анализ времени мигания желтого светодиода принес следующую неожиданность: период импульсов, которые на него подаются, весьма нестабилен и может достигать довольно больших величин. Получалось, что фильтр должен пропускать сигналы никак не выше 0.5-1 гц. Это не есть очень хорошо, поскольку получаем довольно большую задержку в изменении сигнала On-Line, но вполне терпимо.

    Фильтр я решил сделать очень простой. Мы 50 раз, в равные промежутки времени, отслеживаем состояние желтого и красного светодиодов. Если один из них горит, то увеличиваем на единицу специальный счетчик. Затем, проверяем значение счетчика, и если, контрольные светодиоды горят в течении 50% проверяемого времени, то считаем, что он включен, а если меньше, то – выключен. В процессе отладки, пришлось уменьшить эту цифру до 10%. Почему — так и не разобрался.

    окончательный монтаж

    И все заработало! Оставалось только красиво смонтировать плату Arduino в корпусе UPS с помощью двухстороннего скотча и клеящего пистолета.



    Для любопытных:
    получившийся код
    #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;
    }
    

    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 11

      0
      Только это не Nano, а Pro Mini.
        0
        Упс. Приеду домой — поправлю.
        0
        На фото у вас не Arduino Nano, а Arduino Pro Mini
        https://www.arduino.cc/en/Main/ArduinoBoardProMini
          0
          Поправил.
          0
          Через диоды, которые подключены к светодиодам, заряжаем конденсатор. Если конденсатор разрядился, через параллельно подключенный резистор, отрываем транзистор который подключает «On-Line»
            0
            Согласен, аналоговый фильтр — это тоже вариант, но в данном случае цифровой — более кстати.
            0
            Помоему, проще было переделать всю панель вместе с контроллером на ардуине…
              0
              Красивую печатную плату развести и изготовить проще нашлепки из покупной платы?
                0
                Может и не проще, но надёжней и не надо такие костыли выдумывать…
              0

              Не перестаю восхищаться ребятами из Италии, которые сделали такую классную вещь.

                0
                Вы имеете в виду Arduino? Так я только плату использовал (причем, китайского клона), чтобы с макетом не заморачиваться. А программировал как AVR: на чистом C.

              Only users with full accounts can post comments. Log in, please.