PIC18F27Q43 I2C Host-Client通信 1 ＜Host側＞

# 18F27Q43 2台を使ってHost-Client通信
<img width=15%><img width=66% alt="IMG_4307.JPG名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/0855de53-0948-0eb6-1205-40797e6dbf64.jpeg">

:::note info
今回は、I2C Host-Client通信、**Host サイド** の記述です。
:::
従来の16F,18Fでは、**MSSPモジュール**で、I2Cマスター、スレーブ通信を行っていました。Qシリーズになり、I2C,SPIが別々のペリフェラルモジュールになりました。使い方が大きく異なるので、データシートを読みながら、まず、Hostから作りました。
# I2c "master-slave" から "Host-Client" に名称が変更。
　従来のmasterが **Host**に、slaveが **Client**に名称変更されています。
 
# PCF8574, MCP23017 をたたき台に使う。
新しくI2Cﾄﾞﾗｲﾊﾞｰを作成するので、通信相手に8bits IOｴｷｽﾊﾟﾝﾀﾞｰPCF8574と、16bits IOｴｷｽﾊﾟﾝﾀﾞｰ MCP23017を使用します。I2Cプロトコルが単純なので、デバッグにちょうどいいです。MCP23017は設定エリアを読み出します。
![SnapShot1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/91bc403f-ce4a-a1ab-da18-ab0499f00331.jpeg)


# Host側　I2Cﾄﾞﾗｲﾊﾞｰ　書き込み
まずは、Hostサイド, Clietへの書き込みをするコードを見ていきます。
``` C:I2C_Host.c
//*************************************************//
//** 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;
}
```
Hostサイドの組み込みは、データシート36.4.2.7.1 Host Transmisson(7-Bit Address Mode)の記述に従って書きました。

## データシート　36.4.2.7.1 Host Transmission(7-Bit Address Mode)
以下は、PIC18F27Q43のデータシートP.671ページの訳です。Host側は、この説明のシーケンスに従い、C言語に翻訳していく作業になります。P.668　Figure36-36 7-Bit Host Mode Transmissionのタイミングチャート図とともに、I2Cのイベントタイミングを確認していきます。

### アドレスバッファを使用する場合を抜粋しています。
今回のコードは、ｱﾄﾞﾚｽﾊﾞｯﾌｧを使用しています。アドレスバッファとI2C1CNTという送受信専用のカウンタを利用して、半自動的に処理を行っています。I2C1CNTが０になると、基本的にStopが自動で発令されます。

