PC-9801でシリアル通信

FreeBSDのマシンからPC-9801へデータやプログラムを送る方法を考えてみた。一昔前であればフロッピーを使うのが常套手段だったが、もはやフロッピードライブは接続されていないのでシリアル通信を使う方法を調べてみた。

# Disk BASIC編

PC-9801VM2に付属していたN88-日本語BASIC(86) PC-98H44-MW(K)をPC-9801FA2で実行して、シリアル通信(RS-232C)を試して見ました。

PC-9801は内蔵のROM BASICかフロッピーのDisk BASICかMS-DOSで起動できました。DOS上にもN-88 BASICはありましがが、ここではDisk BASICを使っています。初期のPC-9801にはDisk BASICが付属していましたが、後期の製品には付いていませんでした。

NECのパソコンのBASICはN-BASIC -> N-88 BASIC -> N-88 BASIC(86)となりました。


起動すると以下のように表示されます。

```
Disk version
How many files(0-15)?
NEC N-88 BASIC(86) version 3.0
Copyright (C) 1983 by NEC Corporation / Microsoft Corp.
599464 Bytes free
Ok
```

クロスケーブルはHardOffで9ピンのケーブル(220円)を買ってきて手持ちの25ピンのケーブルと切ってつなぎ合わせて作りました。

|25ピン||9ピン|
|:--|:--|:--|
|2(TX)|->|2(RX)|
|3(RX)|<-|3(TX)|
|7(GND)|--|5(GND)|
|4(RTS)|->|8(CTS)|
|5(CTS)|<-|7(RTS)|
|6(DSR)|<-|4(DTR)|
|20(DTR)|->|6(DSR)|

N-88 BASIC(86)でシリアル関係の関数

|関数名|機能|
|:--|:--|
|CLOSE|ファイルを閉じます。|
|COM ON/OFF/STOP|RS-232C回線からの割り込み許可、禁止、停止を制御します。|
|INPUT#|シーケンシャルファイルからデータを読み込み、変数に代入します。|
|INPUT$|指定されたファイルより指定された長さの文字列を読み込みます。|
|LINE INPUT#|シーケンシャルファイルより、1行(255バイト以内)単位のデータを一括して文字型変数に読み込みます。|
|ON COM GOSUB|RS-232C回線からの割り込みが発生したとき、分岐する処理ルーチンの開始行番号を定義します。|
|OPEN|ファイルを開きます。|
|PRINT#|ファイルにデータを書き出します。|
|PRINT# USING|文字列、数値などのデータを編集し、ファイルに出力します。|
|WRITE#|ファイルにデータを書き出します。|


ケーブルを接続してPC-9801側で下記のプログラムを実行します。

```
10 OPEN "COM1:N81NN" AS #1
20 A$=INPUT$(1,#1)
30 PRINT #1,A$;
40 GOTO 20
```

一文字読み取って、一文字出力しています。

