あしたから本気出す
いろいろ工作して情報発信してみる。自作キーボードとか。
開発ブログ
自作キーボード「TypingTrigger-SDZERO」
自作スマートフォン緊急充電器
プログラマブルな自動化キーボード実装編
プログラマブルな自動化キーボード(プロトタイプ編)を作ってみる-後編
開発ブログをもっと見る
ブログなど
ブログはこちら
ダウンロード
JW_WIN外部変形Kiefer
JW_WIN外部変形FastMeasure
お知らせ
2010/10/1 ブログを新しくした
2009/9/4 twitter始めた
2009/7/14 サイトのタイトル変更した

管理者プロフィール (-)
suda 81年神奈川県生まれ
職業:GRAFZ.NETという屋号でプログラム制作してます。
一言:実用性を形にする。工作の日々。
コンタクトは以下までお願いします。感想、応援、苦情とか
RSS
ブログをRSS配信してます。


© GRAFZ.NET 2008-2010
1月24日
PIC16F84Aで自作キーボードを作る


自作キーボード
コンビニのレジを何気なく眺めてふと思う。特殊なキーボードが装備されてるなと。例えば「%引」や「個数修正」、「税金付加」とか。目的に特化したデバイスだけあって、便利そうなホットキーがずらり。うちのPCのキーボードにも「撃墜」ボタンとかがあって、それ押すとハエの一匹でも撃ち落してくんないかな。

自作決意。
って何を?

キーボード作ります!

それから数日後、PIC(Microchip社のマイクロコントローラ)を使ってキーボードを自作する決意をした。目標制作物はテンキーのようなもので、「あいさつ」ボタンのような具体的な入力を意図したデバイス。「forループ」とか、「関数雛形」のような開発支援的なホットキーがあっても便利かも(ニヤリ)。
ただ、僕の未熟な電子工作知識で、果たして目的を達成するような機械が完成するでしょうか?

インターフェースはPS/2。
まずはインターフェース選び。USBが思い浮かぶも敷居高し・・。その場合PIC18F2550のようなハイエンドなPICが必要だしCコンパイラが必要になってくる。次にPS/2。既にレガシー(なにせ20年以上前(2009年現在)にできたものだそうだ)だが、僕のlenovo 3000 J110にはついている。他にはシリアルで接続するとか。いくつか手段はありそうだ。いろいろ考えて PS/2に決まり。レガシーなところが気に入った。 周波数は4MHzくらいあれば十分なようなので、電子工作で人気のあるPIC16F84Aあたりで可能なようだ。 いざ電子工作!

情報集め。
まずは資料集め。さっそく情報量少ない・・。 PS/2は正式な仕様が無いらしい。
えっ!? ・・・。 

けど根気強く探す。 もっともあてになりそうなものが、↓これだろうか。
OADGテクニカルリファレンス
一読したところ、半分くらい意味不明。
他に、役に立ちそうな英語で書かれたwikiがある。↓これ。
www.computer-engineering.org
書いてあることがOADGのリファレンスとだいぶ違う。

分かったことを以下列挙。
・キーボードがパルスを発生させ、それをタイミングの基準としてPCがデータをくれたりデータをあげたりする。このパルスをクロックという。また、こういう通信を同期式シリアル通信だと言うらしい。
・電圧はだいたい0Vと5Vをピコピコさせる。PICと相性が良い。
・電源はPS/2ポートからもらえる。当たり前といえば、当たり前。ただし電流は275mA以内。
・PS/2は通電中の抜き差しは基本的にNG。ホットプラグは不可。
(追記:通電中の抜き差しして特に問題は起きなかった。ただ、いきなり差しても認識はされず。環境によっては過電流が発生する可能性があるらしく、要注意。)
・DINコネクタとPICは、オープンコレクタという方式で接続。


ハードウェアを作る。
回路図はこんなの↓
回路図

