habrahabr

Robotron S6130 — Восставший из мертвых

  • вторник, 4 марта 2025 г. в 00:00:10
https://habr.com/ru/companies/yadro/articles/886254/

История ремонта этой пишущей машинки тянется уже 8 лет! Шутка что ли, это Самый Первый Артефакт моего YouTube-канала!

Robotron S6130 — многофункциональная пишущая машинка формата A2 на базе процессора Z80, с функцией запоминания набранных текстов во встроенной оперативной памяти, с записью и воспроизведением на магнитофон, а также возможностью приема и передачи текстов с ЭВМ по последовательному порту! Мне попадалась информация, что аппарат любили бухгалтеры: на магнитофон можно было записать таблицы и шаблоны документов, чтобы потом оперативно печатать их пачками.

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

Плачевное состояние оригинальной платы. Видны две батарейки Д-0,25, налет на близлежащих микросхемах RAM, а также много пустого места под логику для RS-232.
Плачевное состояние оригинальной платы. Видны две батарейки Д-0,25, налет на близлежащих микросхемах RAM, а также много пустого места под логику для RS-232.

С тех пор я сменил две работы (сейчас работаю в YADRO), женился, завел детей, построил компьютер на реле и на пневмонике, замахнулся на ламповый компьютер, на самодельные микросхемы, а машинка пылилась в гараже и ждала своего часа.

Но не надо думать, что я про нее забыл. Например, в 2019 году я предпринял первую попытку ремонта. Проверил работоспособность платы драйверов шаговых двигателей, а также выпаял микросхемы памяти для снятия прошивки и проверки микросхем RAM на работоспособность. 8 из 16 чипов были мертвы. Зачем-то купил советскую версию машинки «Элема». Ее механика до последнего винтика повторяет серию S6100, но электроника сделана на базе процессора КР580ВМ80А и прочих отечественных комплектующих. Я попытался починить и ее, за что в ответ получил выстрел конденсатором себе в бок. «Роботрон» во второй раз уехал в гараж, а «Элема» — в Муромский кибер-музей.

«Элема» и «Роботрон». Жалкая копия и неповторимый оригинал
«Элема» и «Роботрон». Жалкая копия и неповторимый оригинал

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

Вместо двух десятков мелких корпусов RAM и ROM я поставил по одной микросхеме памяти каждого типа, так как теперь это не дефицит. Особый интерес представлял последовательный порт RS-232 — на оригинальной плате он не был распаян, хотя функционал работы с ним в прошивке был! А значит, очень высок шанс, что интерфейс заведется.

Проект замерзает еще на пару лет, пока один из подписчиков, тов. Folk, не взялся за отрисовку платы в KiCAD. Месяц неспешной работы, и к январю 2022 года рождается она — новая плата для «Роботрона»:

3D-модель новой платы
3D-модель новой платы

Разрабатывалась плата один в один по габаритам старой, даже основные детали были на тех же самых местах. И тут я допустил три стратегических просчета. Серию машинок S6100 выпустили в 1986 году в ГДР. Есть шанс, что часть номенклатуры спустя 40 лет уже не найти и список компонентов стоило бы проверить на «доставабельность». Например, вместо родных диодов пришлось ставить современные в другом корпусе. Но заменить двухвыводной компонент довольно легко. То ли дело rail-to-rail операционный усилитель в корпусе DIP-6! Один у меня был снят с родной платы, а вместо второго пришлось запаивать ОУ в SMD-корпусе, слегка враскорячку. В итоге он заработал, но не буду забегать вперед.

При замене 16 корпусов RAM на один-единственный требуется корректировка сигналов управления. Нам достаточно объединить все сигналы ChipSelect. Ничтоже сумняшеся я собираю их в кучу пачкой диодов, как уже ранее делал в релейном компьютере. Аналогично поступил и для ROM. Стоит ли говорить, что требования к качеству фронтов на 25 Гц и на 2,5 МГц немного различаются? Пришлось все диоды выкусывать и навесным монтажом делать слегка по-другому. Опыт. Сын ошибок трудных…

Еще мне стоило досконально проверить новую схему на предмет ошибок, особенно во внесенных изменениях. Благо пара перерезанных дорожек и 10 минут работы паяльником смогли исправить упущенный косяк.

