Подключение WIFI-ретромодема к компьютеру Z80-MBC2

f50b4ab7310b4a671a9ef2ca7ada97a9.jpg

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

Сегодня наш Отдел Перспективных Разработок представит вам свое новое детище, связанное с доработкой известного в узких кругах ультрабюджетного простейшего многоплатного восьмибитного компьютера Z80-MBC2 для подключения WIFI-модема с целью зайти через него, например, на BBS, или даже, в перспективе, создания подобия простейшего веб-браузера.

Компьютер представляет из себя множество вариантов реализации, раскиданных по всему интернету и поддерживающимися энтузиастами в разных странах, Все реализации, как правило, базируются на примерно одной и той же схеме — основная плата с процессором Z80, 128 Кб статической памяти, переключающей банки памяти одной логической микросхемы (74HC00) и обслуживающего все это хозяйство для связи всех шин передачи данных какого-нибудь контроллера (чаще Atmega-32A, как в оригинальной схеме).

Подробно схему описывать здесь не будем, их полон гугл. Ссылка на оригинал разработчика имеется.

С внешним миром данный компьютер общается посредством эмуляции терминала VT-100, через преобразователь TTL-USB или TTL-COM. На отдельных платах так же существуют реализации интерфейсов позволяющие подключать вывод терминала на VGA-дисплей и производить ввод через PS/2-клавиатуру.

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

Это и CP/M 2.2 и CP/M 3.0, и QP/M, всякие USCD Pascal, и даже Collapse OS. Некоторые умные люди пытаются прикрутить к подобному семейству компьютеров вариант линукса Fuzix, но, пока дело с портированием застряло. Это и языки программирования, от паскаля до фортрана, естественно, не обходя стороной ассемблер.

Пытливый читатель, разумеется, спросит — А зачем все это нужно?

Отдел Перспективных Разработок, конечно же, ответит на этот вопрос:

А не знаю. Мы тут отвечаем на вопросы «Как?». Вопросами же «Зачем?» соседняя лаборатория занимается, но, там сейчас сотрудников нет, поэтому, дело будете иметь со мной, самым молодым сотрудником нашего отдела из одного человека.

Кстати, по запросу «Классический ретро-модем 56K для интернета» ищутся носки на Алиэкспрессе, но, это нам сейчас тоже не нужно.

Так вот, к сути.

Синопсис

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

Как правило, создатель своего мега-творения все же оставляет в нем лазейки в виде каких-либо пинов GPIO или даже просто оставшихся неприкаянными ножек процессора или контроллера для сомнительных применений в грядущих извращениях последователей своего культа. И это уже хорошо, поскольку заставляет иногда подключать мозг, отдаляя приближение синдрома Альцгеймера. Вы же думаете об этом хоть иногда?

В рассматриваемом нами случае все примерно так и обстоит:

Схема Z80-MBC2 имеет один порт TTL ввода-вывода для консоли, а так же, поддержку шин SPI и I2C на уровне контроллера всех этих шин в данной схеме, а именно, Atmega-32A. На шине SPI уже висит модуль SD-карт, с которого производится загрузка операционной системы. Шина I2C обслуживает опционально подключаемый модуль часов DS3231 и микросхему-расширитель портов MCP23017, которая предоставляет пользователю целую гребенку портов GPIO.

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

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

Отталкиваться будем от конечного периферийного устройства.

Ретро-модем, в данном случае, — это устройство, подключающееся к современным сетям передачи информации и имитирующее работу обыкновенного древнего модема путем трансляции пакетов информации читаемых с определенного TCP-порта на физический порт RS-232. Это позволяет подключать не менее древний компьютер через единственный имеющийся у него физический порт для подключения модема, например, посредством Telnet к хосту в интернете, на котором работает, например, еще живая BBS и почитать там фидошную почту. Или загрузить побайтово содержимое интернет-странички (Браузеров для древних операционных систем мне лично не попадалось, но, всегда можно написать самому).

В интернетах нашлась реализация подобного модема, построенная на базе ESP8266 или подобных контроллеров типа Wemos D1 Mini. Последний, как раз, пылился у меня в столе, и потому, был нещадно применен. Прошив плату скачанной прошивкой, я удостоверился, что через виртуальный порт USB-COM модем прекрасно работает на обычном IBM PC через любой эмулятор терминала и позволяет заходить на BBS (и наблюдать медленную загрузку страничек со скоростью 1200 бод, которая установлена по дефолту).

