SSブログ
English Version

ポケコン(G850)用拡張ボード(EborsyEEP)でCP/M(その10)エスケープシーケンス対応 [ポケコン]

 今回開発した PC-G850V(以降ポケコンと記す)で CP/M を動かすことが出来る増設メモリボード(EborsyEEP)をBOOTHで配布した際の反応等からフルキーを実装した手のひらサイズの Z80 コンピュータであるポケコンの魅力を改めて痛感しました。
 しかし、現状のままでは CP/M は動くものの結局はパソコンに接続しないと使い辛い状況であると言わざるを得ません。

 ポケコン単体では使い辛いと感じる主要な原因の一つとして「手軽に使えるエディタが無い」ということがあると思います。OS を CUI ベースで使うとなると最低限、スクリーンエディタが必要ですよね。

 そこで次の改善を行うことにしました。
  1. エスケープシーケンス対応
     現状の仮想スクリーン(80x25)ではスクリーンエディタを動かせないのでエスケープシーケンスに対応することにします。「ポケコン(G850)用拡張ボード(EborsyEEP)でCP/M(その3)CP/M起動」の記事でも少し書きましたが、現状の仮想スクリーンの機能はスクリーンエディタの仮想的なスクリーンと競合してしまい、相性が 悪いのでエディタの方を LCD 画面サイズに対応するようにしました。

  2. ポケコン用スクリーンエディタの準備
     今回実装したエスケープシーケンスを使えば、Turbo Pascal の柔軟にカスタマイズできるエディタも使えるのですが、タブ表示に対応していなかったり、エディタ以外の機能は使わないようなユースケースも想定し、自作のエディタである SKED もポケコン対応することにしました。

  3. コントロールコードの入力対応
     スクリーンエディタを使うためにはキーボードからコントロールコードを入力する必要があります。


 上記の対応でポケコン単体での CP/M の使い勝手がかなり改善されたのではないかと思います。実際に実装した機能について以下に簡単にまとめます。

  1. エスケープシーケンス機能
     下記の最小限の機能を実装しました。

    No.functioncharacter codememo
    1move cursorEsc[(y+1);(x+1)Hmove cursor to (x,y) position
    2clear screenEsc[2Jcursor doesn't move to home
    3erase lineEsc[0Jerase line from cursor to end of line
    4delete lineEsc[Kdelete cursor line
    5set reverseEsc[7mset character attribute to reverse
    6reset reverseEsc[0mset character attribute to normal
    7scroll upEsc[Sscroll up all lines on screen
    8scroll downEsc[Tscroll down all lines on screen
    ※Esc:0x1b、()はアスキーコードの2桁の数値

     尚、現状ではカーソル位置の文字がカーソルで上書きされて表示されない状態ですがプライオリティを下げて対応予定です。
    ★追記 2023/11/05
     対処した GOCPM の評価版を公開しました。

  2. スクリーンエディタ
     CP/M に対応した自作スクリーンエディタである sked をポケコンの LCD 画面に対応しました。名前も入力し易いように ske に短縮します。
     SHIFT J でヘルプが表示されます(LCD 画面範囲より大きいサイズなのでカーソルキーを操作して見てください)。
     参考として TeraTerm での sked のヘルプ表示画面が下図になります。

    sked for CP/M-80 の操作コマンド


  3. コントロールコード入力機能
     shift キーをコントロールキーとして扱い、記号の入力は全て「2ndF」キーを使用して入力することにしました。ESC は「SHIFT TAB」で、DEL は「SHIFT BS」で入力できます。
     また、下記の記号の入力を追加しました。
    • ^ : 2ndF 4
    • ~ : 2ndF 5


 参考として Turbo Pascal 3.0 の TINST でのスクリーン設定例を貼っておきます。一度設定した項目は対応無しに戻せないようなので、最初に「None of above」を選択して必要な項目のみ設定しました。下記は一度設定後に設定内容確認のために設定画面を開いた際の画面です。Highlight command は LCD 表示で見易いように逆に設定しています。

Turbo Pascal 3.0 の tinst での設定例


 上記の説明に対応した評価版の GOCPM.HEX 及び SKE.COM を下記のリンクからダウンロードできます。


 Twitter(X)に投稿した動画付きメッセージを貼っておきます。



★追記 2023/10/28
 パソコンを端末にして使っている時は GAME 言語のソースをコピペできましたが、ポケコンを単独で使用する時はソースをコピペできなくなるので困ってしまいます(GAME 言語の使用頻度は結構多めです^^)
 そこで GAME 言語のソースファイルをメモリ上に展開するツールを作ってみました。ソース修正時はエディタが使えるので GAME 言語のエディタよりも便利です。

 展開先のアドレスはディフォルトでは GAME言語のソース格納領域のディフォルト値である 0x8e00 に展開します。/Aオプションで展開先のメモリアドレスを指定できます。
 ディフォルトのアドレスに展開した場合、GAMEインタープリタがコールドスタートした時点でソースの先頭が 0xff に書き換えられるので下に示す操作例のようにおまじないが必要です。

D>GLOAD
load GAME source into TPA Ver0.01 2023/10/28 by skyriver
 usage; : gload [/axxxx] FileName
   xxxx : load; address(defalut:8E00)

D>GLOAD SAMPLE.GAM
SAMOLE.GAM 8E00-8E23
D>GAMEC
GAME80 for CP/M-80(compiler 0200-1B38) Ver0.03

*READY
:A== A:0)=0 ==

*READY
:


 展開先アドレスを指定した場合は次のような操作になります。

D>GLOAD /A4000 SAMPLE.GAM
SAMOLE.GAM 4000-4023
D>GAMEC
GAME80 for CP/M-80(compiler 0200-1B38) Ver0.03           

*READY
:=$4000 ==

*READY
:


 下記からダウンロードできます。


★追記 2023/10/29
 上記の GLOAD コマンドはロード時間がアッと言うまでパソコンを端末にしている時でさえ使いたくなりますね(コピペする場合はウェイト時間の設定変更等、少々面倒だった)

 指定したファイル名に拡張子が無い場合、.GAM に自動設定するようにしました。
 また、同類ソフトのセーブコマンドである gsave も作成しました。GAME インタープリタを終了(>=0 実行)した後に gsave FileName を実行することでメモリ上の GAME ソースをファイルにセーブできます。

 下記から gload と gsave をダウンロードできます。


★追記 2023/10/30
 GLOAD 実行後のおまじないが面倒なので GAMEC を起動する際に何かしらのアーギュメントを付けた場合、ホットスタートで起動するように GAMEC.COM を変更しました。

 下記からダウンロードできます。
  • GameOnCpm80_20231030_004.zip
  • ※2023/11/01変更 キーセンスの間引き処理廃止
     GAME言語のソース表示中にスペースキーで一時停止できない問題があり、GOCPMの5926Hを 3FH⇒07H に変更することで一時停止できるようになります。
    GameOnCpm80_20231101_004a.zip


★追記 2023/11/02
 CP/M-80 用 GAME インタープリタ&コンパイラの GAMEC.COM にソースファイルのロード(¥<FileName)/セーブ(¥>FileName)機能を実装しました。
 ポケコン(PC-G850V)のCP/Mでもソースのロード/セーブがメチャ楽になりました^^
 「3チップ構成Pic24CPMマイコン(その7)GAMEコンパイラ」の記事からダウンロードできます。


★追記 2023/11/05
 スクリーンエディタ使用時にカーソル位置の文字が見えなかった問題に対処した GOCPM 評価版です。

★追記 2023/11/06
 キー入力せずに放置すると10分程度でスペースコードが入力されてしまう問題に対処
 原因は ROM 内キー入力処理(0BCFDH)でリセット直後に単独で実行し同問題が再現
 キー入力は 0BE53H を使用するように変更し、カーソルブリンク機能も追加

★追記 2023/11/12
 BOOTH でダウンロードできる EborsyEEP_V103.zip では上記のアップデートを全て反映しています。



[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

リアルタイムモニタ(ZealMon)の製作(その2)セマフォの実装 [Z80]

 前回の記事でライトウェイトである程度高速なリアルタイムモニタ/リアルタイムOS(以降、モニタと記す)の製作について書きましたが、今回はこれにセマフォ管理機能を追加したので記録して置きたいと思います。

 前回の記事でも書いたように今回制作したモニタの特徴の一つとしてタスクの状態の情報をTCB(タスクコントロールブロック)には持たずにキューで管理することでTCBの更新箇所を最小限にするというのがあります。今回追加するセマフォ機能もこのコンセプトを継承します。

 セマフォは Wikipedia にも書いてあるようにカウンティングセマフォバイナリセマフォがあります。今回は汎用性の高いカウンティングセマフォを実装することにします。
 セマフォ管理データは一つのセマフォに付き2バイトで初期値を管理対象のリソース数とします。

セマフォ管理データの構造


 セマフォの操作は次のようにしました。
  • セマフォの獲得(P操作)
     セマフォ管理データの上位バイトがゼロ※1で下位バイトが1以上の場合は使用可能なリソースが残っていることを示し、リソース確保のためにこの値をディクリメントします。
     上記以外の場合はセマフォ管理データを待ちキューとして使い、セマフォを要求したタスクのTCBをリンクに追加し、ウェイト状態として扱います。
     ※1 TCBの上位アドレスは零ではない前提を利用した管理方法です
  • セマフォの解放(V操作)
     セマフォ管理データのキューにTCBがある場合(上位バイトがゼロではない場合)は末尾のTCBをキューから外してレディーキューに追加します。セマフォのキューが空になった時はポインタが NULL(=ゼロ)になるのでカウントデータとしてシームレスに使用できます。
     セマフォのキューにTCBが無い場合はセマフォー管理データをカウンタとして使用し、インクリメントします。

 実装はメチャ簡単そうですね。それでは実際にセマフォ機能を使ったデモを作ってみましょう(デモ作りの方が遥かに大変でした)。
 今回のデモは前回と同様にネット上で公開されているブラウザで動作するMSX環境のMSXPenを使ってGAME言語インタープリタを同時に二つ動かしてみます。
 以前CP/Mに移植したGAME言語のインタープリタ&コンパイラをGAMECとして公開しましたが、今回はコンカレントなGAME言語インタープリタなのでCGAMEですw

 画面表示制御は高速化のためVDPを直に制御しています。TEXT2(80字モード)の画面での実行を前提にしているので実行前に mode 80 のコマンドで80文字モードにしています。
 「コントロールA」の操作でキー所有のタスクが変わります。キーの所有管理は上記のセマフォで行っています。

コンカレントGAMEインタープリタ動作中の様子


 今回作成したデモソフトを下記のリンクからダウンロードできます。


 Twitter(X)に投稿した動画付きメッセージを貼っておきます。



★追記 2023/10/15
 上記の動画の動作について若干補足します。
  • 連続表示中にキー資源の所有を失うと少し速くなる理由
     連続表示中にも常にキーセンスをしていてキー資源を所有している場合は BIOS のキーセンス処理をコールしますが、キー資源を所有していない場合は実際のキーセンス処置を呼ばずにキー入力無しと判断するようにしているためです(つまりキーセンス処理が軽くなるので速くなる)。

  • 連続表示中にもう一方のタスクがキー入力状態になると表示が速くなる理由
     キー入力処理ではキーセンス後、キー入力が無い場合、タイマー待ち状態になり、タイムアウト後に再度キーセンスをコールする処理を繰り返しています。このためタスクは多くの時間タイマー待ち状態で止まっていて CPU 資源を殆ど消費しないためです。



[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]

nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

リアルタイムモニタ(ZealMon)の製作 [Z80]

 Twitter(X)のタイムラインで Z80 のスタックを使ったタスク管理の話題(スタック上にタスクアドレスを並べて順次呼び出すもの)を見てリアルタイムモニタ(リアルタイムOS)を作ってみたくなりました。

 リアルタイムOSに関してはネット上にも多くの情報があり、例えばここや "リアルタイム処理の基礎知識" 等に簡潔にまとめられています。

 とは言ってもあまり本格的なものではなく、ライトウェイトである程度高速なものを作りたいと思います。今回作成した Z80 用のリアルタイムモニタ(以降、ZealMon と記す)の特徴を列挙すると
  1. 実装機能は必要最小限
    • 実行中のタスクの中断(プリエンプト)可能
    • タイマーウェイト機能
    • プライオリティは4レベル(拡張は容易)
    • ラウンドロビンタスク切替え要求機能

  2. ライトウェイト
     モニタ本体のコードは約 300 バイト

  3. タスクの状態遷移はキューで管理
     TCB(タスクコントロールブロック)内にタスク状態情報を持たず状態書換え処理を省略し軽量化

  4. アイドルタスクの実装省略
     最低プライオリティでウェイト状態にならないタスクを持ついことで、レディ状態のタスクが無い時に実行するアイドルタスクの実装を省きました。番兵的な発想ですね。

 イベント管理、セマフォ、タスク間のメッセージ管理などの機能はまだありませんが、タイマー管理と同様にすることで追加実装は割合楽だと思います。

 タスクコントロールブロック(TCB)の構造は下記のようにしました。

nameoffsetbytesmearn
TcList02address for TCB link
TcTskNo21high nibble:priority(0:highest) low nibble:task No.
TcTim31timer count
TcEvCnt41how many waiting event except timer
TcSP52stack pointer save area
TcStat71task status !! not used !!


 最近動かしている手持ちの Z80 ボードではタイマー割込みを自由に設定できるものが無かったのでネット上で公開されている MSX のエミュレータである MSXPen で試すことにしました。
 周期割込みとして MSX の垂直帰線割り込み(1/60秒周期)を使用します。割込みモードは mode1(RST 38H 実行) です。
 リアルタイムモニタが動作中の時は割込みでタスク切替が発生しているのでディバッガでブレークしてもディバッガ自身がブレークしたタスクと同じタスクの扱いになり、他のタスクが文字表示等のシステムコールをしている場合、リエントラントにコールすることになるので直ぐにハングしてしまいます。

 結局ディバッガはあまり役に立たずに机上ディバッグ(若しくは割込み機能を停止した上のでブレーク確認)がメインになります。仕事であればICEが使えるのですが、個人としてはそんな高価なものは持っていません。
  MSXPen 上では動作が中々安定せず悩みましたが、割込み禁止(DI)中でも割込みが入るらしく、DI と共に 38H をリターンコードに書き換えるようにしたら安定して動作するようになりました。※1
※1)原因判明(2023/10/05 の追記部分を参照)

 下図は MSXPen でデモプログラムを実行した際の画面です。
 表示が競合し易くなるように1文字表示毎にタイマーウェイトを入れています。

MSXPen でデモプログラム実行時の様子


 下図はディスパッチが最初に実行される直前とタスク3が実行開始した時点でのワークエリアのダンプ表示です。割込みの影響を無くすため、フックした新たな割込み処理の先頭をリターンコードに書き換えています。
 画面中央のダンプ情報からディスパッチ前はタスク1~3がプライオリティ 0 のレディキューにつながっているのが判ります。タスク0は前述のアイドルタスクでプライオリティ3のレディキューにつながっています。下側のダンプはタスク3実行開始時のメモリで実行タスク番号が3でタイマーウェイトキューにタスク1とタスク2が繋がっています。

ZealMon 実行時のメモリ状態


 まだ最適化はしていませんが、ソースも貼っておきます。無いとは思いますが今後、Z80 でリアルタイム制御したい場合に役に立つかもしれませんw

リアルタイムモニタ(ZealMon)とデモのソース(Z80アセンブラ)
;++++++++++++++++++++++++++++++++++++++++ ; ZealMon : Z80 simple real time monitor ; Ver 0.01 2023/10/03 by skyriver ;+++++++++++++++++++++++++++++++++++++++ ; ; TCB ; name ofst byte mearn ; TcList 0 : 2 address for TCB link ; TcTskNo 2 : 1 high nibble:priority(0:highest) low nibble:task No. ; TcTim 3 : 1 timer count ; TcEvCnt 4 : 1 how many waiting event except timer ; TcSP 5 : 2 stack pointer save area ; TcStat 7 : 1 task status !! not used !! ; 0 : not regist ; 1 : running ; 2 : ready ; 3 : wait 0008 TCBSIZ EQU 8 ; TCB size 0000 TcTop EQU 0 ; define offset 0000 TcList EQU TcTop 0002 TcTskNo EQU 2 0003 TcTim EQU 3 0004 TcEvCnt EQU 4 0005 TcSP EQU 5 0007 TcStat EQU 7 ; TCBTBL 4 byte 0:top priority(not used) ; 0000 tcSt_NO EQU 0 ; not regist 0001 tcSt_RUN EQU 1 ; running 0002 tcSt_RDY EQU 2 ; ready 0003 tcSt_WAT EQU 3 ; wait 0004 MAX_PRI EQU 4 ; max priority 0000 PRI0 EQU 00H ; highest 0010 PRI1 EQU 10H 0020 PRI2 EQU 20H 0030 PRI3 EQU 30H ; lowest 000F MSKTNO EQU 00FH ; task No. bit mask 00F0 MSKPRI EQU 0F0H ; priority bit mask 0000' ASEG ORG 0100H 0100 21 2000 INIRTM: LD HL,WORKST 0103 11 2001 LD DE,WORKST + 1 0106 01 002A LD BC,WORKSIZ - 1 0109 36 00 LD (HL),0 010B ED B0 LDIR 010D 21 0232 LD HL,INITCB 0110 11 2009 LD DE,TCB0 0113 01 0020 LD BC,TCBSIZ * TASKS 0116 ED B0 LDIR 0118 11 7FFF LD DE,STACK - 1 011B 21 0261 LD HL,TENTRY + 2 * ( TASKS - 1 ) + 1 011E 3E 04 LD A,TASKS 0120 01 0002 INIR10: LD BC,2 ; set return addres(=task entry) 0123 ED B8 LDDR 0125 E5 PUSH HL 0126 21 FFC2 LD HL,-(TSKSPSIZ - 2) 0129 19 ADD HL,DE 012A EB EX DE,HL 012B E1 POP HL 012C 3D DEC A 012D 20 F1 JR NZ,INIR10 012F 21 2009 LD HL,TCB0 0132 06 04 LD B,TASKS 0134 C5 INIR20: PUSH BC 0135 CD 018C CALL ADDRDY 0138 C1 POP BC 0139 11 0008 LD DE,TCBSIZ 013C 19 ADD HL,DE 013D 10 F5 DJNZ INIR20 013F F3 DI 0140 ED 73 1000 LD (SavSP),SP 0144 CD 0276 CALL IniAp ; dispatch task ; select ready task and run it ; 0147 DISPATCH: 0147 21 2001 LD HL,RDYLNK 014A 06 04 LD B,MAX_PRI 014C 7E DISP10: LD A,(HL) 014D 23 INC HL 014E B6 OR (HL) 014F 23 INC HL 0150 20 04 JR NZ,DISPFND ; find ready 0152 10 F8 DJNZ DISP10 0154 F3 DI ; TASK0(=idle task) is always ready 0155 76 HALT ; not exec here 0156 DISPFND: 0156 2B DEC HL 0157 2B DEC HL ; HL = RDYLINK 0158 CD 016F CALL REMOVE REPT TcTskNo - TcTop INC HL ENDM 015B 23 + INC HL 015C 23 + INC HL 015D 7E LD A,(HL) ; get task No. 015E 32 2000 LD (RUNTSK),A REPT TcSP - TcTskNo INC HL ENDM 0161 23 + INC HL 0162 23 + INC HL 0163 23 + INC HL 0164 5E LD E,(HL) 0165 23 INC HL 0166 56 LD D,(HL) 0167 EB EX DE,HL 0168 F9 LD SP,HL 0169 F1 POP AF 016A C1 POP BC 016B D1 POP DE 016C E1 POP HL 016D FB EI 016E C9 RET ; remove from link ; HL <- link addr(not empty link) ; HL -> removed tail TCB addr ; 016F 4E REMOVE: LD C,(HL) 0170 23 INC HL 0171 46 LD B,(HL) 0172 2B DEC HL 0173 78 LD A,B 0174 B1 OR C 0175 20 04 JR NZ,REMO10 0177 F3 DI 0178 C3 0178 JP $ 017B EB REMO10: EX DE,HL 017C 69 LD L,C 017D 60 LD H,B 017E 4E LD C,(HL) 017F 23 INC HL 0180 46 LD B,(HL) 0181 2B DEC HL ; BC = next 0182 79 LD A,C 0183 B0 OR B 0184 20 F5 JR NZ,REMO10 ; HL = tail TCB, DE = prev 0186 EB EX DE,HL 0187 77 LD (HL),A ; A = 0 0188 23 INC HL 0189 77 LD (HL),A 018A EB EX DE,HL 018B C9 RET ; add TCB to ready que ; HL <- TCB ; HL -> TCB 018C ADDRDY: REPT TcTskNo - TcTop INC HL ENDM 018C 23 + INC HL 018D 23 + INC HL 018E 7E LD A,(HL) ; get task No. & priority REPT TcTskNo - TcTop DEC HL ENDM 018F 2B + DEC HL 0190 2B + DEC HL 0191 E6 F0 AND MSKPRI 0193 0F RRCA 0194 0F RRCA 0195 0F RRCA 0196 5F LD E,A 0197 16 00 LD D,0 ; DE = pri * 2 0199 E5 PUSH HL ; save TCB 019A 21 2001 LD HL,RDYLNK 019D 19 ADD HL,DE 019E D1 POP DE 019F CD 0205 CALL AddLnk ; HL = TCB 01A2 C9 RET ; timer interrupt ; if exist timer wait task, then dec timer and if time up, add rady task link ; 01A3 Timer: 01A4 SavINT EQU $ + 1 ; original int addr 01A3 CD 0000 CALL 0 01A6 F3 Tim00: DI 01A7 E5 PUSH HL 01A8 D5 PUSH DE 01A9 C5 PUSH BC 01AA F5 PUSH AF 01AB CD 021F CALL SaveSP ; save SP into TCB 01AE CD 018C CALL ADDRDY ; add running TCB to ready link 01B1 2A 2029 LD HL,(TIMLNK) ; timer wait task link 01B4 11 2029 LD DE,TIMLNK 01B7 18 1B JR Tim30 01B9 E5 Tim10: PUSH HL REPT TcTim - TcTop INC HL ENDM 01BA 23 + INC HL 01BB 23 + INC HL 01BC 23 + INC HL 01BD 35 DEC (HL) ; decrement timer value 01BE E1 POP HL 01BF 4E LD C,(HL) 01C0 23 INC HL 01C1 46 LD B,(HL) ; BC = next link 01C2 2B DEC HL 01C3 20 0C JR NZ,Tim20 01C5 C5 PUSH BC 01C6 D5 PUSH DE 01C7 CD 018C CALL ADDRDY 01CA D1 POP DE 01CB C1 POP BC ; DE -> (HL:unlink) -> BC 01CC EB EX DE,HL 01CD 71 LD (HL),C 01CE 23 INC HL 01CF 70 LD (HL),B ; unlink from link 01D0 2B DEC HL 01D1 59 Tim20: LD E,C 01D2 50 LD D,B ; DE = next timer waiting task's TCB 01D3 EB EX DE,HL 01D4 7C Tim30: LD A,H 01D5 B5 OR L 01D6 20 E1 JR NZ,Tim10 01D8 C3 0147 JP DISPATCH ; timer wait ; A <- timer value ; 01DB F3 TWait: DI 01DC E5 PUSH HL 01DD D5 PUSH DE 01DE C5 PUSH BC 01DF F5 PUSH AF 01E0 4F LD C,A 01E1 CD 021F CALL SaveSP ; save running task's SP into TCB, HL = TCB 01E4 E5 PUSH HL REPT TcTim - TcTop INC HL ENDM 01E5 23 + INC HL 01E6 23 + INC HL 01E7 23 + INC HL 01E8 71 LD (HL),C ; set timer 01E9 D1 POP DE ; DE = TCB 01EA 21 2029 LD HL,TIMLNK ; ADD TCB to TIMLNK 01ED CD 0205 CALL AddLnk 01F0 C3 0147 JP DISPATCH ; get running TCB addr ; HL -> TCB ; 01F3 GetRTcb: 01F3 3A 2000 LD A,(RUNTSK) 01F6 E6 0F AND MSKTNO 01F8 87 ADD A,A 01F9 5F LD E,A 01FA 16 00 LD D,0 01FC 21 0252 LD HL,TCBTBL 01FF 19 ADD HL,DE 0200 5E LD E,(HL) 0201 23 INC HL 0202 56 LD D,(HL) 0203 EB EX DE,HL ; HL = TCB 0204 C9 RET ; add TCB to link ; HL <- LINK addr ; DE <- TCB addr ; HL -> TCB ; 0205 4E AddLnk: LD C,(HL) 0206 23 INC HL 0207 46 LD B,(HL) 0208 72 LD (HL),D 0209 2B DEC HL 020A 73 LD (HL),E 020B EB EX DE,HL 020C 71 LD (HL),C 020D 23 INC HL 020E 70 LD (HL),B 020F 2B DEC HL 0210 C9 RET ; run next ready task(round robin) ; 0211 F3 ROBIN: DI 0212 E5 PUSH HL 0213 D5 PUSH DE 0214 C5 PUSH BC 0215 F5 PUSH AF 0216 CD 021F CALL SaveSP ; save SP into TCB 0219 CD 018C CALL ADDRDY 021C C3 0147 JP DISPATCH ; set SP into TCB ; HL -> TCB ; 021F SaveSP: 021F CD 01F3 CALL GetRTcb ; get running task's TCB 0222 E5 PUSH HL ; save TCB 0223 11 0005 LD DE,TcSP - TcTop 0226 19 ADD HL,DE 0227 EB EX DE,HL ; DE = TCB's SP save addr 0228 21 0004 LD HL,4 ; CALL THIS + PUSH HL 022B 39 ADD HL,SP 022C EB EX DE,HL 022D 73 LD (HL),E 022E 23 INC HL 022F 72 LD (HL),D ; save SP 0230 E1 POP HL ; HL = TCB 0231 C9 RET ; ++++++++++ test apprication ++++++++ 0004 TASKS EQU 4 ; how many task 0040 TSKSPSIZ EQU 40H ; tasks stack size 8000 STACK EQU 8000H ; stack pointer 000A STREGSIZ EQU 10 ; PC & reg area size on stack ; *** TCB initial data *** 0000 TSK0NoO EQU 0 0001 TSK0No1 EQU 1 0002 TSK0No2 EQU 2 0003 TSK0No3 EQU 3 7FF6 STACK3 EQU STACK - STREGSIZ 7FB6 STACK2 EQU STACK3 - TSKSPSIZ 7F76 STACK1 EQU STACK2 - TSKSPSIZ 7F36 STACK0 EQU STACK1 - TSKSPSIZ 0232 INITCB: ; task0 : idle 0232 0000 DW 0 ; link 0234 30 DB TSK0NoO OR PRI3 ; task No. 0235 00 DB 0 ; timer 0236 00 DB 0 ; event 0237 7F36 DW STACK0 ; stack 0239 02 DB tcSt_RDY ; status(ready) ; task1 023A 0000 DW 0 ; link 023C 01 DB TSK0No1 OR PRI0 ; task No. 023D 00 DB 0 ; timer 023E 00 DB 0 ; event 023F 7F76 DW STACK1 ; stack 0241 02 DB tcSt_RDY ; status(ready) ; task2 0242 0000 DW 0 ; link 0244 02 DB TSK0No2 OR PRI0 ; task No. 0245 00 DB 0 ; timer 0246 00 DB 0 ; event 0247 7FB6 DW STACK2 ; stack 0249 02 DB tcSt_RDY ; status(ready) ; task3 024A 0000 DW 0 ; link 024C 03 DB TSK0No3 OR PRI0 ; task No. 024D 00 DB 0 ; timer 024E 00 DB 0 ; event 024F 7FF6 DW STACK3 ; stack 0251 02 DB tcSt_RDY ; status(ready) 0252 TCBTBL: ; TCB table IRPC X,0123 DW TCB&X ENDM 0252 2009 + DW TCB&0 0254 2011 + DW TCB&1 0256 2019 + DW TCB&2 0258 2021 + DW TCB&3 025A TENTRY: ; TASK entry addr table IRPC X,0123 DW TASK&X ENDM 025A 02A5 + DW TASK&0 025C 02A9 + DW TASK&1 025E 02D7 + DW TASK&2 0260 02EE + DW TASK&3 ; *** CP/M *** 0005 BDOS EQU 0005H 0002 dfPUTC EQU 02H ; D <- data ; *** MSX *** 0039 INTADR EQU 038H + 1 ; interrupt jump addr 0010 INTTIM EQU ( 1000 / 60 ) ; interrupt interval time [ms] 000D CR EQU 13 000A LF EQU 10 ; fuck Blank int for timer ; MSX : mode 1, RST 38H 0262 HookInt: 0262 2A 0039 LD HL,(INTADR) 0265 22 01A4 LD (SavINT),HL 0268 21 01A3 LD HL,Timer 026B 22 0039 LD (INTADR),HL 026E C9 RET ; unfuck blank int ; 026F 2A 01A4 UnHook: LD HL,(SavINT) 0272 22 0039 LD (INTADR),HL 0275 C9 RET ; apprication initialize proc ; 0276 CD 0262 IniAp: CALL HookInt 0279 2A 0001 LD HL,(1) ; get WBOOT addr 027C 11 0009 LD DE,3 * 3 027F 19 ADD HL,DE ; HL = conout 0280 22 028C LD (COUTADR),HL 0283 C9 RET ; putchar ; A <- data ; 0284 4F Putch: LD C,A 0285 F3 DI 0286 3E C9 LD A,0C9H ; But executed DI, interrupt occurs at MSXPen 0288 32 0038 LD (INTADR - 1),A 028C COUTADR EQU $ + 1 028B CD 0000 CALL 0 028E 3E C3 LD A,0C3H 0290 32 0038 LD (INTADR - 1),A 0293 FB EI 0294 C9 RET ; put string ; HL <- string addr ; 0295 7E Puts: LD A,(HL) 0296 B7 OR A 0297 C8 RET Z 0298 E5 PUSH HL 0299 CD 0284 CALL Putch 029C E1 POP HL 029D 23 INC HL 029E 3E 01 LD A,1 02A0 CD 01DB CALL Twait 02A3 18 F0 JR Puts 02A5 00 TASK0: NOP 02A6 C3 02A5 JP TASK0 02A9 21 0305 TASK1: LD HL,MsgStart 02AC CD 0295 CALL Puts 02AF 3E BB LD A,3000 / INTTIM 02B1 CD 01DB CALL TWait 02B4 06 10 LD B,16 02B6 TASK1_1: 02B6 C5 PUSH BC 02B7 3E 32 LD A,800 / INTTIM 02B9 CD 01DB CALL TWait 02BC 21 033C LD HL,MsgTk1 02BF CD 0295 CALL Puts 02C2 C1 POP BC 02C3 10 F1 DJNZ TASK1_1 02C5 21 0362 LD HL,MsgBye 02C8 CD 0295 CALL Puts 02CB F3 DI 02CC CD 026F CALL UnHook 02CF ED 7B 1000 LD SP,(SavSP) 02D3 FB EI 02D4 C3 0000 JP 0 02D7 TASK2: 02D7 3E A7 LD A,2680 / INTTIM 02D9 CD 01DB CALL TWait 02DC 3E 7D TSK2LP: LD A,2000 / INTTIM 02DE CD 01DB CALL TWait 02E1 3E 3E LD A,1000 / INTTIM 02E3 CD 01DB CALL TWait 02E6 21 0358 LD HL,MsgTk2 02E9 CD 0295 CALL Puts 02EC 18 EE JR TSK2LP 02EE TASK3: 02EE 3E B5 LD A,2900 / INTTIM 02F0 CD 01DB CALL TWait 02F3 TSK3LP: 02F3 3E 7A LD A,1960 / INTTIM 02F5 CD 01DB CALL TWait 02F8 3E 7D LD A,2000 / INTTIM 02FA CD 01DB CALL TWait 02FD 21 035D LD HL,MsgTk3 0300 CD 0295 CALL Puts 0303 18 EE JR TSK3LP 0305 MsgStart: 0305 0D 0A 54 61 DB CR, LF, 'Task1 says "hello", Task2 & Task3 say each number.', CR, LF, 0 0309 73 6B 31 20 030D 73 61 79 73 0311 20 22 68 65 0315 6C 6C 6F 22 0319 2C 20 54 61 031D 73 6B 32 20 0321 26 20 54 61 0325 73 6B 33 20 0329 73 61 79 20 032D 65 61 63 68 0331 20 6E 75 6D 0335 62 65 72 2E 0339 0D 0A 00 033C 0D 0A 68 65 MsgTk1: DB CR, LF, "hello, world. I am task1.", 0 0340 6C 6C 6F 2C 0344 20 77 6F 72 0348 6C 64 2E 20 034C 49 20 61 6D 0350 20 74 61 73 0354 6B 31 2E 00 0358 32 32 32 32 MsgTk2: DB "2222", 0 035C 00 035D 33 33 33 33 MsgTk3: DB "3333", 0 0361 00 0362 0D 0A 53 65 MsgBye: DB CR, LF, "See you again.", 0 0366 65 20 79 6F 036A 75 20 61 67 036E 61 69 6E 2E 0372 00 ORG 1000H 1000 SavSP: DS 2 ; original SP save area ORG 2000H 2000 WORKST EQU $ 2000 RUNTSK: DS 1 ; running task No. 2001 RDYLNK: DS 2 * MAX_PRI ; ready task que if 1 IRPC X,0123 TCB&X: DS TCBSIZ ENDM 2009 + TCB&0: DS TCBSIZ 2011 + TCB&1: DS TCBSIZ 2019 + TCB&2: DS TCBSIZ 2021 + TCB&3: DS TCBSIZ else X SET 0 REPT TASKS TCB&X: DS TCBSIZ X SET X+1 ENDM endif 2029 TIMLNK: DS 2 ; timer wait task que 202B WORKEN EQU $ 002B WORKSIZ EQU WORKEN - WORKST END Macros: Symbols: 0205 ADDLNK 018C ADDRDY 0005 BDOS 028C COUTADR 000D CR 0002 DFPUTC 014C DISP10 0147 DISPATCH 0156 DISPFND 01F3 GETRTCB 0262 HOOKINT 0276 INIAP 0120 INIR10 0134 INIR20 0100 INIRTM 0232 INITCB 0039 INTADR 0010 INTTIM 000A LF 0004 MAX_PRI 0362 MSGBYE 0305 MSGSTART 033C MSGTK1 0358 MSGTK2 035D MSGTK3 00F0 MSKPRI 000F MSKTNO 0000 PRI0 0010 PRI1 0020 PRI2 0030 PRI3 0284 PUTCH 0295 PUTS 2001 RDYLNK 017B REMO10 016F REMOVE 0211 ROBIN 2000 RUNTSK 021F SAVESP 01A4 SAVINT 1000 SAVSP 8000 STACK 7F36 STACK0 7F76 STACK1 7FB6 STACK2 7FF6 STACK3 000A STREGSIZ 02A5 TASK0 02A9 TASK1 02B6 TASK1_1 02D7 TASK2 02EE TASK3 0004 TASKS 2009 TCB0 2011 TCB1 2019 TCB2 2021 TCB3 0008 TCBSIZ 0252 TCBTBL 0004 TCEVCNT 0000 TCLIST 0005 TCSP 0007 TCSTAT 0000 TCST_NO 0002 TCST_RDY 0001 TCST_RUN 0003 TCST_WAT 0003 TCTIM 0000 TCTOP 0002 TCTSKNO 025A TENTRY 01A6 TIM00 01B9 TIM10 01D1 TIM20 01D4 TIM30 01A3 TIMER 2029 TIMLNK 0001 TSK0NO1 0002 TSK0NO2 0003 TSK0NO3 0000 TSK0NOO 02DC TSK2LP 02F3 TSK3LP 0040 TSKSPSIZ 01DB TWAIT 026F UNHOOK 202B WORKEN 002B WORKSIZ 2000 WORKST No Fatal error(s)



 Twitter(X)に投稿した動画付きメッセージを貼っておきます。




★追記 2023/10/05
 上記の DI 中に割込みが入っている現象の原因が判りました。
 下記の割込みテストプログラムで確認した結果が下の画面です。繰り返し行っても同様の結果でした。BIOS の1文字表示処理の ROM 内のコードで EI を実施しているため、割込みが入っているようです。

割込み試験実施結果


 本確認で実施した割込み試験プログラムのリストを下記に示します。割込みが入った場合、リターンアドレスとコール元のコードを保存するようにしました。

割込み試験プログラムのソース(Z80アセンブラ)
;+++++++++++++++++++++++++++++++++++ ; IntTest : MSXPen interrupt test ; Ver 0.01 2023/10/05 by skyriver ;++++++++++++++++++++++++++++++++++++ ; *** MSX *** 0039 INTADDR EQU 038H + 1 ; vsync interrupt 0000' ASEG ORG 0100H 0100 2A 0001 Start: LD HL,(1) ; get WBOOT addr 0103 11 0009 LD DE,3 * 3 0106 19 ADD HL,DE ; HL = conout 0107 22 0134 LD (COUTADR),HL 010A 21 0000 LD HL,0 010D 22 0200 LD (SavRET),HL 0110 F3 DI 0111 2A 0039 LD HL,(INTADDR) ; get interrupt addr 0114 22 015C LD (SavInt),HL 0117 21 0137 LD HL,DmyInt 011A 22 0039 LD (INTADDR),HL 011D 06 80 LD B,128 011F C5 Loop: PUSH BC 0120 3E 41 LD A,'A' 0122 CD 0132 CALL Putch 0125 C1 POP BC 0126 10 F7 DJNZ Loop 0128 2A 015C LD HL,(SavInt) 012B 22 0039 LD (INTADDR),HL 012E FB EI 012F C3 0000 JP 0 ; putchar ; A <- data ; 0132 4F Putch: LD C,A 0133 CD 0000 CALL 0 0134 COUTADR EQU $ - 2 0136 C9 RET 0137 22 0202 DmyInt: LD (SavHL),HL 013A ED 53 0204 LD (SavDE),DE 013E ED 43 0206 LD (SavBC),BC 0142 E1 POP HL ; get return addr 0143 E5 PUSH HL 0144 22 0200 LD (SavRET),HL 0147 2B DEC HL 0148 11 0210 LD DE,SavCode 014B 01 0020 LD BC,CSAVLEN 014E ED B0 LDIR 0150 2A 0202 LD HL,(SavHL) 0153 ED 5B 0204 LD DE,(SavDE) 0157 ED 4B 0206 LD BC,(SavBC) 015B C3 0000 JP 0 015C SavInt EQU $ - 2 ORG 0200H 0200 SavRET: DS 2 0202 SavHL: DS 2 0204 SavDE: DS 2 0206 SavBC: DS 2 ORG 0210H 0210 SavCode: 0210 DS 32 ; code save area 0020 CSAVLEN EQU $ - SavCode END Macros: Symbols: 0134 COUTADR 0020 CSAVLEN 0137 DMYINT 0039 INTADDR 011F LOOP 0132 PUTCH 0206 SAVBC 0210 SAVCODE 0204 SAVDE 0202 SAVHL 015C SAVINT 0200 SAVRET 0100 START No Fatal error(s)



★追記 2023/10/06
 上記の様にシステムコール内で割込み許可していることが判ったので、システムコール時は割込みは許可状態のままにしてタスクをディスパッチしない様にしてみました(システムコール毎のタイマーウェイトを無くし、ラウンドロビンコールを追加)。
 その結果、下図のように表示が競合する場合には互いに1文字ずつ表示するようになりました。割込み処理の空振りが無いのでタイマー管理も正確になったはずです。

割込み対処後のデモ画面例


 MSX-DOS 上で GAME 言語を使って BIOS 内の EI(0FBH)を検索した結果を貼っておきます。38H の割込みのエントリーが 0DDA9H なので割込み処理も含まれているのでしょうが結構あるものですね。
 ざっと見た感じ、最後の二つは相対ジャンプのオフセット値ですが、他は EI 命令のコードのようですね。

MSX-DOS BIOS 内の 0FBH コードのサーチ結果



[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

電子メモパッドのペン作成 [3D_printer]

 思い付いたことを直ぐにメモできるように普段から手元にメモ用紙を置くようにしています。数か月前にダイソーで電子メモパッド(以降、メモパッドと記す)を見つけたので購入してみました。細かい文字は書き辛いですが中々便利でメモ用紙の消費が少なくなりました。

 先程、このメモパッドのペンが行方不明になり部屋の中の何処かにあるはずなのに中々見つかりません。
 探している時は「そういえばペンを本体に繋げるように穴が開いていたなぁ」と思いましたが、いざ見つかってみると紐が邪魔になりそうで繋ぐ気にはなれません。

 そこで例え紛失しても困らないように3Dプリンタで作ってみました。そのまま似せて作るのもなんなのでちょっとアレンジしてみました。

メモパッド用のペン(CAD画面)


 このメモパッドは 500 円で電気的に消去できます(部分的な消去や書換えは不可)。

メモパッドとペンたち


 下の写真の上側が今回作成したペンです。手で持つ部分が六角なので手に馴染みます。こんなものがすぐ作れるなんて便利な世の中になったものです。

メモパッドとペンたち


 DesignSpakMechanical で作成していますが、二つのパーツが結合エラーになる状態で STL ファイル化したためか読込み時にエラーが出るかもしれませんが私の環境では出力できました。


 下記からSTLファイルをダウンロードできます。商用利用以外であれば使用可能とします。
[改版履歴]
★Ver0.03 20223/10/01
 STLファイルを修正し、読込み時にエラーが出なくなった
★Ver0.05 2023/10/05
 若干変更(サポート材最小化にも対応)


nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー