公開日:2019/12/07
title.png

8th Season


#8-7 I/Oエクスパンダを使う

 Arduino UNOの場合、I/Oポートは全部で19本しかありません。その内、2本はUSB⇔UART変換チップ(ATmega16U2)との通信用UARTに使われてしまっています。8本のデータバスに、4本くらいストローブ信号が取られると、もう14本を消費してしまうので、カツカツになります。
 つまり何を言いたいかと言うと、I/Oポートは貴重なんです。

 「#2-10 ミニキーボード付きゲームパッド」の時に使用した、4×4マトリクスキーパッド(以下4×4キーパッドと呼びます)は、一気に16個のスイッチを扱えて、とても良いのですが、I/Oポートを8本も消費してしまうのが、困ったちゃんでした(下図)。

ar2a_keypad.jpg

 今回は、I/Oエクスパンダ(拡張I/Oポート)と言うモジュールを使って、この4×4キーパッドを制御してみます。

LCD専用?I/Oエクスパンダモジュール

 そこで購入したのが、以下図の様な、I/Oエクスパンダと呼ばれるモジュールです。

ar87_lcdc.jpg

 このモジュールには、PCF8574というI/Oエクスパンダチップが搭載されています。

  PCF8574のデータシートを検索する(別ウインドウが開きます)

※購入したモジュールに搭載されていたのは、マークからNXP社のPCF8574でした。検索してみたところ、TI社のチップもあるみたいですね。

 データシート(NXP社を参照しました)から読み取った特徴は以下の通りです。

  PCF8574の特徴:

 I2Cなので、Arduino UNOのI/Oポート2本だけ(SCL,SDA端子)を使って通信すると、一気に8本のI/Oポートを拡張できるのです。

 と、ここまでは、PCF8574チップ単体の仕様でした。問題は、購入したI/Oエクスパンダモジュールの仕様です。実はこのモジュール、LCD1602(下図)の制御に特化したモジュールだったのです(よって、以下、LCDCモジュールと呼びます)。

ar31_lcdout.jpg

 PCF8574の8本のI/Oポートの内、1つのポート(P3)が、LCD1602のバックライトLED制御用として、NPNトランジスタを駆動してしまっており、このポートのみ、所謂、I/Oポートとしては使用できない様になっています。
 しかも、PCF8574は8ポートのI/Oエクスパンダなのに、モジュールのピンアウトは16本もあります。このピンアウトは、LCD1602のピンアウトと、ピン配置が合わせられており、必要なピンに8本のポートが分散して割り当てられているのです。
 要は、このモジュールとLCD1602をストレートに接続すると、直接LCD1602を制御できるようになっているんですね(下図)。

ar87_lcdctl.jpg

※LCDCモジュールの1ピンマーク側にLCD1602の16ピンが対応していますが、この方向(並び)で正しく接続できていますので、ご注意ください。

 上図の位置関係で、ブレッドボードを介して、LCDCモジュールとLCD1602がストレートに接続されており、そのまま、制御することができます。この組み合わせなら、「Arduino IDE ⇒ スケッチメニュー ⇒ ライブラリをインクルード ⇒ ライブラリを管理」から、「LCD+PCF8574」辺りで検索するとライブラリが見つかります。ライブラリをインストールすれば、普通にLCD1602へ表示可能となります。
 写真左上側に引き出されている4本のピンヘッダだけで、電源と制御信号線が接続できて、LCD1602を制御できるので、お手軽に使えると思います。

4×4キーパッドをセンスする

 明らかに、LCD1602を制御するためのモジュールだったのですが、筆者は、「#3-3 I2C接続 LCD表示モジュール」で紹介した通り、LCD1602を「LCDサブシステム」として使っているので、LCD1602制御用としては、全く必要のないモジュールでしたw(1x🐽x)w。
 ただの「I/Oエクスパンダ」だと思って、間違って購入してしまったモジュールなのだけど、無駄にするのはもったいないので、何とか、I/Oエクスパンダとして使って、4×4キーパッドを制御してみたいと思います。

