МС6205. Плазменный дисплей советской эпохи
Приветствую всех!
За годы своего существования советская промышленность успела выпустить многие десятки моделей газоразрядных индикаторов, начиная с опытных и заканчивая серийными. Среди них особый интерес вызывают матричные. Их было меньше всего, но именно они являются самыми интересными.
Итак, в сегодняшней статье рассмотрим, пожалуй, самый легендарный индикаторный прибор из этой серии — дисплей МС6205 на базе индикаторной панели ГИП-10000. Узнаем, как его запустить и как он устроен, попутно напишем софт для управления им. Традиционно будет много интересного.
❯ Суть такова
Среди всех железок, которые я хочу заполучить в свою коллекцию, МС6205 был очень давней, ещё со времён начала моего увлечения ГРИ, мечтой. Но десять лет назад заполучить его мне не довелось, потом либо также не было возможности, либо просто было не до того. И вот наконец мне удалось прикупить такой девайс по вполне молодёжной цене. А раз так, самое время пробовать запускать.
❯ Немного о плазменных экранах и ГИП-10000 в частности
Многим из нас знакомы плазменные телевизоры. У многих они настолько прижились, что некоторые до сих пор называют «плазмой» любой телевизор, отличный от кинескопного, пусть даже настоящие PDP (Plasma Display Panel, не путать с ЭВМ от DEC) в потребительском сегменте давно ушли в историю.
Разумеется, подобная технология существовала и в советское время.
Вот для примера панель ИГПП-32/32, матрица разрешением 32×32, использовавшаяся для построения наборных экранов. Это по сути настоящая плазменная панель — внутри газ с добавкой паров ртути (достаточно большая её капля видна в штенгеле), разряд в котором возбуждает зелёный люминофор. Когда-нибудь у меня дойдут руки до запуска и этой панели.
Теперь перейдём к предмету нашего обзора — индикатору, на которой построен МС6205. Но для начала — немного истории.
ГИП-10000 была создана в 1977 году и является детищем рязанского НИИ «Плазма». Сами матрицы, как и модули ИМГ1–02 и мониторы МС6205, производились на заводе «Газотрон» (Ровно), том самом, что выпускал десятки наименований советских ГРИ. Самого предприятия сейчас давно нет, на его месте под маркой «Lummax» выпускают люминесцентные лампы.
Эта панель применялась в целом ряде изделий, от стоек ЧПУ до (по слухам) пультов военного оборудования.
Сама по себе панель почти нигде не применялась, вместо неё использовались различные индикаторные модули.
Вот и пример такого устройства — ИМГ1–02. О том, как его запустить и как управлять им без драйвера, уже писали тут, поэтому повторяться не буду. О других модулях на базе ГИП-10000 мне неизвестно, если такие были — прошу сообщить.
Использовался такой дисплей, например, в одном из первых советских цифровых осциллографов С9–6.
Грустно, но более-менее приемлемое фото данного аппарата удалось найти лишь на сайте перекупщиков…
А вот и ещё более монструозный девайс — С9–5.
Но наибольшую известность получил дисплей МС6205. Это законченное изделие на базе модуля ИМГ1–02, включающее в себя источник питания, знакогенератор, ОЗУ и контроллер интерфейса. Применялся данный монитор в целом ряде изделий.
Вот, например, стойка ЧПУ 2М43–55. Хотя эти устройства давно устарели, их ещё можно встретить на некоторых заводах…
А вот другая стойка, КМ85.
Индикатор в работе.
Также данный прибор стоял в счётчике купюр «Банкнота-1», выпускавшемся уже в первой половине девяностых.
У меня есть сильное предположение, что это какая-то украинская разработка: во-первых, объявлений об их продаже почти нет у нас, но полно на сайтах типа OLX, во-вторых, гривны в списке валют стоят на первом месте.
Несомненно, самым крутым применением данного индикатора является монитор учебного компьютера «Курсор».
Впрочем, были варианты и с обычным ЭЛТ-монитором МС6105…
Монохромные плазменные экраны были, конечно, и в зарубежной технике. Вот, например, старинный ноутбук с таким дисплеем.
❯ Как работает МС6205
И перед началом опытов немного затронем устройство и работу данного прибора.
Всем нам знакомы обычные газоразрядные индикаторы. Так вот, ГИП-10000 совсем недалеко ушла от них.
А вот и устройство данной панели из описания к ней. Она состоит из ортогональных электродов, между которыми помещена матрица в виде стеклянной пластины с отверстиями в нужных местах. Именно там и загорается разряд. Как и прочие ГРИ, внутри панель заполнена неоном с добавкой некоторых других инертных газов.
Устройство самого МС6205 достаточно подробно рассмотрено в том же описании. Прибор помимо самого модуля индикации состоит из трёх узлов: УЗО, УЗП и УБПАК.
УЗО (устройство запоминающее оперативное) — плата интерфейса. На ней же находится ОЗУ, куда помещаются принятые коды символов, и адресный счётчик, задающий положение каждого знака.
УЗП (устройство запоминающее постоянное) — знакогенератор. На этой плате расположено ПЗУ с изображениями символов (по коду знака и номеру столбца выдаётся соответствующий столбец символа), а также цепи генератора высокого напряжения. Эта же плата отвечает за большую часть управления матрицей — там же находится схема развёртки и генератора синхроимпульсов.
УБПАК (устройство буферной памяти и анодных ключей). Здесь находятся сдвиговые регистры, в которых находится текущий отображаемый на экране вертикальный столбец.
❯ Обзор оборудования
Итак, самое время посмотреть на доставшийся мне девайс.
Как и тот вольтметр, ко мне он попал новым с хранения. На этот раз, правда, упаковка не такая эпичная.
А вот и сам индикатор. Первое впечатление, когда берёшь его в руки — насколько же эта штука маленькая! Фотографии совершенно не передают габариты, в реальности его диагональ сравнима с таковой у типичного современного телефона.
Разъём. Для защиты от статики в него вставлена полоска фольги.
Заводская табличка.
А вот и комплектация: паспорт, схемы, инструкция, крепёжные уголки и ответная часть для разъёма.
❯ Внутренности
Разумеется, монитор я разобрал. Сделать это определённо стоило, как минимум, чтобы поменять конденсаторы.
Состоит он из пяти плат — две составляют модуль ИМГ1–02, три других служат для управления им и формирования питающих напряжений. Соединены они стойками. Кстати, закручены они приемлемо: если на девайсах начала двухтысячных их даже пассатижами не снять, то тут всё весьма неплохо.
Первая плата — УЗО. На ней ОЗУ, разъём и интерфейсная логика.
Вторая плата — УЗП. Здесь находится ПЗУшка знакогенератора, а также преобразователь питания. Типичный для отечественных импульсных БП броневой трансформатор. Также видны подозрительные компоненты — шесть очень нехороших и один просто нехороший.
Третья плата — УБПАК. На ней сдвиговые регистры, управляющие анодными ключами на ИМГ1–02. Дальнейшая разборка невозможна по причине того, что к плате со всех сторон припаяны шлейфы.
❯ Подключение
Собираем всё обратно и пробуем запускать.
На просторах быстро нашлась схема соединения данного устройства с параллельным портом компьютера.
Она тут вот такая.
Итак, для сборки кабеля понадобится всего-ничего: сам разъём (который идёт в комплекте с данным агрегатом), вилка DB25, розетка MF-2×10 MRB. А заодно и два шлейфа. LPTшного разъёма в кадре нет, а когда вспомнил про него, то он уже был припаян.
На случай, если ответной части у вас не окажется, показываю нумерацию контактов. В используемом тут разъёме часть контактов не установлена, тем не менее, они всё равно учитываются при подсчёте.
А вот она же в оригинальном разъёме.
И для начала припаяем провода питания. Включаем БП. Раздаётся характерный противный писк преобразователя, и индикатор успешно загорается. Как и следовало ожидать, на экране случайные символы, так как память после подачи питания содержит в себе «кашу». Разделители строк даже после сброса горят постоянно, отключить их нельзя. Сделано это специально — матрицу, в которой уже есть горящие элементы и газ ионизирован, зажечь куда легче, чем погашенную.
Самое время паять интерфейсный кабель.
После получаса пайки получился вот такой шнурок. Также подключил переключатель, сажающий вывод 8А на землю. Он служит для включения и выключения отображения курсора. Кожух пришлось взять от разъёма СНО64–96Р-24–2 из комплекта милливольтметра Ф298–2.
❯ Neon light
Кабель собран, теперь очередь софта. Ещё давным-давно энтузиастами была написана программа для подключения данного монитора к компьютеру. Так что достаём ПК с LPT-портом и пробуем запускать.
А вот и сама утилита. Автор её был тот ещё хохмач и в качестве сообщения по умолчанию вывел страшный сон любого пользователя MS-DOS.
Осталось только жмякнуть «Send», и, если кабель был собран правильно, на экране должно будет появиться примерно следующее:
Яркость совершенно никакая, в полутьме индикатор выглядит куда красивее.
Разумеется, первым делом я написал об успешном запуске в разные чатики.
А вот и часы на таком индикаторе, которые также можно вывести из данной программы.
❯ Подключение к МК
Дисплей работал, но компьютеры с LPT-портом ныне редкость. Поэтому попробуем подключить наш экран к какому-нибудь микроконтроллеру. Работа с этим устройством достаточно простая — у нас есть восьмибитная шина адреса и семибитная — данных. Для экономии пинов их можно мультиплексировать. Для записи предусмотрены контакты 16А и 16Б. При спаде сигнала на каком-либо из них происходит запись данных в ОЗУ или изменение значения адресного счётчика. Звучит всё очень просто, но, забегая вперёд, скажу, что именно эта часть опытов с данным дисплеем делалась дольше всего.
На сайте DekatronPC отыскалась программа для подключения данного экрана к Arduino. Запустить её удалось не сразу: хотя, по идее, соответствие портов и выводов на Arduino UNO и Nano одинаковое, на Nano она не заработала. Пришлось слегка пожертвовать скоростью работы, поменяв «быстрые» обращения напрямую к портам на «медленные» digitalWrite. Заодно и переписал протокол обмена данными.
Получилось примерно так:
#define WR (11)
#define RST (10)
#define ADR (12)
#define STX 0x02
#define ETX 0x03
#define SEND_ADDR 0x00
#define SEND_DATA 0x01
#define CLEAR 0x02
#define RESET 0x03
void setup() {
Serial.begin(115200);
// put your setup code here, to run once:
for (uint8_t i = 2; i <= 12; ++i )
{
pinMode(i, OUTPUT);
digitalWrite(i, LOW);
}
digitalWrite(WR, HIGH);
digitalWrite(ADR, HIGH);
digitalWrite(RST, LOW);
delay(10);
digitalWrite(RST, HIGH);
}
void processPacket() {
uint8_t packet[4];
uint8_t i;
Serial.readBytes(&packet[0], 4);
switch (packet[1]) {
case SEND_ADDR:
sendAddr(packet[2]);
break;
case SEND_DATA:
sendData(packet[2]);
break;
case RESET:
digitalWrite(RST, LOW);
delay(10);
digitalWrite(RST, HIGH);
break;
case CLEAR:
for (i = 0; i < 160; i++) {
sendAddr(i);
sendData(0x5F);
}
sendAddr(0);
break;
}
}
void strobeWr()
{
PORTB &= ~(1 << PB3);
delayMicroseconds(2);
PORTB |= (1 << PB3);
}
void strobeAddr()
{
PORTB &= ~(1 << PB4);
delayMicroseconds(2);
PORTB |= (1 << PB4);
}
void strobeRst(uint8_t row)
{
uint8_t startPos = row < 10 ? row * 16 : 0;
uint8_t endPos = row < 10 ? row * 16 + 16 : 160;
for (uint8_t i = startPos; i < endPos; ++i)
{
sendAddr(i);
sendData(0x5F);
}
sendAddr(0);
}
void sendPort(uint8_t data)
{
for (int i = 0; i < 8; i++) {
digitalWrite(i + 2, (data >> i) & 0x01);
}
}
void sendAddr(uint8_t addr)
{
sendPort(addr);
strobeAddr();
}
void sendData(uint8_t data)
{
sendPort(data);
strobeWr();
}
void loop() {
if (Serial.available())
{
if (Serial.peek() != STX) Serial.read();
else processPacket();
}
}
Теперь у нас есть возможность задавать адрес и данные с компьютера.
Подключаем девайс. Берём ардуинку из первой попавшейся платы, проводами соединяем с ответной частью разъёма. В моём случае ей оказался LPTшный шлейф от какого-то древнего компа. Распиновка простая — контакты со второго по девятый — шина адреса и данных, десятый — сброс, одиннадцатый — строб данных, двенадцатый — адресный строб.
#include
#include
#include
#define STX 0x02
#define ETX 0x03
#define SEND_ADDR 0x00
#define SEND_DATA 0x01
#define CLEAR 0x02
#define RESET 0x03
using namespace std;
HANDLE hSerial;
uint8_t openPort(int portNumber) {
char sPortName[10];
sprintf (sPortName, "COM%i", portNumber);
hSerial = ::CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DCB dcbSerialParams = { 0 };
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if(!GetCommState(hSerial, &dcbSerialParams)){
cout << "getting state error\n";
exit(0);
}
dcbSerialParams.BaudRate = CBR_115200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if(!SetCommState(hSerial, &dcbSerialParams)){
cout << "error setting serial port state\n";
exit(0);
}
cout << "Opened port: " << sPortName << endl;
return 1;
}
uint8_t ReadCOM(uint8_t & sReceivedByte) {
DWORD iSize;
ReadFile(hSerial, &sReceivedByte, 1, &iSize, 0); // получаем 1 байт
return iSize;
}
DWORD WriteCOM(uint8_t data) {
DWORD dwBytesWritten; // тут будет количество собственно переданных байт
DWORD dwSize = 1;
WriteFile(hSerial, &data, dwSize, &dwBytesWritten, NULL);
return dwBytesWritten;
}
void setAddr(uint8_t addr) {
uint8_t packet[4] = {STX, SEND_ADDR, addr, ETX};
for(int i = 0; i < 4; i++) {
WriteCOM(packet[i]);
}
}
void setData(uint8_t data) {
uint8_t packet[4] = {STX, SEND_DATA, data, ETX};
for(int i = 0; i < 4; i++) {
WriteCOM(packet[i]);
}
}
void clearPanel() {
uint8_t packet[4] = {STX, CLEAR, 0x00, ETX};
for(int i = 0; i < 4; i++) {
WriteCOM(packet[i]);
}
}
void resetPanel() {
uint8_t packet[4] = {STX, RESET, 0x00, ETX};
for(int i = 0; i < 4; i++) {
WriteCOM(packet[i]);
}
}
void showString(string s) {
for(uint8_t i = 0; i < s.length(); i++) {
setAddr(i);
setData(~toPanel[i]);
Sleep(1);
}
}
int main()
{
openPort(3);
resetPanel();
Sleep(10);
string s = "HELLO";
showString(s);
CloseHandle(hSerial);
return 0;
}
Дисплей использует кодировку КОИ-7, причём биты данных инвертированы. Английские буквы и цифры, таким образом, отображаются без каких-либо проблем. А вот для русских уже потребуется дополнительное преобразование.
Но всё же с этой программой были некоторые глюки в виде потерь данных или фантомных символов в разных местах экрана. Избавиться от них не удалось ни изменением задержек, ни сменой digitalWrite на обращение к портам.
❯ Так как же всё-таки его запустить?
И тут я вспомнил, что была какая-то библиотека, которая использовала сдвиговый регистр. Самое время попробовать с ним.
Собираем на макетке тестовую схему. Arduino Nano, 74HC595N, разъём для подключения экрана. Проверка показала, что упомянутые глюки не наблюдаются.
Так что пишем программу для управления дисплеем по COM-порту:
#define CLOCK 3 //SH_CP
#define DATA 4 //DS
#define LATCH 5 //ST_CP
#define RESET 6
#define ADDR 7
#define STROBE 8
#define STX 0x02
#define ETX 0x03
void setup() {
pinMode(CLOCK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(LATCH, OUTPUT);
pinMode(STROBE, OUTPUT);
pinMode(RESET, OUTPUT);
pinMode(ADDR, OUTPUT);
digitalWrite(LATCH, HIGH);
digitalWrite(RESET, HIGH);
digitalWrite(ADDR, HIGH);
digitalWrite(STROBE, HIGH);
displayReset();
Serial.begin(115200);
}
void sendData(uint8_t data) {
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, MSBFIRST, data);
digitalWrite(LATCH, HIGH);
}
void sendAddr(uint8_t addr) {
sendData(addr);
digitalWrite(ADDR, LOW);
delay(1);
digitalWrite(ADDR, HIGH);
}
void sendSymbol(uint8_t addr) {
sendData(~addr);
digitalWrite(STROBE, LOW);
delay(1);
digitalWrite(STROBE, HIGH);
}
void displayReset() {
digitalWrite(RESET, LOW);
delay(10);
digitalWrite(RESET, HIGH);
for (int i = 0; i < 160; i++) {
sendAddr(i);
sendSymbol(' ');
}
sendAddr(0);
}
void displayShow(char * str, uint8_t len, uint8_t line) {
for (int i = 0; i < len; i++) {
sendAddr(line * 16 + i);
sendSymbol(str[i]);
}
}
void loop() {
uint8_t buffer[17];
while(Serial.available() < 17);;
Serial.readBytes(buffer,17);
displayShow(&buffer[1],16,buffer[0]);
}
В этот раз достаточно всего одной команды — вывод строки в нужную линию.
#include
#include
#include
#define STX 0x02
#define ETX 0x03
#define SEND_ADDR 0x00
#define SEND_DATA 0x01
#define CLEAR 0x02
#define RESET 0x03
using namespace std;
HANDLE hSerial;
uint8_t openPort(int portNumber) {
char sPortName[10];
sprintf (sPortName, "COM%i", portNumber);
hSerial = ::CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DCB dcbSerialParams = { 0 };
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if(!GetCommState(hSerial, &dcbSerialParams)){
cout << "getting state error\n";
exit(0);
}
dcbSerialParams.BaudRate = CBR_115200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if(!SetCommState(hSerial, &dcbSerialParams)){
cout << "error setting serial port state\n";
exit(0);
}
cout << "Opened port: " << sPortName << endl;
return 1;
}
uint8_t ReadCOM(uint8_t & sReceivedByte) {
DWORD iSize;
ReadFile(hSerial, &sReceivedByte, 1, &iSize, 0); // получаем 1 байт
return iSize;
}
DWORD WriteCOM(uint8_t data) {
DWORD dwBytesWritten; // тут будет количество собственно переданных байт
DWORD dwSize = 1;
WriteFile(hSerial, &data, dwSize, &dwBytesWritten, NULL);
return dwBytesWritten;
}
int main()
{
openPort(3);
//cout << "Hello world!" << endl;
while(1) {
for(uint8_t n = 0; n < 10; n++) {
string s;
cin >> s;
WriteCOM(n);
for(int i = 0; i < s.length(); i++) while(!WriteCOM(s[i]));;
for(int i = 0; i < 16 - s.length(); i++) while(!WriteCOM(' '));;
}
}
return 0;
}
Теперь всё отлично работает — текст выводится без каких-либо глюков.
❯ КОИ-7
Мне удалось написать программу, позволяющую вывести текст, пришедший из COM-порта. Но на этом я не остановился — мне хотелось заставить экран показывать в том числе и русский шрифт.
Дисплей работает в кодировке КОИ-7, в той самой, которой мы обязаны сообщениями в духе «Инжалид дежице» на старых компьютерах. В ней русский текст приблизительно повторяет латинский в транслитерации. Если латинские буквы ничем не отличаются от обычных ASCII, то для русских потребуется преобразование. Проще всего сделать это при помощи таблицы символов, где нужный выбирался бы по его коду. Получилось примерно так:
const uint8_t codetable[] = {
0x61, //А
0x62, //Б
0x77, //В
0x67, //Г
0x64, //Д
0x65, //Е
0x76, //Ж
0x7A, //З
0x69, //И
0x6A, //Й
0x6B, //К
0x6C, //Л
0x6D, //М
0x6E, //Н
0x6F, //О
0x70, //П
0x72, //Р
0x73, //С
0x74, //Т
0x75, //У
0x66, //Ф
0x68, //Х
0x63, //Ц
0x7E, //Ч
0x7B, //Ш
0x7D, //Щ
0x78, //Ъ
0x79, //Ы
0x78, //Ь
0x7C, //Э
0x60, //Ю
0x71, //Я
};
Ну, а отправка данных принимает следующий вид:
uint8_t currentSymbol = s[i];
if(currentSymbol <= 0x7F) {
while(!WriteCOM(currentSymbol));;
}
else if(currentSymbol >= 0x80 && currentSymbol <= 0x9F) {
while(!WriteCOM(codetable[currentSymbol - 0x80]));;
}
Поскольку в консоли используется кодовая страница 866, необходимо вычитать 80h — код символа «А».
❯ Neon Light нового поколения
Дисплей работал, но останавливаться на достигнутом не хотелось. И я решил написать более современный аналог того софта Neon Light, которое позволяло управлять этим агрегатом с компьютера. Несколько часов издевательств над CodeBlocks и изучения WinAPI прямо на ходу, и на свет было выкачено вот такое приложение:
Оно мало чем отличается от того старого — текстовые поля для каждой из десяти строк и кнопки для отправки данных, очистки ввода и очистки экрана.
Как показали опыты, всё стабильно работает. Всё так, как и планировалось. Ссылку на этот софт я оставлю традиционно в конце статьи.
❯ Вот как-то так
Увы, несмотря на всю свою красоту, ГИП-10000 оказался весьма ненадёжным девайсом. Индикаторы частенько вылетали или просто выгорали. Даже в ЗИП к устройствам, где применялись эти устройства, сразу клали запасную индикаторную панель.
Мой прибор — новый с хранения, я достал его из запечатанной коробки, но даже на нём видны дефекты: часть строк не горит.
Другой проблемой является преобразователь — писк его поистине отвратителен. Так что для постоянного использования этот девайс тоже не пойдёт.
Тем не менее, прибор этот по-своему интересный и красивый, так что разобраться с его запуском было определённо круто.
Такие дела.