`I2C1CON2.ABD=0;` //address buffer disable bit
#### 1.1. 準備   
`I2C1CON2.ABD=0`を使用する場合、アドレスバッファ`I2C1ADB1は有効`にしています。ClientアドレスをR/Wビットとともに`I2C1ADB1バッファ`へ格納する。データバイト数を`I2C1CNT`に格納。最初に送信するバイトデータを`I2C1TXB`に格納。これらのﾚｼﾞｽﾀのセットが終わったら、Start(`I2C1CON0.S`)をセットして通信開始。hostハードは、I2Cバスが解放された状態、バスフリーであるか、確認します`I2C1STAT0.BFRE==1:Free`。
#### 2.　Startコンディション出力
hostは`I2C1STAT0.BFREビット`がセットされるのを待ちます。バスがIdle状態なら、Startコンディションを出力します。`I2C1STAT0.MMAビット`をセットします。`I2C1PIR.SCIF`もセットされます。
#### 3. HostはClietアドレス＋RWビットを送信します。
#### 4. この時、バッファが空の場合
Clientアドレス出力、SCLの８クロック目で、`I2C1TXB`が空(`I2C1STAT1.TXBE==1`)、`I2C1CNT!=0`、クロックストレッチ有効(`I2C1.CSD=0`)の場合、`I2C1CON0.MDR`がセットされて、`I2C1TXB`にデータが書きこまれるまでクロックがストレッチされます。`I2C1TXB`にデータが書きこまれたら、バスが解放されます。
#### 5.データ送信９クロック目で、ClientからACK返信を待ちます。
ACK受信の場合：`I2C1TXB`のデータがシフトレジスタに転送され、`I2C1CNT`が-1デクリメントされます。
NACK受信の場合：Stopを出力しようとします。Clientがクロック保持している場合、バスフリーになるまでHostは待ちます。
#### 6.Host `I2C1CNT`が０になったの場合
`I2C1CON2.ABD=0`の場合:hostハードはストップコンディションを出力します。または、リスタートする場合、`I2C1CON0.MDR`をセットして、Start(I2C1CON0.S）をセットするまで待ちます。
#### 7.Host ハードが、データバイトを送信します。
#### 8.８番目のクロックの立下りで、
`I2C1TXB`が空の場合(I2C1STAT1.TXBE=1)、`I2C1CNT!=0`、かつ`I2C1CON1.CSD=0`の場合、I2C1TXIFがセットされます。`I2C1CON0.MDR`がセットされ、クロックがストレッチされます。`I2C1TXB`にデータを書きこむと、バスが解放されます。

`I2C1CNT==0`の場合、HostハードがStopコンディションを出力します。

#### 9. ステップ５　から　ステップ８を　繰り返します。
***
上記シーケンスに従ってコーディングすれば、Clientデバイスにデータを送信することができます。

# # Host側　I2Cﾄﾞﾗｲﾊﾞｰ　読み出し
Clientからデータを1バイト読み出します。（24.5.9　I2C1CNT＝＝１でNack返信を追記。必要ならコメントアウトをキャンセルしてください。なくても動くデバイスもあります。)
``` C:I2C_Host.c
//*************************************************//
//** 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;            //リスタート開始
     while(!I2C1CON0bits.MDR);      //RSENセットで１になる。(読み出しでクリア）
    I2C1CON0bits.S=1;               //Startコンディションセット
    I2C1ADB1 = _deviceAdd|0x01;     //Clientｱﾄﾞﾚｽ＋R/Wビット
    //I2C1CON1bits.ACKCNT=1;          //I2C1CNT==0でNack返信
    I2C1CNT=1;                      //読み出し数
    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;
}
```
データシートにフロー解説が乗っています。

# データシート　36.4.2.7.2 Host Reception(7-Bit Address Mode)
P.670 Figure36-37 7-Bit Host Mode Receptionのタイミングチャートと見比べながら読んでいきます。
### アドレスバッファを使用した場合について抜粋します。
#### 1.1. 準備   
`I2C1CON2.ABD=0`を使用する場合、アドレスバッファ`I2C1ADB1は有効`にしています。ClientアドレスをR/Wビットとともに`I2C1ADB1バッファ`へ格納する。データバイト数を`I2C1CNT`に格納。最初に送信するバイトデータを`I2C1TXB`に格納。これらのﾚｼﾞｽﾀのセットが終わったら、Start(`I2C1CON0.S`)をセットして通信開始。hostハードは、I2Cバスが解放された状態、バスフリーであるか、確認します`I2C1STAT0.BFRE==1:Free`。
#### 2.　Startコンディション出力
hostは`I2C1STAT0.BFREビット`がセットされるのを待ちます。バスがIdle状態なら、Startコンディションを出力します。`I2C1STAT0.MMAビット`をセットします。`I2C1PIR.SCIF`もセットされます。
#### 3. HostはClietアドレス＋RWビットを送信します。
#### 4. Hostハードは、Clientがクロックストレッチをしているかサンプリングします。
#### 5. Hostハードが9番目のﾊﾟﾙｽを送信、ClientからACK,NaKを受信します。
ACK受信の場合：`I2C1TXB`のデータがシフトレジスタに転送され、`I2C1CNT`が-1デクリメントされます。
NACK受信の場合：Stopを出力しようとします。Clientがクロック保持している場合、バスフリーになるまでHostは待ちます。
#### 6. 前に受信したデータがバッファに残っているか？
当たらしいデータの7ビット目を受信したときに、`I2C1RXB`バッファに前のデータが残っている場合、`I2CCON0.MDR`がセットされます。7番目クロックの下がりエッジのあと、クロックがストレッチされます。この間に、I2C1RXBバッファに残っているデータを読み出します。I2C1STAT1.RXBFがクリアされ、バスが解放されます。
#### 7. 8番目のクロックで、シフトレジスタでバイトデータを受信します。そのあとRXBFバッファに移送されます。I2C1RXIFがセットされて、`I2C1CNT`がデクリメント(-1)されます。1バイトの受信終了。
#### 8. Hostハード、I2C1CNTが０でないかチェック、ACK返信
I2C1CNTが０出ない場合:I2C1CON1.ACKDTにセットされている値を、レスポンスとしてClientに返す。
I2C1CNTが０の場合:I2C1CON1.ACKCNTにセットされている値を、レスポンスとして、Clientに返す。同時にStop
コンディションを発令する。通常、通信の終了にNACKを返す。自動的にSTOPコンディションになる。
#### 9. Hostハード、次の7ビットまでの、データをシフトレジスタに受信。
#### 10.ステップ６から９を、受信バイト数まで、繰り返す。
***


