2001年11月30日作成 2014年7月23日更新

完成図

2010年9月:MPLAB 付属の HI-TEC C(PICC) 用に、新しく C言語で書き直しました。


いつまでたっても文書が完成しないので、とりあえず動いてるソースを置いときます。すんません。

(タブは4文字で書いてます)

追記:PCオープン・アーキテクチャー推進協議会テクニカル・リファレンスに、PS/2キーボードの詳しい仕様がのってるのを見つけた。

X68000のキーボードを
PS/2ポートにつないでみる

完成図 完成図

パソコンの調子はいいですか? うちはまあ調子よく動いてるんですけど、キーボードの調子があまりよくありません。

どうも最近、パソコンを使っていると、[SHIFT]キーの入りが悪くて、範囲選択をしてる最中に範囲が解除されちゃったりして、「キーボード買わなきゃダメかな」などと思っていたんです。それでパーツ屋さんにいってキーボードを見たりしてきたんだけれど、どうでもいいキーボードは 1,000円くらいで安く売ってるんだけど、それなりのキーボードは 10,000円くらいするじゃないですか。

貧乏なオレには買えないなぁと、我慢して調子の悪いまま使っていたんですが、押入れの中に(古いだけに)作りの高級な X68000 のキーボードがあるじゃないですか。さらにずいぶんまえ(96年ころ)に買ったまま使っていない、PIC マイコンと 秋月のプログラマもあるわけです。ならばマイコンを使って X68000 のキーボードを AT 互換機につないでみようと思って作り始めたら、けっこうすんなりと動いちゃったのです。

「X68000 を使っていたころはなんだか楽しかったよなー」などと思い出にひたりながら AT 互換機を使うのもいいものです。

PICマイコン

どんな回路にしたら良いかを考えます。「お金が無いから作ってやろう」という方針なので、部屋の中に転がっている部品だけで作る事にします。まずマイコンは PIC16C84 に決まりです。部屋にはこれしか無いからです。

PIC16C84 というマイコンは、Microchip Technology 社が出している 1チップマイコンの一つで 1kワードのプログラムメモリ、36バイトのデータRAM、64バイトのデータEEPROMを内蔵していて、水晶をつないで電源を加えるだけで動く大変便利なマイコンです。しかも小さいくせに、最高10MHzで動作するとても高速なマイコンです(10MHz動作時、1命令を400nsで実行します)。随分前に買ったものなので、もう現行商品ではないのですが、データメモリが増えて20MHzで動作する PIC16F84 というものに回路やプログラムの変更なしに置き換え可能です。

キーボードの通信の様子

X68000 のキーボードがどのような通信を行っているのかは、たまたま本棚にあった「Inside X68000」によると、2400bps、データ長8ビット、パリティ無し、ストップビット1のごく普通の非同期シリアル通信(RS232Cと同じ)だということです。

今回使う PIC16C84 は、シリアルインターフェースを内蔵していませんので、これをソフトウェアで実現する必要があります。

PS/2キーボードの通信の様子も調べなければなりません。いろいろと調べてみたところ、次のような回路になっているようです。

キーボードからPC(ホスト)への通信では

  1. キーボードがCLKラインがHであることを確認します。もしLであれば、通信できません。
  2. キーボードはDATAラインがHであることを確認します。もしLであれば、ホストがスタートビットをセットしているので、受信処理を行います。
  3. キーボードはデータをセットして、クロックを送り出します。ホストはクロックの立下りでデータを取り込みます。 このとき、何らかの理由でホストが通信を中断したい場合、CLKラインをLにしますので、キーボードは自分がCLKをHにしているのに、CLKがLになっているのを検出したら、直ちに通信を中断します。
    これを繰り返して、通信は終了します。(スタートビット1、データビット8、パリティビット1、ストップビット1)

ホストからキーボードへの通信は

  1. ホストはまずスタートビットをセットします。
  2. キーボードはスタートビットを検出すると、クロックを送り出します。ホストはクロックを受けるたびに、次のデータをセットします。
    これを繰り返し、ホストはストップビットまで送り出します。
    もし、ストップビットが見つからなければ、キーボードはDATAラインがHになるまでクロックを送出します。
  3. キーボードは、ストップビットまで受信したら、データラインをLにしてクロックを送出し、さらにデータラインをHにしてクロックを送出します。

以上で通信は完了します。

これを元にこのように回路を作りました。といっても、ブレッドボードにありあわせの部品を差し込んでいったら、こんな風になっちゃったって状態なんですけどね。

クロックは速ければ速いほど、プログラムに余裕ができるのですが、今回は持ち合わせの都合で 8MHzとしました。

初めてのプログラム

あとはプログラムを書けば完成となるのですが、いままでPICマイコンのプログラムを書いた事がありません。ぜんぜん解っていないので、実習しながら少しずつプログラムを書いてゆきました。

とりあえず命令の一覧を見てみましょう。少々ニーモニックが独特で、はじめはとまどうかもしれませんが、命令数が少ないですからプログラムを書くときに、横にニーモニック一覧を開いておけばぜんぜん問題ないです。だいたいただでさえ命令数が少ないのに、よく使うのは代入、加算、減算、ビット検査、ビット操作程度ですから、実際に何か書いてみればすぐになじむと思います。

なにより、実際にプログラムを書いてみる事が必要です。

まずはいちばん簡単なプログラムということで、I/Oポートに0と1を繰り返し出力するプログラムを書いてみました。


    1|;
    2|; ためしに動かしてみる
    3|;                                      2001/11/14
    4|;
    5|; 秋月アセンブラ(PA.EXE)用
    6|
    7|
    8|
    9|;              ----__----
   10|;       RA2 1-|*         |-18 RA1
   11|;       RA3 2-|          |-17 RA0
   12|; RA4 TOCKI 3-|          |-16 OSC1 CLKIN
   13|;     /MCLR 4-|          |-15 OSC2 CLKOUT
   14|;       Vss 5-| PIC16C84 |-14 Vdd
   15|;   RB0 INT 6-|          |-13 RB7
   16|;       RB1 7-|          |-12 RB6
   17|;       RB2 8-|          |-11 RB5
   18|;       RB3 9-|          |-10 RB4
   19|;              ----------
   20|
   21|
   22|
   23|    .16c84              ; デバイスの設定(省略できません)
   24|
   25|    .osc        HS      ; 発振タイプの設定
   26|                        ; LP	ローパワー発振子(〜約200kHz)
   27|                        ; HS	高速クリスタル(約4MHz〜20MHz)
   28|                        ; RC	RC発振
   29|                        ; XT	クリスタル・セラロック(〜約4MHz)
   30|
   31|    .wdt        off     ; ウォッチドックタイマの有効・無効の設定
   32|                        ; 省略時は“on”
   33|
   34|    .pwrt       on      ; パワーアップタイマの有効・無効の設定
   35|
   36|    .protect    off     ; プロテクトの設定
   37|                        ; “on”にすると、その IC からプログラムを読み出す事が
   38|                        ; 出来なくなる
   39|                        ; 省略時は“off”
   40|
   41|;   eeorg       {value} ; データ EEPROMのアセンブラ内部アドレスをセットする
   42|                        ; (eedata 命令で書き込むアドレスをセットする)
   43|                        ; (PIC16x84 のみ有効)
   44|
   45|;   eedata      {value[,value..]}   ; データ EEPROM の初期値をセットする
   46|                                    ; この命令を使用することで、プログラムの
   47|                                    ; 書き込みと同時にデータ EEPROM の内容を
   48|                                    ; 書き込む事が出来る
   49|                                    ; (PIC16x84 のみ有効)
   50|
   51|; -----------------------------------------------------------------------------
   52|
   53|STATUS  equ     03h     ; ステータスレジスタ(BANK0)
   54|RP0     equ     5       ; STATUS,RP0 RAMのバンク切り替え
   55|TRISA   equ     05h     ; ポートA出力/入力設定 0:出力 1:入力(BANK1)
   56|TRISB   equ     06h     ; ポートB出力/入力設定 0:出力 1:入力(BANK1)
   57|PORTA   equ     05h     ; I/OポートA(BANK0)
   58|PORTB   equ     06h     ; I/OポートB(BANK0)
   59|OPTION  equ     01h     ; オプションレジスタ(BANK1)
   60|RBPU    equ     7       ; OPTION,RBPU ポートBのプルアップ 0:行う 1:行わない
   61|
   62|        org     0ch
   63|a       ds      1       ; 0ch から 1 バイト分のメモリを確保し、
   64|                        ; ラベル“a”を付ける(変数a)
   65|
   66|; -----------------------------------------------------------------------------
   67|
   68|        org     000h        ; リセット時は 000h からプログラムがスタート
   69|        goto    start
   70|
   71|        org     004h        ; 割り込み時 004h にジャンプしてくる
   72|        retfie
   73|
   74|start
   75|        bsf     STATUS,RP0  ; bank1
   76|
   77|        movlw   11100000b   ;
   78|        movwf   TRISA       ; ポートAを出力に設定
   89|
   80|        movlw   00000000b   ;
   81|        movwf   TRISB       ; ポートBを出力に設定
   82|
   83|        bcf     STATUS,RP0  ; bank0
   84|
   85|        clrf    a           ; 変数aをクリア
   86|
   87|loop
   88|        movlw   00011111b   ; w=00011111b
   89|        movwf   PORTA       ; PORTA=w
   90|        movlw   11111111b   ; w=11111111b
   91|        movwf   PORTB       ; PORTB=w
   92|
   93|        movlw   00000000b   ; w=00000000b
   94|        movwf   PORTA       ; PORTA=w
   95|        movwf   PORTB       ; PORTB=w
   96|
   97|        goto    loop