・必要な部品をかき集めて、ブレッドボードでハードウェアを組む。
テスト用のハード完成。接続したPCが破壊されないといいが。
ブレッドボード上で
回路はこれで問題なく動きそうなので、 基板を作ってみる。
基板のマスキング
生基板をカットし、アクリル絵の具でパターンを描く。パターンはレジストペンで描いてもOKかも。細い線は烏口(からすぐち)という製図器具が便利(のちに、ドクターマーチンのBLACK STARというインクの方が良いことが判明)。乾いたらエッチング液で湯せんする。こんな感じ(温度適当。ぬるい風呂くらい。)↓
基板のマスキング
エッチング終了間際にアクリル絵の具のマスクが剥離しはじめていたが、なんとなく出来た。 基板のマスキング
0.75ミリのピンバイスで穴開ける。適当にやるとICソケットがはまらなくなってしまうので、そこだけは慎重に開けるとよい。フラックスを綿棒で全面に塗布。
基板のマスキング
抵抗とコンデンサは表面実装型の部品を使用して小型化を狙ってみる。部品はジャンクのマザーボードから調達(彫刻刀で削り落とす)。
基板のマスキング
まあまあコンパクトに収まった。ちなみにスイッチは別パーツ。
基板のマスキング

いよいよパソコンと通信。
PS/2インターフェースの肝であるPCとの通信方法について。厳密にはマザーボード上の、キーボードコントローラと呼ばれるマイクロコントローラが相手。ハンドシェイク(タイミング合わせ)に使うクロック信号(以下CLK)と、データ信号(以下DATA)の2線を使う。調歩同期シリアル通信とも言うらしい。基本的にクロック信号はデバイス側で発生させる。「」の通信時には、ホストがクロックに合わせてビットをDATA線に乗せてくる。逆に「デバイス>ホスト」の場合はデバイスがビットを乗せ、クロックを出す。

CLKをLOWにする継続時間は30~50μs。受信の場合、最低でもLOWにする5μs前には、読み取られるDATAをHIかLOWの状態にせねばならないそうだ。CLKが終わってから5μsは、DATAはそのままにしておけとも書いてある。送信の場合、CLKの立上がりから5~25μsの期間にDATAを読み取る。

PCに1バイトを送る。
ここでやっと例。0xAAを送る場合。仕様によると、クロックをHIに戻して5μs経つまではデータはいじるなと書いてある。だが、どうやらたいていの場合、クロックの立下りから1μsとかでDATAを読み取ってくれるらしい。つまり下の図の赤丸が、PCがDATA線を読んでいるタイミングである(と思いたい)。
「図版製作中・・・」
LOWにして30μsキープして、HIに戻して30μs待つ。

DATAは、0xAAの場合2進数で10101010。これを下位ビット、つまり一番右の0から順に0,1,0,1,0,1,0,1と送るのだが、先頭にまずスタートビットと呼ばれる0をつけて、終わりにパリティビットとストップビットもつけねばならない。まずパリティビット。これは何かというと、データが正しく送受信されているか確認するために使用されるようだ。パリティビットは、全体として1の数が奇数になるように調整するだとか(スタートビットとストップビットはこの計算に加味しない)。例えば0xAAの場合。2進数で10101010なので、1が4回出てくる。そこでパリティビットを1にしてやると、5になって、奇数でめでたし、という具合。受信したときにパリティが不正な場合、正しく受信できていないということが分かる(2分の1の確立で。つまり、パリティが正しくても正しく受信できているとは限らない場合だってある)。
そして、最後にストップビット(常にHI)をセットする。

たかが1バイトを送るためにこんな苦労をしなければいけないのです。しかも、通信としての速度としてはめっちゃ遅い。1バイトで1/2000秒。つまり1秒で2KByteも送れるかどうかってところ。

BIOSにキーボードとして認識してもらうためには、電源が入って450ms~2.5sの間に0xAAを送らねばならないらしい。ちなみに500ms~700msの間に、という説も。電源が入ってからPICにリセットがかかる時間(だいたい100msとすると)も考えて、600ms経ったタイミングで0xAAを送ってやる。

ちなみにこの1バイトをタイミングよく送ることができないと、ビープ音とエラーメッセージと共にPCが起動してくれる。もしくは起動すらしない。 0xAAを送ることが出来てBIOSに認識されても、残念ながらOSには認識してもらえない。OSに認識してもらうためには、PCから送られてくる様々なメッセージ(ホストコマンド)に対して適切に応答してやらねばならない。