# main.c
```C:main.c
// PIC18F27Q43 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator Selection (Oscillator not enabled)
#pragma config RSTOSC = HFINTOSC_64MHZ// Reset Oscillator Selection (HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1)

// CONFIG2
#pragma config CLKOUTEN = OFF   // Clock out Enable bit (CLKOUT function is disabled)
#pragma config PR1WAY = OFF     // PRLOCKED One-Way Set Enable bit (PRLOCKED bit can be set and cleared repeatedly)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)

// CONFIG3
#pragma config MCLRE = EXTMCLR  // MCLR Enable bit (If LVP = 0, MCLR pin is MCLR; If LVP = 1, RE3 pin function is MCLR )
#pragma config PWRTS = PWRT_OFF // Power-up timer selection bits (PWRT is disabled)
#pragma config MVECEN = ON      // Multi-vector enable bit (Multi-vector enabled, Vector table used for interrupts)
#pragma config IVT1WAY = ON     // IVTLOCK bit One-way set enable bit (IVTLOCKED bit can be cleared and set only once)
#pragma config LPBOREN = OFF    // Low Power BOR Enable bit (Low-Power BOR disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled , SBOREN bit is ignored)

// CONFIG4
#pragma config BORV = VBOR_1P9  // Brown-out Reset Voltage Selection bits (Brown-out Reset Voltage (VBOR) set to 1.9V)
#pragma config ZCD = OFF        // ZCD Disable bit (ZCD module is disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON)
#pragma config PPS1WAY = OFF     // PPSLOCK bit One-Way Set Enable bit (PPSLOCKED bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Low Voltage Programming Enable bit (Low voltage programming enabled. MCLR/VPP pin function is MCLR. MCLRE configuration bit is ignored)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Extended Instruction Set and Indexed Addressing Mode disabled)

// CONFIG5
#pragma config WDTCPS = WDTCPS_31// WDT Period selection bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = OFF       // WDT operating mode (WDT Disabled; SWDTEN is ignored)

// CONFIG6
#pragma config WDTCWS = WDTCWS_7// WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC      // WDT input clock selector (Software Control)

// CONFIG7
#pragma config BBSIZE = BBSIZE_512// Boot Block Size selection bits (Boot Block size is 512 words)
#pragma config BBEN = OFF       // Boot Block enable bit (Boot block disabled)
#pragma config SAFEN = OFF      // Storage Area Flash enable bit (SAF disabled)
#pragma config DEBUG = OFF      // Background Debugger (Background Debugger disabled)

// CONFIG8
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block not Write protected)
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers not Write protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not Write protected)
#pragma config WRTSAF = OFF     // SAF Write protection bit (SAF not Write Protected)
#pragma config WRTAPP = OFF     // Application Block write protection bit (Application Block not write protected)

// CONFIG10
#pragma config CP = OFF         // PFM and Data EEPROM Code Protection bit (PFM and Data EEPROM code protection disabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "Q_peripheral27Q43.h"
#include "Q_interrupt27Q43.h"
#include "Q_initialize.h"
#include "Q_I2C1.h"
#include "I2C_LCD.h"
#include "stringFormat.h"

void portInit(void);
void oscillatorInit(void);
void vicInit(void);
uint8_t ret[5];
void main(void)
{
    uint8_t val, i;
    //CPUハード初期化-----------------------
    portInit();
    oscillatorInit();
    vicInit();
    
    //周辺機能初期化--------------------------------
    //timer0Init();
    //iocInit();
    usartInit();
    printf("START I2C\n");
    //adcInit();
    I2C1_Init();
    
    //MCP23017の設定
    I2C1_b2Write(0x4C,0x0A,0x80);
    I2C1_b2Write(0x4C,0x00,0x00);
    I2C1_b2Write(0x4C,0x09,0x00);
    I2C1_b2Write(0x4C,0x0A,0x00);
    I2C1_b2Write(0x4C,0x10,0x00);
    I2C1_b2Write(0x4C,0x19,0x00);
    I2C1_b2Write(0x4C,0x1A,0x00);
    
    //MCP23017の設定読み出し
    for(i=0; i<=0x0A; i++)
    {
        val=I2C1_b1Read(0x4C,i);
        printf("[%x]=%x\n",i,val);
    }
     for(i=0x10; i<=0x1A; i++)
    {
        val=I2C1_b1Read(0x4C,i);
        printf("[%x]=%x\n",i,val);
    }
    
    while(1)
    {
        __delay_ms(500);
        I2C1_b1Write(0x4A,0x55);//PCF8574 
        I2C1_b2Write(0x4C,0x0A,0xAA);//MCP23017 LATA
        I2C1_b2Write(0x4C,0x1A,0xAA);//MCP23017 LATB
        
        __delay_ms(500);
        I2C1_b1Write(0x4A,0xAA);//PCF8574
        I2C1_b2Write(0x4C,0x0A,0x55);//MCP23017 LATA
        I2C1_b2Write(0x4C,0x1A,0x55);//MCP23017 LATB
        
    }
    return;
}

void oscillatorInit(void)
{
    //オシレータ設定----------------
    OSCCON3bits.CSWHOLD=1;//Hold
    OSCCON1bits.NDIV=1;//64Mhz/2=32Mhz;
    while(!OSCCON3bits.NOSCR);
    while(!PIR0bits.CSWIF);//ready state
    PIR0bits.CSWIF=0;
    OSCCON3bits.CSWHOLD=0;
    while(!OSCCON3bits.ORDY);
}

void portInit(void)
{
    //ポート設定----------------------    
    PORTA=0x00;
    LATA=0x00;
    ANSELA=0x00;
    TRISA=0x00;
    
    PORTB=0x00;
    LATB=0x00;
    ANSELB=0x00;
    TRISB=0x00;
    
    PORTC=0x00;
    LATC=0x00;
    ANSELC=0x00;
    TRISC=0x00;
    
     //PPS---------------------
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    
    //I2C1-------------------------
    //RC4 for SDA
    RC4PPS=0x38;
    I2C1SDAPPS=0x14;
    //RC3 for SCL
    RC3PPS = 0x37;
    I2C1SCLPPS=0x13;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
    ODCONCbits.ODCC3=1;
    ODCONCbits.ODCC4=1;
    
    RC3I2Cbits.TH=1;
    RC4I2Cbits.TH=1;
    
}

void vicInit(void)
{
 //割り込みテーブルaddress設定----------------------------------
    INTCON0bits.GIE=0;//Enable all masked interrupts
    IVTLOCK=0x55;
    IVTLOCK=0xAA;
    IVTLOCKbits.IVTLOCKED=0;
    IVTBASE = 0x000008;
    IVTLOCK=0x55;
    IVTLOCK=0xAA;
    IVTLOCKbits.IVTLOCKED=1;
} 
```

# 次回　I2C Clientサイドの処理
従来の16F,18Fで使用していたMSSPモジュールは、割と素直にI2Cの基本フローどうりにフラグ処理をすれば、動いてくれました。I2CとSPIが別々のペリフェラルモジュールになっているPICマイコン、18F27Q43もそうですが、I2Cシーケンスが半自動化されています。Clientサイドは、割込み制御(interrput driven)が基本になります。スタートコンディション発令で割込み、ACK確認で割り込み、アドレス受信で割り込み、Hostからの受信、送信で割込み、
Stopコンディション発令で割込み、といった形で、何度もI2Cシーケンスのイベントタイミングで、一つの割り込み処理関数に、飛び込んできます。割り込みイベントの発生で、通信状態の経過を把握し、次のシーケンスに行く準備をします。状態の遷移を、把握しながら、I2Cシーケンスを進めていくことになるので、datasheetを深読みします。
**Hostサイドのようにデータシートの解説フローの通りに作っても、なかなか動作しない可能性**があります。