test.asm
  • 23〜49行目、この辺が秋月電子版アセンブラ専用
    アセンブラの擬似命令でコンフィグレーションヒューズの設定が出来ます。
  • 68〜69行目、電源投入時とリセット時には、0h 番地からプログラムが実行されますので、0h番地にメインルーチンへジャンプする命令をおいておきます。
  • 71〜72行目、割り込み発生時には、4h 番地にジャンプしてきますので、4h 番地から割り込み処理を記述します。今回のように割り込みを使わない場合は、何も記述する必要はありませんが、とりあえず何もしないでリターンします。
  • 75〜83行目、I/Oポートの設定を行っています。
    • 75行目、TRISA、TRISBレジスタ(I/Oポート設定レジスタ)が、バンク1にあるので、バンク切り替え。
    • 77行目、wレジスタに設定値を代入。0=出力、1=入力
      ここではポートAのビット0からビット4まで出力に設定。 (定数の代入はwレジスタに代入する命令しかないので、いったんwレジスタに代入します。)
    • 78行目、wレジスタの値をTRISAレジスタに代入(設定値を書き込む)
    • 80〜81行目、ポートBに対しても、同じように出力に設定。
    • 83行目、バンク切り替え。バンク0に戻す。
  • 88〜89行目、I/OポートAのビット0からビット4を1にする
  • 90〜91行目、I/OポートBのビット0からビット7を1にする
  • 93〜95行目、I/OポートAとBを0にする
  • 97行目、くり返す。(ラベル loop にジャンプ)

ふつうなら、I/OポートにLEDでもつないで点滅させてみるところですが、そうすると目で見えるように、ウエイトを入れなければなりません。初めて書くプログラムで空ループなんかでウエイト処理を作るのは荷が重いという考えと、何よりLEDを配線するのがめんどくさいじゃないか、ということで、出力はシンクロスコープ(ゴミ捨て場から拾ってきたもの)で確認する事にして、プログラムのほうは何も考えずただI/Oポートに0と1を繰り返し出力するようにしました。

で動かしてみると、ちゃんとパルスが出力されてます。とりあえず一安心です。

hello world ?

helloとりあえず動きましたので、X68000のキーボードとの通信処理を書いてみたいのですが、通信の部分だけを書いても正しく動作しているのかを確かめるのが非常に困難です。パソコンのプログラムを書いている場合なら、とりあえず動作状態を printf で表示してみるとかすればいいのですが、なにせマイコンには表示装置がありません。

そんなわけで、デバッグ用に液晶表示器を接続することにします。表示器にはたまたまジャンク箱に入っていた、16文字×2行表示の液晶表示器を使います。これはコントローラにHD44780を使った、非常にポピュラーなもののようです。形は少し異なりますが、秋月電子通商で800円くらいで売られているものと同じです。