PCから1バイトを受け取る。
コマンドに応答するためには、まずコマンドを受信しなければ話が進まない。
コマンドの受信の仕方は送信の手順と似ている。ただ、ストップビットを受信した後に回線制御ビットの送信や、受信失敗した場合の再送要求などが加わるぶん、ちょっとだけ長い。とりあえず図。

信号

PCがDATAをLOWにしてきたら、コマンドが来るぞという合図。受信する体制をとらねばならない。すでにこれがスタートビット。スタートビットから1発目のCLKまでに、15ms以上のウェイトが必要。CLKを発生させて、データの1ビット目をDATAにのせてもらう。CLKの立上がりから10μs後にDATA線を読み取る。1Byteつまり8ビットを受信したら、パリティビットとストップビットを受信する。問題が無ければ、回線制御ビット(acknowledge bit)というやつをこちらでセットし(LOWをセットし)、ホストに読み取ってもらうためにクロックを発生させてやる必要がある。回線制御ビットがどのような役割を果たしているのかはよく分からない。そのCLKを送り終わったら、DATA線はHIに戻しておく。そしたら受信完了。パリティかストップビットのどちらかにエラーがあった場合は、100μsくらい待って再送要求コマンドをPCに送って、受信処理終了。パリティエラーだった場合は、まずCLKを調べて、PCがCLKをLOWにしていなければ再送要求コマンドを送信して終了。あと、ストップビットがエラーだった場合(つまりLOWだった場合)は、ストップビットよこせという意味で、よこすまでCLKを送り続ける。ストップビットが来たら、まずCLKを調べ、ホストがLOWにしていたらHIになるまで待って、その後再送要求する。パリティがエラーになっていなくても、ストップビットがエラーだったらそれまで受信していたデータは破棄してしまおう。正しいデータかどうか怪しいので。

以上がホストコマンドの受信。これでやっと、コマンドをホストとの間でやりとりする準備が整った。

PCからのコマンドに、いつ何を応えるのか。
さて、どのようなコマンドが来るのか。それは、BIOSやOSの種類によって千差万別のようだ。じゃあどうしたらいいか。野球のバッターのように、来る球に応じて打ち返してやればよい。幸い、コマンドの種類は十数種類。

コマンドを受信してから20ms以内に応答せねばならないようだが、応答が早すぎてもだめっぽい。100μsだとだめだった。1msだと、成功と失敗が50:50くらい。だから10ms後くらいに応答してみる。

各種のコマンドに対する応答を、以下に列挙してみる。

・リセット(0xFF)
コマンドの中でも特に重要。うち環境に限っていうと、OSが起動するときに(なぜか)頻繁に投げられてくる。このコマンドを受け取ったら、100μs経過後に0xFA(ACK)を送り返し、600ms経過後に0xAA(BAT正常完了)を送る。これらリセットコマンドのやりとりが正常に行われないと、キーボードとしての認識が(BIOSには行われたとしても)OSによってされない。

・ステータスインジケーター(0xED)
キーボード上のLED関連。ACKで応答、100μs後にオプションバイト受信。Caps Lock、Num Lock、Scroll LockなどをON/OFFせよというコマンド。ACKで応答。オプションバイトとして受信したものが他のコマンドの可能性があり(7ビット目が0で無い場合その可能性が濃厚)、その場合には、どのようなコマンドなのか再度調べて処理する。
・エコー(0xEE)
単純に0xEEを返す。
・スキャンコードの指定・通達(0xF0)
まずACKで応答した後、オプションバイトを受信する。オプションバイトが0x00だった場合、こちらのスキャンコードを送信する。現状スキャンコードセットは2のみの対応なので、0x02を送信する。オプションバイトがそれ以外だった場合、キーボードが送出するキーコードを、指定のスキャンコードセットで送ってやる必要があるが、コードセットはとりあえず2のみ実装。
・IDの通達(0xF2)
まずACKで応答し、100μs後に0xAB、さらに100μs後に0x83を送信。
・リピートレート設定(0xF3)ACKで応答、100μs後にオプションバイト受信、ACKで応答。ただし、オプションバイトが他のコマンドの場合、別処理(ステータスインジケーター参照)。
・イネーブル(0xF4)ACKで応答
・ディセーブル(0xF5)ACKで応答
・デフォルト(0xF6)ACKで応答
・キーセットが3のとき関連(0xF7、0xF8、0xF9、0xFA、0xFB、0xFC、0xFD)今回は実装せず。ACKで応答。
・再送(0xFE)最後に送ったバイトを送る

