マイコンで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に文字を表示したいと思います。https://rikeden.net/?p=81センサーなどから値を読み取りたい場合にはこちらも参考にどうぞ。https://rikeden.net/?p=233






貴重な記事ありがとうございます。
アドレスマスク(SSPxMSK)は、
複数スレーブへの同時配信時に使用します。
(アドレスの割り振り方に工夫が必要です)
マスクされたアドレスビットがDon’t careとなり、
マスクされていないアドレスビットのみの一致で
自分への配信とみなすことができるようになります。