表示器をつなぎましたので、とりあえず何か表示させて見ましょう。お約束で“Hello World”でいこうかとおもいましたが、長いのは面倒くさいので“Hello”と表示させて見ます。


    1|;
    2|; 16x2 LCDに表示してみる
    3|;                                      2001/11/16
    4|;
    5|; 秋月アセンブラ用
    6|
    7|
    8|
    9|;              ----__----
   10|;       RA2 1-|*         |-18 RA1
   11|;       RA3 2-|          |-17 RA0
   12|; RA4 TOCKI 3-|          |-16 OSC1 CLKIN
   13|;     /MCLR 4-|          |-15 OSC2 CLKOUT
   14|;       Vss 5-| PIC16C84 |-14 Vdd
   15|;   RB0 INT 6-|          |-13 RB7
   16|;       RB1 7-|          |-12 RB6
   17|;       RB2 8-|          |-11 RB5
   18|;       RB3 9-|          |-10 RB4
   19|;              ----------
   20|
   21|;	I/Oポートの割り当て
   22|; PORT A
   23|LCD_E       equ     0   ; RA0 - LCD E
   24|LCD_RS      equ     1   ; RA1 - LCD RS
   25|
   26|; PORT B
   27|LCD_DB4     equ     4   ; RB4 - LCD DB4
   28|LCD_DB5     equ     5   ; RB5 - LCD DB5
   29|LCD_DB6     equ     6   ; RB6 - LCD DB6
   30|LCD_DB7     equ     7   ; RB7 - LCD DB7
   31|
   32|;   LCDの余ったピンの処理
   33|;   LCD DB0  - GND
   34|;   LCD DB1  - GND
   35|;   LCD DB2  - GND
   36|;   LCD DB3  - GND
   37|;   LCD R/~W - GND (常時ライト)
   38|
   39|
   40|
   41|    .16c84              ; デバイスの設定(省略できません)
   42|    .osc    HS          ; 発振タイプの設定
   43|    .wdt    off         ; ウォッチドックタイマの有効・無効の設定
   44|    .pwrt   on          ; パワーアップタイマの有効・無効の設定
   45|    .protect    off     ; プロテクトの設定
   46|
   47|; -----------------------------------------------------------------------------
   48|
   49|;   システムレジスタにラベル付け
   50|STATUS      equ     03h     ; ステータスレジスタ(BANK0)
   51|RP0         equ     5       ; STATUS,RP0 RAMのバンク切り替え
   52|PORTA       equ     05h     ; I/OポートA(BANK0)
   53|PORTB       equ     06h     ; I/OポートB(BANK0)
   54|TRISA       equ     05h     ; ポートA出力/入力設定 0:出力 1:入力(BANK1)
   55|TRISB       equ     06h     ; ポートB出力/入力設定 0:出力 1:入力(BANK1)
   56|OPTION      equ     01h     ; オプションレジスタ(BANK1)
   57|RBPU        equ     7       ; OPTION,RBPU ポートBのプルアップ 0:行う 1:行わない
   58|
   59|CONST_WAIT_MS   equ     200 ; ms 単位のウエイト用定数
   60|                            ; CONST_WAIT_MS = (動作クロック[MHz]/4)*100
   61|                            ; 10MHz 時 250
   62|                            ;  8MHz 時 200
   63|                            ;  6MHz 時 100
   64|
   65|; -----------------------------------------------------------------------------
   66|
   67|;   変数を宣言
   68|        org     0ch
   69|temp1       ds      1       ; 0ch から 1 バイト分のメモリを確保し、
   70|                            ; ラベル“temp1”を付ける(変数temp1)汎用一時変数
   71|temp2       ds      1       ; 汎用一時変数
   72|temp3       ds      1       ; 汎用一時変数
   73|wait_count1 ds      1       ; ウエイトルーチンで使うカウンタ
   74|wait_count2 ds      1       ; ウエイトルーチンで使うカウンタ
   75|
   76|; -----------------------------------------------------------------------------
   77|
   78|        org     000h        ; リセット時は 000h からプログラムがスタート
   79|        goto    start
   80|
   81|        org     004h        ; 割り込み時 004h にジャンプしてくる
   82|        retfie
   83|
   84|start
   85|;   I/Oポートの設定
   86|;   ポートA
   87|        bsf     STATUS,RP0  ; bank1
   88|        movlw   11100000b
   89|        ;          ||||+----- LCD_E         out
   90|        ;          |||+------ LCD_RS        out
   91|        ;          +++------- RA2-RA4 未使用
   92|        movwf   TRISA       ; 設定レジスタに書き込み
   93|
   94|;   ポートB
   95|        movlw   00000000b
   96|        ;       ||||++++----- RB0-RB3 未使用
   97|        ;       |||+--------- LCD_DB4       out
   98|        ;       ||+---------- LCD_DB5       out
   99|        ;       |+----------- LCD_DB6       out
  100|        ;       +------------ LCD_DB7       out
  101|        movwf   TRISB       ; 設定レジスタに書き込み
  102|        bcf     STATUS,RP0  ; bank0
  103|
  104|
  105|
  106|
  107|;   LCDの初期化
  108|
  109|        bcf     PORTA,LCD_E
  110|
  111|        movlw   15
  112|        call    wait            ; 15ms 待つ
  113|
  114|        bcf     PORTA,LCD_RS    ;  RS DB7 DB6 DB5 DB4
  115|        movlw	00110000b       ;  0   0   0   1   1
  116|        call    lcd_write4
  117|
  118|        movlw   5
  119|        call    wait            ; 5ms 待つ
  120|
  121|        movlw   00110000b
  122|        call    lcd_write4
  123|
  124|        movlw   1
  125|        call    wait            ; 1ms 待つ
  126|
  127|        movlw   00110000b
  128|        call    lcd_write4
  129|
  130|        movlw   00100000b       ;  DB7 DB6 DB5 DB4
  131|        call    lcd_write4      ;   0   0   1   0
  132|                                ; 4ビットモードに設定
  133|
  134|        movlw   00101000b       ; 4bit, 1/16duty, 5x7
  135|        call    lcd_write
  136|
  137|        movlw   00000001b       ; 表示クリア
  138|        call    lcd_write
  139|
  140|        movlw   2               ; クリアには少し時間がかかるので
  141|        call    wait            ; 2ms 待つ
  142|
  143|        movlw   00000110b       ; エントリーモードセット
  144|        call    lcd_write       ; (カーソルの進み方の設定)
  145|
  146|        movlw   00001110b       ; 表示オン/オフコントロール
  147|        call    lcd_write       ; 表示オン, カーソル表示あり, ブリンクなし
  148|
  149|
  150|
  151|
  152|
  153|; -----------------------------------------------------------------------------
  154|
  155|;   ためしに“Hello”と表示させてみる
  156|
  157|        bsf     PORTA,LCD_RS        ; RS を 1
  158|
  159|        movlw   048h                ; H
  160|        call    lcd_write
  161|
  162|        movlw   065h                ; e
  163|        call    lcd_write
  164|
  165|        movlw   06ch                ; l
  166|        call    lcd_write
  167|
  168|        movlw   06ch                ; l
  169|        call    lcd_write
  170|
  171|        movlw   06fh                ; o
  172|        call    lcd_write
  173|
  174|
  175|loop
  176|        goto    loop
  177|
  178|
  179|
  180|
  181|
  182|; -----------------------------------------------------------------------------
  183|
  184|;   LCDへの書き込み
  185|;   wレジスタの内容をLCDに書き込む
  186|
  187|lcd_write
  188|        movwf   temp1       ; temp1=w
  189|        call    lcd_write4  ; 上位4ビットの書き込み
  190|
  191|        swapf   temp1,0     ; temp1 の上位4ビットと下位4ビットを入れ替えて
  192|                            ; wレジスタに代入
  193|
  194|        call    lcd_write4  ; 下位4ビットの書き込み
  195|
  196|        return
  197|
  198|
  199|;
  200|;   書き込みサブルーチンのサブルーチン
  201|;   wレジスタの上位4ビットだけを書き込む
  202|
  203|;   ポートBの下位4ビットは変化しない
  204|
  205|lcd_write4
  206|        andlw   11110000b   ; w = w & 11110000b
  207|        movwf   temp2       ; 書き込むデータの上位4ビットのみを退避
  208|
  209|        movf    PORTB,0     ; w=PORTB
  210|        andlw   00001111b   ; w=w&00001111b
  211|                            ; ポートBの下位4ビットのみを取り出し
  212|
  213|        iorwf   temp2,0     ; w = w | temp2
  214|        movwf   PORTB       ; ボートBの下位4ビットとデータの上位4ビットを
  215|                            ; 合成してポートBに出力
  216|
  217|        bsf     PORTA,LCD_E ; イネーブル信号を出力
  218|        nop
  219|        bcf     PORTA,LCD_E
  220|
  221|        movlw   1
  222|        call    wait        ; 少し待つ
  223|
  224|        return
  225|
  226|
  227|
  228|
  229|
  230|; -----------------------------------------------------------------------------
  231|
  232|;   wレジスタの値に応じて ms 単位のウエイトを入れる
  233|
  234|;   例、約2ms待つ
  235|;   1:  movlw    2
  236|;   2:  call     wait
  237|
  238|;   メモ
  239|;   使用クロックに応じて、定数 CONST_WAIT_MS を定義してください
  240|
  241|;   CONST_WAIT_MS = (動作クロック[MHz]/4)*100
  242|
  243|;   例、8Mhz時
  244|;   CONST_WAIT_MS   equ	200
  245|
  246|wait
  247|        movwf   wait_count1         ; wait_count1=w
  248|wait_loop1
  249|            movlw   CONST_WAIT_MS
  250|            movwf   wait_count2     ; wait_count2=CONST_WAIT_MS
  251|wait_loop2
  252|                nop
  253|                nop
  254|                nop
  255|                nop
  256|                nop
  257|                nop
  258|                nop
  259|                decfsz  wait_count2,1   ; wait_count2=wait_count2-1
  260|                goto    wait_loop2      ; if wait_count2!=0 goto wait_loop2
  261|            decfsz  wait_count1,1   ; wait_count1=wait_count1-1
  262|            goto    wait_loop1      ; if wait_count1!=0 goto wait_loop1
  263|        return                      ; else return
lcd.asm
  • 107〜147行、液晶表示器の初期化処理。ここは液晶表示器の説明書通りに、I/Oポートを操作します。
  • 153〜172行、1文字づつ“Hello”と表示する。
  • 182〜224行、液晶表示器に上位4ビット、下位4ビットの順でデータを書き込みます。今後、ポートBの下位4ビットを使用することを考えて、ポートBの下位4ビットは変化しないように気をつけました。
  • 59、230〜263行、ウエイトサブルーチン。およそ1ミリ秒単位の時間待ちをします。
    251〜260行のループの実行に1ミリ秒かかるように、動作クロックに応じて59行目の値を調整します。

シリアル通信

表示部も出来ました。X68000のキーボードとの通信処理を考えるのですが、まずここで非同期のシリアル通信というのがどういうものかを見てみましょう。

図:非同期シリアル通信

こんなふうになっています。

通信していない状態では、信号ラインが“H”になっています。そこへ送信側はこれから通信をはじめますよという意味で、スタートビット“L (0)”を置きます。その後データを下位ビットから順に1ビットずつ置いてゆきます。最後に通信が終わりましたよという意味で、ストップビット“H (1)”を置いて通信を終了します。(今回は関係ありませんが、パリティを使用する場合は、ストップビットの前に入ります。)ビット幅は今回の場合だと2400bps(bits per second、ビット毎秒、1秒間に2400ビット)なので、1/2400秒=416.67マイクロ秒となります。

受信処理は、ずーっと信号ラインをチェックしておいて、スタートビットを見つけたら、その後ビットの中心で信号ラインを読み取ってゆけばよい事になります。

では、受信処理は実際にどのように書いたらよいでしょうか。

まず、スタートビットを見つけた後は正確に、1/2400秒ごとに信号ラインを読み取っていかなければなりません。そこで指定した間隔で発生するタイマー割り込みを使用して、処理を行います。

割り込みの間隔は、ボーレートの4分の1、1/9600秒とします。これはビット幅の真ん中あたりを読み取る事で、通信をより確実にする為です。

この割り込みが発生するたびに、信号ラインを読み取ってゆきます。

図:非同期シリアル通信 受信処理
  1. スタートビットを見つけたら、受信処理を開始します。
    今回はなんとなく、3回続けて信号ラインが“L”だったらスタートビットと判断しています。
  2. その後、割り込みが4回発生するごとに信号ラインを読み取って、受信バッファに格納します。
  3. これを8回くり返して、データを取り込みます。
  4. 最後に、ストップビットを読み取って、受信処理を終了します。
    もしここで、ストップビットだと思われるところが“L”だったら、おかしなタイミングで受信処理をはじめてしまったということで、ここで受信したデータは無効ということになります。(フレーミングエラー)

送信処理は、受信処理のようにいろいろ考える必要は無く、割り込みが4回発生するごとに、スタートビット、データ、ストップビットの順で出力してやれば完了です。

さて、通信の手順もわかったことですし、さっそくプログラムを書いてみましょう。

割り込み動作の確認

まずは簡単に、割り込み処理の動作テストをしてみます。PIC16C84の割り込みについて見てみましょう。

PIC16C84では次の現象で、割り込みを発生させる事が可能です。

これらは、それぞれINTCONレジスタで割り込みの許可、禁止の設定が可能なほか、INTCONレジスタのGIEビットで、全ての割り込みをいっぺんに禁止する事もできます。

割り込みが発生すると、割り込みを禁止(GIEビットをクリア)し、プログラムカウンタをスタックに退避して、アドレス 004hにジャンプします。

ここで、今回使う TMR0 カウンタのオーバーフロー割り込みについて、もう少し詳しく見てみましょう。

この割り込みは、入力クロックにより、TMR0 レジスタを1づつ加算(インクリメント)してゆき、オーバーフローした(ffh から 00hになった)時に割り込みが発生します。入力クロックは、外部(RA4ピン)か内部(動作クロックの1/4)を選ぶ事が出来、プリスケーラで 1/256 まで分周することも出来ます。

入力を内部クロックに設定し、プリスケーラを使用しない場合、8MHz動作時 0.5μs ごとにTMR0レジスタがインクリメントされ、0.5μsの256倍の 128μs ごとに割り込みが発生する事になります。このTMR0レジスタには書き込みも可能で、TMR0レジスタに値を書き込むことで、任意の間隔で割り込みを発生させる事が可能です。例えば、TMR0レジスタに“156”と書き込むと、書き込んでから 0.5μs の 100(256-156)倍の 50μs 後に割り込みが発生するわけです。

それでは、実際にプログラムを書いて、割り込み動作のテストをしてみましょう。

割り込みが発生するたびにI/Oポートを反転させて、それをシンクロスコープで観測してみます。


     1|;
     2|; 割り込み動作のテスト
     3|;
     4|;
     5|;                                      2001/11/16 作成
     6|;
     7|; 秋月アセンブラ用
     8|
     9|
    10|    .16c84              ; デバイスの設定(省略できません)
    11|    .osc        HS      ; 発振タイプの設定
    12|    .wdt        off     ; ウォッチドックタイマの有効・無効の設定
    13|    .pwrt       on      ; パワーアップタイマの有効・無効の設定
    14|    .protect    off     ; プロテクトの設定
    15|
    16|; -----------------------------------------------------------------------------
    17|
    18|;   システムレジスタにラベル付け
    19|TMR0        equ     01h     ; タイマ
    20|
    21|STATUS      equ     03h     ; ステータスレジスタ(BANK0)
    22|C           equ     0       ; STATUS,C   キャリーフラグ
    23|DC          equ     1       ; STATUS,DC  DCフラグ
    24|Z           equ     2       ; STATUS,Z   ゼロフラグ
    25|PD          equ     3       ; STATUS,PD  パワーダウンフラグ
    26|TO          equ     4       ; STATUS,TO  タイムアウトフラグ
    27|RP0         equ     5       ; STATUS,RP0 RAMのバンク切り替え
    28|
    29|PORTA       equ     05h     ; I/OポートA(BANK0)
    30|PORTB       equ     06h     ; I/OポートB(BANK0)
    31|
    32|INTCON      equ     0bh     ; 割り込み制御レジスタ
    33|RBIF        equ     0       ; RB4-RB7 ポートチェンジ割り込みフラグ
    34|INTF        equ     1       ; INT(RA4)割り込みフラグ
    35|T0IF        equ     2       ; TMR0オーバーフロー割り込みフラグ
    36|RBIE        equ     3       ; RBIF割り込み許可
    37|INTE        equ     4       ; INTF割り込み許可
    38|T0IE        equ     5       ; TMR0割り込み許可
    39|EEIE        equ     6       ; データEEPROM書き込み終了割り込み許可
    40|GIE         equ     7       ; 全体割り込み許可
    41|
    42|OPTION      equ     01h     ; オプションレジスタ(BANK1)
    43|RBPU        equ     7       ; OPTION,RBPU ポートBのプルアップ 0:行う 1:行わない
    44|TRISA       equ     05h     ; ポートA出力/入力設定 0:出力 1:入力(BANK1)
    45|TRISB       equ     06h     ; ポートB出力/入力設定 0:出力 1:入力(BANK1)
    46|
    47|; -----------------------------------------------------------------------------
    48|
    49|;   変数を宣言
    50|        org     0ch
    51|temp1       ds      1
    52|int_w       ds      1       ; 割り込み処理での wレジスタ退避用
    53|int_status  ds      1       ; 割り込み処理での STATUSレジスタ退避用
    54|
    55|; -----------------------------------------------------------------------------
    56|
    57|        org     000h
    58|        goto    start
    59|
    60|; -----------------------------------------------------------------------------
    61|
    62|;   割り込み処理
    63|
    64|        org     004h
    65|
    66|        movwf   int_w           ; wレジスタを退避
    67|        movf    STATUS,0
    68|        movwf   int_status      ; ステータスレジスタを退避
    69|
    70|        movlw   125
    71|        movwf   TMR0            ; TMR0プリセット
    72|
    73|        btfsc   temp1,0         ; 割り込み動作のテスト
    74|        goto    int_portclr     ; 割り込みが入るたびにポートBを反転する
    75|        goto    int_portset
    76|int_portclr
    77|        clrf    temp1
    78|        clrf    PORTB
    79|        goto    int_return
    80|int_portset
    81|        decf    temp1,1
    82|        movlw   0ffh
    83|        movwf   PORTB
    84|
    85|;   割り込み処理の終了
    86|int_return
    87|        bcf     INTCON,T0IF     ; TMR0オーバーフロー割り込みフラグをクリア
    88|        movf    int_status,0
    89|        movwf   STATUS          ; ステータスレジスタを元に戻す
    90|        movf    int_w,0         ; wレジスタを元に戻す
    91|        retfie                  ; 割り込みを許可してリターン
    92|
    93|; -----------------------------------------------------------------------------
    94|
    95|start
    96|        bsf     STATUS,RP0  ; bank1
    97|
    98|        movlw   11000000b           ; プリスケーラを使用、分周比1:2
    99|        ;       |||||||+----- PS0       -+
   100|        ;       ||||||+------ PS1        +- プリスケーラ分周比
   101|        ;       |||||+------- PS2       -+
   102|        ;       ||||+-------- PSA       プリスケーラ設定        0:TMR0  1:WDT
   103|        ;       |||+--------- RTE       TMR0信号のエッジ        0:↑    1:↓
   104|        ;       ||+---------- RTS       TMR0信号のソース        0:内部  1:外部
   105|        ;       |+----------- INTEDG    INT 割り込みのエッジ    0:↓    1:↑
   106|        ;       +------------ RBPU      ポートBのプルアップ     0:行う  1:行わない
   107|        movwf   OPTION
   108|
   109|;   I/Oポートの設定
   110|        movlw   00000000b
   111|        movwf   TRISA
   112|        movwf   TRISB       ; 全部出力
   113|
   114|        bcf     STATUS,RP0  ; bank0
   115|
   116|;   割り込み許可
   117|        movlw   10100000b
   118|        movwf   INTCON          ; TMR0割り込み許可
   119|
   120|; -----------------------------------------------------------------------------
   121|
   122|loop
   123|        goto    loop