これでやっとキーボードから文字を入力する準備が整った。

タクトスイッチ押下で画面に「a」?
試しに「A」を送ってみる。キーコード0x1CをPCに送ると画面に「a」が。
次に、スイッチ押下で「ab」が表示されるように変更。文字間は適当に4msくらいウェイトをかけてやる。調子に乗って、「for(var i:int=0;i<aaa;i++){}」。特に問題なし。

発案から3ヶ月経っていた。窓の外には桜の花びらが心地よい風にそよいでいる。

PICのポートはあと7つ空いている。さあ、あなたなら何をプログラムしますか?

MPASM用アセンブリのソース(keyemu.asm)
;------------------------------------
;------KEYBOARD EMULATER BETA0-------
;------------------------------------
;------COPYRIGHT GRAFZ.NET 2009------
;------------------------------------

    LIST P=16F84A
    #INCLUDE

    ERRORLEVEL -302
    __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC

    CBLOCK 0x0C
    WAIT_COUNT1
    WAIT_COUNT2
    COM_STATUS
    PARITY
    COUNT_BYTE
    BYTE_IN
    BYTE_OUT
    LASTDATA
    SW0_STATUS
    CNT_LOOP
    CNT_WAIT
    BUFFER_LETTER
    ENDC

#DEFINE PS2_CLKOUT D'0'
#DEFINE PS2_CLKIN D'1'
#DEFINE PS2_DATAOUT D'2'
#DEFINE PS2_DATAIN D'3'
#DEFINE CONST_WAIT_MS D'250'
#DEFINE CONST_WAIT_US D'8'
#DEFINE F_VALIDDATA D'3' ;in COM_STATUS
#DEFINE PS2BREAK D'5'

    ORG 0x00
    GOTO START
    ORG 0x04
    RETFIE

START
    BSF STATUS,RP0 ;BANK1
    MOVLW B'11111111'
    MOVWF TRISA
    MOVLW B'11111010';RB0 AND RB2 ARE OUTPUT
    MOVWF TRISB
    CLRF COM_STATUS
    
    MOVLW B'11111111'
    MOVWF SW0_STATUS

    BCF STATUS,RP0 ;BANK0
    
INITPOR
    MOVLW D'200'
    CALL WAIT
    MOVLW D'150'
    CALL WAIT
    MOVLW D'150'
    CALL WAIT
    
    BCF STATUS,RP0;bank0
    BTFSS PORTB,PS2_DATAIN
    GOTO MAIN ;WHEN HOST MAKES DATA LO
    MOVLW H'AA' ;BAT IS OK
    CALL TRANSMITDATA

MAIN
    CALL RECEIVEDATA
    BTFSC COM_STATUS,F_VALIDDATA
    GOTO HOSTCOMMAND
    
;WHEN NO HOSTCOMMAND RECIEVED
    CALL KEYMAIN1
    CALL KEYMAIN2
    CALL KEYMAIN3
    CALL KEYMAIN4
    CALL KEYMAIN5
    CALL KEYMAIN6
    CALL KEYMAIN7
    CALL KEYMAIN8
    GOTO MAIN

HOSTCOMMAND
    MOVLW D'10'
    CALL WAIT

;-------------------------------
;--------HOSTCOMMAND------------
;-------------------------------

HC_START
HC_RESET
    MOVLW H'FF'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_LED
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    MOVLW D'100' ;emulation of por of PIC itself
    CALL WAIT
    GOTO INITPOR ;BAT
    
HC_LED
    MOVLW H'ED'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_ECHO
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    CALL WAIT100US
HC_LED_LOOP
    CALL RECEIVEDATA
    BTFSS COM_STATUS,F_VALIDDATA
    GOTO HC_LED_LOOP
    
    BTFSC BYTE_IN,7;IF RESERVED BIT ISN'T 0,
    GOTO HC_START;IT'S NOT OPTION BYTE
    
    CALL WAIT100US
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN
    
HC_ECHO
    MOVLW H'EE'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SCANCODE
    MOVLW H'EE'
    CALL TRANSMITDATA
    GOTO MAIN

