|目次
|車
|PC
|基地祭
|写真
|雑記
|質問の前に
|作者
|
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(ホスト)への通信では
- キーボードがCLKラインがHであることを確認します。もしLであれば、通信できません。
- キーボードはDATAラインがHであることを確認します。もしLであれば、ホストがスタートビットをセットしているので、受信処理を行います。
- キーボードはデータをセットして、クロックを送り出します。ホストはクロックの立下りでデータを取り込みます。
このとき、何らかの理由でホストが通信を中断したい場合、CLKラインをLにしますので、キーボードは自分がCLKをHにしているのに、CLKがLになっているのを検出したら、直ちに通信を中断します。
これを繰り返して、通信は終了します。(スタートビット1、データビット8、パリティビット1、ストップビット1)
ホストからキーボードへの通信は
- ホストはまずスタートビットをセットします。
- キーボードはスタートビットを検出すると、クロックを送り出します。ホストはクロックを受けるたびに、次のデータをセットします。
これを繰り返し、ホストはストップビットまで送り出します。
もし、ストップビットが見つからなければ、キーボードはDATAラインがHになるまでクロックを送出します。
- キーボードは、ストップビットまで受信したら、データラインを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 ?
とりあえず動きましたので、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秒とします。これはビット幅の真ん中あたりを読み取る事で、通信をより確実にする為です。
この割り込みが発生するたびに、信号ラインを読み取ってゆきます。
- スタートビットを見つけたら、受信処理を開始します。
今回はなんとなく、3回続けて信号ラインが“L”だったらスタートビットと判断しています。
- その後、割り込みが4回発生するごとに信号ラインを読み取って、受信バッファに格納します。
- これを8回くり返して、データを取り込みます。
- 最後に、ストップビットを読み取って、受信処理を終了します。
もしここで、ストップビットだと思われるところが“L”だったら、おかしなタイミングで受信処理をはじめてしまったということで、ここで受信したデータは無効ということになります。(フレーミングエラー)
送信処理は、受信処理のようにいろいろ考える必要は無く、割り込みが4回発生するごとに、スタートビット、データ、ストップビットの順で出力してやれば完了です。
さて、通信の手順もわかったことですし、さっそくプログラムを書いてみましょう。
割り込み動作の確認
まずは簡単に、割り込み処理の動作テストをしてみます。PIC16C84の割り込みについて見てみましょう。
PIC16C84では次の現象で、割り込みを発生させる事が可能です。
- TMR0 タイマ/カウンタのオーバーフロー
- ポートBのPB0/INTピンからの外部割り込み
- ポートBのRB4〜RB7のポートチェンジ
- データ EEPROM の書き込み終了
これらは、それぞれ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キーコード
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キーコード
[←]キーのコードを間違えていたので修正(2014年7月23日)
PS/2キーボードにも、LEDの点灯制御や、リピートレートの設定、リセット処理を行う為に、いくつかのコマンドを受け付けるようになっています。なかでも、PCの電源投入時に、PCがリセットコマンドを送ってきますが、これに応答しなければPCのパワーオンセルフテストでキーボードエラーと診断されてしまいますので、それなりに処理する必要があります。
また、X68000のキーボードとは異なり、キーボードからPCへ向けて発行するコマンドもあります。
主要なコマンドは次の通り。
ホストコマンド(ホスト(PC)→キーボード)
- “ED” Set Status LED's
pc |
|
keyboard |
ED |
→ |
|
|
← |
FA (ACK) |
STATUS
bit7 |
bit6 |
bit5 |
bit4 |
bit3 |
bit2 |
bit1 |
bit0 |
- |
Caps Lock |
Num Lock |
Scrool Lock |
| → | |
0:消灯 1:点灯
bit3〜bit7は無視される
|
|
|
|
← |
FA (ACK) |
- “EE” Echo
pc |
|
keyboard |
EE |
→ |
|
|
← |
EE |
EE を送ると EE が帰ってくる(テスト用?) |
- “F0” Set Scan Code Set
pc |
|
keyboard |
F0 |
→ |
|
|
← |
FA (ACK) |
コードセット(01〜03) |
→ |
|
|
← |
FA (ACK) |
キーコードの種類を設定する。02 が標準。
手持ちのPS/2キーボードで試してみたところ、01 も 03 も、標準のキーコードとはがらっと変わってしまいます。
キーボードが想定しない値をセットした場合、2回目の ACK の代わりに、現在のコードセットが返されるようです。(手持ちのキーボードに 00 を送ってみたら、02 が返ってきた) |
- “F3” Set Typematic Repeat Rate
pc |
|
keyboard |
F3 |
→ |
|
|
← |
FA (ACK) |
rate
bit7 |
bit6 |
bit5 |
bit4 |
bit3 |
bit2 |
bit1 |
bit0 |
0 |
delay |
rate.b |
rate.a |
| → | |
リピートが始まるまでの時間
=(delay+1)×250[ms]
リピートの間隔
=(8+rate.a)×2(rate.b)×4[ms]
|
|
|
|
← |
FA (ACK) |
- “F4” Keyboard Enable
pc |
|
keyboard |
F4 |
→ |
|
|
← |
処理後 FA (ACK) |
キーボードの出力バッファをクリアし、キースキャンを可能にする |
- “F5” Keyboard Disable
pc |
|
keyboard |
F5 |
→ |
|
|
← |
処理後 FA (ACK) |
キーボードをリセットし、キースキャンを無効にする |
- “FE” Resend
pc |
|
keyboard |
FE |
→ |
|
|
← |
前回のデータ(1バイト) |
前回送ってきた1バイトを再度送信させる |
- “FF” Reset
pc |
|
keyboard |
FF |
→ |
|
|
← |
FA (ACK) |
|
← |
処理後 AA (Self Test Passed) |
キーボードをリセットする |
キーボードコマンド(キーボード→ホスト)
- “FA” acknowledge
キーボードはホストから1バイト受信するたびに FA を返す
- “AA” Power On Self Test Passed
ホストがリセットコマンドを送ってきた時にも FA につづけて AA を返す
- “EE”
Echo Command への返答
- “F0” Release code
- “FE” Resend
keyboard |
|
pc |
FE |
→ |
|
|
← |
前回のデータ(1バイト) |
前回送ってきた1バイトを再度送信させる |
- “00” Error or Buffer Overflow
- “F0” Error or Buffer Overflow
キーコードの変換
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から置いてあります。
|
続く… 予定
|目次
|車
|PC
|基地祭
|写真
|雑記
|質問の前に
|作者
|privacy policy
|
webmaster@kyoutan.jpn.org
佐藤恭一 を著作者とするこのページは クリエイティブ・コモンズの 表示 4.0 国際 ライセンスで提供されています。
('A`)