int.asm
  • 95〜118行、初期設定
    • 98行目、割り込みの設定(OPTIONレジスタ)。TMR0 カウンタののソースは内部クロック、プリスケーラは TMR0 で使用、プリスケーラ分周比 1:2 に設定
      プリスケーラを使用して、TMR0カウンタへのクロックの入力を 1/2 にしているので、TMR0 レジスタは、8MHz動作時で 1/8000000×4×2 = 0.000001s = 1μs ごとにインクリメントされる事になります。
    • 110〜112行、I/Oポートは全て出力に設定
    • 117〜118行、INTCONレジスタのT0IEビットをセットして TMR0 オーバーフロー割り込みを許可、その他の割り込みは使用しない。GIEビットをセットして、割り込みを許可
  • 122〜123行、メインループ。何もしません。ただ割り込みが発生するのを待つのみ
  • 64〜91行、割り込み処理。割り込みが発生するたびに、ここが実行されます
    • 66〜68行、wレジスタとSTATUSレジスタの退避。後で割り込み処理に入る前の状態に戻す必要があるので、まず内容を保存しておきます。
    • 70〜71行、TMR0 レジスタのプリセット。70行の数字を書き換えて、割り込み間隔の変化を確認してみてください
    • 73〜83行、実行されるたびに I/OポートB を反転させます
    • 87〜91行、割り込み処理の終了。wレジスタとSTATUSレジスタを元に戻して、割り込み処理から復帰します。
      このときに retfie 命令を使うと、自動的にGIE ビットをセットし、割り込みを許可して復帰するので便利です。

X68000キーボードとの通信処理部分の作成

割り込み動作の確認もできたことですし、いよいよX68000キーボードとの通信処理を作ってしまいましょう。

デバッグ用にLCDも付けましたので、キーボードが送ってきたスキャンコードをLCDに表示させてみる事にします。

図:非同期シリアル通信 受信処理

受信処理の大まかな流れは、ボーレートの4分の1、1/9600秒ごとに割り込みが発生するように設定しておいて、割り込みが発生するたびにI/Oポートをチェックして、スタートビットを確認したら、以後割り込みが4回発生するごとにデータを取り込んでゆくことになります。


  148|; -----------------------------------------------------------------------------
  149|; -----------------------------------------------------------------------------
  150|
  151|;   割り込み処理
  152|
  153|        org     004h
  154|
  155|interrupt
  156|
  157|        movwf   int_w                   ; wレジスタを退避
  158|        movf    STATUS,0
  159|        movwf   int_status              ; ステータスレジスタを退避
  160|        movlw   CONST_COMSPEED
  161|        movwf   TMR0                    ; TMR0プリセット
  162|
  163|
  164|
  165|; -----------------------------------------------------------------------------
  166|
  167|; x68キーボードとの通信処理
  168|; 2400bps,8ビット,ストップビット1,パリティなし
  169|
  170|; 受信すると x68rxd にデータが入り
  171|; キーレディ信号 PORTA,X68_READY を 0
  172|; データ有効フラグ com_status,x68rxdvalid を 1 にセットする
  173|
  174|; 受信処理中は 受信処理中フラグ com_status,x68receiving が 1 になる
  175|
  176|; プログラムで受信データを引き受けたら、
  177|; キーレディ信号 PORTA,X68_READY を 1
  178|; にすることで、次のデータの受信を行う
  179|; (PORTA,X68_READY を 1 にしなければ、キーボードが次のデータを送信してこない)
  180|
  181|
  182|
  183|;   受信処理
  184|
  185|        movf    int_x68rmode,0          ; 内部状態に応じて分岐
  186|        addwf   PCL,1                   ; PCL=PCL+int_x68rmode
  187|        goto    int_x68rmode0           ; スタートビット検出処理
  188|        goto    int_x68rmode1           ; 本当にスタートビット?
  189|        goto    int_x68rmode2_9         ; データの受信1
  190|        goto    int_x68rmode2_9         ; データの受信2
  191|        goto    int_x68rmode2_9         ; データの受信3
  192|        goto    int_x68rmode2_9         ; データの受信4
  193|        goto    int_x68rmode2_9         ; データの受信5
  194|        goto    int_x68rmode2_9         ; データの受信6
  195|        goto    int_x68rmode2_9         ; データの受信7
  196|        goto    int_x68rmode2_9         ; データの受信8
  197|        goto    int_x68rmode10          ; 受信処理終わり
  198|
  199|
  200|int_x68rmode0
  201|        btfsc   PORTB,X68_RxD
  202|        goto    int_x68rbreak           ; スタートビットを確認せず 中断
  203|
  204|;   X68_RxDが0 スタートビットがきた
  205|        bsf     com_status,x68receiving	; 受信中フラグセット
  206|        movlw   2
  207|        movwf   int_x68rcount           ; int_x68rcount=2
  208|        incf    int_x68rmode,1          ; int_x68rmode=int_x68rmode+1=1
  209|        goto    int_x68rend
  210|
  211|
  212|int_x68rmode1
  213|        btfsc   PORTB,X68_RxD
  214|        goto    int_x68rbreak           ; スタートビットを確認せず 中断
  215|
  216|;   スタートビットを確認した
  217|        decf    int_x68rcount,1         ; int_x68rcount=int_x68rcount-1
  218|        btfss   STATUS,Z
  219|        goto    int_x68rend             ; int_x68rcountがゼロでなければ終了
  220|
  221|;   3回続けてスタートビットを確認したので受信処理に入る
  222|        movlw   4
  223|        movwf   int_x68rcount           ; int_x68rcount=4
  224|        incf    int_x68rmode,1          ; int_x68rmode=int_x68rmode+1=2
  225|        bcf     com_status,x68rxdvalid  ; 受信データ有効フラグクリア
  226|        clrf    x68rxd                  ; 受信バッファクリア
  227|        goto    int_x68rend
  228|
  229|
  230|int_x68rmode2_9
  231|        decf    int_x68rcount,1         ; int_x68rcount=int_x68rcount-1
  232|        btfss   STATUS,Z
  233|        goto    int_x68rend             ; int_x68rcountがゼロでなければ終了
  234|;   実際の受信 (割り込みが4回発生するごとに1回実行される)
  235|        rrf     x68rxd,1                ; 受信バッファを右シフト
  236|        btfsc   PORTB,X68_RxD
  237|        bsf     x68rxd,7                ; RxDが1なら受信バッファのbit7をセット
  238|        movlw   4
  239|        movwf   int_x68rcount           ; int_x68rcount=4
  240|        incf    int_x68rmode,1          ; int_x68rmode=int_x68rmode+1
  241|        goto    int_x68rend
  242|
  243|
  244|int_x68rmode10
  245|        decf    int_x68rcount,1         ; int_x68rcount=int_x68rcount-1
  246|        btfss   STATUS,Z
  247|        goto    int_x68rend             ; int_x68rcountがゼロでなければ終了
  248|
  249|        btfss   PORTB,X68_RxD
  250|        goto    int_x68rbreak           ; ストップビットを確認せず 中断
  251|
  252|;   受信正常終了
  253|        bcf     PORTA,X68_READY
  254|        bsf     com_status,x68rxdvalid  ; 受信データ有効フラグセット
  255|
  256|;   受信異常終了
  257|int_x68rbreak
  258|        bcf     com_status,x68receiving ; 受信中処理フラグクリア
  259|        clrf    int_x68rmode
  260|        clrf    int_x68rcount
  261|int_x68rend



  351|; -----------------------------------------------------------------------------
  352|
  353|;   割り込み処理の終了
  354|
  355|int_return
  356|        bcf     INTCON,T0IF             ; TMR0オーバーフロー割り込みフラグをクリア
  357|        movf    int_status,0
  358|        movwf   STATUS                  ; ステータスレジスタを元に戻す
  359|        movf    int_w,0                 ; wレジスタを元に戻す
  360|
  361|;       goto    interrupt
  362|
  363|        retfie                          ; 割り込みを許可してリターン




     |; -----------------------------------------------------------------------------
  491|        movlw   255
  492|        call    wait
  493|
  494|dadada
  495|        btfsc   com_status,x68transmitrq
  496|        goto    dadada
  497|
  498|        movlw   10000000b       ; LED全部点灯
  499|        movwf   x68txd
  500|        bsf     com_status,x68transmitrq
  501|
  502|
  503|loop
  504|
  505|        btfsc   PORTA,X68_READY
  506|        goto    loop            ; データを受信するまで何もしない
  507|
  508|        movf    x68rxd,0        ; 何か受信したら
  509|        call    disp_h          ; キーコード表示
  510|
  511|        bsf     PORTA,X68_READY ; X68キーボードデータ送出許可
  512|
  513|        goto    loop