HC_SCANCODE
    MOVLW H'F0'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETID
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    CALL WAIT100US
HC_SCANCODE_LOOP
    CALL RECEIVEDATA
    BTFSS COM_STATUS,F_VALIDDATA
    GOTO HC_SCANCODE_LOOP
    
    BTFSC BYTE_IN,7;IF RESERVED BIT ISN'T 0,
    GOTO HC_START;IT'S NOT OPTION BYTE
    
    CALL WAIT100US
    MOVLW H'00'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SCANCODE2
    MOVLW H'FA'
    CALL TRANSMITDATA
    CALL WAIT100US
    MOVLW H'02'
    CALL TRANSMITDATA
    GOTO MAIN
HC_SCANCODE2
    MOVLW H'FA'
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETID
    MOVLW H'F2'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_REPEATRATE
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    CALL WAIT100US
    MOVLW H'AB'
    CALL TRANSMITDATA
    CALL WAIT100US
    MOVLW H'83'
    CALL TRANSMITDATA
    GOTO MAIN

HC_REPEATRATE
    MOVLW H'F3'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_ENABLE
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    CALL WAIT100US
HC_REPEATRATE_LOOP
    CALL RECEIVEDATA
    BTFSS COM_STATUS,F_VALIDDATA
    GOTO HC_REPEATRATE_LOOP
    
    BTFSC BYTE_IN,7;IF RESERVED BIT ISN'T 0,
    GOTO HC_START;IT'S NOT OPTION BYTE
    
    CALL WAIT100US
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_ENABLE
    MOVLW H'F4'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_DISABLE
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_DISABLE
    MOVLW H'F5'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_DEFAULT
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_DEFAULT
    MOVLW H'F6'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_RESEND
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_RESEND
    MOVLW H'FE'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY0
    MOVF LASTDATA,0
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY0
    MOVLW H'FD'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY1
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY1
    MOVLW H'FC'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY2
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY2
    MOVLW H'FB'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY3
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY3
    MOVLW H'FA'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY4
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY4
    MOVLW H'F9'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY5
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY5
    MOVLW H'F8'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_SETKEY6
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_SETKEY6
    MOVLW H'F7'
    SUBWF BYTE_IN,0
    BTFSS STATUS,Z
    GOTO HC_END
    MOVLW H'FA' ;ACK
    CALL TRANSMITDATA
    GOTO MAIN

HC_END
    MOVLW H'FE'
    CALL TRANSMITDATA
    GOTO MAIN
    
;------------------------------------
;-------------RECEIVE----------------
;------------------------------------
RECEIVEDATA
    BCF COM_STATUS,F_VALIDDATA
    
;STARTBIT
    BCF STATUS,RP0 ;bank0
    BTFSC PORTB,PS2_DATAIN
    RETURN ;WHEN NO STARTBIT FOUND
    MOVLW D'1'
    MOVWF PARITY ;INITIALISE
    MOVLW D'8'
    MOVWF COUNT_BYTE
    MOVLW D'15'
    CALL WAIT
    
;DATA
RECEIVEDATA_data
    RRF BYTE_IN,1
    CALL SENDCLK
    BTFSC PORTB,PS2_DATAIN ;CHECK HI OR LOW
    GOTO RECEIVEDATA_b1
RECEIVEDATA_b0
    BCF BYTE_IN,7
    GOTO RECEIVEDATA_data_end
RECEIVEDATA_b1
    BSF BYTE_IN,7
    INCF PARITY,1
RECEIVEDATA_data_end
    BTFSS PORTB,PS2_CLKIN
    RETURN ;WHEN HOST MAKES CLK LOW, ABORT
    DECFSZ COUNT_BYTE,1
    GOTO RECEIVEDATA_data
    
;PARITY
    BTFSS PORTB,PS2_CLKIN
    RETURN ;WHEN HOST MAKE CLK LOW, ABORT
    MOVLW B'00000001'
    ANDWF PARITY,1 ;BIT0 OF PARITY
    CALL SENDCLK
    BTFSC PORTB,PS2_DATAIN ;CHECK HI OR LOW
    GOTO RECEIVEDATA_PARITY1
