温度センサ(ADT7410)の制御では、液晶モジュールとは違ったI2C通信手順が必要です。その通信手順を中心に説明します。
液晶モジュール制御と異なるところ
温度センサのI2C通信制御は、液晶モジュールのそれと大きく異なるところがあります。
液晶モジュールの制御は、PICマイコンから液晶モジュールに対してデータを送りつけるだけでした。PICマイコンから動作設定のデータを送ったり、表示文字のデータを送ったりという具合に、データの送信はPICマイコンから液晶モジュールの一方向でした。
温度センサ制御の場合、液晶モジュールとは異なります。PICマイコンが温度センサから温度データを取得する場合は、温度センサからPICマイコンにデータを返信してもらう必要があります。このようにI2C通信手順でスレーブからマスターにデータを返信する場合、新しい通信手順が必要になります。
そこでこの記事では、温度センサのI2C通信手順について、以下の項目に分けて説明します。
- 動作設定の通信手順
温度センサの動作設定のI2C通信手順は、液晶モジュールの設定方法とほぼ同じです。 - 温度データ取得の通信手順
スレーブからマスターにデータを返信する必要があります。この場合、今まで出てきた通信手順に加え、新しい通信手順が必要になります。
(1) 動作設定のI2C通信手順
温度センサ(ADT7410)の動作設定を行う場合、動作設定のレジスタ(アドレス0x03)にデータ(1バイト)を書き込むことになります。
0x03に動作設定データを書き込む手順は以下のようになります。
動作設定のI2C通信手順は液晶モジュールを制御した時とだいたい流れは同じです。以下にその流れを説明します。
最初に、I2C通信を開始するために、マスターはスタートコンディションを生成します。
次にマスターは、スレーブアドレス7ビット+読み書き指定1ビットの8ビットを送信します。スレーブアドレスは0x48です。また動作設定を行うための書き込みですので、「スレーブアドレス+読み書き設定」は以下のように0x90になります。
マスターがこの1バイトデータを送信した後、スレーブはACKを返信します。
次にマスターは書き込み先のレジスタのアドレスを送信します。動作設定のアドレスは0x03ですので、0x03を送信します。マスターが0x03を送信した後、スレーブはACKを返信します。
続いてマスターは動作設定の1バイトデータを送信します。この1バイトの中身については次回の記事で詳細に説明します。
マスターが1バイトの動作設定データを送信した後、スレーブはACKを返信します。
これで、0x03に設定データが書き込まれましたので、マスターはI2C通信を終了するためにストップコンディションを生成します。
この通信手順において、いずれの動作も液晶モジュール制御プログラムの時のI2C通信関数が使用できますので、温度センサの動作設定プログラムはそれらの関数を使用して作成します。
温度データ取得のI2C通信手順
繰り返しになりますが、液晶モジュールの制御では、マスター(PICマイコン)からスレーブ(液晶モジュールや温度センサ)にデータを送るだけでした。スレーブが生成するACK/NACK信号は別にして、スレーブからマスターへのデータの流れはありませんでした。ところが温度データを取得する場合、温度センサからPICマイコンへデータが送信されることになります。
このようなスレーブからマスターへのデータ送信手順は初めて出てきますので、最初に概要を説明します。
温度センサが測定した温度データは、温度センサのメモリマップのアドレス0x00と0x01に格納されています。この0x00と0x01のデータを読むには、人間に近いやり取りで表現すると以下のようになります。
- マスター(PICマイコン)からスレーブ(温度センサ)に「0x00番地からデータを送って!」と伝える
- スレーブがマスターに0x00番地に格納されている1バイトデータを送る
- 続いてスレーブがマスターに0x01番地に格納されている1バイトデータを送る
(1)でマスターがスレーブに対して読み取るアドレスを指定すると、(2)ではそのアドレスのデータ、(3)ではその次のアドレスのデータ、というようにスレーブは指定されたアドレスから順次データをマスターに返信します。
次に、このやり取りをI2C通信に近いやり取りで表現すると以下のようになります。
- マスターからスレーブに、読み取るデータの開始アドレスである「0x00」を書き込む
- マスターが、スレーブから0x00番地のデータに格納されているを読み取る
- マスターが、スレーブから0x01番地に格納されているデータを読み取る
このやり取りが、実際のI2C通信手順ではどうなるか考えてみます。I2C通信手順では、スタートコンディション生成の直後に、必ずスレーブアドレス(7ビット)と読み書き指定(1ビット)を送信します。上の手順で(1)はマスターからスレーブにデータを書き込むわけですから、この読み書き指定の1ビットは「書き込み(0)」となります。
一方、(2)と(3)ではマスターがスレーブからデータを読み取っています。(2)と(3)は読み書き指定の1ビットは「読み取り(1)」になるはずですが、(1)で書き込み指定していますので、矛盾が出てきてしまいます。
そこでこのような矛盾がないように、このようなケースでは、(1)と(2)の間で再度スタートコンディションを発行して通信の仕切り直しをします。
以下は、上の(1)から(3)のやり取りのI2C通信手順詳細について、マスターとスレーブ間でのやり取りを図解したものです。
通常のI2C通信では、スタートコンディションから始まりストップコンディションで終わります。
一方、上のやり取りのように書き込みと読み取りが混在する場合、読み取りの通信と書き込みの通信の間に、再度スタートコンディションを発行します。そのスタートコンディションで読み取り指定を行い、実際にデータの読み取りを行って、最後のストップコンディション生成で終わります。
このように、通信途中で生成するスタートコンディションのことを「リピートスタートコンディション」(スタートコンディションの繰り返し)と呼びます。リピートスタートコンディションといっても、SCL/SDA信号はスタートコンディションと同じです。
ただ、PICマイコンのI2Cモジュールは内部処理の関係でリピートスタートコンディションの生成はスタートコンディションの生成とは別に用意されています。そのため、リピートスタートコンディション用のI2C通信関数を作成しておくことにします。
また、読み取り通信では、データはスレーブからマスターに送信されますので、データ受信の状態(データが正しく受け取れたか)をマスターからスレーブに返信する必要があります。マスターからスレーブにACK/NACK信号を返信する処理も必要ですので、マスターのACK/NACK返信用のI2C通信関数も作成します。
ということで上の通信を「リピートスタートコンディション」に修正すると以下のようになります。
なお、注意点があります。読み取り通信の最後にマスターからスレーブにACKではなくNACKを返信します。これはデータの読み取りが最後であることを意味しています。
最後にデータシートのI2C通信手順を確認しましょう。以下の図はデータシートの記載されている、温度データを読み取るI2C通信手順です。今までの説明と合わせて、納得できるまで通信手順を追ってみてください。
このデータシートではストップコンディションがありませんが、おそらく書き忘れだと思います。
関数の作成
I2C通信のデータ読み取りでは、以下の新しい処理が必要です。そのためこれらの関数を作成しておきます。
- リピートスタートコンディション生成
- マスターのスレーブからのデータ受信
- マスターからスレーブへのACKまたはNACK返信
(1) リピートスタートコンディション生成
リピートスタートコンディションを生成するには、SSP1CON2レジスタのRSENビットを1にします。RSENに1を設定すると、(勝手に)PICマイコンがSCL信号とSDA信号を制御してスタートコンディションを生成してくれます。
ただし、RSENを1にしてからPICマイコンがスタートコンディションの生成を終えるまで、待ってあげる必要があります(待たずに割り込みで処理する方法もありますが、処理が難しくなるので実践編では待つことにします)。PICマイコンはスタートコンディションの生成を終えると、「SSP1IF」というレジスタを1にしてくれます。
スタートコンディションを生成するには以下の手順になります。
- SSP1IFを0にしておく
- SSP1CON2のRSENを1にする
- SSP1IFが1になるのを待つ(=PICマイコンがスタートコンディションの生成を終えるのを待つ)
- SSP1IFを0に戻しておく
これをプログラムにすると以下のようになります。なおこの関数は引数、返り値はありません。
// リピートスタートコンディション生成
void i2cProtocolRepeatStart() {
SSP1IF = 0;
SSP1CON2bits.RSEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return;
}
(2) マスターのスレーブからのデータ受信
マスターがスレーブから1バイトのデータを受信するには以下の手順になります。
- SSP1IFを0にしておく
- SSP1CON2のRCENを1にする
- SSP1IFが1になるのを待つ(=PICマイコンが1バイトのデータ受信を終えるのを待つ)
- SSP1IFを0に戻しておく
- この時点で受信したデータはSSP1BUFレジスタに格納されている
これをプログラムにすると以下のようになります。なおこの関数は引数はなく、返り値は受信した1バイトデータです。
// 1バイトデータ受信
uint8_t i2cProtocolReceiveData() {
SSP1IF = 0;
SSP1CON2bits.RCEN = 1;
while (SSP1IF == 0) {}
SSP1IF = 0;
return SSP1BUF;
}
(3) マスターからスレーブへのACK/NACK返信
マスターからスレーブに対してACK信号を生成するには、SSP1CON2レジスタのACKDTとACKENを使用します。ACKDT(Ack Data)はACKかNACKかを指定します。ACKの場合は0、NACKの場合は1をセットします。ACKDTにACKかNACKかを指定したら、ACKEN(Ack Enable)に1をセットすると、PICマイコンのI2C通信モジュールがACK/NACK信号を生成してくれます。以下はACK/NACK信号を生成する手順になります。
- SSP1CON2レジスタのACKDTにACKの場合は0、NACKの場合は1をセットする
- SSP1CON2レジスタのACKENを1にセットする
- SSP1IFが1になるのを待つ(=PICマイコンがスタートコンディションの生成を終えるのを待つ)
関数の引数をACKかNACKにして1つの関数で対応する方法がありますが、ACK返信とNACK返信の2つの関数にしました。特に深い意味はありません。
// Ack送信
void i2cProtocolSendAck() {
// ACKDTにACKをセット(負論理なので0を設定)
SSP1CON2bits.ACKDT = 0;
// NACK信号生成
SSP1CON2bits.ACKEN = 1;
while (SSP1CON2bits.ACKEN) {}
return;
}
// Nack送信
void i2cProtocolSendNack() {
// ACKDTにNACKをセット(負論理なので1を設定)
SSP1CON2bits.ACKDT = 1;
// NACK信号生成
SSP1CON2bits.ACKEN = 1;
while (SSP1CON2bits.ACKEN) {}
return;
}
更新履歴
日付 | 内容 |
---|---|
2018.8.12 | 新規投稿 |
返信ありがとうございます
概ね理解しました
お陰で基礎的な事は分かって来たので
無駄に買ってしまった半導体で色々と独自に
実験していこうかと思います
お世話になります
リピートスタートコンディションでスレーブ側が(クロック)信号を受け取り
仕切り直し(リセット)するという事ですが
もしやと思いスタートコンディションでも出来るんではと思ったら
案の定問題なく動作しました
これはデーターが多くなった場合等の為に、リセットして誤作動が起こらない様にする為の仕様という事ですかね?
ご質問どうもありがとうございます。
すみません、記事内の説明が悪かったです。
リピートスタートコンディションはクロックのリセットという意味合いではなく、通信路を占有して通信を継続する、という意味になります。プログラムは1対1で通信していますので、リピートスタートコンディションでなく、スタートコンディションでも動作します。
なお、受信側が受信データが処理できずに、マスターに対して送信を待って欲しいときのために、I2C通信では「クロックストレッチ」という機能があります。これは、スレーブ側がマスター側に、データ通信をちょっと待って、という信号を送る機能です。具体的にはスレーブ側がクロック信号をLOWにします。