x68read.asm

ちょっと長いので、受信処理だけを抜き出してあります。ソース全体は上の“X68read.asm”をダウンロードして見てくださいっ。

  • 494〜500行、キーボードへの送信動作
    • 495〜496行、現在送信処理中かどうかを確認、送信が終わるまで待つ。
      (電源が入ったばかりだから、送信中のわけないけれど、本番のプログラムの練習のため、 ちゃんとチェックしておきます。)
    • 498〜499行、送信データをセット。 ここではキーボードのLEDを全部点灯させるコマンドを発行しています。
    • 500行目、送信要求を実施。
  • 503〜513行、メインのループ
    • 505〜506行、キーボードからデータを受信するまで、なにもしない。
    • 508〜509行、なにか受信したら、受信したデータを表示する。
    • 511行目、キーボードへデータの送出を許可する。
  • 148〜363行、割り込み処理
    • 153〜161行、割り込み処理の開始
      • 157〜159行、wレジスタとSTATUSレジスタの退避
      • 160〜161行、TMR0 カウンタのプリセット。
      • 割り込み間隔の調整の為、TMR0 カウンタに値をセットします。 セットする値は次のようになります。

        1/9600秒ごとに割り込みを発生させるわけですが。8MHz動作時で、プリスケーラを使用しない場合、1/8000000秒×4=1/2000000秒ごとにTMR0カウンタがインクリメントされます。2000000/9600≒208回カウントすると、1/9600秒となり

        255-208=47 をTMR0カウンタにあらかじめセットしておけばよいことになります。

        8MHz動作に限定すればこれで良いのですが、10MHzで動作させる場合には、(1/9600)/(1/10000000×4)≒260となり、255を越えてしまいます。そこで、プリスケーラを使用して、TMR0カウンタへの入力周波数を1/2にすることにしました。

        この場合、TMR0カウンタにプリセットする値は 255-(1/9600)/(1/動作周波数×4×2)となり、

        8MHz動作時で、151

        10MHz動作時で、125

        になります。が、実際には、割り込みが発生してから、割り込み処理ルーチンにジャンプするのに2サイクル、wレジスタの退避に1サイクル、STATUSレジスタの退避に2サイクル、TMR0カウンタに書き込んでからそれが反映されるまでに4サイクル(?)すでに経過しているので、この値から (9サイクル分、プリスケーラを使用しているので、9/2≒4) 4を加算してセットします。

        要するに、8MHz動作時には 155 をセットします。

    • 183〜261、キーボードからの受信処理
      • 185〜197行、受信処理の進み具合を保存している変数 int_x68rmode の内容に応じて分岐する。プログラムカウンタ(PCL レジスタ)にint_x68rmodeを加算する事で、後ろにならぶ goto 文へジャンプする。int_x68rmode が 0 なら、goto int_x68rmode0 が実行され、1 なら goto int_x68rmode1 が実行されるといった具合。
      • 200〜209行、スタートビットの検出処理。スタートビットを検出したら、受信処理に入る。
      • 212〜227行、スタートビットの確認。(完成してから思ったんだけど、この処理必要ないですよね。ただ時間待ちしとけばいいだけだ。)
      • 230〜241行、1ビットずつデータを受信する。
      • 244〜254行、ストップビットの確認。ストップビットを確認出来なかった場合は、受信エラー。
        ストップビットを確認したなら、キーボードの READY 信号を L にして、正常受信フラグをセット。
      • 256〜261、受信の終了処理。受信処理の内部状態を初期状態に戻す。
    • 351〜363行、割り込み処理の終了
      • 356行、割り込みフラグをクリア
      • 357〜359行、STATUS レジスタと、w レジスタを割り込み処理実行前の状態に戻す。
      • 363行目、割り込みを許可して復帰。

PS/2インターフェースの通信処理部分の作成

