マイコンで Lチカが出来たら次は LCD に文字を表示したり、色々なセンサーを扱いたいですよね。今回はマイコンと周辺機器の通信によく使われる I2C通信のやり方をちょっとだけ詳しく解説していきます。
目次
I2C通信とは
I2C (IICやI2Cとも) とは Inter-Integrated Circuit の略で、”あい すくえあーど しー”と読みます。普段は面倒なので”あい つー しー”って呼びますけどね。
I2C通信の特徴は、
- SDA (Serial Data) と SCL (Serial Clock) の2本の線だけで通信ができる
- 複数のデバイスを同一の線に接続できる(複数のデバイスがあっても線は2本だけ)
といったところですね。I2C と並んでよく使われる通信方式に SPI がありますが、そちらはデバイスごとに1本ずつ追加で線が必要になるので、ピンの少ないマイコンでは I2C通信の方が便利なことが多いと思います。
通信フォーマット
I2C通信では通信する2者のうち一方がマスター、他方がスレーブとなり、マスターがクロックを生成して通信を制御します。
I2C通信には 7-bitモードと 10-bitモードがあるのですが、一般的に使われるのは 7-bitモードの方なので、今回はそちらで説明していきます。
また、クロックの周波数は規格化されていて 100 kHz か 400 kHz で通信を行います。
通信のフォーマットはこのようになっています。
マスターがスタートコンディションを送信し、その後 7 bits のスレーブアドレスと 1 bit の Read/Write (Read:1 Write:0) の 8 bits を送信したら、スレーブから ACK (Acknowledge) が返ってきます。
その後は 8 bits のデータを送信したら ACK を返信するサイクルを繰り返します。図では 2サイクルしか描いていませんが、もっと連続して通信することも可能です。
受信のときは最終データを受信したら NACK (Not Acknowledge) を返信して最終データであることをスレーブにつたえます。
データの送受信が完了したらマスターがストップコンディションを送信して通信を終了します。
関連レジスタ
PIC で I2C通信をするときには、MSSP (Master Synchronous Serial Port) モジュールを使います。型番によっては MSSP を搭載していないものもあるので買う前にデータシートで確認しましょう。また、MSSP を2つ搭載してあるものもあります。
以下のレジスタの x は MSSP が 1つの場合には無視、2つあるときは 1 か 2 に読み替えてください。
ここでは各ビットの簡単な説明はしますが、使わないところは本当に簡単な説明しかないので、詳しく知りたい方はご自分で調べてください。また、マスターとして使うときの初期化の例も紹介します。
SSPxSTAT
各ビットの説明
SMP (SPI Data Input Sample bit)
1:スルーレート制御無効化(100 kHz) 0:有効化(400 kHz)
CKE (SPI Clock Edge Select)
1:SMBus互換入力有効化 0:無効化
D/A (Data / Adress)
1:受信データ=データ 0:アドレス
P (Stop)
1:ストップコンディション検出 0:未検出
S (Start)
1:スタートコンディション検出 0:未検出
R/W (Read / Write information)
(スレーブの場合)
1:Read受信 0:Write受信
(マスターの場合)
1:送信中 0:レディ
UA (Update Adress)
(10-bitモードのみ)
1:更新必要 0:不要
BF (Buffer Full Status)
1:データ転送中 0:転送完了
MSSP モジュールは SPI通信も可能なので SPI と名前のついているビットもありますが、I2C通信時も使うビットとなっています。
SSPxSTAT (SSP Status) レジスタは通信状態を管理するレジスタですが、マスターの場合は自分で通信を制御するのであまり重要ではありません。
設定が必要なのは SMP のみです。100 kHzで使う場合には 1、400 kHz で使う場合には 0 に設定します。
今回は 100 kHzで使いますので、
としておきます。
また、プログラム中では R/W ビットを確認して送信完了を待つのにも使います。
SSPxCON1
各ビットの説明
WCOL (Write Collision Detect)
1:衝突発生 0:正常
SSPOV (Receive Overflow Indicator)
1:オーバーフロー発生 0:正常
SSPEN (SSP Enable)
1:MSSPモジュール有効化 0:無効化
CKP (Clock Polarity Select)
1:ストレッチオフ 0:オン
SSPM (SSP Mode Select)
1110:I2Cスレーブモード 7ビットアドレス 割り込みあり
1000:I2Cマスターモード
0110:I2Cスレーブモード 7ビットアドレス
※他にもモードはあるが割愛
SSPxCON1 (SSP Control 1) レジスタは MSSP モジュールを制御するレジスタで、ここで設定が必要なのは SSPEN と SSPM の2つです。
SSPEN:1、SSPM:1000 とすれば良いので、
とすれば良いでしょう。
SSPxCON2
各ビットの説明
GCEN (General Call Enable)
1:同胞検出許可 0:禁止
ACKSTAT (Acknowledge Status)
1:ACK未受信 0:受信済み
ACKDT (Acknowledge Data)
返信するACK設定
1:NACK 0:ACK
ACKEN (Acknowledge Sequence Enable)
1:ACKDTビットを返信(返信後自動クリア)
RCEN (Receive Enable)
1:受信許可 0:禁止
PEN (Stop Condition Enable)
1:ストップコンディション送信(送信後自動クリア)
RSEN (Repeated Start Condition)
1:リピートスタートコンディション送信(送信後自動クリア)
SEN (Start Condition)
1:スタートコンディション送信(送信後自動クリア)
SSPxCON2レジスタは実際に通信を制御するときに使用するレジスタで、初期化で設定すべきことはありませんので、
としておきましょう。
SSPxCON3
各ビットの説明
ACKTIM (Acknowledge Time Status)
1:ACK中 0:非ACK中
PCIE (Stop Condition Interrupt Enable)
1:Stop割り込み許可 0:禁止
SCIE (Start Condition Interrupt Enable)
1:Start割り込み許可 0:禁止
BOEN (Buffer Overwrite Enable)
1:バッファ上書き許可 0:禁止
SDAHT (SDA Hold Time Selection)
SDA保持時間
1:300 ns 以上 0:100 ns 以上
SBCDE (Slave Mode Bus Collision Detect Enable)
1:スレーブバス衝突検出有効化 0:無効化
AHEN (Adress Hold Enable)
1:アドレス保持有効化 0:無効化
DHEN (Data Hold Enable)
1:データ保持有効化 0:無効化
SSPxCON3 は主にスレーブのときに使用するレジスタで、マスターのときには特に使用しないので、
としておきます。
SSPxADD
スレーブのとき:
SSPxADD<7:1> の 7 bits にスレーブアドレスを設定することができます。
マスターのとき:
マスターのときはアドレスとは関係がなく、クロックの周波数の設定に使われます。
という計算式ですので、
となります。
SSPxBUF
SSPxBUF は通信に使用するバッファです。ここにデータを書き込めば勝手に送信してくれます。受信したデータもここに格納されます。
SSPxMSK
SSPxMSK はスレーブのときに受け取ったスレーブアドレスにマスクをかけて自分のアドレスと照合できるようです。
正直使い道が分かりませんが…
I2C制御ライブラリ
続いてこれらのレジスタを使って I2C通信を制御するライブラリを作成します。1から作るのも良いですがすでに多くの方がネットでライブラリを公開されているので、それをベースに作ろうと思います。
最後にまとめたものを掲載するので、過程はどうでもいいという方は下の方にスクロールしてください。
参考にしたのはこちらのサイトです。I2C通信について細かく書かれていますが、英語をたくさん読むのは面倒なので下の方にあるサンプルプログラムだけ頂いちゃいましょう。
頂いたプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void I2C_Master_Init(const unsigned long c){ SSPCON = 0b00101000; //SSP Module as Master SSPCON2 = 0; SSPADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSPSTAT = 0; TRISC3 = 1; //Setting as input as given in datasheet TRISC4 = 1; //Setting as input as given in datasheet } void I2C_Master_Wait(){ while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSPBUF = d; //Write data to SSPBUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); RCEN = 1; I2C_Master_Wait(); temp = SSPBUF; //Read data from SSPBUF I2C_Master_Wait(); ACKDT = (a)?0:1; //Acknowledge bit ACKEN = 1; //Acknowledge sequence return temp; } |
プログラムの内容は上記のレジスタの説明を見れば理解できるかと思います。
とても簡潔ですばらしいのですが、このライブラリは PIC16F877A 用に作られているので、自分の環境に合わせてカスタマイズしていきます。
今回は PIC16F18326 で MSSP2 を使います。
変更点
- 2行目 SSPCON → SSPCON1 (これはミスだと思うのですが…)
- 全体 SSP → SSP2
- 5行目 0 → 0x80
- 6,7行目 → 削除 (main関数で初期化する)
- 14行目 SEN → SSP2CON2bits.SEN
- 18行目 RSEN → SSP2CON2bits.RSEN
- 22行目 PEN → SSP2CON2bits.PEN
- 35行目 (a)?0:1 → a
主には MSSP モジュールが 2個あることへの対応です。全体の SSP を SSP2 に変えるのは、エディタの置換機能で全て置換してしまうと良いです。
35行目に関しては、a が 0 なら 1 を代入し、1 なら 0 を代入するというのが、直感的ではないと感じたので a をそのまま代入するようにしました。
これでちゃんと動くと思うのですが、私の環境では動きませんでした…
色々試してみた結果、デフォルトのピンに明示的に割り付けを設定すると上手くいきました。下記プログラムの11~14行目です。
//理由は分からないけどとりあえず動いてるからヨシ
はプログラマーの定番ですよね?この部分の解説はしませんが、もしも私と同様に上手くいかない場合は試してみてください。
以下が完成したライブラリです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include<xc.h> #define _XTAL_FREQ 4000000 void I2C_Master_Init(const unsigned long c){ SSP2CON1 = 0x28; //SSP2 Module as Master SSP2CON2 = 0x00; SSP2CON3 = 0x00; SSP2ADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSP2STAT = 0x80; //SSP2CLKPPS = 0x14; //PPS Settings //SSP2DATPPS = 0x15; //RC4PPS = 0x1a; //RC5PPS = 0x1b; } void I2C_Master_Wait(){ while ((SSP2STAT & 0x04) || (SSP2CON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SSP2CON2bits.SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); SSP2CON2bits.RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); SSP2CON2bits.PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSP2BUF = d; //Write data to SSP2BUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); SSP2CON2bits.RCEN = 1; I2C_Master_Wait(); temp = SSP2BUF; //Read data from SSP2BUF I2C_Master_Wait(); SSP2CON2bits.ACKDT = a; //Acknowledge bit 1:NACK 0:ACK SSP2CON2bits.ACKEN = 1; //Acknowledge sequence return temp; } |
1 2 3 4 5 6 7 |
void I2C_Master_Init(const unsigned long c); void I2C_Master_Wait(); void I2C_Master_Start(); void I2C_Master_RepeatedStart(); void I2C_Master_Stop(); void I2C_Master_Write(unsigned d); unsigned short I2C_Master_Read(unsigned short a); |
i2c.c と i2c.h をプロジェクトにインポートして、i2c.h をインクルードすればライブラリが使えるようになります。よく分からない場合は i2c.c の関数を main.c にコピペしても問題ありません。
ここまで来たら気を付けなければいけないのはあと1つだけです。
変更前のライブラリにも書いてありましたが、SDA と SCL はデジタルで”入力”にしないといけません。
それでは次回はこのライブラリを使って LCD に文字を表示したいと思います。
センサーなどから値を読み取りたい場合にはこちらも参考にどうぞ。
貴重な記事ありがとうございます。
アドレスマスク(SSPxMSK)は、
複数スレーブへの同時配信時に使用します。
(アドレスの割り振り方に工夫が必要です)
マスクされたアドレスビットがDon’t careとなり、
マスクされていないアドレスビットのみの一致で
自分への配信とみなすことができるようになります。