Модификация операционной системы

Первая же всплывшая очевидная проблема на нашем грязном извилистом пути — выход модема некуда подключить к Z80-MBC2, лишних портов RS232 просто нет. Нет не просто физического порта, но и программной части, которая обслуживала бы еще один порт ввода-вывода помимо основного.

Ой, да не проблема это, подумал я, и взялся ковырять исходники CP/M 3.0 в той части, которая обеспечивает ввод-вывод консоли. Для CP-M 3.0 часть функций БИОСа переписана создателем компьютера Z80-MBC2, дабы оная на нем и запускалась. Все драйвера устройств кроме CON немилосердно вырезаны.

Однако, исходники как самой операционки, так и модификаций имеются в свободном доступе, потому, итогом пары ночей курения ассемблера Z80, компиляции на древних компиляторах под эмуляторами (zxcc) и играми с блоками БИОСа стало порождение модификации функций БИОСа операционки, которое добавляет второе виртуальное физическое устройство SIO, позволяющее читать и писать в виртуальный порт RS-232.

Здесь немножечко ассемблерного кода

; CHARIO.ASM, компонент bnkbios3.spr, содержащий функции ввода-вывода CP/M
.................................................
;Опкоды для новых операций ввода-вывода
SERTX$APC    EQU    090H      ; AUX SERIAL TX opcode
AUXPORT$APC  EQU    091H      ; AUX SERIAL RX opcode

.................................................
;;;;; ?CINIT (BCINIT FOR BANKED)  Функции инициализации для каждого устройства
	; PHYSICAL CODE FOR DEVICE INITIALIZATION:  
  ...............................................
	CALL	DEV$DISPATCH	;GO TO CORRECT INIT ROUTINE
	DW	CINIT0		;INIT FOR DEVICE 0
	DW	CINIT0		;INIT FOR DEVICE 1 - Поскольку все устройства работают через один
  							;порт, инициализировать их приходится одним и тем же способом
.................................................
;;;;; ?CI (BCI FOR BANKED) Функции ввода для каждого устройства
.................................................
  CALL	DEV$DISPATCH
	DW	CI0		;DEVICE 0 INPUT    Функция ввода для CON
	DW	CI1		;DEVICE 1 INPUT    Отдельная функция ввода для SIO
  ...............................................
  
;;;;; ?CO (BCO FOR BANKED)  Функции вывода для каждого устройства
	; PHYSICAL CODE FOR DEVICE OUTPUT:
  .................................................
	CALL	DEV$DISPATCH	;GO TO CORRECT DEVICE OUTPUT HANDLER
	DW	CO0		;DEVICE 0 OUTPUT   Вывод на консоль здя COM
	DW	CO1		;DEVICE 1 OUTPUT   Вывод на консоль для SIO
  ...............................................
  
;;;;; ?CIST (BCIST FOR BANKED)Функция получения информации о состоянии порта ввода
	; PHYSICAL CODE FOR DEVICE INPUT STATUS:
  .................................................
	CALL	DEV$DISPATCH
	DW	CIST0		;DEVICE 0 INPUT STATUS Для CON
	DW	CIST1		;DEVICE 1 INPUT STATUS Для SIO
  ...............................................
  
;;;;; ?COST (BCOST FOR BANKED)Функция получения информации о состоянии вывода
	; PHYSICAL CODE FOR DEVICE OUTPUT STATUS:
  .................................................
	CALL	DEV$DISPATCH	;GO TO CONSOLE OUTPUT STATUS HANDLER
	DW	COST0		;DEVICE 0 OUTPUT STATUS Одинаковая для всех
	DW	COST0		;DEVICE 1 OUTPUT STATUS - CON и SIO всегда готовы 
  ...............................................
  
  CIST1:				; DEVICE 1 Статус устройства ввода
                ; По сравнению с DEVICE 0 все проще, пусть будет всегда готово
    mvi      a, 0ffH        ; Return CP/M a char ready flag ($FF)
    ret
.................................................

