Доброго времени суток! Бывало такое, что у вас или у ваших знакомых есть какая‑то, казалось бы, неработающая электроника, которая лежит без дела? С такой ситуацией столкнулся и я. На руках у меня оказался индикатор из темы к этой статье. И так как я уже работал с Arduino, пришла мысль, а почему бы не попробовать запустить этот индикатор самому без электроники от производителя.
Итак, разберем выполнение этой идеи фикс по порядку.
Через поиск получилось найти некоторые полезные источники:
АМС-100Ф. «Народный» кассовый аппарат двухтысячных
Кассовый аппарат АМС-100Ф (2003 г.в.)
Но без даташита этих материалов оказалось недостаточно, чтобы разобраться с распиновкой.
Найти разъемы и подключить на 5в катод (нить накала) получилось не особо сложно путем научного тыка и осмотра, куда ведут токопроводящие дорожки. А дальше возникли проблемы из‑за невнимательного изучения теории.
Казалось бы, что для каждой цифры на каждый сегмент должен идти свой контакт, но, во‑первых, это не так, а, во‑вторых, также нужно питать сетку. И вот, когда через увеличитель напряжения (до 25в) было перепробовано питание не по одному контакту, а по два — наконец‑то сегмент загорелся. Подобнее вместе с видео описал это у себя в тг канале: https://t.me/kozminal/7.
Дальше получилось нарисовать свой даташит:
При этом стоит сказать, что сегменты используются одни для всех сеток. Как это работает? За счет того, что глаз не реагирует на быстрые изменения, можно использовать мультиплексирование. Оно устроено так, что каждая сетка запитывается в порядке очереди (вместе с соответствующими ей сегментами). Получается, что за один круг не проходит достаточно времени для реакции.
Мерцание заметно становится лишь тогда, когда период увеличивается (частота обновления кадра уменьшается), или иногда при наблюдении с камеры, которая реагирует в свою очередь иначе.
Теперь возникла другая проблема: как контролировать высокое напряжение низким? Можно было бы использовать реле, но этот вариант был отброшен из‑за дороговизны. Поэтому были поспешно куплены самые дешевые полевые транзисторы, из‑за чего пришлось опять слегка поплатиться.
Повезло, что существует схема двухкаскадного ключа на NPN и PNP. Устроен он достаточно просто, если разобраться! PNP транзистор перестает проводить ток, когда на базу подается напряжение практически достающее напряжение относительно эмиттера. Поэтому, если соединить базу и эмиттер через резистор с высоким сопротивлением (10 кОм, можно меньше, возможно даже лучше), то ток с высоким напряжением не пойдет на коллектор. А если добавить к базе минус (что как раз позволяет сделать NPN транзистор), то ток начинает уходить, и PNP открывается. Это также делается через резистор с высоким сопротивлением и возможно имеет смысл понижать именно его с 10 кОм, чтобы индикатор горел лучше. В качестве PNP использовались транзисторы BC327 и 2N2907, а в качестве NPN — BC337 и 2N2222.
Оглядываясь назад, хочется сказать, что, возможно, оптимальным вариантом с точки зрения цены, компактности и простоты был бы MOSFET‑транзистор вместо ранее описанного подхода. Однако в использованной схеме также есть свои преимущества — она предоставляет больше свободы при выборе компонентов.
Проблема, которая возникла во время проверки решения, заключалась в использовании A6 и A7 на Arduino Nano, которые нельзя использовать как цифровые выводы. После того, как были использованы другие — все заработало.
Пример со всеми включенными пинами:
#include <Arduino.h>const int segments[] = {2, 3, 4, 5, 6, 7, 8, 9};const int grids[] = {10, 11, 12, 13, A0, A1, A2, A3, A4, A5};void setup() { // Сегменты for (int i = 0; i < 8; i++) { pinMode(segments[i], OUTPUT); digitalWrite(segments[i], HIGH); } // Сетки for (int i = 0; i < 10; i++) { pinMode(grids[i], OUTPUT); digitalWrite(grids[i], HIGH); }}void loop() {}
Затем, модернизировав код по большей части с помощью ИИ, получилась прошивка, которая принимает последовательность из цифр, букв (ENG/РУС) и пробелов/точек/запятых через Serial порт и отображает эту последовательность непосредственно на индикаторе. Для английских букв маски были отредактированы согласно изображению.
#include <Arduino.h>// Распределение пиновconst int segments[] = {2, 3, 4, 5, 6, 7, 8, 9}; // БИТЫ: [1:DP, 2:Низ, 3:ПрНиз, 4:ЛвНиз, 5:Центр, 6:ПрВерх, 7:ЛвВерх, 8:Верх]const int grids[] = {10, 11, 12, 13, A0, A1, A2, A3, A4, A5}; // 10 сеток#define NUM_GRIDS 10#define NUM_SEGMENTS 8// Буфер отображенияvolatile byte displayBuffer[NUM_GRIDS];// Временный буфер для сбора декодированных символовbyte textBuffer[NUM_GRIDS];int textIndex = 0;byte utf8_high_byte = 0;// Маски (обратный порядок относительно распределения)// Цифрыconst byte fontNumbers[] = { 0b11101110, // 0 0b00100100, // 1 0b10111010, // 2 0b10110110, // 3 0b01110100, // 4 0b11010110, // 5 0b11011110, // 6 0b10100100, // 7 0b11111110, // 8 0b11110110 // 9};// Английский алфавитconst byte fontAlphaENG[] = { 0b11111100, // A 0b01011110, // b 0b11001010, // C 0b00111110, // d 0b11011010, // E 0b11011000, // F 0b11110110, // G 0b01111100, // H 0b00100100, // I 0b00100110, // J 0b01111000, // K 0b01001010, // L 0b10001100, // M 0b00011100, // n 0b11101110, // O 0b11111000, // P 0b11110100, // q 0b11001000, // r 0b11010110, // S 0b01011010, // t 0b01101110, // U 0b00001110, // v 0b01100010, // W 0b00110100, // X 0b01110110, // Y 0b10111010 // Z};// Русский алфавитconst byte fontAlphaRUS[] = { 0b11111100, // А 0b11011110, // Б 0b11111110, // В 0b11001000, // Г 0b00111110, // Д 0b11011010, // Е 0b00011000, // Ж 0b10110110, // З 0b01101110, // И 0b11101100, // Й 0b01111000, // К 0b01001010, // Л 0b10001100, // M 0b01111100, // Н 0b11101110, // О 0b11101100, // П 0b11111000, // Р 0b11001010, // С 0b01011010, // Т 0b01110110, // У 0b11011000, // Ф 0b00110100, // Х 0b00100110, // Ц 0b01110100, // Ч 0b00011000, // Ш 0b00011000, // Щ 0b01101110, // Ъ 0b01111100, // Ы 0b01011110, // Ь 0b11011010, // Э 0b01100110, // Ю 0b11110100 // Я};byte getCharMask(byte character, byte isRussian) { if (isRussian) { if (character >= 0 && character < 32) return fontAlphaRUS[character]; return 0b00000000; } if (character == ' ') return 0b00000000; if (character >= '0' && character <= '9') return fontNumbers[character - '0']; if (character >= 'A' && character <= 'Z') return fontAlphaENG[character - 'A']; if (character >= 'a' && character <= 'z') return fontAlphaENG[character - 'a']; if (character == '-') return 0b00010000; if (character == '_') return 0b00000010; return 0b00000000;}void setup() { Serial.begin(9600); for (int i = 0; i < NUM_SEGMENTS; i++) { pinMode(segments[i], OUTPUT); digitalWrite(segments[i], LOW); } for (int i = 0; i < NUM_GRIDS; i++) { pinMode(grids[i], OUTPUT); digitalWrite(grids[i], LOW); displayBuffer[i] = 0b00000000; } // Настройка Таймера 2 (прерывания каждые ~2 мс) cli(); TCCR2A = 0; TCCR2B = 0; TCNT2 = 0; OCR2A = 249; TCCR2A |= (1 << WGM21); TCCR2B |= (1 << CS22) | (1 << CS20); TIMSK2 |= (1 << OCIE2A); sei();}// Фоновое мультиплексирование в прерыванииISR(TIMER2_COMPA_vect) { static byte currentGrid = 0; // Blanking: гасим текущую физическую сетку digitalWrite(grids[currentGrid], LOW); currentGrid++; if (currentGrid >= NUM_GRIDS) currentGrid = 0; // РЕВЕРС: Отображаем буфер задом наперед byte mask = displayBuffer[(NUM_GRIDS - 1) - currentGrid]; for (int seg = 0; seg < NUM_SEGMENTS; seg++) { if (mask & (1 << seg)) { digitalWrite(segments[seg], HIGH); } else { digitalWrite(segments[seg], LOW); } } digitalWrite(grids[currentGrid], HIGH);}void loop() { while (Serial.available() > 0) { byte c = Serial.read(); if (c == '\n' || c == '\r') { if (textIndex > 0) { // Смещение вправо, чтобы строка заканчивалась строго на начальной (правой) сетке int offset = NUM_GRIDS - textIndex; cli(); for (int i = 0; i < NUM_GRIDS; i++) { if (i < offset) { displayBuffer[i] = 0b00000000; } else { displayBuffer[i] = textBuffer[i - offset]; } } sei(); textIndex = 0; utf8_high_byte = 0; } continue; } // Точка/запятая зажигает первый бит (DP) без смещения сетки if ((c == '.' || c == ',') && textIndex > 0) { textBuffer[textIndex - 1] |= 0b00000001; continue; } // Декодер UTF-8 if (c == 0xD0 || c == 0xD1) { utf8_high_byte = c; continue; } byte mask = 0; if (utf8_high_byte == 0xD0) { if (c >= 0x90 && c <= 0xBF) { mask = getCharMask((c >= 0xB0) ? (c - 0xB0) : (c - 0x90), 1); } else if (c == 0x81) { mask = getCharMask(5, 1); } utf8_high_byte = 0; } else if (utf8_high_byte == 0xD1) { if (c >= 0x80 && c <= 0x8F) { mask = getCharMask(c - 0x80 + 16, 1); } else if (c == 0x91) { mask = getCharMask(5, 1); } utf8_high_byte = 0; } else { mask = getCharMask(c, 0); } if (textIndex < NUM_GRIDS) { textBuffer[textIndex] = mask; textIndex++; } }}
Стоит немного прояснить, как устроен сам код.
Объявляется буфер для отображения. Указываются пины для сегментов и сеток. Берется 10 сеток, так как больше не позволяет количество самих пинов на Arduino Nano. При желании можно модифицировать разработку под, например, сдвиговый регистр 74HC595 (но с увеличением сеток, также нужно будет менять и мультиплексирование). Отдельно задаются цифры и алфавиты для разных языков. getCharMask переводит символ в маску для сегментов.
В setup запускаются все сегменты и сетки, а потом настраивается таймер для прерывания каждый 2 мс, чтобы отображать очередную сетку. Если посчитать частоту, то получится значение как раз комфортное для человеческого глаза:
Прерывание описывается отдельно. В loop читается значение с Serial порта и записывается в имеющийся буфер.
Спасибо, что дочитали до конца. Оставляйте комментарии и заглядывайте в ТГ‑канал, чтобы следить за моими разработками.
ссылка на оригинал статьи https://habr.com/ru/articles/1057274/