引き続き、シリアル通信 I2C について勉強しています。今回は、送られてきたアドレスが自己アドレスと一致したときだけ ACK を返す回路と、受信したデータを出力する回路を作ってみました。
前回は、とりあえず ACK を返すことで、アドレスに続くデータをコントローラが送るようにしています。それ自体は難しいことではなかったのですが、欲を出して高速モードプラス (1MHz) にしてみて、うーん、もうちょっと回路を考えないとなぁ、と感じたのでした。
高速モードプラスはさておき、アドレスとデータをそれぞれどう取り出そうか、ちょっとばかり悩んでしまいました。こちらも、うーん、もっと回路を工夫しないとなぁ、な気分です。
データ受信のタイミングダイヤグラム

図1 は、コントローラ (Arduino) から送られてくる I2C 信号を受信するタイミングダイヤグラムです。クロックをカウントして ACK を送出するまでは、前回と同じです。
スタートコンディションパルス ST から最初のキャリーオーバ CO までの FAD の間がアドレスデータですので、これを検出して自己のアドレスと照合します。アドレスが一致し、RW が 0 (Write モード) ならば ACK を返します。一致しなければ NACK (ACK を返さない) とします。
データはシフトレジスタでパラレルデータに変換します。クロックの初めの 8クロックをシフトレジスタクロックとしてシフトし、最後の 9個目をレジスタクロックとしてストレージレジスタへ出力します。
回路図
各部の回路図です。前回までの回路も再掲してあります。
初期化回路

初期化回路 (図2)、コントローラ回路 (図3) には変更ありません。前回記事を参照ください。
下のスケッチも変更ありません。転送速度は高速モード (400KHz) としています。
実験では、SDA 側のプルアップ抵抗を 1KΩにすることで高速モードプラス (1MHz) にも対応できました。
コントローラのスケッチ
- #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);
- }
- }
通信中検出回路

図4 は、スタートコンディションからストップコンディションまでの間、通信中 BUSY を出力する回路です。基本的には前回と変わりないのですが、NOT と AND で構成していた立ち下がりエッジの検出部を NOR ゲート 74HC02 (U7D) に置き換えました。
カウンタおよび ACK送出回路

図5 は、クロックのカウントを行なうカウンタ回路と、ACK を送出するオープンドレイン回路です。前回と変更ありませんが、今回は否定応答である NACK も利用します。
アドレス検出回路

図6 は今回追加した回路、コントローラから送られてきたペリフェラルのアドレスを検出し、自己のアドレスと一致するかを照合する回路です。一致しないとき NACK は LOW になり、ACK の送出を行ないません。
8回路 Dフリップフロップ U8 (74HC377) は SIPOシフトレジスタを構成しています。ストレージレジスタのないシフトレジスタ (Ex. 74HC164) があればそれでいいのですが、部品箱になかったのでこれを使いました。アドレス 7ビットと RWフラグ 1ビットの 8ビットをパラレル出力し、LED 表示しています。
XOR (74HC86) と OR (74HC32)、NOR (74HC02) の組み合わせ回路は、シフトレジスタから出力された 8ビットデータがアドレス=0x54、RW=0 (0b10101000) であれば、出力 NACK を HIGH にします。
排他的論理和 XOR は入力が不一致ならば 1 を出力します。それをすべて OR しますから、すべてのビットが一致したときだけ 0 が出力されます。が、最後のゲートが NOR なので、NACK は 1 になります。
Dフリップフロップ U9A (74HC74) は、スタートコンディションパルス ST でセットされ、クロックカウンタのキャリーオーバ CO でリセットされる FAD を出力します。FAD はシフトレジスタのクロック入力を有効にして、最初の 8パルス分だけデータを取り出しています。
データ出力回路

図7 は、アドレスに続いて送られてくるデータビットを出力する回路です。
クロック SCK の初めの 8クロックをシフトレジスタ U14 (74HC595) のシフトクロック SRCLK として入力し、データ SDT をシフトします。最後の 9クロック目 (CO が HIGH のとき) はレジスタクロック RCLK に切り替え、データをストレージレジスタへ送ります。
アドレスを受けている FAD が LOW の間は、シフトレジスタを停止します。
なお、電源オン時の初期化は CLR を入れているだけなので不規則に点灯する場合があります。
ブレッドボード
I2C の受信回路の実験を行なったブレッドボードです。
本来はブロックごとに分けて作ることを原則にしているのですが、今回は IC の空き回路を積極的に使っています。そのために配線の行き戻りが多くなっています。まぁ、そのときの気分なんで。
上部の Arduino Nano Every がコントローラです。I2C 信号を受信すると、中央付近の LEDがアドレスと RW の 8ビットを表示します。
アドレスが一致すれば、下部の LEDがデータを表示します。一致しなければ消灯します。
毎度の、こんなんにしてやりました、ってことで。
ちなみに、Chrome なら画像を右クリックして「新しいタブで開く」と原寸にできますので、お好みでどうぞ。
後記
今回は、Arduino をコントローラとして送信される I2C 信号を受信して、LEDに表示する回路を作りました。が、I2C 通信がどんなものなのかを確かめながらでしたので、なんだかツギハギの回路になったなぁと感じています。
これまでシリアル通信について勉強してきました。SPI はコントローラとペリフェラルとでデータを交換していました。UART はデータを送りっぱなしです。
でも I2C は、コマンドで送信か受信かを選択し、データを受け取ったら ACK を返すというやり取りがあります。なので、受信モードも含めた全体の動きを考えて検討する必要があるよなぁ、って。もしロジックIC でなどと酔狂いうなら、もう一度最初から考えなおそうね。考えなおそう、そのうち、もし、気が向いたらね。
論理回路の勉強をやり始めたのは、もう 1年半前になります。
なんだかんだいろいろやってきましたけど、そろそろ論理回路はおしまいにしようかなと思います。でも、まだまだ知らないこと、わからないことだらけですので、また気が向いたらやりますケド。