Осталось эту плату заказать. В то время я сотрудничал с PCBWay, и, вследствие обстоятельств непреодолимой силы, к концу 2022 года у них скопился передо мной небольшой должок. Я предложил забрать его натурой: заказал кучу деталей для проекта «ГИП овер 10к на стероидах», а также вожделенную новую плату для машинки, которая, на секундочку, имеет размер 130х450 мм! Доставка в РФ, правда, тоже была невозможна... В марте 2023 года я как раз поехал в командировку в Китай, на пару часов заглянув в Ханчжоу!

В офисе PCBWay, г. Ханчжоу
В офисе PCBWay, г. Ханчжоу

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

И наконец в январе 2025, спустя еще полтора года после этих событий, я разом запаиваю полный набор компонентов на новую печатную плату. Проект реставрации набрал критическую массу и резко перешел в активную фазу

Сборка

Новая плата собрана и установлена на место. Справа видны корректировки схемы ChipSelect. Батарейку пока запаивать не стал.
Новая плата собрана и установлена на место. Справа видны корректировки схемы ChipSelect. Батарейку пока запаивать не стал.

Не считая пары микросхем враскорячку, сборка новой платы прошла без осложнений. За 8 долгих лет я не только не растерял родные детали, но и набрал приличное количество доноров от различной техники Robotron, так что этого запаса микросхем хватит еще на одну машинку. С одного такого донора я и снял недостающие микросхемы для СОМ-порта. Буквально за несколько вечеров все компоненты были на месте, и пришло время подавать питание. 

Bring-Up

Прежде чем начать «поднимать» плату, ознакомимся, наконец, со структурной схемой машинки:

Структурная схема пишущей машинки
Структурная схема пишущей машинки

Сердцем системы является процессорный комплект на базе U880 — нелицензионного клона самого популярного 8-битного процессора Zilog Z80. Сам процессор работает на частоте 2,5 МГц. ROM была представлена в виде 7 корпусов по 2 Кб каждый. К диагностическому разъему машинки можно подключить специальное устройство с тестовой программой, и ROM этого устройства будет располагаться в восьмом корпусе по адресу 3800-3FFFh. В новой схеме с одним чипом это изначально поддерживалось, но после ремонта ChipSelect — временно нет. 8 Кб RAM находятся чуть выше — с 4000h до 5FFFh.

Для процессора вся область памяти доступна по сигналу MREQ без дополнительных плясок с бубном. Хочешь — иди в ROM, хочешь — в RAM.

Периферия же хоть и сидит на общей шине адреса и данных, но доступна через IOREQ, то есть только через инструкции IN/OUT, а также дьявольскую OTIR — пакетную выгрузку данных из памяти в порт. Адресация тут уже 8-битная. {VA7, VA6} == 00h — разрешают доступ к чипам, {VA4, VA3, VA2} выбирают один чип из семи (восьмой вывод дешифратора остался в воздухе), а младшие биты VA0 и VA1 выбирают канал микросхемы — A/B и управление/данные соответственно. Таким образом, у каждой микросхемы есть четыре возможных адреса для чтения или записи. Сами микросхемы должны быть хорошо знакомы любому спектрумисту:

  • контроллер параллельного интерфейса U855 — клон Z80-PIO,

  • контроллер последовательного интерфейса U856 — клон Z80-SIO,

  • микросхема четырехканального счетчика U857 — клон Z80-CTC.

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

  • Системный таймер CTC.cs1. Активно используются все четыре счетчика. Например: 

    1. Канал 3 настроен на период в 1 мс, и его обработчик проверяет нажатие кнопки СТОП. А также каждые 32 мс дергает контроллер дисплея. 

    2. Канал 1 используется всеми моторами для обновления состояния выходов на драйверах. В зависимости от того, какой сейчас работает мотор, подгружается нужный адрес обработчика прерывания.

  • Контроллер дисплея PIO.cs2. Каждые 32 мс через него отправляется 16 байт данных в плату индикации. Технически это коды 12 символов для отображения на матричном дисплее 5х7 плюс пара светодиодов. Изображения символов хранятся в ROM. Плата самостоятельно выводит их на сегменты с помощью схемы динамической индикации. Эту таблицу мы достанем позднее.

  • Контроллер моторов PIO.cs3. Всего здесь четыре мотора — протяжка бумаги, перемещение каретки, выставление нужной литеры и перемотка печатающей ленты. Драйверы моторов простейшие — четыре силовых ключа, по два мотора на каждый из 8-битовых каналов. Никакого STEP/DIR и микрошага — обмотки приходится переключать вручную, для чего в ROM даже лежат уже готовые к отправке в порты байты.

Клавиатура тут большая — всего 89 клавиш, схематически разбитых на алфавитно-цифровую область и на область функциональных клавиш.

Схема подключения алфавитно-цифровой (слева), и функциональной (справа) клавиатурных матриц к портам ввода-вывода.
Схема подключения алфавитно-цифровой (слева), и функциональной (справа) клавиатурных матриц к портам ввода-вывода.
  • Микросхема PIO.cs5 на канале А активирует одну строку, а микросхема PIO.cs4 считывает состояние столбцов. Канал А отвечает за опрос функциональных клавиш, канал В — за алфавитно-цифровую область. Канал B микросхемы PIO.cs5 нам дополнительно интересен как отвечающий за сигналы приема и передачи на магнитофон. Еще пара линий уходит на драйвер шаговых двигателей, а схема на резисторах, dip-переключателях и 8-входовом И — используется для настройки скорости работы последовательного порта. Ни в оригинале, ни на новой плате эти элементы не запаяны, прошивку я не анализировал и узнал об этом в самый последний момент, найдя еще один документ.

  • Последние два чипа — контроллер последовательного интерфейса SIO.cs7 и микросхема таймер CTC.cs6 — отвечают за порт RS-232 с полной поддержкой контроля потока — сигналов DCD/DTR и CTS/RTS. Один из каналов таймера явно тактирует схемотехнику последовательного порта, но в CPU прерывания от него уже не заходят, только от самого SIO. Необходимые для работы COM-порта ±12 В формируются на материнской плате очень сложной схемой делителя-удвоителя. 

Сам процессор, U880, представляет собой 40-ногую микросхему с однополярным питанием +5В, с раздельными 16-разрядной шиной адреса и 8-разрядной шиной данных. Также, у него имеются дополнительные выводы:

  • Вход CLK, куда мы логично получаем тактирование как самого процессора, так и периферии. Исходная частота кварцевого резонатора 9832 КГц, но она сразу делится до 2458 КГц.

  • Вход RESET, в активном состоянии которого все регистры процессора и его состояние сбрасываются в нули, а после снятия сигнала начинается исполнение инструкции по адресу 0x0000h. Микросхема RAM при этом не очищается. При старте прошивки очистка памяти тоже не происходит. На плате можно установить батарейку, чтобы информация в памяти сохранилась аж до трех недель. Это позволяет не бояться отключения электроэнергии — вы не потеряете набранные тексты.

  • Выход M1, активный сигнал на котором означает, что процессор в данный момент занимается выборкой инструкции. Если в это время активен еще и сигнал MREQ, значит, процессор считывает инструкцию из RAM.

  • Выход IOREQ активен, когда процессор обращается к периферийным устройствам. Выводы RD, WR позволяют определить направление данных — от процессора или к нему.

  • Вход INT используется для получения уведомления о прерывании от периферии. У Z80 довольно богатый функционал по организации прерываний. В нашем случае используется векторный режим: предварительно в каждую микросхему периферии записывается номер вектора от 00h до FFh, и при получении сигнала прерывания этот вектор запрашивается процессором назад. Вместе с I-регистром, в котором зашит старший байт адреса (40h для машинки), мы получаем адрес указателя, куда надо прыгнуть процессору для обработки прерывания. Например, для прерывания Sys_Timer_ch0 вектор прерывания 48h: и по адресу 4048h лежит адрес обработчика прерывания, 9D5h.

  • Вход WAIT используется для остановки работы процессора извне. Например, чтобы подождать медленную периферию (чем пользуется последовательный порт) или же реализовать пошаговую отладку.

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

Питание подано, тактирование на CLK имеется, фронты хорошие, RESET снят, сигнал HALT также отсутствует. На шине адреса и шине данных кипит жизнь, процессор активно что-то выполняет, как вдруг…

Ошибочная схема подключения микросхемы ROM к адресной шине
Ошибочная схема подключения микросхемы ROM к адресной шине

…я замечаю, что линии адреса на ROM идут слегка не по порядку, а значит, и инструкции будут запрашиваться совсем не те, что мы ожидаем. Можно, конечно, переложить байты в бинарнике согласно новой разводке, а можно за 10 минут сделать хард-фикс паяльником. История этого косяка такова, что подключение шины адреса к RAM в исходной плате сделано вразнобой, исходя из простоты разводки, Это свойство перекочевало и в новую схему подключения RAM, а потом Ctrl+C/Ctrl+V — и вот мы уже перерезаем дорожки и кидаем перемычки.

Первичный анализ жизнедеятельности процессора 16-канальным логическим анализатором
Первичный анализ жизнедеятельности процессора 16-канальным логическим анализатором

