Я собрал для Psion Organiser II интерфейс, который позволяет отправлять на него сообщения через USB-serial с помощью Arduino Nano. В основе лежит выполнение органайзером машинного кода, хранящегося в строке на языке OPL. Получившийся интерфейс полностью соответствует формату шины ЦПУ. При этом он очень прост и работоспособен.

Сама идея собрать такую штуковину возникла, когда Мартин захотел использовать Psion для отображения уведомлений, но не смог нигде в продаже найти модуль “CommsLink” для его подключения к ПК. Мне такой проект на базе Arduino показался весьма занятным, поэтому я решил его реализовать.

Весь код и файлы для 3D-печати доступны на GitHub.

Если вы работаете с одним из таких устройств, то рекомендую заглянуть на страницу Яапа, посвящённую Psion II. Там содержится масса полезной информации и документации.

▍ Аппаратная часть


У Psion есть целых три слота для подключения периферии. Первые два расположены в нижней части устройства и используются для “датапаков” — стираемых EPROM, на которых можно хранить код и данные. Поверх этих EPROM в Psion используется странная файловая система «только для записи». Она, как мне кажется, при «удалении» файлов лишь устанавливает бит, указывающий, что файла больше нет, но само выделенное под него пространство не обнуляет. Единственный способ освободить занятую память — это засветить окно стирания ультрафиолетом, чтобы удалить всё сразу:


Третий слот расположен вверху, и использовать мы будем именно его. Этот слот имеет 16 контактов. Среди них непосредственную важность представляют контакты D0…D7, подключённые непосредственно к шине данных ”порта 2” ЦПУ. Среди контактов также есть земля, +5В и ряд других сигналов, которые мы проигнорируем. У Яапа есть страничка с техническими данными этого верхнего слота.

Сначала я решил, что проще всего осуществлять запись байтов с Arduino в программу Psion, просто отправляя байт на шину порта 2 и оставляя его там. Тогда программа на Psion сможет обращаться к шине в нужное время. Когда потребуется отправить очередной байт, мы поместим его на место имеющегося, и Psion поймёт, что это новый байт, потому что его значение изменилось. Если нам нужна возможность записи повторяющихся байтов, то можно вставлять между ними 0x00.

К сожалению, этого оказалось недостаточно, поскольку так мы ограничиваем работоспособность датапаков. Все три слота подключены к одной шине ЦПУ, но при этом один контакт указывает, какой из них выбран (CS1, CS2, CS3). Большинство контактов всех трёх слотов совпадают. Если вы попытаетесь поместить что-либо на шину, в то время как её будет использовать другое периферийное устройство, то возникнет конфликт, который в лучшем случае приведёт к некорректной работе компьютера, а в худшем к его повреждению.

Что же с этим делать? Идеальное решение — это добавить между Arduino и органайзером соединительную логику, которая будет тестировать вывод CS3 (и желательно «разрешение выхода»), помещая данные на шину только тогда, когда выбран верхний слот, и ЦПУ пытается произвести считывание с шины, а не запись на неё. (Выполнять эту логику в ПО на Arduino будет нецелесообразно, так как, даже превосходя тактовую частоту Psion в 16 раз, Arduino окажется недостаточно быстр).

Но поскольку единственными подключёнными к шине устройствами являются датапаки и ЦПУ, которые должным образом воспринимают управляющие сигналы, а нужна нам лишь однонаправленная связь, то можно всё немного упростить. Я соединил несколько цифровых выводов Arduino с выводами данных на Psion через резисторы на 510 Ом (выбирал навскидку). Такое сопротивление достаточно мало, чтобы преодолеть внутренние подтягивания на линиях данных, но и достаточно высоко, чтобы не мешать датапакам или ЦПУ производить запись на шину. Это действительно простое решение, которое отлично работает. Теперь, когда все вопросы по аппаратной части закрыты, осталось реализовать ПО.

Изначально я всё это проделал на макетке, а когда убедился в работоспособности комплекта, собрал его уже на макетной плате с помощью паяльника:



При соединении двухрядной гребёнки с макетной платой возникли сложности из-за отсутствия удобного способа подключения к обоим рядам. Эту проблему я решил припаяв разъём с одной стороны и приклеив его с другой.


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

▍ Программное обеспечение


Нам нужна возможность считывать порт 2 и производить запись на дисплей. На Psion используется язык программирования OPL, который в некоторой степени аналогичен BASIC. Делать запись на дисплей легко, поскольку можно просто использовать PRINT — об остальном позаботится OPL. А вот считывание с порта 2 уже производить сложнее.

Здесь нам поможет страница Яапа, посвящённая системным переменным. На ней мы узнаем, что доступ к порту 2 происходит через адрес памяти 0x0003, а направление (чтение или запись) контактов устанавливается по адресу 0x0001:

Регистр направления данных порта 2. Бит 0 задаёт направление бита 0 [порта 2] (1=выход, 0=вход), а бит 1 устанавливает направление битов 1-7 [порта 2].

Значит, нам нужно записать в адрес 0x0001 нули, а затем считать биты с контактов данных верхнего слота через адрес 0x0003.

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

Активацией слотов мы управляем через запись в порт 6. Подробнее этот процесс описан на странице Яапа.

В OPL есть команда POKEB, с помощью которой можно записывать байты в произвольные адреса, а также функция PEEKB(), через которую можно выполнять считывание из произвольных адресов. Весьма удобно, не так ли? С их помощью мы можем выполнять чтение из верхнего слота.

Не тут-то было. Делая жизнь немного интереснее, OPL намеренно блокирует любую попытку чтения или записи адреса ниже 0x0040. Это очень озадачивает. Вероятно, так сделано, чтобы «вы не сломали девайс». Однако: а) если вы записываете значения в случайные адреса памяти, то уже должны осознавать возможные риски; б) устройство всё равно можно сломать и при использовании обходного пути.

А обходной путь — это написать на ассемблере код для доступа к памяти, собрать его вручную, записать байты в строку и выполнить эту строку из OPL, подобно вызову нативной подпрограммы машинного кода (наш случай). Довольно легко, но раздражает сам факт необходимости этого. Разработчики ничем не рисковали, если бы просто не стали блокировать доступ из OPL. (На самом деле, возможно, самым простым способом обхода данной проблемы было бы выяснить, где именно в памяти реализованы функции POKEB и POKEB(), после чего заменить в них проверку на NOP…).

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

Более того, я впервые получил рабочий пайплайн, заставив вводимый в Linux текст — отображаться на Psion:

В конечном счёте я написал следующую подпрограмму, которая устанавливает порт 2 в нужное состояние, считывает из него байт, закрепляет этот байт в регистре X и возвращает его (на моём выдуманном диалекте языка ассемблера):

72 18 16  oim $18, $16 # разрешаем запись в биты 0x10,0x08 на порту 6
71 18 17  aim $18, $17 # активируем верхний слот и отключаем два других
71 00 01  aim 0, $01 # режим ввода на порту 2
96 03     ld a, ($03) # считываем с порта 2 в A
c6 00     ld b, 0
36        push a
37        push b
38        pull x # получаем 16-битное значение из 0 и считываемого байта
39        rts

В написании этого кода мне помогла предложенная Яапом документация по набору инструкций HD6303.

Ранее мне не доводилось программировать HD6303, поэтому кое-что здесь может оказаться глупостью (например, использование 4-х инструкций для расширения байта в A до двух байтов в X), но с задачей моё решение вполне справляется. Когда есть машинный код, остаётся лишь написать программу OPL для его выполнения.

В итоге получилось вот что:

DISPLAY:
LOCAL S$(17),A%,B%,C%
POKEB $7C,0
S$=CHR$($72)+CHR$($18)+CHR$($16)+CHR$($71)+CHR$($18)+CHR$($17)+CHR$($71)
S$=S$+CHR$(0)+CHR$(1)+CHR$($96)+CHR$(3)+CHR$($C6)+CHR$(0)+CHR$($36)
S$=S$+CHR$($37)+CHR$($38)+CHR$($39)
WHILE KEY<>0:ENDWH
WHILE KEY=0
A%=USR(ADDR(S$)+1,0)
IF A%<>B%
C%=USR(ADDR(S$)+1,0)
IF A%=C%
IF A%<>0
PRINT CHR$(A%);
ENDIF
B%=A%
ENDIF
ENDIF
ENDWH

Локальная переменная S$ содержит наш машинный код. Каждая строка в OPL содержит 1 байт, указывающий на её длину, за которым следует содержимое самой строки. ADDR(x) даёт нам адрес переменной, а USR (addr, arg) выполняет машинный код. В итоге для выполнения кода мы вводим USR(ADDR(S$)+1,0. Всё это я выяснил с помощью руководства Яапа по машинному коду.

Изначальная POKEB $7C,0 инструктирует Psion не выключать экран в результате простоя. Основной цикл находится внутри WHILE KEY=0, что позволяет выйти из него нажатием любой клавиши.

Этот основной цикл считывает байт из верхнего слота. Если значение отличается от последнего известного, тогда считывание повторяется. Если второе считывание снова даёт иное значение, тогда игнорируются оба. Это делается для того, чтобы не получать испорченных результатов считывания в период, когда программа Arduino находится в процессе обновления значений на шине.

Когда же два последовательных считывания дают одинаковое значение, мы выводим этот байт на экран при условии, что он не 0 (который используется для разграничения последовательных выводов). Затем мы обновляем последний известный байт на тот, что только что считали, и повторяем цикл.

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

▍ 3D-печать


Последний этап — это сборка для всего этого комплекта электроники аккуратного корпуса. Недавно я установил на мой Prusa Mini сопло 0.6 мм (до этого в нём стояло стандартное с диаметром 0.4 мм). Думаю, это отличный апгрейд. Теперь детали печатаются намного быстрее, получаются (предположительно) прочнее, а само сопло не так легко засоряется. Утрата детализации при этом — оказывается минимальной и проявляется лишь в плоскости X/Y, поскольку при желании можно печатать с той же высотой слоя. Думаю, что сопло 0.6 мм должно использоваться по умолчанию.


Мне очень нравятся винты с потайной головкой, так как они симпатично вписываются вровень с поверхностью детали.


Больше фотографий корпуса и платы





Комментарии (1)


  1. dlinyj
    10.03.2022 13:15
    +2

    Помню, когда появилась статья "Обзор Psion Organiser II XP с внутренностями и Тетрисом" от Azya, то я очень загорелся этим аппаратом. Понимал полную его бессмысленность, но мне он казался очень прикольным.

    Но, к сожалению, так и не купил, потому что не придумал сферу применения. А с такой приставкой, конечно можно сделать что-то более весёлое.
    Ну и в статье, конечно забавное решение применить байткод для обращение к портам, это реально прикольный хак.