※今回間違って購入してしまったLCD1602制御用タイプとは別に、純然たる「I/Oエクスパンダ」として動作するモジュールも販売されているので、皆様は、ちゃんと用途によって買い分けましょう(1^_^;)。

 ちょっと復習です。「#2-10 ミニキーボード付きゲームパッド」にて、4×4キーパッドを制御した時には、縦に走った列線をプルダウンして、キースキャンしました(回路図を参照する)。PCF8574の場合には、ポートをHIGH出力に設定した時にweakプルアップされているので、これをうまく使うと、プルダウン抵抗を省略できます。
 ただし、NPNトランジスタを駆動している、PCF8574のP3端子だけは、入力ポートとしては使用できません。出力で使うにも、外付けのプルアップ抵抗が必要となります。プログラムを組むときにも、NPNトランジスタを通る分、論理が逆転することに注意が必要です。

 これらの点に注意して作成した回路図は以下の様になります。

ar87_schem.png

 青破線(縦線)より左側は、LCDCモジュール基板上の回路(の一部、関係する所のみ記載)、右側の青破線囲み内は4×4キーパッド内の回路なので、外付けに構成が必要なのは、K(P3)端子のプルアップ抵抗R1のみです。そこ以外は、図の通りに配線だけ繋いでください。

※PCF8574チップを搭載した、純然たるI/Oエクスパンダモジュールであれば、外付け回路無しで、4×4キーパッドを制御できるところでした。惜しい。

