引き続き、シリアル通信 I2C について勉強しています。今回は、Arduino Nano Every が I2C で送ってきたアドレスに対して ACK (肯定応答) を返し、次のデータを送信するようにしてみました。
前回は、Arduino Nano Every からライブラリ Wire を使って I2C で送られてくる信号波形を確認しました。結果、寄生容量のためにパルスの立ち上がりがかなりなまっていて、適当な外部プルアップ抵抗が必要になることがわかりました。
ところで、前回は有効なペリフェラルが接続されておらず、アドレス信号を受信しても ACK を返さないので、次のデータが送られてきません。そこで今回は、ACK を返す回路を作ってみることにします。
ACK送出のタイミングダイヤグラム
図1 に、ACKを送出するタイミングダイヤグラムを示します。
SCL および SDA (Controller) は、Arduino が Wire によって送ってくるクロックとデータです。I2C 信号の特徴は、クロック SCL が LOW の間にデータ SDA が変化する、クロックの立ち上がりエッジや立ち下がりエッジではない、というところです。
SDA (Peripheral) は、今回作った回路が返送する ACK です。ところがこれはクロック SCL の立ち下がりエッジで出力されており、送られてくるデータの LSB の一部を食ってしまっています。送られてくるデータと同様に出力しようとすると、90度位相のずれたトリガが必要になってしまうため、ちょっと面倒です。そのために、立ち下がりエッジで出力することにしました。もちろん、I2C の特性上、SCL が HIGH のときにデータを変化させてはいけませんが、クロックの立ち下がりエッジでなら、問題はないようです。
スタートコンディションからストップコンディションまで、通信中として BUSY を出力しています。
LD はカウンタの初期値ロード信号です。SCL の最初の立ち上がりで LD を HIGH にすることで、クロックのカウント動作を開始します。カウントはクロックの立ち下がりエッジで行います。カウンタは初期値を 0x7 とし、0xF までカウントしてキャリーオーバー CO を出力します。CO は LD を LOW にしてカウント動作を停止、初期値 0x7 をロードして待機します。
また、CO により FET をドライブし、オープンドレインで ACK を出力します。
データ受信後に SCL が HIGH に戻り LD がセットされてしまうので、ストップコンディションでリセットしています。全体の動作には影響ありません。
タイミングダイヤグラムはアドレスとデータ 1個だけを描いていますが、9クロック毎に ACK を返すだけなので、データが複数あっても受信できます。まぁ受信してもバッファがないので捨てられるだけなんですけどね。
回路図
Arduino Nano Every から I2C 信号を出力し、それを受信して ACK を返す回路です。
初期化回路
図2 は、毎度の初期化回路です。
R1、C1 のタイマにより、電源オン時 100ms の LOW を出力します。これによりフリップフロップやカウンタを初期化します。
R2 はシュミットトリガインバータ 74HC14 の保護抵抗です。
コントローラ (Arduino) 回路
図3 は、Arduino Nano Every によるコントローラです。ライブラリ Wire は内部プルアップを有効にしていますが、ピンの寄生容量により出力波形がなまってしまいますので、外部プルアップ抵抗 4.7KΩをつけて影響を小さくしています。
外部プルアップ抵抗は、できれば波形を確認して選定してください。値が小さいほうが立ち上がりが速くなりますが、むやみに小さくしても効果はなく、シンク電流が大きくなってしまうだけです。
ちなみに、内部プルアップを無効にする必要はありません。
ダイオード D1 は、電源の逆流防止用です。Arduino の電源は 9~12V が良いでしょう。
通信中検出回路
図4 は、通信中 BUSY の検出を行なう回路です。先頭のシュミットトリガインバータ 74HC14 で入力波形の整形を行なっています。
U2D (74HC08) がデータ SDT の立ち下がりエッジを、U2A (74HC08) が立ち上がりエッジをそれぞれ検出しています。これをクロック SCK と AND することでスタートコンディション ST とストップコンディション SP を取り出し、フリップフロップ 74HC74 をセットして BUSY としています。
ちなみに、U2D、U3B、U3E は NORゲートに置き換えでき、次の回路で使っている U7 (74HC02) の空き回路が使えます。各部分を順番に作っていたのでそのままにしてありますが、これから組むなら変更するとよいでしょう。IC の数が減らせます。
カウンタおよび ACK送出回路
図5 は、クロックのカウントを行なうカウンタ回路と、ACK を送出するオープンドレイン回路です。
カウンタは、これも毎度の 4ビット同期カウンタ 74HC161A を使いました。クロック SCK の立ち上がりエッジでフリップフロップ 74HC74 をセット、ロード信号 LD を HIGH にしてカウント動作を開始します。カウンタの初期値は 0x7 です。SCK の立ち下がりエッジでカウントし、0xF でキャリーオーバー CO を出力、これによりフリップフロップをリセットしてカウント動作を停止します。 BUSY が HIGH の場合はカウントを行いません。
CO により FET 2N7000 のゲートをドライブし、オープンドレインとして ACK を出力します。
U5C (74HC08) は NACK (否定応答) の場合に ACK 出力を止めるための入力です。今回は使っていませんので、NACK は +5V につないでおいてください。
コントローラのスケッチ
Arduino Nano Every の I2C コントローラのスケッチです。前回のスケッチとほぼ同じですが、通信速度を高速モード (400KHz) にしています。今回作った回路は高速モードでも十分に動作します。
ペリフェラルアドレスは 0x54、送信するデータは 8ビットのアップカウントデータにしてみました。
- #include <Wire.h>
- void setup() {
- Wire.begin();
- Wire.setClock(400000);
- }
- void loop() {
- static byte address = 0x54;
- static byte data = 0x00;
- for(data = 0x00; data <= 0xFF; data++) {
- Wire.beginTransmission(address);
- Wire.write(data);
- Wire.endTransmission();
- delay(100);
- }
- }
Arduino Nano Every は高速モードプラス (1MHz) にも対応していますが、その場合は外部プルアップ抵抗を調整して入力波形をもう少し整えてやる必要があります。実験では、SDA 側のプルアップ抵抗を 1KΩ にすることで対応できました。SCL 側は 4.7KΩのままです。詳しく確かめていませんが、こちらも小さくすると Arduino の動作が不安定になるような感じです。
もちろん、回路全体を高速モードプラスに対応できるように検討しなおすことも必要だと思います。
出力波形
図6 は、整形後の I2C 信号波形です。伝送速度は高速モード (400KHz) です。
最初のデータ 7ビットがアドレスで 0x54 となっています。8ビット目 RW は 0 で write モードです。
9ビット目が ACKで、これはペリフェラルが返送しているビットです。
ACK を返したので、次のデータが送られてきています。アップカウントデータで、ここでは 0x1D になっています。LSB が次の ACK に食われていることがわかりますね。
後記
今回は、I2C コントローラ (Arduino) から受けた信号に ACK を返す回路を作り、アドレスのあとに送られてくるデータを受信できるようにしました。
受信すること自体は難しいことではなかったのですが、欲をだして高速モードプラス (1MHz) に対応させようとすると、もう少し回路を考えないといけないなぁって感じです。なにより、送られてくる信号がなまっているときにどう受け取るか、今後の課題です。
さてと、とりあえず信号を受信することができましたので、今度はアドレスとデータを取り出してみましょう。そうだ、自分のと異なるアドレスが送られてきたとき NACK を返す (ACK を返さない) ようにしないといけませんね。それも考えましょう。