;RECEIVEDATA_PARITY0
    MOVF PARITY,1
    BTFSS STATUS,Z
    GOTO RECEIVEDATA_REQ_RESEND
    GOTO RECEIVEDATA_PARITYEND
RECEIVEDATA_PARITY1
    MOVF PARITY,1
    BTFSC STATUS,Z
    GOTO RECEIVEDATA_REQ_RESEND
RECEIVEDATA_PARITYEND

;STOPBIT
    CALL SENDCLK
    BTFSS PORTB,PS2_DATAIN ;CHECK HI OR LOW
    GOTO RECEIVEDATA_CONTROL ;WHEN NO STOPBIT FOUND
    
;OK
    BSF COM_STATUS,F_VALIDDATA ;FLAG = TRUE
    BSF PORTB,PS2_DATAOUT ;CONTROL BIT ON
    CALL SENDCLK
    BCF PORTB,PS2_DATAOUT ;CONTROL BIT OFF
    RETURN
RECEIVEDATA_CONTROL
RECEIVEDATA_CONTROL2
    BTFSC PORTB,PS2_DATAIN
    GOTO RECEIVEDATA_REQ_RESEND ;WHEN DATA TURNS HI
    CALL SENDCLK ;SEND CLK UNTIL DATA BECOMES HI
    GOTO RECEIVEDATA_CONTROL2
RECEIVEDATA_REQ_RESEND
    BTFSS PORTB,PS2_CLKIN ;WAIT UNTIL CLK BECOMES HI
    GOTO RECEIVEDATA_REQ_RESEND
RECEIVEDATA_REQ_RESEND1
    CALL WAIT100US
    BTFSS PORTB,PS2_DATAIN
    GOTO RECEIVEDATA
    MOVLW H'FE' ;RESEND
    CALL TRANSMITDATA
    BTFSC COM_STATUS,PS2BREAK
    GOTO RECEIVEDATA_REQ_RESEND1 ;TRY ONCE AGAIN
    RETURN

;------------------------------------
;-------------TRANSMIT---------------
;------------------------------------
TRANSMITDATA
    MOVWF BYTE_OUT
    MOVWF LASTDATA
    BSF COM_STATUS,PS2BREAK
    MOVLW D'1'
    MOVWF PARITY
    MOVLW D'8'
    MOVWF COUNT_BYTE

    BCF STATUS,RP0 ;BANK0
;STARTBIT
    BTFSS PORTB,PS2_CLKIN
    GOTO TRANSMITDATA_BREAK ;ABORT
    BTFSS PORTB,PS2_DATAIN
    GOTO TRANSMITDATA_BREAK ;ABORT
    BSF PORTB,PS2_DATAOUT ;STARTBIT IS LOW
    CALL SENDCLK

;DATA
TRANSMITDATA_DATA
    BTFSC BYTE_OUT,0
    GOTO TRANSMITDATA_DATA1
;TRANSMITDATA_DATA0
    BSF PORTB,PS2_DATAOUT
    GOTO TRANSMITDATA_CLK
TRANSMITDATA_DATA1
    BCF PORTB,PS2_DATAOUT
    INCF PARITY,1
TRANSMITDATA_CLK
    CALL SENDCLK
    BTFSS PORTB,PS2_CLKIN
    GOTO TRANSMITDATA_BREAK ;ABORT
    RRF BYTE_OUT,1 ;SHIFT
    DECFSZ COUNT_BYTE,1
    GOTO TRANSMITDATA_DATA ;REPEATS 8 TIMES

;PARITY
    BTFSC    PARITY,0
    GOTO TRANSMITDATA_PARITY1
;TRANSMITDATA_PARITY0
    BSF PORTB,PS2_DATAOUT
    GOTO TRANSMITDATA_PARITY_CLK
TRANSMITDATA_PARITY1
    BCF PORTB,PS2_DATAOUT
TRANSMITDATA_PARITY_CLK
    BTFSS PORTB,PS2_CLKIN
    GOTO TRANSMITDATA_BREAK ;ABORT
    CALL SENDCLK

;STOPBIT
    BCF PORTB,PS2_DATAOUT
    CALL SENDCLK

;OK
    BCF COM_STATUS,PS2BREAK
    RETURN
TRANSMITDATA_BREAK
    BSF COM_STATUS,PS2BREAK
    BCF PORTB,PS2_DATAOUT
    RETURN
    
