オリジナルキーボードを作ってみる その8「アクティブロー回路への変更」
前回はキーマトリックスを使って2x6のキーボードをつくりました。
ここまで来ればキーを増やすのは簡単です。
ちなみに今回作ろうと思っているキーボードは3×6+5の片手分23キーなので、 その回路は実質4×6の24キーキーボードとほぼ同じですね(Let's Splitも4×6)
実際のキーボードを作る前に、もう一段階、目指すものにに近づけたテスト回路を作りましょう。
前回の記事はこちら。
前回の最後でプログラムはこうなっていたと思います。
#include "Keyboard.h" const int rowNum = 2; const int colNum = 2; const int rowPin[rowNum] = { 3, 4 }; const int colPin[colNum] = { 5, 6 }; const byte keyMap[rowNum][colNum] = { { 0x61, 0x62 }, { 0x63, 0x64 } }; bool currentState[rowNum][colNum]; bool beforeState[rowNum][colNum]; int i,j; void setup() { for( i = 0; i < rowNum; i++){ pinMode(rowPin[i],OUTPUT); } for( i = 0; i < colNum; i++){ pinMode(colPin[i],INPUT); } for( i = 0; i < rowNum; i++){ for( j = 0; j < colNum; j++){ currentState[i][j] = LOW; beforeState[i][j] = LOW; } digitalWrite(rowPin[i],LOW); } Serial.begin(9600); Keyboard.begin(); } void loop() { for( i = 0; i < rowNum; i++){ digitalWrite( rowPin[i], HIGH ); for( j = 0; j < colNum; j++){ currentState[i][j] = digitalRead(colPin[j]); if ( currentState[i][j] != beforeState[i][j] ){ Serial.print("key("); Serial.print(i); Serial.print(","); Serial.print(j); Serial.print(")"); if ( currentState[i][j] == HIGH){ Serial.println(" Push!"); Keyboard.press( keyMap[i][j] ); } else { Serial.println(" Release!"); Keyboard.release( keyMap[i][j] ); } beforeState[i][j] = currentState[i][j]; } } digitalWrite( rowPin[i], LOW ); } }
今回はは2行6列の回路を組んでみます。
プログラムを書き換えるとこうなります。
#include "Keyboard.h" const int rowNum = 2; const int colNum = 6; const int rowPin[rowNum] = { 3, 4 }; const int colPin[colNum] = { 5, 6, 7, 8, 9, 10 }; const byte keyMap[rowNum][colNum] = { { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 }, { 0x67, 0x68, 0x69, 0x70, 0x71, 0x72 } }; bool currentState[rowNum][colNum]; bool beforeState[rowNum][colNum]; int i,j; void setup() { for( i = 0; i < rowNum; i++){ pinMode(rowPin[i],OUTPUT); } for( i = 0; i < colNum; i++){ pinMode(colPin[i],INPUT); } for( i = 0; i < rowNum; i++){ for( j = 0; j < colNum; j++){ currentState[i][j] = LOW; beforeState[i][j] = LOW; } digitalWrite(rowPin[i],LOW); } Serial.begin(9600); Keyboard.begin(); } void loop() { for( i = 0; i < rowNum; i++){ digitalWrite( rowPin[i], HIGH ); for( j = 0; j < colNum; j++){ currentState[i][j] = digitalRead(colPin[j]); if ( currentState[i][j] != beforeState[i][j] ){ Serial.print("key("); Serial.print(i); Serial.print(","); Serial.print(j); Serial.print(")"); if ( currentState[i][j] == HIGH){ Serial.println(" Push!"); Keyboard.press( keyMap[i][j] ); } else { Serial.println(" Release!"); Keyboard.release( keyMap[i][j] ); } beforeState[i][j] = currentState[i][j]; } } digitalWrite( rowPin[i], LOW ); } }
ブレッドボードの回路にもスイッチをあと8個足しましょう。
ちょっとめんどくさいので写真は省きますが、きっと問題なく動きますね。
でもどうでしょう。なんか回路が煩雑な気がします。
これしか無理ならしょうがないけど、もうちょっと簡単にならないかなーって感じがしますね。
なります!Arduinoなら!
ArduinoのINPUTピンについて、現在のpinModeはINPUTですが、これをINPUT_PULLUPに変えれば もっと回路を単純化することができます。
でもそれだけじゃ動かないので、まずここでINPUT_PULLUPとはどういうことかの軽い説明をします。
pinMode INPUT_PULLUPとは?
Arduino入門編の記事を覚えているでしょうか。
記事はこちら。
この中でスイッチの回路を組む際、以下のように書きました。
スイッチを押してない時は都合よくGNDと繋がってくれるように、3番ピンを更に抵抗を使ってGNDとつなぎます。 (この抵抗をプルダウン抵抗といいます。)
今は2×6のキーマトリックスなので、ブレッドボード上には6個のプルダウン抵抗が刺さっていると思います。
プルダウン抵抗と言うのは、スイッチを押した時にHIGHになる回路において、スイッチを押してないときに確実にLOWにするための仕組みです。 ちなみにスイッチを押した時にHIGHになる回路のことを「アクティブハイ」といいます。
アクティブハイに対して、スイッチを押した時にLOWになる回路のことを「アクティブロー」といいます。
アクティブロー回路の場合は、スイッチを押していない時は確実にHIGHになってくれないと困ります。
そのため、抵抗を使って5Vと繋ぎ、この抵抗のことをプルアップ抵抗といいます。
pinModeのINPUT_PULLUPとは、ただのINPUTとは違い、Arduinoの内蔵プルアップ抵抗を使用するという意味です。
これを指定すると外部にプルアップ抵抗を置く必要がなくなります。
個人的な感覚としては、スイッチを押したらHIGH!という気がして、アクティブハイの方が自然に感じます。 そのためここまでアクティブハイで回路を組んできました。
しかし個人の感覚はともかく、いろんな歴史的理由で、世の中のハードウェアにおいてアクティブローはよく使用されています。
INPUT_PULLDOWNはないのにINPUT_PULLUPがあるのはそのような理由からなのかもしれません。
なんにせよ、アクティブハイのままではプルダウン抵抗が必要ですが、アクティブロー回路に変更することで、回路がとても単純化できそうです。
アクティブロー回路へ変更
ではまずプログラムを書き換えましょう。
HIGHだったものがLOWになり、INPUTがINPUT_PULLUPになる感じですね。
#include "Keyboard.h" const int rowNum = 2; const int colNum = 6; const int rowPin[rowNum] = { 3, 4 }; const int colPin[colNum] = { 5, 6, 7, 8, 9, 10 }; const byte keyMap[rowNum][colNum] = { { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 }, { 0x67, 0x68, 0x69, 0x70, 0x71, 0x72 } }; bool currentState[rowNum][colNum]; bool beforeState[rowNum][colNum]; int i,j; void setup() { for( i = 0; i < rowNum; i++){ pinMode(rowPin[i],OUTPUT); } for( i = 0; i < colNum; i++){ pinMode(colPin[i],INPUT_PULLUP); } for( i = 0; i < rowNum; i++){ for( j = 0; j < colNum; j++){ currentState[i][j] = HIGH; beforeState[i][j] = HIGH; } digitalWrite(rowPin[i],HIGH); } Serial.begin(9600); Keyboard.begin(); } void loop() { for( i = 0; i < rowNum; i++){ digitalWrite( rowPin[i], LOW ); for( j = 0; j < colNum; j++){ currentState[i][j] = digitalRead(colPin[j]); if ( currentState[i][j] != beforeState[i][j] ){ Serial.print("key("); Serial.print(i); Serial.print(","); Serial.print(j); Serial.print(")"); if ( currentState[i][j] == LOW){ Serial.println(" Push!"); Keyboard.press( keyMap[i][j] ); } else { Serial.println(" Release!"); Keyboard.release( keyMap[i][j] ); } beforeState[i][j] = currentState[i][j]; } } digitalWrite( rowPin[i], HIGH ); } }
このスケッチをアップロードしてみても動きません…。
なぜでしょう?
ダイオードを逆向きに変更
アクティブロー回路においてスイッチがオフになっている時、 INPUTピンは抵抗を通じてVCCに接続されているので5Vに定位しています。
またOUTPUTピンも普段はHIGHになっているので5Vに定位していて何も電流は流れません。
さて、では例えばRow1をLOWに変更した場合何が起こるでしょうか?
Row1ピンが内部でGNDに接続されるため、0Vに定位します。
キーが何も押されていない場合は回路が繋がっていないため何も起こりませんが、
(1,1)キーを押すとRow1とCol1が接続されますね。
すると、Row1に引っ張られてCol1も0Vに定位することになります。
この状態でdigitalRead(colPin[1])
するとLOWが取得されるわけですね。
ということは電気が流れる方向はColピンからRowピンになるため、 アクティブハイ回路とは逆方向になります。
INPUT_PULLUPからOUTPUT方向に電気が流れるというのは感覚的におかしな感じがするかも知れないですが、 とにかくそんな理由でアクティブロー回路の場合はダイオードの向きが逆になります。
完成
さて、これでアクティブロー回路に変更できたので無駄な抵抗が必要なくなりとてもシンプルな回路になったと思います。
前回の2x2回路でアクティブローに変更した場合は以下のようになると思います。
さて、実はこの状態でもうキーボードが作れます。
軸同士を手配線すれば、あとはこのファームウェアを適当に変更するだけでちゃんと機能するキーボードになるはずです。
右手分をのを参考にしつつやってたら、無駄のない綺麗な配線になってきた。 pic.twitter.com/e9HdCLURSF
— ゆかり@MFT H-14-2 (@eucalyn_) 2017年5月29日
しかし左右分離型キーボードにするためには、左右でキー情報のやり取りをする必要がありますね。
もちろん各々にArduinoを載せてそれぞれを別にPCに繋ぐこともできなくはないのですが、 それだと後々レイヤー機能などを載せた場合に困ります。
なので次回は左右のシリアル通信を実装します。
まとめ記事はこちら。