Подключаемся 16-канальным логическим анализатором к шине данных процессора и наблюдаем первые инструкции:

Кусок трассы с анализатора сразу после сигнала Сброс
Кусок трассы с анализатора сразу после сигнала Сброс

Что-то работает! Сразу после сброса выполняется инструкция по адресу 0h. По шине данных в процессор приходит три байта 31h, 40h, 40h — инициализация стека. Он находится по адресу 4040h и растет вниз, до 4000h. 

Следующая инструкция FFh — rst 38h. Не думайте, что это сброс, это просто CALL размером в один байт вместо трех — все для экономии места памяти программ. Rst точно так же кладет в стек указатель возврата. Посмотрим, что там:

Check_debug:                              
     ld      a, a            
     push    af              
     ld      a, (Debug_code)
     cp      52h ; 'R'       ; if Debug Code is started with R
     jr      z, Run_debug    
     ld      a, i            
     jr      z, loc_46      
     halt                    ; halt if I reg already set
         loc_46:                                
     pop     af              
     ret                    
         Run_debug:                              
     pop     af              
     jp      Debug_routine  

Ячейка DebugCode по адресу 0x3800h проверяется на наличие символа R. Если он будет найден, то мы прыгнем в функцию Debug_routine по адресу 3803h — в тот самый восьмой корпус ROM, расположенный в сервисной плате. Мне удалось найти фотографии таких устройств. Например, для машинки S6010 оно выглядит так:

Диагностическое устройство для пишущей машинки Robotron S6010
Диагностическое устройство для пишущей машинки Robotron S6010

Разъемом она подключается к шине процессора и имеет существенно больший контроль над системой, чем микросхемы на плате. Согласно сервисному мануалу на S6010 (стр. 7), на устройстве хранится прошивка для тестирования всей периферии. Оно использовалось либо выходным ОТК завода, либо сервисной службой.

Этой платы у нас нет, да и временные изменения схемы ChipSelect полностью передают полномочия над всей областью 0000h-3FFFh внутренней ROM. Так что мы вернемся обратно в адрес 0004h, где нас ожидает безусловный переход в адрес 19EFh. После него должен быть Call 1ee0h, но его нет: процессор запрашивает только один байт из 19F0h и делает прыжок на 19F5h, пролетая мимо нужной мне инструкции. Внимание, вопрос: как, при наличии 16-канального логического анализатора мониторить одновременной 8 линий данных, 16 линий адреса, не забывая про служебные линии? Никак.

32-канальный логический анализатор подключенный к диагностическому разъему через переходную плату
32-канальный логический анализатор подключенный к диагностическому разъему через переходную плату

Благо удалось дешево купить 32-канальный логический анализатор Hantek 4032L, хотя я уже успел придумать проект 64-канального анализатора на плате RV901T и заказать ее… 

Для работы с анализатором я настоятельно рекомендую OpenSource проект sigrok.org — он поддерживает несколько сотен различных устройств и предоставляет мощный интерфейс для работы. А главное — декодеры многих популярных процессоров! Дабы не сойти с ума и каждый раз не накидывать на процессор 32 мелких крокодила, я изготовил очень простой переходник с диагностического разъема на IDC40 с распиновкой как у анализатора. Запускаем PulseView, именуем каналы, добавляем декодер процессора Z80 и собираем трассу:

Программа PulseView с настроенным декодером сигналов Z80
Программа PulseView с настроенным декодером сигналов Z80

Все шевелится, и мы получаем полную трассу инструкций, приправленную данными с RAM и периферии! Как человек, который 6 лет работал в Intel над поддержкой Processor Trace в различных пользовательских сценариях, я был счастлив и печален одновременно. Счастлив — потому что я точно вижу, что делал процессор в то или иное время. Печален — потому что вьетнамские флешбеки, вот почему. 

Сохраняем выход с декодера в текстовый файл, применяем немного магии питона, который оформляет данные в трассу следующего вида:

1057881:   0000 31 40 40              LD SP,4040h              
1057891:   0003 FF                    RST 38h                  PUSH 0004 -> 0x403F 
Call Check_debug@0038 | Unknown -> Stack
1057902:   0038 7F                    LD A,A                   
1057906:   0039 F5                    PUSH AF                  PUSH 0054 -> 0x403D 
1057917:   003A 3A 00 38              LD A,(3800h)             op: 0x3800(FF) 
1057930:   003D FE 52                 CP 52h                   
1057937:   003F 28 07                 JR Z,$+9                 
1057944:   0041 ED 57                 LD A,I                   
1057953:   0043 28 01                 JR Z,$+3                 
1057965:   0046 F1                    POP AF                   POP 0054 <- 0x403C 
1057975:   0047 C9                    RET                      
Return from Check_debug@0038 | Stack -> Unknown
1057985:   0004 C3 EF 19              JP 19EFh                 
1057995:   19EF CD E0 1E              CALL 1EE0h               PUSH 19F2 -> 0x403F 
Call Setup_all@1EE0 | Unknown -> Stack
1058012:   1EE0 CD 57 02              CALL 0257h               PUSH 1EE3 -> 0x403D 
Call IOnVarInit@0257 | Setup_all@1EE0 -> Stack
1058029:   0257 21 28 02              LD HL,0228h              
1058039:   025A CD 8A 02              CALL 028Ah               PUSH 025D -> 0x403B 
Call IO_WriteFromTable@028A | IOnVarInit@0257 -> Stack

Теперь у нас есть дамп трассы в удобном виде, который можно читать одновременно с дампом в IDA. Здесь инструкция CALL 1EE0h выполняется корректно, так как я уже сделал хардфикс поверх хардфикса линий адреса, перепутав местами два проводника.

Кстати, вы заметили что я отдельно трассирую вызовы функций и возврат из них? Сохраним их в формате Trace Events и скормим в perfetto.ui

Последовательность выполнения функций в графическом представлении. Работа с портами ввода-вывода отображается отдельно по каждому каналу
Последовательность выполнения функций в графическом представлении. Работа с портами ввода-вывода отображается отдельно по каждому каналу

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

В процессе настройки машинки идет постоянный опрос клавиатуры
В процессе настройки машинки идет постоянный опрос клавиатуры
KeyboardReadColumn:                    
    ld      e, a            
    cp      10h            
    jr      c, Kb_A_less_10h ;  if A < 10h, jump
    and     38h ; '8'       ; A = A & (0b00111000) mask
    rrca                    
    rrca                    
    rrca                    ; A = {00000, A[5:3]}
    call    BV_A            ; B = (1<<A[5:3])
    ld      d, b            ; D = B
    ld      a, e            ; restore A value
    and     7               ; Apply 00000xxx mask
    call    BV_A            ; B = (1<<A[2:0])
    ld      c, 0Dh          ; ColumnDataB
    bit     7, e            ; Status.Z = ~A[7]
    jr      z, loc_10E7     ; Jump if A[7] is Zero
    ld      c, 0Ch          ; ColumnDataA
    bit     6, e            ; Status.Z = ~A[6]
    jr      z, loc_10E7     ; Jump if A[6] is zero
    ld      c, 11h          ; RowDataB
    ld      a, b            ; Move o1 <- o2
    cpl                     ; A = ~(1<<A[5:3])
    ld      b, a            ; B = A
loc_10E7:                              
    push    de              
    call    GetKbColValue   ; Arguments:
                            ; B - value to port
                            ; C - Selected input channel for read
                            ; Return:
                            ; D == A = ~KbCol(A or B).value
                            ; Note: Interrupt Disabled if 40BDh.bit6 is set
    pop     de              
    bit     7, e            ; if A[7] is set
    jr      z, loc_10F5     ; A = f(KbColA) & (1<<A[5:3])
loc_10F0:                              
    bit     6, e            ; If A[6] is set
    jr      z, loc_10F5     ; A = f(KbColA) & (1<<A[5:3])
    cpl                     ; We are here only if A[7:6] == 11
                            ; A = KbRowB
loc_10F5:                              
    and     d               ; A = f(KbColA) & (1<<A[5:3])
    ret                     ; Return: A - Value from port, Z flag if A is empty
b_A_less_10h:                          
   rlca                    
   rlca                    
   rlca                    
   rlca                    ; A <<< 4
   ld      d, a            
   in      a, (Keyboard_Col_Data_A) ; Input from port to A
   cpl                     ; A = ~KbColDataA.{BE,Tygs,Paper,SP24}
   and     d               ; Apply mask from D
   ret                     ; Return: A - Value from port, Z flag if A is empty

Аргумент кладется в регистр А. Если он меньше 10h, то мы формируем из него маску вида xxxx0000 и накладываем на инвертированный результат чтения из порта Keyboard_Col_Data_A. Там висит несколько датчиков. Если при наложении маски ничего в регистре А не останется, мы получим вожделенный флаг Status.Zero. По трассе видно, что в А записано число 99h, следовательно, нам надо идти по основной ветке.

