あしたから本気出す
利用価値のあるものを工作し、情報発信してみる。
工作
Jw_cadの外部変形の作り方2
Jw_cadの外部変形の作り方
戦前のタイプライターをUSB化してみる。
テンキーをホットキーに改造する。
工作記事をもっと見る
ブログ:最近やっていること
かっこいい電卓発見。
ノートPCのディスプレイが昇天ス
アルミのロウ付けに挑戦
ブログをもっと見る
ダウンロード
JW_WIN外部変形Kiefer
JW_WIN外部変形FastMeasure
お知らせ
2009/9/4 twitter始めました。
2009/7/14 サイトのタイトル変更しました
2008/4/30 サーバーダウンについて

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

最近やってること:
ŋ߂Ă邱
    follow me on Twitter

    © GRAFZ.NET 2008-2009
    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化)。」はこちらからどうぞ。
    読んで下さってありがとうございます。<(_ _)>