;------------------------------------
;-------------UTILS------------------
;------------------------------------
SENDCLK
    CALL WAIT10US
    CALL WAIT10US
    BSF PORTB,PS2_CLKOUT
    CALL WAIT10US
    CALL WAIT10US
    CALL WAIT10US
    BCF PORTB,PS2_CLKOUT
    CALL WAIT10US
    RETURN
    
WAIT10MS
    MOVLW D'90'
    MOVWF CNT_WAIT
LOOP_WAIT10MS
    CALL WAIT100US
    CALL WAIT10US
    DECFSZ CNT_WAIT,F
    GOTO LOOP_WAIT10MS
    RETURN
    
WAIT1MS
    MOVLW D'9'
    MOVWF CNT_WAIT
LOOP_WAIT1MS
    CALL WAIT100US
    CALL WAIT10US
    DECFSZ CNT_WAIT,F
    GOTO LOOP_WAIT1MS
    RETURN
    
WAIT100US
    MOVLW D'49'
    MOVWF CNT_LOOP
LOOP_WAIT100US
    GOTO $+1
    DECFSZ CNT_LOOP,F
    GOTO LOOP_WAIT100US
    RETURN
    
WAIT10US
    MOVLW D'4'
    MOVWF CNT_LOOP
LOOP_WAIT10US
    GOTO $+1
    DECFSZ CNT_LOOP,F
    GOTO LOOP_WAIT10US
    RETURN
    
WAIT6US
    MOVLW D'2'
    MOVWF CNT_LOOP
LOOP_WAIT6US
    GOTO $+1
    DECFSZ CNT_LOOP,F
    GOTO LOOP_WAIT6US
    RETURN
    
WAIT
    MOVWF WAIT_COUNT1
WAIT_LOOP1
    MOVLW CONST_WAIT_MS
    MOVWF WAIT_COUNT2
WAIT_LOOP2
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    DECFSZ WAIT_COUNT2,1
    GOTO WAIT_LOOP2
    DECFSZ WAIT_COUNT1,1
    GOTO WAIT_LOOP1
    RETURN
WAITUS
    MOVWF WAIT_COUNT1
WAITUS_LOOP1
    MOVLW CONST_WAIT_US
    MOVWF WAIT_COUNT2
WAITUS_LOOP2
    DECFSZ WAIT_COUNT2,1
    GOTO WAITUS_LOOP2
    DECFSZ WAIT_COUNT1,1
    GOTO WAITUS_LOOP1    
    RETURN
    
;------------------------------------
;----------AUTOMATION----------------
;------------------------------------
KEYMAIN1
    BCF STATUS,RP0 ;BANK0
IF4 BTFSS PORTB,4
    GOTO IF4FALSE
IF4TRUE ;WHEN CURRENT IS 1
    BTFSS SW0_STATUS,0
    CALL BREAK_P;CURRENT 1, LAST 0
    BSF SW0_STATUS,0
    RETURN
IF4FALSE ;WHEN CURRENT IS 0
    BCF SW0_STATUS,0
    RETURN
BREAK_P
    CALL WAIT10MS
    BTFSC PORTB,4
    CALL MAKE_PHRASE1 ;SKIPPED WHEN CHATTERING
    RETURN

KEYMAIN2
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTB,5
    GOTO KEYMAIN2_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,1 ;LASTDATA
    CALL KEYMAIN2_BREAK
    BSF SW0_STATUS,1
    RETURN
KEYMAIN2_CURRENT_LO
    BCF SW0_STATUS,1
    RETURN
KEYMAIN2_BREAK
    CALL WAIT10MS
    BTFSC PORTB,5
    CALL MAKE_PHRASE2
    RETURN

KEYMAIN3
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTB,6
    GOTO KEYMAIN3_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,2 ;LASTDATA
    CALL KEYMAIN3_BREAK
    BSF SW0_STATUS,2
    RETURN
KEYMAIN3_CURRENT_LO
    BCF SW0_STATUS,2
    RETURN
KEYMAIN3_BREAK
    CALL WAIT10MS
    BTFSC PORTB,6
    CALL MAKE_PHRASE3
    RETURN
    