С помощью двух старших битов регистра А мы выбираем, куда будем писать — в ColumnDataB(0x), ColumnDataA(10) или же RowDataB(11). Функции BV_A делают очень простую операцию B = (1<<A) — это маски записи A(00000xxx) и чтения А(00ххх000). Регистр А содержит номер строки и столбца для опроса, а также канал, то есть обозначает одну-единственную кнопку для считывания.

Все, что нам осталось, это понять, что нулевой флаг будет тогда, когда замаскированный бит выставится в ноль. В нашем случае А = {2’b10 3’b011 3’b001} — выставляем мы вторую строку клавиатуры и опрашиваем четвертый столбец группы A.

Что у нас там по схеме? Концевой выключатель крышки. Разумеется, я безрезультатно пробовал его переключать на запитанной машинке. А вот если его замкнуть и потом включить машинку, то… каретка идет на парковку. Всего каких-то 6 дней, новый логический анализатор и дизассемблирование прошивки, чтобы выяснить что машинка отказывается стартовать с открытой крышкой… Другой вопрос, почему она отказывается стартовать, когда крышка закрывается?

Как минимум я теперь бегло читаю опкоды Z80 и распарсил заметную долю прошивки. Собираем новую трассу бута, но с откинутым концевиком.

Визуапизация процесса запуска пишущей машинки.
Визуапизация процесса запуска пишущей машинки.

Ай какая красота! За 4 секунды работы машинка сначала перемещает каретку влево до упора, потом вращает ромашку с литерами в нулевое положение до срабатывания датчика, а в финале возвращает каретку на начальный отступ, после чего ожидает команды с клавиатуры. Кроме того, мы явно видим, что UART контроллер получает настройки, а потом активно опрашивается!

Тем временем я добрался до инструкции и начал печатать всякое. И все работает! Особенно после очистки соленоида и установки новых пятаков на пленочную клавиатуру. Машинка официально ожила после 8 лет простоя только лишь у меня — это не считая еще пары-тройки лет забвения до.

Найди меня, или глава о спрятанной функциональности

У «Роботрона» есть два способа связи с внешним миром. Первый — выход на магнитофон. Можно набрать любой документ и записать его на магнитофонную ленту. В любой момент спустя день, неделю или год можно загрузить текст с ленты обратно в машинку и напечатать его вновь, символ в символ.

Второй интерфейс — RS-232. Он также упоминается в документации, но изначально на плате он не был распаян, да и в меню MOD не отображается. Однако еще при самом первом изучении дампа в далеком 2019 году код работы с интерфейсом был найден, а полученные выше трассы показали, что микросхемы SIO и CTC даже получают настройки! Значит, режим просто сделали недоступным для пользователя.

Меню MOD согласно документации и его изначальное состояние у машинки 
Меню MOD согласно документации и его изначальное состояние у машинки 

Задача №1: надо вывести иконки на дисплей

Блок-схема визуализации исполняемого кода
Блок-схема визуализации исполняемого кода

Текущая схема получения трассы из Z80 выглядит так: к диагностическому разъему подключен логический анализатор, в PulseView отображаются сигналы и работает декодер инструкций Z80. Их мы сохраняем и прогоняем уже через свой питоновский скрипт.

Из IDA Pro экспортируем адреса функций, дабы отобразить их в Perfetto UI. Эту схему можно значительно улучшить, взяв консольный sigrok-cli, и, объединив функционал встроенного декодера с моим, сразу получать готовые дампы и трассы для вьювера без множества ручных операций. Но потом.

Визуализация процесса захода в меню MOD
Визуализация процесса захода в меню MOD

Активно входим в меню и выходим из него обратно, параллельно собирая новую трассу. Во вьювере мы видим пару областей, похожих на то, что в эти моменты мы были внутри контекстного меню. На дисплей все также отправляется 16 байт информации, но теперь вместо пустых 12 символов F0 подряд их 4 — а нам бы хотелось иметь только одно пустое место.

Последовательность символов, отправляемых на дисплей. Одинаковый цвет означает одинаковое значение
Последовательность символов, отправляемых на дисплей. Одинаковый цвет означает одинаковое значение

Согласно дампу трассы, данные в порт отправляются с адресов 40AC-40BB:

LD A,(HL)    op: 0x40AF(0F)  <---
CPL          
OUT (04h),A  OUT F0 -> 0x0004: Display_Data_A
...
LD A,(HL)    op: 0x40AE(0F)  <---
CPL          
OUT (04h),A  OUT F0 -> 0x0004: Display_Data_A
...
LD A,(HL)    op: 0x40AD(0F)  <---
CPL          
OUT (04h),A  OUT F0 -> 0x0004: Display_Data_A
...
LD A,(HL)    op: 0x40AC(0F)  <---
CPL          
OUT (04h),A  OUT F0 -> 0x0004: Display_Data_A

Так как это RAM, выше по трассе ищем, кто туда записывает значения. Находим два места — либо с 4191h-419Ch, либо при вызове меню MOD с 419Dh-41A8h, что тоже RAM. Следующая итерация поиска приводит нас к области ROM F5h, из которой копируется 7 байт данных. Из них три штуки 0F — пустой код.

LD HL,00F5h              
LD BC,0007h              
LDIR  0x00F5(BC) -> 0x419D(BC)
LDIR  0x00F6(03) -> 0x419E(03)
LDIR  0x00F7(B8) -> 0x419F(B8)
LDIR  0x00F8(88) -> 0x41A0(88)
LDIR  0x00F9(0F) -> 0x41A1(0F) <--
LDIR  0x00FA(0F) -> 0x41A2(0F) <--
LDIR  0x00FB(0F) -> 0x41A3(0F) <--

На дисплейной плате расположено небольшое ROM размером 2 Кб. Считываем с него прошивку, и каждые 8 байт подряд превращаем питоном в символ:

Таблица отображаемых на дисплее символов
Таблица отображаемых на дисплее символов

Коды BCh, 03h, B8h, 88h как раз соответствуют уже отображаемым иконкам. Нам же нужны символы BBh и BAh. Дополнительно выведем и символ смены языка 94h, хоть у нас и нет других ромашек. Табличка, к слову, поддерживает и русский, и немецкий — c ее помощью вполне удастся напечатать на экране какое-нибудь straße.

Модифицируем прошивку, прошиваем (использую для этого TL866 Plus II) и проверяем: 

В меню MOD появились нужные нам иконки!
В меню MOD появились нужные нам иконки!

Успех! Но мы лишь на полпути, так как на нажатие соответствующих клавиш машинка не реагирует. Соответственно...

Задача №2: войти в меню приема и передачи

Возвращаемся обратно в трассу и смотрим, что там по опросу клавиш. Мы ведь помним, что вызов KeyboardReadColumn(A) при активных старших битах А опрашивает ровно одну кнопку, индекс которой закодирован в значении регистра. 

Опрос клавиш внутри меню MOD
Опрос клавиш внутри меню MOD

Будучи в меню MOD, мы опрашиваем ровно четыре клавиши! Справа есть какой-то непонятный мусор, который привлекает мое внимание. Вызваниваем клавиатуру и убеждаемся, что коды 88h, 89h, 90h и 8Bh соответствуют клавишам «удар», «шаг», «картридж» и «выравнивание справа». Этот список грузится с адреса 41B0h по три байта на символ, где первый — код кнопки, а два других — 16-битный адрес обработчика кнопки. Так как адрес опять лежит в области RAM, ищем, кто туда положил значение, — и находим, что 21 байт данных, то есть 7 кнопок, копируются с региона E0h:

LD HL,00E0h              
LD BC,0015h      
 LDIR  op: 0x00E0(88) op: 0x41B0(88) <-- Код кнопки
 LDIR  op: 0x00E1(4A) op: 0x41B1(4A)
 LDIR  op: 0x00E2(18) op: 0x41B2(18) <-- Обработчик кнопки по адресу 184Ah
 LDIR  op: 0x00E3(89) op: 0x41B3(89)
 LDIR  op: 0x00E4(6F) op: 0x41B4(6F)
 LDIR  op: 0x00E5(18) op: 0x41B5(18)
 LDIR  op: 0x00E6(90) op: 0x41B6(90)
 LDIR  op: 0x00E7(8D) op: 0x41B7(8D)
 LDIR  op: 0x00E8(18) op: 0x41B8(18)
 LDIR  op: 0x00E9(8B) op: 0x41B9(8B)
 LDIR  op: 0x00EA(F1) op: 0x41BA(F1)
 LDIR  op: 0x00EB(18) op: 0x41BB(18)
 LDIR  op: 0x00EC(00) op: 0x41BC(00) <--- 8Ch UART Receive
 LDIR  op: 0x00ED(E6) op: 0x41BD(E6)
 LDIR  op: 0x00EE(18) op: 0x41BE(18)
 LDIR  op: 0x00EF(00) op: 0x41BF(00) <--- 9Dh UART Send
 LDIR  op: 0x00F0(C8) op: 0x41C0(C8)
 LDIR  op: 0x00F1(18) op: 0x41C1(18)
 LDIR  op: 0x00F2(00) op: 0x41C2(00) <---
 LDIR  op: 0x00F3(A4) op: 0x41C3(A4)
 LDIR  op: 0x00F4(18) op: 0x41C4(18)

