PIC18F27Q43で、I2C16ビットIOエキスパンダーMCP23017を使うときの仕様と、I2C,1バイト読込のコードの要点をまとめました。
MCP23017のデータシートから、必要なところをピックアップしました。
・PCF8574( Texas Instruments)というI2Cデバイスの8ビットIOエキスパンダーが販売終了。
・代わりにこの16ビットIOエキスパンダーMCP23017(I2C)が、入手しやすいのでその使い方をまとめておきます。
・Q43のI2Cモジュールの読み取りは、特殊です。MSSPモジュールだと、すべてのシーケンスをコード上で、把握できたのですが、Q43の場合、バイトカウンタにあわせて、半自動化になったことが、 難易度を高くしている感じがします。
・オシロ、または、ロジックアナライザーで波形を確認しながらの作業が、効率的だと思われます。
まず、1バイト読込の時に必要なMCP23017の仕様をざっと確認しておきます。
1.MCP23017 端子機能
2.MCP23017 Client(Slave)addressの設定
3.MCP23017 コントロールレジスタ
MCP23017のレジスタアドレスは、2種類のアドレステーブルがあります。IOCONレジスタのBANKビットで選択します。
・IOCON.BANK=0 のとき、PORTAとPORTB関連レジスタは、交互に連続して配置されます。
・IOCON.BANK=1 のとき、0番から0Aまでが、PORTA関連レジスタに割り当てられて、0x10から0x1Aまでは、PORTB関連レジスタが割り当てられます。
・パワーオンリセット時、IOCON.BANKビットは0なので、レジスタは交互に並んでいます。
※今回は、MCP23017の初期化の際に、BANKを切り替えてBANK1のアドレステーブルで、ポートを操作しています。
4.MCP23017 I2Cプロトコル
write operation 書き込み
シーケンシャルにレジスタアクセスできるのですが、基本的に1バイト通信で事足りると思います。
<I2C1バイト書き込み>
スタート→Clientアドレス+Wビット送信→Ack(Clinet)→レジスタアドレス送信→Ack(Clinet)→書き込みデータ送信→Ack(Clinet)→ストップ
標準的なバイト書込みシーケンスです。
read opreration 読み込み
今回は、設定を確認するだけなので、1バイト読込だけをコーディングしました。
<I2C 1バイト読込>
スタート→Clientアドレス+Wビット送信→Ack(Clinet)→リスタート→Clientアドレス+Rビット送信→Ack(Clinet)→1バイトデータ受信→Nack(PICマイコン)→ストップ
これも、標準的な1バイト読込のシーケンスです。
MCP23017のI2Cは、標準的なプロトコルなので、たたき台にちょうどいいはずです。
5.PIC18F27Q43 I2C1モジュール 2バイト書込みと1バイト読込のコード
I2C1モジュール 2バイト書込みと、1バイト読込のコードです。MCP23017は、レジスタアドレスを指定して、書き込み、読込を行います。2バイト書込みと1バイト読込ができれば、MCP23017を制御できます。エラー処理は、全く考えていませんので、通信がずっこけたら、フリーズします。まずは、正常系の動作が完成した後に、エラー処理を組み込んだほうが、理解が速いと思います。
#ifndef Q_I2C1_H
#define Q_I2C1_H
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "Q_peripheral27Q43.h"
extern void I2C1_Init(void);
extern bool I2C1_b1Write(uint8_t _deviceAdd, uint8_t _data1);
extern uint8_t I2C1_b2Write(uint8_t _deviceAdd, uint8_t _data1, uint8_t _data2);
extern uint8_t I2C1_b1Read(uint8_t _deviceAdd,uint8_t _address);
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* Q_I2C1_H */
#include <pic18f27q43.h>
#include "Q_I2C1.h"
uint8_t i2c1Cnt;
//*************************************************//
//** I2C1初期化
//*************************************************//
void I2C1_Init(void)
{
//Set Clear Buffer Flag
I2C1STAT1bits.CLRBF = 1;//I2Cバッファ、RXBF、TXBEビットクリア
I2C1CON0bits.MODE=0b100;//Host mode, 7bits address
I2C1CON1bits.ACKCNT=1; //最終ACK確認で返す値0:ACK 1:NACK
I2C1CON2bits.ABD=0; //I2C1ADB1をアドレスバッファとして使用
I2C1CON2bits.BFRET=0b00;//8clocksスタートになる準備パルス数
I2C1CLK=0b0011; //MFINTOSC500kHz//I2C用クロックソース
I2C1PIR=0x00; //I2C1専用割り込みフラグのクリア
I2C1CON0bits.EN=1; //I2C1モジュール有効
}
//*************************************************//
//** I2C1 1バイト送信
//*************************************************//
bool I2C1_b1Write(uint8_t _deviceAdd, uint8_t _data1)
{
I2C1ADB1 = _deviceAdd; //アドレスバッファにClientアドレスをセット
I2C1CNT=1; //送信データ数をセット
I2C1TXB=_data1; //送信データを1バイトだけ、前もってセット
I2C1CON0bits.S=1; //Startコンディション
while(!I2C1STAT0bits.BFRE); //I2Cバスが解放されているか?
while(!I2C1STAT1bits.TXBE); //送信バッファが空か?(セットしたデータの送信完了)
while(I2C1PIRbits.PC1IF==0); //Stopコンディションになったか?
I2C1PIRbits.PC1IF=0; //Stopフラグクリア
while(I2C1STAT0bits.MMA); //Host mode active を見て終了確認 0:not active 1:active
//Set Clear Buffer Flag
I2C1STAT1bits.CLRBF = 1; //I2Cバッファ、RXBF,TXBEのクリア
return true;
}
//*************************************************//
//** I2C1 2バイト送信
//*************************************************//
uint8_t I2C1_b2Write(uint8_t _deviceAdd, uint8_t _data1, uint8_t _data2)
{
I2C1ADB1 = _deviceAdd; //Clientデバイスアドレスセット
I2C1CNT=2; //送信データ数
I2C1TXB=_data1; //送信バッファに最初のデータをセット
I2C1CON0bits.S=1; //スタートコンディションセット
while(!I2C1STAT0bits.BFRE) //I2Cバスが解放されているか?
{};
while(I2C1STAT1bits.TXBE!=1)
{}; //最初のデータ送信完了?
//while(!I2C1CON1bits.ACKSTAT);
I2C1TXB=_data2; //次の送信データをセット
while(I2C1STAT1bits.TXBE!=1); //送信完了?
while(I2C1PIRbits.PC1IF==0); //ストップコンディション確定?
I2C1PIRbits.PC1IF=0; //ストップフラグクリア
while(I2C1STAT0bits.MMA); //Host mode Active確認 0:not Active 1:active
//Set Clear Buffer Flag
I2C1STAT1bits.CLRBF = 1; //I2C1バッファ、TXBE,RXBFクリア
return true;
}
//*************************************************//
//** I2C1 1バイト受信
//*************************************************//
uint8_t I2C1_b1Read(uint8_t _deviceAdd,uint8_t _address)
{
uint8_t ret;
I2C1ADB1 = _deviceAdd; //Clientアドレスセット
I2C1CNT=1; //送信バイト数(この場合は、引数の_address)
I2C1TXB=_address; //送信バイトセット
I2C1CON0bits.S=1; //Startコンディションセット
while(!I2C1STAT0bits.BFRE); //I2Cバス解放?確認
while(I2C1STAT1bits.TXBE!=1); //送信バッファ 0:full 1:empty 送信完了確認
//リスタート(本来は、送信ストップコンディションのところでリスタート。)
I2C1CON0bits.RSEN=1; //リスタート開始(I2C1CNT==0の時)
while(!I2C1CON0bits.MDR); //RSENセットで1になる。SCLは、Hold状態
I2C1ADB1 = _deviceAdd|0x01; //Clientアドレス+R/Wビット
I2C1CON1bits.ACKCNT=1; //I2C1CNT==0でNack返信
I2C1CNT=1; //読み出し数
I2C1CON0bits.S=1; //Startコンディションセット
while(!I2C1STAT1bits.RXBF); //読み込み完了待ち
ret=I2C1RXB; //バッファから読み出し
I2C1CON0bits.RSEN=0; //リスタート解除
I2C1PIR=0x00; //PIRクリア
while(I2C1STAT0bits.MMA); //HostmodeActime 0:not Active 1:active
//Set Clear Buffer Flag
I2C1STAT1bits.CLRBF = 1; //I2C1バッファクリア
return ret;
}
6.PIC18F27Q43の1バイト読込シーケンス
下のダイアグラムは、7bit Host Mode Receptionのタイミングチャートです。
リスタート後の、読取り動作のフラグのタイミングが、大体把握できます。ですが、このダイアグラムに載っていないビットフラグも使用しないと、動作しませんでした。
(データシートDS40002147E-page 670)
6.1 読込シーケンスはリスタートの設定が、重要です。
リスタートの設定がキーポイントになってきます。 オシロやロジックアナライザーで波形を見ながら、デバッグコーディングして、コードの実行性を確かめながら、理解するのが一番です。自動化された部分の動きを理解するのに、データシートの説明だけでは、半自動化の動作を確認するのが難しくなります。
6.2 ここからは、自分が組んだI2C 1バイト読込の概略フロー解説です。
<1バイト読取りコードの解説>
0. アドレスバッファにClientアドレスをセット。バイトカウンタに送信データ数をセット。TX送信バッファに、MCP23017のレジスタアドレスをセットしておきます。
-
Sビットをセット。バスフリーなら、すぐにスタートコンディションから発行され、ClientアドレスとRWビットが送信されます。このときは、まだ書き込みモードなので、ACKは、Clientから返信されます。これで、ACKを受信したら、Clientアドレスの送信に成功です
-
MCP23017のレジスタアドレスが自動送信されます。読込先のレジスタアドレスです。書き込みモードなので、ClientからのAck返信を待ちます。ここでAckだったら、次は、リスタートして、指定データの読み込みを行います
-
送信部分は完了したので、通常の送信だったらここでストップコンディションを発行して、トランザクションを終了するタイミングです。読取りデータをMCP23017から出力させたいので、このタイミングで、リスタートを開始します
-
コントロールレジスタ0のRSENビットをセットします。このとき、バスはまだ、リスタート状態になっていません。
RSENビットをセットした時に、バイトカウンタI2C1CNTがゼロなので、MDRビットがセットされます。
MDRビットがセットされると、Hostは、送信、受信バッファの処理が終わるまで、クロックラインをストレッチしたまま、つまりホールドしたまま、通信を中断しています。
MDRビットがセットされるまで、while文で待っています。これがないと、フリーズします。
データシートには、このタイミングで、MDRがセットされることは書かれていますが、このビットがセットするまで待つことは、明示されていない、とおもいます。この部分が、難解な感じがします -
MDRビットがセットされて、通信が中断している間に、送信データ、ClientアドレスとRWビットをABD1レジスタにセット。バイトカウンタに読込数をセット。読込最終バイト時にHostは、Nackを返信したいので、ACKCNTビットに、Nackを返信するように指示します
-
準備すべきデータセットが終わったら、スタートビットをセットします。ここで、MDRビットがクリアされて、クロックストレッチが、終了します
-
バスフリーなら、スタートコンディションを発行されます
-
Clientアドレスが送信され、AckがClientから返信されます
-
次に8ビットは、先ほど指定したレジスタアドレスの格納データ値が、ClientからHostに送信されます。これも自動的に処理されて、RXBFビットがセットするまで、待つだけです
-
RXBFビットがセットされたら、受信完了なので、受信バッファからデータを取り出します
-
1バイト読込なので、ここで読込終了です。通常、読込シーケンスの最終バイトの後のAck確認では、HostからNackを返信するのがセオリです。これも、さきほど、前もって、ACKCNTビットで指定したNackが自動的に出力されます。
-
I2C1の割込みフラグを、クリアしておきます
-
MMAビットで、Hostモードがアクティブであるか、どうかチェックします
非アクティブなら、トランザクションは完了したので、I2C1の送受信バッファをクリアして、終了します。