KEYMAIN4
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTB,7
    GOTO KEYMAIN4_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,3 ;LASTDATA
    CALL KEYMAIN4_BREAK
    BSF SW0_STATUS,3
    RETURN
KEYMAIN4_CURRENT_LO
    BCF SW0_STATUS,3
    RETURN
KEYMAIN4_BREAK
    CALL WAIT10MS
    BTFSC PORTB,7
    CALL MAKE_PHRASE4
    RETURN
    
KEYMAIN5
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTA,0
    GOTO KEYMAIN5_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,4 ;LASTDATA
    CALL KEYMAIN5_BREAK
    BSF SW0_STATUS,4
    RETURN
KEYMAIN5_CURRENT_LO
    BCF SW0_STATUS,4
    RETURN
KEYMAIN5_BREAK
    CALL WAIT10MS
    BTFSC PORTA,0
    CALL MAKE_PHRASE5
    RETURN
    
KEYMAIN6
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTA,1
    GOTO KEYMAIN6_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,5 ;LASTDATA
    CALL KEYMAIN6_BREAK
    BSF SW0_STATUS,5
    RETURN
KEYMAIN6_CURRENT_LO
    BCF SW0_STATUS,5
    RETURN
KEYMAIN6_BREAK
    CALL WAIT10MS
    BTFSC PORTA,1
    CALL MAKE_PHRASE6
    RETURN
    
KEYMAIN7
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTA,2
    GOTO KEYMAIN7_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,6 ;LASTDATA
    CALL KEYMAIN7_BREAK
    BSF SW0_STATUS,6
    RETURN
KEYMAIN7_CURRENT_LO
    BCF SW0_STATUS,6
    RETURN
KEYMAIN7_BREAK
    CALL WAIT10MS
    BTFSC PORTA,2
    CALL MAKE_PHRASE7
    RETURN
    
KEYMAIN8
    BCF STATUS,RP0 ;BANK0
    BTFSS PORTA,3
    GOTO KEYMAIN8_CURRENT_LO
;CURRENT IS HI
    BTFSS SW0_STATUS,7 ;LASTDATA
    CALL KEYMAIN8_BREAK
    BSF SW0_STATUS,7
    RETURN
KEYMAIN8_CURRENT_LO
    BCF SW0_STATUS,7
    RETURN
KEYMAIN8_BREAK
    CALL WAIT10MS
    BTFSC PORTA,3
    CALL MAKE_PHRASE8
    RETURN

;---------------
MAKE_LETTER
    MOVWF BUFFER_LETTER
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    MOVLW    H'F0'
    CALL TRANSMITDATA
    CALL WAIT1MS
    MOVF BUFFER_LETTER,0
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    RETURN
    
MAKE_LETTER_EX
    MOVWF BUFFER_LETTER
    MOVLW    H'E0'
    CALL TRANSMITDATA
    CALL WAIT1MS
    MOVF BUFFER_LETTER,0
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    MOVLW    H'E0'
    CALL TRANSMITDATA
    CALL WAIT1MS
    MOVLW    H'F0'
    CALL TRANSMITDATA
    CALL WAIT1MS
    MOVF BUFFER_LETTER,0
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    RETURN

MAKE_SHIFT
    MOVLW H'12'
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    RETURN

BREAK_SHIFT
    MOVLW H'F0'
    CALL TRANSMITDATA
    CALL WAIT1MS
    MOVLW H'12'
    CALL TRANSMITDATA
    MOVLW D'4'
    CALL WAIT
    RETURN
    
;---------------
MAKE_PHRASE1
    MOVLW H'1C' ;a
    CALL MAKE_LETTER
    RETURN
    
MAKE_PHRASE2
    CALL MAKE_SHIFT
    MOVLW H'1C' ;A
    CALL BREAK_SHIFT
    RETURN
    
MAKE_PHRASE3
    RETURN
    
MAKE_PHRASE4
    RETURN
    
MAKE_PHRASE5
    RETURN
    
MAKE_PHRASE6
    RETURN
    
MAKE_PHRASE7
    RETURN
    
MAKE_PHRASE8
    RETURN

    END

改造記「テンキーを自作キーボードに改造する(ついでにUSB化)。」はこちらからどうぞ。
読んで下さってありがとうございます。<(_ _)>