И тут бросаются в глаза подозрительные три нулевых байта. Прописываем сюда правильные опкоды кнопок (правда, кнопку «К» вызвонить не удалось), загоняем новую прошивку в ROM и... Мы вошли в меню! Победа!

Меню приема данных по последовательному порту
Меню приема данных по последовательному порту

Но нет, RS-232 все еще не работает. Вы же еще помните, что машинка не стартовала с открытой крышкой?

В первые дни дебага я обнаружил, что настройки в системный таймер приходят, однако сигналов прерывания от него нет. Я поменял местами микросхемы CTC, запросы прерывания появились, но машинка все равно не стартовала... И вот тут я уже дошел до датчика открытой крышки.

Теперь дохлый СТС стоит… на последовательном порту. Настройки в него приходят, однако сигнала тактирования он не выдает! Дежавю: я успел забыть про перестановку микросхем! Мертвый чип, к слову, родной, с оригинальной платы. Видимо, его тоже сожрало кислотой от батарейки. Выпаиваем таймер еще с одного донора, меняем — и на ножке TO появились импульсы! Анализируем отправляемую в SIO конфигурацию:

Usart_ctrl_A:  
     db  18h     ; WR0:
                 ; Channel Rst
     db    4     ; WR4:
     db  40h     ; Parity disable
                 ; Sync modes enable
                 ; 8 bit sync character
                 ; X1 Clock mode
     db    1     ; WR1:
     db  18h     ; INT On All Rx Characters
     db    5     ; WR5:
     db  28h     ; Tx Enable. 7bit character
     db    3     ; WR3:
     db  41h     ; Rx Enable
                 ; 7bit character

Эм, что еще за синхронный режим такой? Меняем 40h на 44h, включив привычный нам асинхронный режим с одним стоп-битом, еще раз обновляем прошивку на машинке, подключаемся к ней с помощью полноценного нуль-модемного кабеля, выставляем 110 бод, 7 бит, 1 стоп-бит, контроль четности отсутствует, и… поехали.

Одна досада: машинка либо получает информацию с удаленной машины, либо отправляет ее. Активировать режим терминала, включив оба режима одновременно, у меня не вышло. Еще несколько напрягает разница кодов символов — например, маленькие буквы э, ш и щ на машинке имеются, но при передаче отправляются коды 07h, 02h и 0Ah, при получении которых обратно происходит совсем другое. Если отправить в машинку все коды 00h-FFh, буквы э, ш и щ все еще остаются недоступными. Исправить это можно, скорректировав табличку кодировки. Она очень похожа на КОИ7-Н1, но при ее наполнении что-то пошло не так. У машинки есть Escape-последовательности, например включение подчеркивания делается командой 1Bh 52h. Может, буквы спрятаны где-то там?

В ЧАЩАХ ЮГА ЖИЛ БЫ ЦИТРУС? ДА, НО ФАЛЬШИВЫЙ ЭКЗЕМПЛЯР!
В ЧАЩАХ ЮГА ЖИЛ БЫ ЦИТРУС? ДА, НО ФАЛЬШИВЫЙ ЭКЗЕМПЛЯР!

Вместо заключения

Целое десятилетие машинка бороздила бескрайние просторы загробного мира, но теперь она официально ожила, причем с большими возможностями чем при выпуске с завода в 1989 году!

Но что дальше? Не мешало бы оживить и «Элему». Прошивка для нее тоже есть в репозитории, равно как и схема, и инструкция по эксплуатации. Уверен: по схожему маршруту, да с разработанным инструментарием это можно сделать быстро (флейм-чарт в Trace View — потрясающий инструмент). Посадочное место под последовательный порт на материнской плате «Элемы», к слову, тоже имеется. 

Что до «Роботрона», то у него все еще остались слабые места вроде чрезмерного нагрева снаббера в источнике питания и очень странного перегрева некоторых элементов в драйвере моторов. Я бы не стал пока оставлять машинку надолго без присмотра с такими проблемами, но фестиваль Chaos Constructions 2025 уже анонсировали, так что я еще вернусь…