CI1:				;DEVICE 0 INPUT
						;Примитивно выводим в порт код операции, а потом ждем с порта
            ;прилетевший байт по I2C
GetChrA:
    mvi     a, AUXPORT$APC  ; A = READ AUX Opcode
    out     STO$OPCD        ; Write the opcode        
    in      EXC$RD$OPCD
ChkFFA:                      
    ret                     
................................................

;Функции вывода байта - по сути одна и та же функция вывода в один и тот же порт
;Меняется лишь опкод операции для CO0 и CO1
CO1:				; DEVICE 1 OUTPUT
    mvi     a, SERTX$APC    ; A = SERIAL TX opcode
    jp	    Cmainc
CO0:				; DEVICE 0 OUTPUT
    mvi     a, SERTX$OPC    ; A = SERIAL TX opcode
Cmainc:
    out     STO$OPCD        ; Write the opcode
    mov     a, c
    out     EXC$WR$OPCD     ; Send A to serial Tx
    ret
................................................

;Таблица настроек устройств
@CTBL:
	DB	'CRT   '		    ;CONSOLE (DEVICE 0)
	DB	MB$IN$OUT
	DB	BAUD$NONE
	DB	'LPT   '		    ;VIRTUAL LPT (DEVICE 1)
	DB	MB$IN$OUT+MB$SERIAL+MB$SOFT$BAUD+MB$XON$XOFF
	DB	BAUD$NONE
	DB	'SIO  '		    ;VIRTUAL SERIAL (DEVICE 2) Наше новое устройство
	DB	MB$IN$OUT+MB$SERIAL+MB$SOFT$BAUD
	DB	BAUD$9600
................................................

;; BOOT.ASM, компонент bnkbios3.spr, содержащий функции ввода-вывода CP/M
................................................
?INIT:
    ; ASSIGN CONSOLE INPUT AND OUTPUT TO CRT:
    LXI H,8000H         ;SIGNIFIES DEVICE 0
    SHLD    @CIVEC      ;CONSOLE INPUT VECTOR
    SHLD    @COVEC      ;CONSOLE OUTPUT VECTOR
    ; Назначим виртуальные устройства AUX и LST на наше новопридуманное
    ; устройство SIO
    LXI H,4000H         ;SIGNIFIES DEVICE 1
    SHLD    @LOVEC      ;LST OUTPUT VECTOR
    LXI H,2000H         ;SIGNIFIES DEVICE 1
    SHLD    @AIVEC      ;AUX INPUT VECTOR
    SHLD    @AOVEC      ;AUX OUTPUT VECTOR    
    

Со стороны операционной системы CP/M второй порт ввода-вывода представлен логическим устройством AUX. В оригинальной реализации (далее — оригинальная реализация CP/M 3.0 для Z80-MBC2) порт AUX просто переназначен на физическое устройство CON и читает и пишет на основную консоль ввода/вывода. Поэтому, его пришлось отвязать и переназначить на новонаписанное устройство SIO.

Так же добавлено новое виртуальное физическое устройство LPT, которое является тем же самым устройством SIO, но, используется только для вывода (для возможности назначения виртуальному устройству LST, дабы, в перспективе, еще и вывод на принтер сделать).

Само виртуальное физическое устройство SIO при обращении к нему должно было делать следующие вещи:

  • При записи в устройство посылать на контроллер шины команду с кодом 0×90 (коды взяты от балды, просто первые свободные из всего списка кодов), сигнализирующую о том, что сейчас будет послан байт в наш дополнительный порт, а затем посылать искомый байт.

  • При чтении с устройства посылать на контроллер шины команду с кодом 0×91, сигнализирующую о том, что нужно считать один байт с нашего дополнительного порта, а затем запрашивать один байт.

Все исходники для сборки есть на гитхабе, если вдруг кому нужно.

Итак, виртуальный COM-порт у нас уже есть, но, толку от того никакого, поскольку, реализации в железе для него нет. Посмотрев, что есть свободного в наличии на плате самого Z80-MBC2, я сначала хотел привязаться к шине SPI. Но, лишнего пина для выбора устройства на контроллере шины (Atmega32) просто не нашлось, да и GPIO, как я упоминал ранее, у меня просто нет.