PS/2インターフェースの方は、こちらが出すクロックにあわせて、ホストの方がデータを出したり、受けたりするので、タイミングの事はあまり考える必要は無く、作るのは楽ちんです。


     1|;   I/Oポートの割り当て
     2|; PORT B
     3|PS2_CLKOUT  equ     0   ; RB0 - PS/2 CLK
     4|PS2_CLKIN   equ     1   ; RB1 - PS/2 CLK
     5|PS2_DATAOUT equ     2   ; RB2 - PS/2 DATA
     6|PS2_DATAIN  equ     3   ; RB3 - PS/2 DATA
     7|
     8|
     9|
    10|; -----------------------------------------------------------------------------
    11|; -----------------------------------------------------------------------------
    12|;   PS/2
    13|;   送信処理
    14|;   wレジスタにデータを入れてコールすると
    15|;   PS/2キーボードへ向けて送信する
    16|;   送信がホストに中断されると com_status,ps2break フラグを1にして戻ります
    17|
    18|ps2transmit
    19|        movwf   ps2txd
    20|        movwf   lastdata
    21|        bsf     com_status,ps2break
    22|        movlw   1
    23|        movwf   ps2parity   ;パリティ計算用変数を初期化
    24|        movlw   8
    25|        movwf   ps2count    ; count=8
    26|
    27|        btfss   PORTB,PS2_CLKIN     ; CLKがLなら
    28|        goto    ps2transmit         ; Hになるまで待つ
    29|        btfss   PORTB,PS2_DATAIN    ; DATAがLなら、なにもしない
    30|        return                      ; 戻り先で受信処理を行って欲しい
    31|
    32|ps2transmit_startbit
    33|        bsf     PORTB,PS2_DATAOUT   ; スタートビットをセット
    34|        call    ps2clk              ; クロックを出す
    35|        btfss   PORTB,PS2_CLKIN     ; ホストがCLKをLにしてきたら
    36|        goto    ps2transmit_break   ; 処理中断
    37|
    38|ps2transmit_data
    39|        btfsc   ps2txd,0            ; データをセット
    40|        goto    ps2transmit_data1
    41|ps2transmit_data0
    42|        bsf     PORTB,PS2_DATAOUT
    43|        goto    ps2transmit_wait1
    44|ps2transmit_data1
    45|        bcf     PORTB,PS2_DATAOUT
    46|        incf    ps2parity,1         ; parity+1
    47|ps2transmit_wait1
    48|        call    ps2clk              ; クロックを出す
    49|        btfss   PORTB,PS2_CLKIN     ; ホストがCLKをLにしてきたら
    50|        goto    ps2transmit_break   ; 処理中断
    51|        rrf     ps2txd,1            ; 右シフト
    52|        decfsz  ps2count,1          ; count-1
    53|        goto    ps2transmit_data
    54|
    55|ps2transmit_parity
    56|        btfsc   ps2parity,0         ; パリティをセット
    57|        goto    ps2transmit_parity1
    58|ps2transmit_parity0
    59|        bsf     PORTB,PS2_DATAOUT
    60|        goto    ps2transmit_wait2
    61|ps2transmit_parity1
    62|        bcf     PORTB,PS2_DATAOUT
    63|ps2transmit_wait2
    64|        call    ps2clk              ; クロックを出す
    65|        btfss   PORTB,PS2_CLKIN     ; ホストがCLKをLにしてきたら
    66|        goto    ps2transmit_break   ; 処理中断
    67|
    68|ps2transmit_stopbit
    69|        bcf     PORTB,PS2_DATAOUT   ; ストップビットをセット
    70|        call    ps2clk              ; クロックを出す
    71|
    72|;   送信おしまい
    73|ps2transmit_end
    74|        bcf     com_status,ps2break
    75|ps2transmit_break
    76|        bcf     PORTB,PS2_DATAOUT
    77|        return
    78|
    79|
    80|
    81|ps2clk
    82|        movlw   2
    83|        call    waitus
    84|        bsf     PORTB,PS2_CLKOUT
    85|        movlw   1
    86|        call    waitus
    87|        bcf     PORTB,PS2_CLKOUT
    88|        movlw   1
    89|        call    waitus
    90|        return

送信処理を抜き出して、流れを見てみましょうか。

  • 18〜30行、送信の準備。
    • 19〜25行、使用する変数の初期化。
      (送信を中断した場合などに、あとで再送信できるように、lastdata変数に送信するデータを保存しておきます)
    • 27〜28行、CLKがL(ホストの準備が出来ていない)なら、Hになるまで待つ。
    • 29〜30行、DATAがL(ホストが送信しようとしている)なら、処理を中断。
  • 32〜36行、スタートビットの送出。スタートビット(L)をセットして、クロックを出すだけ。とても簡単です。
    こちらがCLKをHにしている時に、ホストがLにして、通信処理の中断を要求してきたら、直ちに処理を中断します。
  • 38〜53行、データの送信。送信データの下位ビットから順番に1ビットずつ送信します。これを8回くり返して、1バイト送信します。
  • 55〜66行、パリティビットの送出。エラー検出の為のパリティビットを送出します。奇数パリティなので、データ8ビットとパリティビット合わせて、1の数が奇数になるようにパリティービットを送出します。
    bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 parity 全体で“1”が奇数個になるようにする。データの送出時に“1”の数を数えておいて、偶数だったら、パリティを“1”に。奇数だったらパリティを“0”にすればよい。
    1 1 0 1 0 0 1 1 0
    1 1 0 1 0 0 1 0 1
  • 68〜70行、ストップビット(H)の送出。ここまで処理が進むと、ホストから通信を中断される事は無いので、CLKラインのチェックはしていません。
  • 73〜77行、送信処理の終了。正常終了時には、通信異常終了フラグをクリア。異常終了時にはDATAラインがLになっているかもしれないので、DATAラインをHにして終了します。
  • 81〜90行、CLKを送出するサブルーチン。多用するのでサブルーチンにしました。
    CLKをLにして、少し待って、Hにして、少し待つだけです。

通信処理は、これで完成したので、あとはキーボードから送られてきたデータを、PC用に変換する処理を作れば、終わりです。

X68000キーボードの通信データ

まず、X68000キーボードと、PS/2キーボードがそれぞれどのようなデータをやり取りしているのかを見てみましょう。

まず、X68000キーボードですが、キーボードのボタン一つ一つに、番号(キーコード)が付いていて、キーが押された時と離された時に、次のようなデータを送信します。

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0:キーが押された
1:キーが離された
キーコード

キーが押し続けられたばあいは、リピートレートに合わせて、キーが押された時のデータがくり返し送信されます。

この他に、リピートレートの設定やキーボードのLEDの点灯制御のために、いくつかのコマンドをキーボードが受け付けるようになっています。

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 機能
1 全角 ひらがな INS CAPS コード入力 ローマ字 かな キーボードのLEDの点灯/消灯制御
0:点灯 1:消灯
0 1 1 0 REP.DELAY キーが押されてから、リピートが始まるまでの時間
200+(REP.DELAY)×100[ms](リセット時 500ms)
0 1 1 1 REP.TIME リピートの間隔
30+(REP.TIME)2×5[ms](リセット時 110ms)
(この他に、テレビコントロール関連等のコマンドがありますが、関係ないので省略)

X68000キーコード

図:X68000キーコード

PS/2キーボードの通信データ

一方PS/2キーボードの方ですが、こちらもX68000キーボードと似たようなデータをやり取りしています。

PS/2キーボードも、ボタン一つ一つにキーコードが割り振られていて、キーが押された時にキーコードがそのまま送出され、キーが離されたときには、リリースコード“F0”に続いてキーコードが送出されます。押しつづけていると、リピートするのは、X68000のキーボードと同様です。

[A]キーの場合を見てみると、押された時には“1C”が送出され、離された時には“F0 1C”が送出されます。

通常のキーは以上の通りなのですが、この他に拡張キーと、さらに特別なキーがあって少々面倒です。

まず、拡張キーですが、名前の通りきっと大昔のPCには、独立したカーソルキーや[DEL]キーなんかが無くて「不便だな」と思ったメーカーの人が、文字通り拡張したのでしょう。(PCのキーボードを初めて見た時(AXでしたが…)、独立したカーソルキーのほかに、10keyにカーソルキーや[DEL]キーが割り当てられていて、不思議だなぁと思いましたが、こんな歴史があった為だったのでしょう)