[FreeBSDをインストールしたValueStar](https://qiita.com/yamori813/items/2cdc2f8164e3973eb240)に接続して確認して見ます。

```
% sudo cu -l /dev/cuau0 -s 1200
Connected
123
```

簡単なファイル転送プログラムを作ってみました。PC-9801で以下のプログラムを実行します。

```
10 LINE INPUT "File Name "; F$
20 OPEN F$ FOR OUTPUT AS #1
30 OPEN "COM1:N81NN" AS #2
40 LINE INPUT #2,L$
50 IF L$="." THEN GOTO 80
60 PRINT #1,L$
70 GOTO 40
80 CLOSE #1
90 CLOSE #2
```

FreeBSD側では[SerialPort](https://github.com/monami-ya-mrb/mruby-serialport)を入れたmrubyで以下のコードを使います。

```ruby:save.rb
#!/usr/local/bin/mruby

sp = SerialPort.new("/dev/cuau0", 1200, 8, 1, 0)

File.open(ARGV[0]){|f|
  f.each_line{|line|
    sp.puts line.chomp + "\r\n"
  }
}
sp.puts ".\r\n"
```

以下のように実行します。

```
% sudo ./save.rb test.bas
```

N88-日本語BASIC(86)ではSAVE "COM1:N81NN"でシリアルに出力されるのですが、EOFはCTSで確認できました。

```ruby
while 1
  p sp.gets
  l = sp.get_signals
  if l[:cts] == 0
    break
  end
end
```

なぜか同じようにしてもLOAD "COM1:N81NN"が終了しません。なにを終了条件にしているのかわかりません。上に書いたプログラムを使う方法でファイル転送するのがよさそうです。

# MS-DOS 3.3編

MS-DOSではCOPYAコマンドでシリアル通信ができますが、CONFIG.SYSに以下の設定が必要です。

```
DEVICE=RSDRV.SYS
```

ボーレートなどの設定はSPEEDコマンドで行います。

手元にオシロがないので確認できないのですが、バイナリでの転送のEOFは信号線ではなくて、break信号な気がしています。

SPEEDで設定してCTTY AUXするとMS-DOSがシリアル経由で操作できます。漢字コードはもちろんSJISです。

アセンブラでエコーするコードです。

```
        org 100h

loop:
        mov ah,3
        int 21h

        mov dl,al
        mov ah,4
        int 21h

        jmp loop
```

# CCT98編

昔買った、CCT-98II(Ver2.5)のディスクが出てきたので、ファイル転送を試してみました。FreeBSD側にlrzszのpkgを入れて、昔懐かしいXMODEMでファイルを送信します。

```sh
% sudo sh -c "lsx test.txt < /dev/cuau0 > /dev/cuau0"
Sending test.txt, 1 blocks: Give your local XMODEM receive command now.
Bytes Sent:    256   BPS:70                              
```

FreeBSD側のデフォルトの通信速度が9600なので、CCT98側で合わせてシフト+F4がXMODEMで、ファイル名を入力するとファイル受信が始まります。

XMODDMは128バイトのブロック転送するので、ファイルのサイズが変わります。またPC-9801FAは9600ボーでしか通信できないので、とっても時間がかかります。

これらを回避するために、昔はLHAで圧縮して送受信していましたが、LHAは使用の中止が呼びかけられているので、解凍するlhasaのpkgはありますが、圧縮のコマンドのpkgは用意されていません。

いろいろ考えてみてzipコマンドはpkgもあり[DOSのコマンド](https://www.vector.co.jp/soft/dos/util/se010844.html)もあるので、zipで圧縮してDOS側でunzipする事にしました。

FreeBSDに標準で入っている圧縮コマンドのxzはDOS用のバイナリもありましたがPC-9801のDOS(3.3)では動きませんでした。

# XMODEM

ファイル転送のたびにCCTを起動するのが面倒なので、XMODEMの受信プログラムを作ってみました。エラーチェックなしタイムアウトなしのいい加減な実装です。SPEEDコマンドで設定をして、送信側を準備してから起動する必要があります。ファイル名を引数に渡します。

直結の場合、まったくといっていいほどエラーは起きないのでチェックサムやCRCはあまり必要ないと思います。

```
; MS-DOS xmodem revice program

SOH     EQU     01H
STX     EQU     02H
EOT     EQU     04H
ACK     EQU     06H
NAK     EQU     15H
CAN     EQU     18H

        ORG     100H

        MOV     BX,80H
        XOR     CX,CX
        ADD     CL,[BX]
        DEC     CX
        MOV     SI,82H
        MOV     DI,FOPT
L5:
        MOV     AL,[SI]
        MOV     [DI],AL
        INC     SI
        INC     DI
        LOOP    L5
        MOV     [DI],BYTE 0

; create
        MOV     DX,FOPT
        XOR     CX,CX
        MOV     AH,3CH
        INT     21H
        JC      ER

; open
        MOV     DX,FOPT
        MOV     AH,3DH
        MOV     AL,01H
        INT     21H
        MOV     [FHAND],AX

; XMODEM

        MOV     AH,04H
        MOV     DL,NAK
        INT     21H

L2:
        MOV     AH,03H
        INT     21H
        CMP     AL,EOT
        JE      L1

        MOV     CX,131
        MOV     BX,BUFF
L3:
        MOV     AH,03H
        INT     21H
        MOV     [BX],AL
        INC     BX
        LOOP    L3

        MOV     AH,04H
        MOV     DL,ACK
        INT     21H

; write
        MOV     BX,[FHAND]
        MOV     CX,128
        MOV     DX,BUFF
        ADD     DX,2
        MOV     AH,40H
        INT     21H

        JMP     L2

L1:
        MOV     AH,04H
        MOV     DL,ACK
        INT     21H

; close
        MOV     BX,[FHAND]
        MOV     AH,3EH
        INT     21H
        JMP     L4

ER:
        MOV     DX, ERRSTR
        MOV     AH,09H
        INT     21H

; EXIT
L4:
        MOV     AH,4CH
        INT     21H

        ERRSTR  DB      'ERROR$'
        FHAND   DW      0
        BUFF    DB      256 DUP(0)
        FOPT    DB      32 DUP(0)
```

引数の先頭にスペースが入っているのに気づかずはまりました。nasmかfasmでアセンブルできます。jwasmではとおりません。

XMODEMはファイルサイズを送らないので、最後のブロックがパディングされるのですが、バイナリファイルの場合、それがパディングによるものか、もともとあったものかはわからないので、受信側で削除できません。パディングされたもののファイルのハッシュ値を確認すると違っているので、今日では利用は控えたほうが良いのかもしれません。

# YMODEM

XMODEMのコードをいじってYMODEMの受信プログラムも作ってみました。

https://github.com/yamori813/xymdos

いつものようにトライアンドエラーでFreeBSDでアセンブルしてPC-9801のxrdos.comでyrdos.comを受け取って確認しました。

YMODEMでは先頭のブロックに、ファイル名やファイルサイズが入っています。ファイルサイズはアスキーで入っていて、16ビットだと64Kまでしか扱えません。さすがにそれではまずいのでBXレジスタとAHレジスタの24ビットで数値化して、ブロック数と最終ブロックのサイズに分けて保存するようにしました。

当たり前ですが、16ビットの最大値のFFFFとFFFFを足すと1FFFEで1以上の繰上げはありません。

ロジックは上位のAHを先に10回足して、次にBXを10回足します。BXを足してる間にキャリーが発生した場合はAHをINCします。終わったら数字のアスキーから'0'を引いて数値にしてBXに足します。ここでもキャリーが発生したときにはAHをINCします。スペースがあわられるまでこれを繰り返して、BXの下位7ビットを最終ブロックのサイズとして、AHの7ビットとBXの9ビットをブロック数としています。これで8Mバイトまでのファイルに対応できます。（24ビットフルで使えば16Mですが、1ビット捨ててるので8Mになります。）

ブロック番号の最初の0がファイル名などが入っているブロックでその後に実際のデーターが入ります。ブロック番号の255の後は0になるようでした。

AUTOEXEC.BATに以下を書いておくと自動的に設定されます。

```BAT:AUTOEXEC.BAT
SPEED RS232C-0 9600 B8
```

FreeBSD側でcshなどであればlsbをaliasしておくと便利です。

```
alias ys        'sudo sh -c "lsb -b '\!:1' < /dev/cuau0 > /dev/cuau0"'
```

CRCのチェックはしてないので、[MD5](https://qiita.com/yamori813/items/8bef11262cf50479d1fe)などで確認してください。

# ブートストラップ

CCTがあったので最初のコピーができましたが、もしCCTがない場合にどうするのが良いか考えて見ました。

パソコン通信で使われていたISH.COMだけはPC-9801側にある状態であればどうにかなりました。

初期のパソコン通信ではバイナリの保存ができなかったので、ISHの様なアスキーに変換するツールで、掲示板に貼り付けたりしていました。

PC-9801のMS-DOSにはCOPYAというコマンドがあります。このコマンドでテキストファイルの受信は以下のmrubyスクリプトからできます。

```ruby:copya.rb
#!/usr/local/bin/mruby

sp = SerialPort.new("/dev/cuau0", 9600, 8, 1, 0)

File.open(ARGV[0]){|f|
  f.each_line{|line|
    sp.puts line.chomp + "\r\n"
  }
}
sp.puts "\x1A"
```

FreeBSD側ではishのpkgをインストールしておきます。ishのpkgはメンテナーがいないよって警告がでますね。ishはOSのタイプを入れるようなので-mでMS-DOSを指定します。

```
% sudo pkg install ish
% ish -s -m a.exe
ish file converter for UNIX Ver2.01a5
original sources are Human68K Ver1.11
original idea By M.ishizuka(for MS-DOS)
copyright 1988 - Pekin - (by Ken+Gigo)
    non-kana by keizo   July 12, 1990
multi-vol restore supported by taka@exsys.prm.prug.or.jp 1992
multi-vol store   supported by aka@redcat.niiza.saitama.jp 1993

< Create to a.ish >
a.exe (8712 Bytes)... converted.
```

PC-9801のCOPYAをテキストの受信状態にしてFreeBSD側で下記を実行します。

```
% ./copya.rb a.ish
```

受け取ってISH.COMで展開すればバイナリの受信が可能です。

```
A>ISH A.ISH
ish file converter for MS-DOS  Ver. 2.03
Copyright (C) 1986, 1987, 1989, 1990   by  M. Ishizuka
[ restore <new> (m,*) ]
a.ish: 
[2] a.exe(m) : (152/152) ooo                                                   
   file restored successfully.

   1 file(s)/ 0 volume(s) restored.

```

この方法で上記のxmodemやymodemの実行ファイルを渡せば、次からはxmodemやymodemでバイナリの受信が可能になります。

# Windows 3.1編

Windows 3.1にはターミナルアプリケーションが標準で入っています。

![term.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/354da6a7-5267-af60-4da3-40357c644db3.png)

XMODEMの送受信ができます。

![x3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/1ea4a19d-9d5d-8119-8d7e-0ebd1b7f3348.png)