Потому, была выбрана шина I2C. Гребенку пинов от нее я зачем-то оставил еще при проектировании платы своего варианта компьютера, и они оставались бесхозными до сего момента.

Модификация контроллера шины

В реализации Z80-MBC2 контроллер шины, а именно, Atmega-32A, привязан только к одной адресной ножке A0 процессора Z80, потому, используются лишь два порта процессора — 1 для вывода и 0 для ввода.

Прошивка контроллера шины была так же модифицирована, дабы работать с посланными кодами. Предполагалось, что в системе появится новое устройство Slave I2C с адресом 0×08, которое будет нужным нам модемом, и будет принимать-передавать байты по шине при запросе к нему. Контроллер шины, читая с порта вывода процессора Z80 байт команды, будет принимать решение — считать с модема байт данных и передать его в порт процессора Z80, или, наоборот, считать с порта Z80 байт и передать его по шине в модем.

Прошивка Atmega-32A, Кусочек функции проверки входящих кодов с порта процессора:

Bool moduleSERIALPORT; // Флаг наличия подключенного устройства I2C_slave с адресом 0x08.
 // Проверяется на старте, и, если устройство найдено, флаг устанавливается
...................................................

//В функции loop() производится чтение приходящих кодов операций с порта 01 процессора Z80
//два раза подряд. Сначала в ioOpcode для определения кода операции, затем байт данных в ioData для операций с ним. Проверяется отдельно для установленного RD или WR на процессоре.
//Проверка для установленного WR:
switch (ioOpcode) {
...................................................
case  0x90:
            // AUX TX:
            //                I/O DATA:    D7 D6 D5 D4 D3 D2 D1 D0
            //                            ---------------------------------------------------------
            //                             D7 D6 D5 D4 D3 D2 D1 D0    ASCII char to be sent to serial
            // READ from PUNCH device

            if (moduleSERIALPORT) {                //send byte to i2c serial extender
              Wire.beginTransmission(SLAVE_ADDR);  // transmit to device #9
              Wire.write(ioData);                  // sends byte from IN 0 to I2C slave device
              Wire.endTransmission();
            }
            break;
...................................................

//Точно так же проверка для установленного RD:
switch (ioOpcode) {
...................................................
          case  0x91:
            // READ_AUX (CPM READER PORT):
            ioData = 0;
            int ii = 0;
            bool dn = false;
            AuxPortHasChar = false;  //Флаг для операции запроса байта состояний SYSFLAGS.
            //Если в данном цикле loop() считался байт с модема,
            //то, по запросу системой атрибута SYSFLAGS, 4 бит в нем
            //должен быть 1, что означает, что c AUXIN можно читать байт
            LastRxIsEmptyAUX = 0;    //Флаг для операции запроса байта состояний SYSFLAGS.
            //Поскольку плата I2C-транслатора отправляет два байта,
            //Вторым байтом приходит флаг отсутствия конца буфера
            //считанных с модема байт. Еслили буфер еще не кончился,
            //Вторым байтом прилетает 1, соответственно, флаг нужно тоже установить в 1,
            //И в 5 бит в SYSFLAGS пойдет 1, что будет означать, что на
            //логическом порту CP/M READER (AUXIN) есть следующий
            //байт для следующего цикла чтения
            byte attr = 2;
            while (!dn) {            //Вертим цикл пока не считан байт данных, или пока не прошло 100 итераций
              delayMicroseconds(50);
              Wire.requestFrom(SLAVE_ADDR, 2);
              if (Wire.available() > 0 ) {
                ioData = Wire.read();   //Первый – байт данных
                attr = Wire.read();      //Второй байт – атрибут. Если 1, то после этого байта данных у модема
                //есть еще данные в буфере. Если же 2, то данных никаких не читалось
                if (/*ioData > 0*/ attr < 2) {  //Если атрибут не 2, то, значит, принят нормальный байт с модема
                  dn = true;
                  AuxPortHasChar = true;        //.4 бит в SYSFLAGS будет установлен в 1, значит, c AUXIN можно считать байт
                }
                if (attr == 1) LastRxIsEmptyAUX = 1;   //Если атрибут = 1, 5 бит в SYSFLAGS установим.
                //if (atr > 0) AuxPortHasChar = true;
              }
              ii++;
              if (ii > 100) dn = true;
            }
            break;
...................................................            
            
            //Отправка байта атрибутов SYSFLAGS по запросу операционки
case  0x83:
            // SYSFLAGS (Various system flags for the OS):
            //                I/O DATA:    D7 D6 D5 D4 D3 D2 D1 D0
            //                            ---------------------------------------------------------
            //                              X  X  X  X  X  X  X  0    AUTOEXEC not enabled
            //                              X  X  X  X  X  X  X  1    AUTOEXEC enabled
            //                              X  X  X  X  X  X  0  X    DS3231 RTC not found
            //                              X  X  X  X  X  X  1  X    DS3231 RTC found
            //                              X  X  X  X  X  0  X  X    Serial RX buffer empty
            //                              X  X  X  X  X  1  X  X    Serial RX char available
            //                              X  X  X  X  0  X  X  X    Previous RX char valid
            //                              X  X  X  X  1  X  X  X    Previous RX char was a "buffer empty" flag
            //                              X  X  X  0  X  X  X  X    Serial AUX RX buffer empty
            //                              X  X  X  1  X  X  X  X    Serial AUX RX char available
            //                              X  X  0  X  X  X  X  X    Previous AUX RX char valid
            //                              X  X  1  X  X  X  X  X    Previous AUX RX char was a "buffer empty" flag
            // NOTE: Currently only D0-D5 are used
// Здесь добавлены 4 и 5 биты для флагов нашего второго порта
            ioData = autoexecFlag | (foundRTC << 1) | ((Serial.available() > 0) << 2) | ((LastRxIsEmpty > 0) << 3) | ((AuxPortHasChar) << 4) | ((LastRxIsEmptyAUX > 0) << 5);
            break;

Далее потребовалось модифицировать прошивку ретро-модема, дабы она могла отправлять байты не на свои пины RX/TX, а на шину I2C. Но, как водится, здесь тоже подвернулся огромный такой подводный булыжник:

Оказалось, что контроллеры на базе ESP8266 внезапно не хотят работать как Slave-устройства с шиной I2C и не видятся устройством Master. Интернет пестрит жалобными cообщениями людей, которые сталкивались с данной проблемой, но, адекватного решения из нескольких перебранных мне так и не подвернулось.

Если вы имеете счастливый опыт решения проблемы подключения ESP8266 как I2C-Slave, пожалуйста, ткните пальцем на него мне в комментариях, я буду вам очень, очень-очень благодарен.

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

Модификация Wifi-модема

Чем-то еще в нашем случае стал отдельный контроллер-транслятор из шины I2C на обычные TTL RX/TX (далее — транслятор), а именно, еще одна отысканная в столе плата Arduino Pro Mini. Именно данная плата должна подключаться на шину I2C компьютера Z80-MBC2 в качестве устройства I2C-Slave с адресом 0×08 и транслировать прилетевшие байты со своих пинов RX/TX Serial-порта на RX/TX-пины Serial-порта платы Wemos D1 (модема). Вот такой вот странный бутерброд.

Бутерброд из модема с платой-трансляторомБутерброд из модема с платой-транслятором

Реализация через плату-транслятор тоже оказалась, к сожалению, не совсем гладкой:

Внезапно, это всё, наконец, заработало, но, не полностью. Часть читаемых с модема байт данных куда-то пропадала.

Выяснилось, что транслятор, как Slave-устройство, прерывается принудительно по прерыванию извне, дабы запросить с него по I2C байт данных. А модем, не подозревая об этом, шлет в Serial данные, которые некому считать в этот момент. Потребовалось как-то притормозить модем, дабы не слать их на плату-транслятор, который в момент отправки данных на I2C занят и ничего читать не может.

По сути, плата-транслятор получилась, как-бы, Slave-прокладкой между двумя Master-устройствами, и проблема эта была метко окрещена в нашем отделе «Double penetration»,  однако, подробности сего мы опустим.

Для решения проблемы я соединил два пина — один выходной D9 на плате-трансляторе с входным пином D3 на плате модема. Эта линия должна была сигнализировать модему о том, что можно писать данные в Serial. Если пин установлен в HIGH, модем счастливо валит свой накопленный буфер в Serial, откуда его загребает плата-транслятор. Если LOW, то модем, соответственно, ждет.

Прошивка транслятора получилась такой:

#include 
#include 
#include 

// Define Slave I2C Address
#define SLAVE_ADDR 0x08

// Define Slave answer size
#define RDBUFFERLEN 1

//Если хочется, можно применить SoftSerial
//#define USE_SOFT_SERIAL 1

//Для SoftSerial-порта выбраны отдельные пины
//Это сделано для возможности отладки через основной Serial-порт
#define RS232_RX_PIN 3  // Rx Pin
#define RS232_TX_PIN 2  // Tx Pin

//Пин для сигнализирования модему о том, что можно посылать данные
//Если HIGH – модем посылает, мы их читаем отсюда с SoftSerial
#define PIN_ACCESS 9

#ifdef USE_SOFT_SERIAL
SoftwareSerial Sserial(RS232_RX_PIN, RS232_TX_PIN);  // (Rx, Tx)
#endif

bool DataReaded = false;
bool DataNeedOut = false;
String ReadedData = "";
String OutData = "";
uint8_t rec[RDBUFFERLEN];
byte reclen = 0;
byte recptr = 0;

char outrec[256];
byte outreclen = 0;
byte outrecptr = 0;
bool locked = false;
//String receiveBuffer = "";
byte lastch = 0;

//Чтение по i2c прилетевшего байта и сразу отправка его в Serial-порт модема.
//Все равно, при этом событии он простаивает
void receiveEvent(int numBytes) {
  PORTB |= _BV(1);
  while (Wire.available()) {
    byte x = Wire.read();
#ifdef USE_SOFT_SERIAL
    Sserial.write((char)x);
#else
    Serial.write((char)x);
#endif
  }
}

//Отправка по запросу с Master'a на шину i2с байт с буфера считанных с Serial модема данных
//Отправляется один байт, за ним байт флага. Если байт последний в буфере, то, флаг = 0, иначе 1
//Если же данных с модема вообще небыло, вернем флаг = 2
void requestEvent() {
  uint8_t pack[2];
  pack[0] = 0;
  pack[2] = 2;    //Атрибут байта установим по умолчанию в 2. Это значит, что с модема ничего не читалось,
				  //и нам нечего возвращать на шину I2C компьютеру
  PORTB |= _BV(1);
  if (DataReaded) {
    pack[0] = (uint8_t)rec[recptr++];
    pack[1] = 1;
    if (recptr == reclen) {
      DataReaded = false;
      reclen = 0;
      recptr = 0;      
      pack[1] = 0;
    }
  }
  Wire.write(pack,2);
  delayMicroseconds(1);
}

//Цикличная функция чтения с порта модема в буфер накопленных байт.
//Поскольку, на прием-передачу Serial прерываний не предусмотрено,
//могут возникать ситуации, что при рассинхронизации готовности к передачи модема и готовности
//к приему данной платы-транслятора окна приема и передачи могут расходиться.
//Опытным путем установлено, что чем меньше этот буфер (RDBUFFERLEN), тем стабильнее работает прием-передача,
//и байты не теряются.
void readData() {
  //byte sz = 0;
  if (!DataReaded) {
    locked = true;
    reclen = 0;
    //Устанавливаем в HIGH пин разрешения записи в порт для модема
    //digitalWrite(PIN_ACCESS, HIGH);
    PORTB &= ~(_BV(1));
#ifdef USE_SOFT_SERIAL
    //while ((Sserial.available() > 0) && (reclen <= RDBUFFERLEN)) {
    while ((Sserial.available() > 0) && (reclen <= RDBUFFERLEN)) {
      rec[reclen++] = Sserial.read();
      //reclen++;
      delayMicroseconds(5);
    }
#else
    delayMicroseconds(10);
      while (Serial.available() && (reclen < RDBUFFERLEN)) {
        rec[reclen++] = Serial.read();
        //delayMicroseconds(2);
      }
#endif
    //Устанавливаем в LOW пин разрешения записи в порт для модема
    //digitalWrite(PIN_ACCESS, LOW);
    PORTB |= _BV(1);
    if (reclen > 0) {
      recptr = 0;
      DataReaded = true;
    }
    locked = false;
  }
}

.....................................
  
  
void setup() {
  pinMode(RS232_RX_PIN, INPUT_PULLUP);
  pinMode(RS232_TX_PIN, OUTPUT);

  pinMode(PIN_ACCESS, OUTPUT);
  digitalWrite(PIN_ACCESS, LOW);

  Wire.begin(SLAVE_ADDR);
  // Function to run when data requested from master
  Wire.onRequest(requestEvent);
  // Function to run when data received from master
  Wire.onReceive(receiveEvent);

  // Setup Serial Monitor
#ifdef USE_SOFT_SERIAL
  Sserial.begin(4800);
#endif
  //4800 бод, господа. Делать выше чревато мусором по Serial
  Serial.begin(4800/*,SERIAL_8E2*/);
  delay(500);
}

void loop() {
  //По сути, в цикле нужно вертеть лишь функцию чтения с Serial
  //дабы отследить прилетевшие байты с модема
  readData();
#ifdef USE_SOFT_SERIAL
  printData();
  getSerial();
  outData();
#endif

}

Прошивку модема я модифицировал незначительно.

Сначала добавил команду AT$CA для включения режима остановки по проверке пина D3, а потом и просто принудительное включение режима при обнаружении факта, что с D3 читается HIGH.

Добавил процедуру приостановки при проверке пина:

//Функция замедления, если на пине PIN_ACCESS сигнал LOW
void printAccessDelay2() {
  if (digitalRead(PIN_ACCESS) == HIGH) accessmode = true;
  if (accessmode) {
    int j = 0;
    while ((digitalRead(PIN_ACCESS) == LOW) && (j < 100)) {
      j++;
      yield();
    }
  } else {
    if (digitalRead(PIN_ACCESS) == HIGH) accessmode = true;
  }
}

В процессе отладки пришлось поиграться так же с буфером чтения платы транслятора с Serial модема. Проще говоря, байты продолжали изредка куда-то теряться из-за каких-то неведомых рассинхронизаций времени срабатывания функций, и потому, чтение байт транслятором с модема свелось к простой последовательности — разрешить модему запись и считать 1–2 байта, отправить по запросу их на I2C, снова разрешить модему запись и считать 1–2 байта, и т.д.

Но и это всё еще не всё!

Реализация терминала

Следующей проблемой стало то, что ни одна терминальная программа под CP/M не хотела работать с моим псевдомодемом из самой операционки, поскольку, видимо, не могла конфигурировать что-то там у себя, либо получать нужную информацию с модема.

Нужна была, по сути, простейшая терминальная программа для CP/M, которая бы работала через виртуальный Serial-порт AUX операционки напрямую, читая и записывая байты как есть, без контролей четности, скорости и т.п.  Даже тот же VT100 не нужно было имитировать, поскольку, само по себе консольное устройство CON и подключаемые к нему внешние устройства для Z80-MBC2 могут обрабатывать ESC-последовательности и брать реализацию терминала на себя.

Сил переписывать исходники каких-нибудь утилит типа IMP, Kermit, QModem и подобных у меня уже не оставалось, да и слабоваты мои знания в области организации всех этих Xor/Xoff, 8N1 и контролей четности.

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

Код ее я уже приводить не буду, найдете его сами в гитхабе, если интересно.

И вуаля, я в сети.

Терминальное окноТерминальное окноЗвонок на BBSЗвонок на BBS

Скорость работы всей этой конструкции оказалась довольно низкой, но, много мне тут и не было нужно. Скорость работы самого модема удалось поднять до космических 4800 бод, визуально же, пройдя через все аппаратно-программные трансляции, скорость загрузки на компьютер осталась примерно на уровне 2400 бод, как в древние времена.

Если ты дочитал до этих строк, значит, как минимум, тема статьи тебе небезразлична. Потому, если ты обладаешь информацией, как модифицировать любой нормальный терминал под CP/M типа IMP для работы через COM-порт напрямую, минуя контроль, прошу раскрыть свои секретные секреты в комментариях. Может быть, вместе у нас выйдет что-то удачное, а потом мы даже придумаем всему этому назначение, и, взявшись за руки, упрыгаем по цветочному полю в горизонт.

Возможно, полезные ссылки:

© Habrahabr.ru