復活!CP/M ワンボードマイコンでCP/Mを!
CP/MがTK−80互換のワンボードマイコンの上で復活します
ND80ZVとMYCPU80の上でCP/Mが走ります!
[第124回]
●BS(Back Space)プログラム
CP/Mでは、キーボードからの入力も、画面への表示もBIOSをコールすることで行ないます。
前回の説明ではそこのところがちょっとあいまいになっていました。
通常のユーザーはキーボードからの入力にはファンクションコール01(コンソール入力)を使い、画面への表示はファンクションコール02(コンソール出力)を使います。
しかし今回のお話は、そもそもそのファンクションコール01と02のプログラムをどうしようか、というお話ですから、もっと下のレベルでのお話になります。
通常のユーザーはBIOSのレベルには立ち入りません。
しかしシステムを作り上げるためにはBIOSを使わないわけにはいきません。
BIOSレベルでのキーボードからの入力はCONIN、画面への表示はCONOUTになります。
それだけの説明を読みますと、単純にファンクションコール01=CONIN、ファンクションコール02=CONOUT、というように考えてしまうかも知れませんが、実体は全く異なります。
CONINはただキーボードから入力されたコードを受取るだけで、何の判断も行ないません。
CONOUTも渡されたコードを画面表示用文字コードとして送出するだけで、そのほかの何の情報も送りません。
スタンダードなCP/Mの機能としては、たとえば画面の表示位置(カーソルポイント)の制御などは全く備えておりません。
ただのCONIN、CONOUTがあるだけです。
しかし前回お話をしました、BS(Back Space)は皆様ご存知の通り、カーソルポイントを1つ前に戻します。
カーソル制御のできないシステムでどうやってBSの機能を実現するのか?
というのが前回からの宿題でありました。
それでは、前振りはこのくらいにいたしまして、さっさく本題にかかりましょう。
CP/Mはカーソル制御の機能をもってはいませんが、唯一その制御に関わる機能が存在します。
それが前回ヒントとして書きましたCR(Carriage Return)です。
復帰などと訳します。
もともとはタイプライターで印字ヘッドを左端に戻すことを意味した言葉だったようです。
TEXTファイルなどではCRコード(0DH)だけで復帰だけではなくて改行も一緒に行なってしまうようなテキストエディタが一般的ですが、もともとのCRコードには改行の意味はありません。
改行のためのコードとしてはLF(Line Feed。0AH)を使います。
ですから、復帰・改行を合わせて、0D・0Aと2バイトを画面に送ることで、カーソルポイントが画面左端の1行下に移動します。
では、唯一のカーソル制御コードであるCRを使って、どうすればカーソルポイントを1文字前に戻すことができるのでしょうか?
その答えといいますか、考え方を先に説明してから、あとで実際のプログラムをお見せすることにしましょう。
前回の終わりのところでBSを実際に使っている画面をお見せしました。
A>abcde_
という表示です。
ここでA>はシステムによる表示で、abcdeはキーボードからそのように入力したときのエコー表示です(実はこのエコーもシステムによって行なわれています)。
この状態を図で示すと、こうなります。
ある1行の表示画面の左端を00として、そこから順に1文字ずつ番号をつけます。
これがカーソル位置を示す番号になります。
メモリにカーソル位置カウンタを用意します。
さて、そのカウンタはいつクリアするとよいのでしょう?
そうです。
CRコードをCONOUTで送出したときに、カーソル位置カウンタも00クリアします。
そして、CR以外の文字コードをCONOUTで送るたびに、カーソル位置カウンタを+1すればよいですね。
さて、そこで、今カーソルは07の位置にあります。
ここでBSコードが入力されてきました。
必要な作業は「実際の画面」に表示された文字’e’を消して、同時にカーソルを06の位置に「実際の画面」で戻すことです。
そんなことはムリ…。
と思ってしまいますが、上の図をよーく見ていると、ほら、できるじゃありませんか。
まず、CRコードをCONOUTで送出します。
ただしこのときはカーソル位置カウンタはクリアしないようにします。
すると実際の画面上でカーソルは00の位置に戻ります。
ここからがマジックのはじまりです。
現在のカーソル位置(ポジション00)から、カーソル位置カウンタの数値と同じだけ、CONOUTを使ってコード20H(スペース)を送出します。
すると図の、実際の表示画面のように、表示がクリアされます。
本当はポジション06だけをクリアすればよいのですが、カーソル移動機能がありませんから、最初からその位置までスペースを表示させていきます(このときもカーソル位置カウンタは更新しないようにします)。
カーソル位置カウンタの値が00のときは以下の作業はパスします。つまりBSは行なわれません。
そして最後の仕上げです。
実は図の上側に表示していた文字列なのですが、これはカーソル位置カウンタと同様に、同じタイミング(CONOUT送出時)に、あらかじめ用意しておくラインバッファに図の順序で格納しておくのです。
カーソル位置カウンタは常にそのバッファの現在のアドレスを示す位置ポインタでもあります。
CRコードを送出するときにカーソル位置カウンタは00クリアしますが、ラインバッファはクリアする必要はありません(無駄な作業はする必要はありません)。
なぜ、クリアしなくてもよいのか、ということにつきましては、この説明を最後まで読んでいただければ納得できることと思います。
最後の仕上げとして、ラインバッファの先頭から1文字ずつ、カーソル位置カウンタの値−1回分、CONOUTで送出します(カーソル位置カウンタの値が01のときは、なにも送出しません)。
そして、その値をカーソル位置ポインタにセットします。
表示されていた文字が1文字消えて、カーソルが前に移動しました。
しかし。
このBSの機能を実現するためだけに、ラインバッファを用意して、そして画面に文字コードを送るたびに、毎回バッファへの文字コードの書き込みと、カーソル位置カウンタの更新を欠かさず行なわなければなりません。
なかなかに面倒ではあります。
実は、この機能はファンクションコール02に組み込んでありますから、それを使わないでユーザーが勝手にBIOSのCONOUTルーチンを使って画面表示を行なうと、BSは正しく機能しなくなります。
CP/Mに限らないのですが、ユーザーがBIOSを直接コールすることは危険が伴うということのひとつの参考例でもあります。
ところで。
ファンクションコールにはコンソールバッファ入力の機能もあります(ファンクションコール0A)。
上で説明しましたラインバッファは、そのコンソールバッファなのか?とお考えかも知れません。
ここで説明しましたラインバッファは、コンソールバッファとは別物です。
あくまでBSのためだけに必要な表示用のバッファなのです。
考え方はそういうことだとわかったとしまして、理解できたこととそれをプログラムに書く、ということとはまた別の次元になります。
どのようなプログラムを書けばよいのでしょうか?
こういうプログラムがそれほど悩まなくても書けるようになれば、一人前といえるでありましょう。
これが実際のプログラム(アセンブルリスト)のファンクションコール01と02の部分です。
BSの処理は中ほどにあります。
たったこれだけ、というくらい短くまとまっておりますでしょう。
ファンクションコール02がファンクションコール01でもコールされていることに注目してください。
; ;CONSOLE INPUT FCALL 01 ; C4DE C5 CONIN:PUSH BC C4DF CD09D2 CALL B_CONIN C4E2 F5 PUSH AF C4E3 5F LD E,A C4E4 CDEAC4 CALL CONOUT C4E7 F1 POP AF C4E8 C1 POP BC C4E9 C9 RET ; ;CONSOLE OUTPUT FCALL 02 ; C4EA E5 CONOUT:PUSH HL C4EB C5 PUSH BC C4EC 2A64D0 LD HL,(CURSOR) C4EF 7B LD A,E C4F0 FE08 CP 08;BS C4F2 CA18C5 JP Z,CONOUT2 C4F5 FE09 CP 09;TAB C4F7 CA45C5 JP Z,CONOUT3 C4FA FE0D CP 0D;CR C4FC CA5AC5 JP Z,CONOUT4 C4FF FE0A CP 0A;LF C501 CA5AC5 JP Z,CONOUT4 C504 FE1B CP 1B C506 DA64C5 JP C,CONOUT44;ctrl+ C509 4B CONOUT12:LD C,E C50A 71 LD (HL),C C50B CD0CD2 CALL B_CONOUT C50E 23 INC HL C50F 7D LD A,L C510 FE50 CP 50 C512 CA5EC5 JP Z,CONOUT42 C515 C361C5 JP CONOUT43 ;BS C518 7D CONOUT2:LD A,L C519 B7 OR A C51A CA64C5 JP Z,CONOUT44 C51D 0E0D LD C,0D;CR C51F CD0CD2 CALL B_CONOUT C522 45 LD B,L C523 0E20 LD C,20 C525 CD0CD2 CONOUT22:CALL B_CONOUT C528 05 DEC B C529 C225C5 JP NZ,CONOUT22 C52C 0E0D LD C,0D;CR C52E CD0CD2 CALL B_CONOUT C531 2D DEC L C532 CA61C5 JP Z,CONOUT43 C535 45 LD B,L C536 2100D1 LD HL,LINEBF C539 4E CONOUT23:LD C,(HL) C53A CD0CD2 CALL B_CONOUT C53D 23 INC HL C53E 05 DEC B C53F C239C5 JP NZ,CONOUT23 C542 C361C5 JP CONOUT43 ;TAB C545 0E20 CONOUT3:LD C,20 C547 71 LD (HL),C C548 CD0CD2 CALL B_CONOUT C54B 23 INC HL C54C 7D CONOUT32:LD A,L C54D FE50 CP 50 C54F CA5EC5 JP Z,CONOUT42 C552 E607 AND 07 C554 C245C5 JP NZ,CONOUT3 C557 C361C5 JP CONOUT43 ;CR,LF C55A 4B CONOUT4:LD C,E C55B CD0CD2 CALL B_CONOUT C55E 2100D1 CONOUT42:LD HL,LINEBF C561 2264D0 CONOUT43:LD (CURSOR),HL C564 C1 CONOUT44:POP BC C565 E1 POP HL C566 C9 RET |