0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[PIC18F27Q43] 16bits IOエキスパンダー MCP23017使い方と、I2C "1バイト読込のコード" 解説

Posted at

PIC18F27Q43で、I2C16ビットIOエキスパンダーMCP23017を使うときの仕様と、I2C,1バイト読込のコードの要点をまとめました。

MCP23017のデータシートから、必要なところをピックアップしました。
IMG_4605.JPG

・PCF8574( Texas Instruments)というI2Cデバイスの8ビットIOエキスパンダーが販売終了。
・代わりにこの16ビットIOエキスパンダーMCP23017(I2C)が、入手しやすいのでその使い方をまとめておきます。
・Q43のI2Cモジュールの読み取りは、特殊です。MSSPモジュールだと、すべてのシーケンスをコード上で、把握できたのですが、Q43の場合、バイトカウンタにあわせて、半自動化になったことが、 難易度を高くしている感じがします。
・オシロ、または、ロジックアナライザーで波形を確認しながらの作業が、効率的だと思われます。

まず、1バイト読込の時に必要なMCP23017の仕様をざっと確認しておきます。

1.MCP23017 端子機能

MCP23017ピン配.jpg

ピン番号 ピン名 機能
1~8 GPBx GPIO Bポート
9 VDD 電源1.8v~5.5v
10 VSS グランド
11 NC 未使用
12 SCK  I2C クロック線
13 SDA  I2C データ線
14 NC 未使用
15 A0  I2C スレーブアドレス指定bit0
16 A1  I2C スレーブアドレス指定bit1
17 A2  I2C スレーブアドレス指定bit2
18 /RESET  リセット
19 INTB  割込み出力
20 INTB  割込み出力
21~28 GPBx GPIO Aポート

2.MCP23017 Client(Slave)addressの設定

データシート DS20001952C-page 15より
ControlByte.png

MCP23017のI2C Clientアドレスは、15,16,17ピン A0,A1,A2をつかってユーザーが、定義できます。

※今回は、ブレッドボード上で、A1,A2をプルアップし、A0だけをグランドにプルダウンしました。Clientアドレスは、R/Wビットを含めた、0x4C(0100 1100)として、設定しています。

3.MCP23017 コントロールレジスタ

データシートDS20001952C-page 16より
コントロールレジスタ.png

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 書き込み

I2Cwrite.png
シーケンシャルにレジスタアクセスできるのですが、基本的に1バイト通信で事足りると思います。

<I2C1バイト書き込み>
スタート→Clientアドレス+Wビット送信→Ack(Clinet)→レジスタアドレス送信→Ack(Clinet)→書き込みデータ送信→Ack(Clinet)→ストップ

標準的なバイト書込みシーケンスです。

read opreration 読み込み

I2Cread.png
今回は、設定を確認するだけなので、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を制御できます。エラー処理は、全く考えていませんので、通信がずっこけたら、フリーズします。まずは、正常系の動作が完成した後に、エラー処理を組み込んだほうが、理解が速いと思います。

Q_I2C1.h
#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 */
QI2C1.c
#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)
7bitHostReceptionDiagram.png

6.1 読込シーケンスはリスタートの設定が、重要です。

リスタートの設定がキーポイントになってきます。 オシロやロジックアナライザーで波形を見ながら、デバッグコーディングして、コードの実行性を確かめながら、理解するのが一番です。自動化された部分の動きを理解するのに、データシートの説明だけでは、半自動化の動作を確認するのが難しくなります。

6.2 ここからは、自分が組んだI2C 1バイト読込の概略フロー解説です。

7bitHostReadDiagram.png
<1バイト読取りコードの解説>
0. アドレスバッファにClientアドレスをセット。バイトカウンタに送信データ数をセット。TX送信バッファに、MCP23017のレジスタアドレスをセットしておきます。

  1. Sビットをセット。バスフリーなら、すぐにスタートコンディションから発行され、ClientアドレスとRWビットが送信されます。このときは、まだ書き込みモードなので、ACKは、Clientから返信されます。これで、ACKを受信したら、Clientアドレスの送信に成功です

  2. MCP23017のレジスタアドレスが自動送信されます。読込先のレジスタアドレスです。書き込みモードなので、ClientからのAck返信を待ちます。ここでAckだったら、次は、リスタートして、指定データの読み込みを行います

  3. 送信部分は完了したので、通常の送信だったらここでストップコンディションを発行して、トランザクションを終了するタイミングです。読取りデータをMCP23017から出力させたいので、このタイミングで、リスタートを開始します

  4. コントロールレジスタ0のRSENビットをセットします。このとき、バスはまだ、リスタート状態になっていません。
    RSENビットをセットした時に、バイトカウンタI2C1CNTがゼロなので、MDRビットがセットされます。
    MDRビットがセットされると、Hostは、送信、受信バッファの処理が終わるまで、クロックラインをストレッチしたまま、つまりホールドしたまま、通信を中断しています。
    MDRビットがセットされるまで、while文で待っています。これがないと、フリーズします。
    データシートには、このタイミングで、MDRがセットされることは書かれていますが、このビットがセットするまで待つことは、明示されていない、とおもいます。この部分が、難解な感じがします

  5. MDRビットがセットされて、通信が中断している間に、送信データ、ClientアドレスとRWビットをABD1レジスタにセット。バイトカウンタに読込数をセット。読込最終バイト時にHostは、Nackを返信したいので、ACKCNTビットに、Nackを返信するように指示します

  6. 準備すべきデータセットが終わったら、スタートビットをセットします。ここで、MDRビットがクリアされて、クロックストレッチが、終了します

  7. バスフリーなら、スタートコンディションを発行されます

  8. Clientアドレスが送信され、AckがClientから返信されます

  9. 次に8ビットは、先ほど指定したレジスタアドレスの格納データ値が、ClientからHostに送信されます。これも自動的に処理されて、RXBFビットがセットするまで、待つだけです

  10. RXBFビットがセットされたら、受信完了なので、受信バッファからデータを取り出します

  11. 1バイト読込なので、ここで読込終了です。通常、読込シーケンスの最終バイトの後のAck確認では、HostからNackを返信するのがセオリです。これも、さきほど、前もって、ACKCNTビットで指定したNackが自動的に出力されます。

  12. I2C1の割込みフラグを、クリアしておきます

  13. MMAビットで、Hostモードがアクティブであるか、どうかチェックします
    非アクティブなら、トランザクションは完了したので、I2C1の送受信バッファをクリアして、終了します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?