この拡張キーの動作ですが、拡張キーであることを示す“E0”に続けてキーコードを送出します。具体的に[DEL]キーの例を見てみると、キーが押された時には“E0 71”を送出し、離された時には“E0 F0 71”を送出します。

[Print Screen]キーと[Pause]キーは拡張キーとも異なり、とても長いコードが使われています。

[Print Screen]キーは、押された時に“E0 12 E0 7C”、リピート時には“E0 7C”、離された時には、“E0 F0 7C E0 F0 12”を送出します。ちょうど拡張キーを二つ組み合わせたような動作になっています。

[Pause]キーは、押された時だけ“E1 14 77 E1 F0 14 F0 77”を送出し、離された時には何も送出しません。リピートもしません。押された時にリリースコードもいっしょに送出してしまっているようです。

PS/2キーコード

図:PS/2キーコード

[←]キーのコードを間違えていたので修正(2014年7月23日)

PS/2キーボードにも、LEDの点灯制御や、リピートレートの設定、リセット処理を行う為に、いくつかのコマンドを受け付けるようになっています。なかでも、PCの電源投入時に、PCがリセットコマンドを送ってきますが、これに応答しなければPCのパワーオンセルフテストでキーボードエラーと診断されてしまいますので、それなりに処理する必要があります。

また、X68000のキーボードとは異なり、キーボードからPCへ向けて発行するコマンドもあります。

主要なコマンドは次の通り。

ホストコマンド(ホスト(PC)→キーボード)

キーボードコマンド(キーボード→ホスト)

キーコードの変換

X68000キーボードのスキャンコードから、PS/2キーボードのスキャンコードへの変換処理を考えます。

ちょうどPICマイコンには、retlw という w レジスタに定数をセットして、サブルーチンから復帰するという命令がありますので、この命令を使って、変換テーブルを作ればよさそうです。


    1|main
    2|        movlw   0
    3|        call    table
    4|
    5|loop
    6|        goto    loop
    7|
    8|
    9|
   10|table
   11|        addwf   PCL,1
   12|        retlw   12
   13|        retlw   34
   14|        retlw   56
   15|        retlw   78

2〜3行で、wレジスタに値をセットして、tableをコールすると、セットする値によって、wレジスタに定数をセットして、戻ってきます。

この例では、w レジスタに

0 をセットした場合には、 12 が、

1 をセットした場合には、 34 が、

2 をセットした場合には、 56 が、

3 をセットした場合には、 78 が、

w レジスタに セットされて帰ってきます。

このように、プログラムカウンタを直接操作する祭、注意しなければならない事があります。

プログラムカウンタ(PC)は、13ビット長のレジスタですが、PCLレジスタでは、PCの下位8ビットだけしか変更することが出来ません。そのため、PCLATHレジスタが用意されていて、PCLレジスタが操作されると、PCのbit8〜bit12にPCLATHレジスタの内容が、自動的に補われます。

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
PCLATH PCL
bit12 bit11 bit10 bit9 bit8 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
PC

PCLATHレジスタの内容が、リセット時には0なので、PCが000h〜0FFhの範囲内であれば、PCLATHレジスタの事を無視していても動作するのですが、それ以外ではPCLATHレジスタに正しい値をセットしなければなりません。

また、PCLレジスタを操作してテーブル処理を行う場合、

また、今回は割り込み処理内でも、PCLを直接操作していますが、キーコードの変換処理でPCLATHを書き換えた状態で、割り込みが入ってしまうと、割り込み処理内のPCLレジスタを操作している部分で、予期しない動作をしてしまいます。

そのため、キーコードの変換処理中は、割り込みを禁止することにします。

実際に書いてみると、次のようになりました。


   1|main_keyin
   2|       btfsc   PORTA,X68_READY ; キーボードから
   3|       goto    main            ; データを受信しなければ何もしない
   4|main_keyin1
   5|       btfsc   com_status,x68transmitrq    ; キーボードへ送信処理中なら
   6|       goto    main_keyin1                 ; 終わるまで待つ
   7|
   8|       bcf     INTCON,GIE      ; 割り込み禁止
   9|
  10|                               ; 割り込み処理内でテーブル処理を
  11|                               ; 行っているので、
  12|                               ; PCLATH を書き換えている間に割り込みが入ると
  13|                               ; まずいので、割り込み禁止にする
  14|
  15|       movlw   003h
  16|       movwf   PCLATH
  17|
  18|       movlw   01111111b
  19|       andwf   x68rxd,0
  20|       call    table
  21|       movwf   keycode
  22|               ;
  23|               ; ここに、PS/2のキーコード送出処理を書く
  24|               ;
  25|main_keyin_end
  26|       bsf     INTCON,GIE      ; 割り込み許可
  27|       goto    main
  28|
  29|
  30|
  31|; -----------------------------------------------------------------------------
  32|; -----------------------------------------------------------------------------
  33|;  キーコード変換テーブル
  34|
  35|;  キーの割り当てを変えたい場合は、このテーブルを書き換えてね
  36|
  37|
  38|       org     300h
  39|table
  40|       addwf   PCL,1
  41|       retlw   000h        ; 00h
  42|       retlw   076h        ; 01h  [ESC]
  43|       retlw   016h        ; 02h  [1]
  44|       retlw   01Eh        ; 03h  [2]
  45|       retlw   026h        ; 04h  [3]
  46|       retlw   025h        ; 05h  [4]
  47|       retlw   02Eh        ; 06h  [5]
  48|       retlw   036h        ; 07h  [6]
  49|       retlw   03Dh        ; 08h  [7]
  50|       retlw   03Eh        ; 09h  [8]
  51|       retlw   046h        ; 0Ah  [9]
  52|       retlw   045h        ; 0Bh  [0]
  53|       retlw   04Eh        ; 0Ch  [-]
  54|       retlw   055h        ; 0Dh  [^]
  55|       retlw   06Ah        ; 0Eh  [\]
  56|       retlw   066h        ; 0Fh  [BS]
  57|       retlw   00Dh        ; 10h  [TAB]
  58|       retlw   015h        ; 11h  [Q]
  59|       retlw   01Dh        ; 12h  [W]
  60|       retlw   024h        ; 13h  [E]
  61|       retlw   02Dh        ; 14h  [R]
  62|       retlw   02Ch        ; 15h  [T]
  63|       retlw   035h        ; 16h  [Y]
  64|       retlw   03Ch        ; 17h  [U]
  65|       retlw   043h        ; 18h  [I]
  66|       retlw   044h        ; 19h  [O]
  67|       retlw   04Dh        ; 1Ah  [P]
  68|       retlw   054h        ; 1Bh  [@]
  69|       retlw   05Bh        ; 1Ch  [[]
  70|       retlw   05Ah        ; 1Dh  [RETURN]
                     :
                     : 続く
  • 8行目で割り込みを禁止して、15〜16行目でPCLATHレジスタに値をセットします。今回は変換テーブルが128個分(7bit分)と大きめなので、テーブルをプログラムメモリの後ろの方(0300h〜)に置いてあります。
  • 18〜19行目、X68000のキーボードから送られてきたデータのbit7(押されたのか、離されたのかのフラグ)を取り除いて、キーコードだけを取り出します。結果はwレジスタに入ります。
  • 20〜21行目、変換テーブルを呼び出し、変換結果を変数keycodeに代入します。
  • 38行目〜 、変換テーブル。プログラムメモリの0300hから置いてあります。

続く… 予定 図:回路図



int.asm
  • 定食


webmaster@kyoutan.jpn.org

('A`)