18F27Q43 2台を使ってHost-Client通信
今回は、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は設定エリアを読み出します。
Host側 I2Cドライバー 書き込み
まずは、Hostサイド, Clietへの書き込みをするコードを見ていきます。
//*************************************************//
//** 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が0になると、基本的に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の8クロック目で、I2C1TXB
が空(I2C1STAT1.TXBE==1
)、I2C1CNT!=0
、クロックストレッチ有効(I2C1.CSD=0
)の場合、I2C1CON0.MDR
がセットされて、I2C1TXB
にデータが書きこまれるまでクロックがストレッチされます。I2C1TXB
にデータが書きこまれたら、バスが解放されます。
5.データ送信9クロック目で、ClientからACK返信を待ちます。
ACK受信の場合:I2C1TXB
のデータがシフトレジスタに転送され、I2C1CNT
が-1デクリメントされます。
NACK受信の場合:Stopを出力しようとします。Clientがクロック保持している場合、バスフリーになるまでHostは待ちます。
6.Host I2C1CNT
が0になったの場合
I2C1CON2.ABD=0
の場合:hostハードはストップコンディションを出力します。または、リスタートする場合、I2C1CON0.MDR
をセットして、Start(I2C1CON0.S)をセットするまで待ちます。
7.Host ハードが、データバイトを送信します。
8.8番目のクロックの立下りで、
I2C1TXB
が空の場合(I2C1STAT1.TXBE=1)、I2C1CNT!=0
、かつI2C1CON1.CSD=0
の場合、I2C1TXIFがセットされます。I2C1CON0.MDR
がセットされ、クロックがストレッチされます。I2C1TXB
にデータを書きこむと、バスが解放されます。
I2C1CNT==0
の場合、HostハードがStopコンディションを出力します。
9. ステップ5 から ステップ8を 繰り返します。
上記シーケンスに従ってコーディングすれば、Clientデバイスにデータを送信することができます。
# Host側 I2Cドライバー 読み出し
Clientからデータを1バイト読み出します。(24.5.9 I2C1CNT==1でNack返信を追記。必要ならコメントアウトをキャンセルしてください。なくても動くデバイスもあります。)
//*************************************************//
//** 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セットで1になる。(読み出しでクリア)
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が0でないかチェック、ACK返信
I2C1CNTが0出ない場合:I2C1CON1.ACKDTにセットされている値を、レスポンスとしてClientに返す。
I2C1CNTが0の場合:I2C1CON1.ACKCNTにセットされている値を、レスポンスとして、Clientに返す。同時にStop
コンディションを発令する。通常、通信の終了にNACKを返す。自動的にSTOPコンディションになる。
9. Hostハード、次の7ビットまでの、データをシフトレジスタに受信。
10.ステップ6から9を、受信バイト数まで、繰り返す。
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サイドのようにデータシートの解説フローの通りに作っても、なかなか動作しない可能性があります。