プログラムの設計

 PCF8574は、I2Cで通信します。購入したLCDCモジュールは、基板上で、I2Cアドレスを決定する端子がすべてプルアップされているので、デフォルトの状態では、スレーブアドレス0x27でアクセスできます。各アドレスピンをGNDへショート可能なパタンが基板上に配置されているので、複数のモジュールを同時にI2Cバスに接続する場合には、このジャンパパタンでアドレスの変更が可能です(動作は未確認)。

 さて、LCDCモジュールには、I2C経由で、8ビットのデータをリード/ライトすることができます。

 従って、Wireライブラリを使って、I2Cスレーブアドレス0x27へのリード/ライトができれば、簡単に制御することができます。I2Cのプログラムについては、これまでに何度か解説していますので、逆引き索引の「I2C通信」から記事をあたってみてください。

 4×4キーパッド横方向の線の内、1本だけをLOWにドライブして、他の3本はHIGHにしておきます。その状態で、縦線の信号レベルを読み取ると、スイッチが押されたラインのみLOWが読み出せることで、押されたキーがわかる仕組みです(読み取り方法については、「#2-10 ミニキーボード付きゲームパッド」にて解説済みです)。

  // drive low to p[idx], read p7~p4
  Wire.beginTransmission (i2c_PCF8574);
  Wire.write (i2c_wrdat[idx]);
  errcod=Wire.endTransmission ();
  if (errcod>0)
  {
    // ERROR:
    return (ErrI2cAccess);
  }
  Wire.requestFrom (i2c_PCF8574,1);
  if (Wire.available ()<=0)
  {
    // ERROR:
    return (ErrI2cAccess);
  }
  dat=Wire.read ();

  // key status
  if ((dat&0x80)==0)
    kpad[ cnt++ ]=dtmf_code[idx*4 + 3];
  if ((dat&0x40)==0)
    kpad[ cnt++ ]=dtmf_code[idx*4 + 2];
  if ((dat&0x20)==0)
    kpad[ cnt++ ]=dtmf_code[idx*4 + 1];
  if ((dat&0x10)==0)
    kpad[ cnt++ ]=dtmf_code[idx*4 + 0];
  if (cnt>2)
  {
    // WARNING: detect too many keys pushed
    kpad[0]=0;
    kpad[1]=0;
    break;
  }


 この構成のマトリクスキーでは、3キー以上が同時に押された場合、押していないキーが押されたように誤判定される場合があるため、3キー以上が押された場合に、キーが押されていないように偽装する判定が最後に入っています(この誤判定についても、「#2-10 ミニキーボード付きゲームパッド」にて解説済みです)。

DTMF配列だったね

 そういえば、4×4キーパッドのキー配置がDTMF配列だと気が付きました。そこで、「#8-4 電話をかける」で作ったDTMF発信プログラム(play_dtmf.ino)に組み込んで、キーを押すと、対応するDTMFを鳴らすようにして見ました。……使い道があるかどうかわからないけど、ホントに電話機みたい(1^ー^)。

 他に、DTMFの回路とプログラムを応用して、何か作れないか、と考えたところ、良いアイディアが浮かびました。DTMFは、2つのトーンを同時に鳴らせる様に回路とプログラムを構成してあったので、これを利用して、「電子オルガン」風に仕上げてみました。4×4キーパッドへの音階の割り当てを以下に示します。

4×4キーパッド   音階の割り当て
1 2 3 A  ⇒  B3 D#4 E4 B4
4 5 6 B C#4 D4 A4 A#4
7 8 9 C C4 G4 G#4 D5
* 0 # D F4 F#4 C5 C#5

※アルファベットは音階、数字はオクターブ、A4が440Hz ラ音です。

 オルガン(ピアノ)の鍵盤配列とはかけ離れているのですが、「#」付きが黒鍵、それ以外が白鍵なので、45°時計回りに回転してもらうと、なんとなく、良さそうな配列になるように意識してあります。

ar87_keybd.jpgar87_key.png

 DTMFの時と同様に、割り当てた音階に対する、PWM周期を計算しておきます。

音階 周波数[Hz] OCRxA+1
シ3(B3) 247 506
ド4(C4) 262 478
ド#4(C#4) 277 451
レ4(D4) 294 426
レ#4(D#4) 311 402
ミ4(E4) 330 379
ファ4(F4) 349 358
ファ#4(F#4) 370 338
ソ4(G4) 392 319
ソ#4(G#4) 415 301
ラ4(A4) 440 284
ラ#4(A#4) 466 268
シ4(B4) 494 253
ド5(C5) 523 239
ド#5(C#5) 554 225
レ5(D5) 587 213

※音階と周波数の関係については「鍵盤ハモニカで演奏する」を参照してください。
※PWM周期とタイマの設定については「#8-4 電話をかける」を参照してください。


 OCRxAレジスタの設定値で、赤文字にしたところは、8ビットを超過してしまったので、TC1(16ビットタイマ)には、そのまま適用可能ですが、TC2(8ビットタイマ)には使えません。そこで、電子オルガンモードでは、TC2のカウントクロックをfcpu/64から一段落としてfcpu/128に変更し、上表のOCRxAの値を1/2にしたものをOCR2Aレジスタに設定します。

演奏してみよう

 まずは、完成版プログラムをリンクしておきます。

  完成版プログラム:play_dtmf.ino

 「#8-4 電話をかける」で作成したプログラムをアップデートしてあります。「#8-4 電話をかける」で作ったDTMF出力回路(以下再掲)と、上で示した4×4キーパッド回路をArduino UNOで構成しておいてください。

ar84_schem.png

※高群と低群に分かれていて、鳴らし分けられたDTMFと異なり、電子オルガンモードでは、TC1(D9端子)でも、TC2(D11端子)でも、あらゆる音階が演奏されることになります。時定数CRによって、TC1(D9)側のLPFの方が、VP-P(従って音量)が大きく設定されている(DTMF前提だった)ので、不自然に感じた場合は、時定数を調整してください。

 プログラム(play_dtmf.ino)をコンパイル&書き込みすると、シリアルモニタから以下のコマンドを受け付けるようになっています。

 実験中の様子です(下図)。

ar87_expr.jpg

※アンプ内蔵スピーカへ出力して、実験しました。出力先はイヤホン等でもOKです。


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【mode,1】
  OK: organ 2 key mode

 DTMFの実験は、「#8-4 電話をかける」で実施済みなので、早速、電子オルガンモードにして、演奏してみましょう。



 電子オルガンモード1(2キーモード)で、調律に使う440Hzラ音と、ドレミの音階を演奏後、電子オルガンモード2(2トーンモード)に切り替えて、きらきら星を演奏してみました。

今回のお買い物

 今回のお買い物は、以下の通りです。


 「和音」で調べてみると、三和音とか、四和音とかが出てきます。2音だと、「和音」とは言わないのかもしれませんね。Arduinoで3音を同時に鳴らそうとすると、タイマがもう一本必要になります。UNO(ATmega328)だと、後はシステムタイマ(TC0)を使うしかなくて、非現実的ですが、タイマ数に余裕のあるSTM32とかATmega32U4(Micro等)なら、三和音の演奏も可能かもしれません(詳細未検討)。


btn_arduino.png

btn_prev.png btn_next.png

home