鉄道模型、プラモデル展示台用の音源としてPICマイコンで作成しているWAV再生ボード。ちょっと小さめに作ってみた。
I2C通信・・複数バイトの送受信テスト・・
Nゲージのレイアウトで使用する制御盤、ポイントマシンの制御、信号LEDの制御などをラズベリーパイとPICマイコンを使用して製作中。
ラズベリーパイとPICマイコン間のデータのやり取りはI2C通信で行いたい。
だが、「I2C通信・・複数バイトの受信終了がわからない・・」という問題があった。
ちょっと試行錯誤の上、なんとか動くんじゃないか?というテストプログラムができた。
Index
今回作ろうとしているのは、I2CのマスターがRaspberry Pi、スレーブがPICマイコン。
「I2C通信・・複数バイトの受信終了がわからない・・」ということで調べたところの結論から言うと、複数バイトのデータの送信送完了時にはStop Conditionビットを立てる必要があるが、Raspberry Pi側をc言語で作った場合、このビットの操作が難しいということ。
なので、Stop Conditionビットではなく他の方法を考慮した方が近道らしいということ。
と、いうことで先にも検討したように、Raspberry Pi側から複数バイトのデータを送る時は、最初に送るデータのデータ長を送信しておき、PICマイコン側では指定のバイト数を受信したら受信完了とみなす方法をとることにした。
レングス 割り込み データ 割り込み データ 割り込み データ 割り込み
PICマイコン側では割り込みの回数をカウントしながらデータを受信し、レングス分データを取得したら受信完了とみなす。
逆に、PICマイコン側からRaspberry Pi側に送信するときは。
PICマイコンはStop Conditionビットを容易に立てることができるが、同一のデータフォーマットとするため、PICマイコンからの送信データも上記のように先頭のデータは送るデータのデータ長としてみた。
マスター側プログラム
Raspberry Pi側。
文字列をスレーブ側にデータを送信し、その後データを受信する。
送受信したデータはprintfでコンソールに表示するため、このテストプログラムでは表示可能な文字を送受信しているが、実際に使用するときはバイナリデータを送受信しても問題はない。
// raspberry pi // // I2Cで複数バイトのデータを送受信するテストプログラム // マスター側 // // コンパイル // gcc -o i2cserver i2cserver.c -lwiringPi // // 実行 // ./i2cserver // // 停止 // コンソールからctrl+cで停止 // #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/resource.h> #include <wiringPi.h> #include <wiringPiI2C.h> #define I2CADDR_P1_T_COM1 0x11 // I2Cスレーブのアドレス //*********************************************** // グローバル変数 //*********************************************** int dev; // I2C filehandle unsigned char sendbuf[20]; // 送信バッファ unsigned char receivebuf[20]; // 受信バッファ //*********************************************** // 内部関数のプロトタイプ宣言 //*********************************************** int init_dev(void); // I2C初期化 void main(int, char **); // メイン //*********************************************** // I2C初期化 //*********************************************** int init_dev(void){ dev = wiringPiI2CSetup(I2CADDR_P1_T_COM1); return dev; } //*********************************************** // メイン //*********************************************** void main(int argc, char *argv[]){ int result; // I2C初期化 if(init_dev() == -1){ printf("I2C init error : %s\n", strerror(errno)); return; } // I2Cデータ送信 strncpy(&sendbuf[1], "abcde", 5); // 送信バッファに送信データセット sendbuf[0] = (unsigned char)6; // 先頭にバッファ長をセット result = write(dev, sendbuf, 6); if (result <= 0){ printf("I2C send error : %s\n", strerror(errno)); return; } sendbuf[6] = 0; // printfするためのストッパーセット printf("I2C send : %s\n", &sendbuf[1]); // スレーブ側の処理もデバッグ用で細部まで作り込んでいないので、一旦スリープして待つ sleep(3); // 3秒スリープ // I2Cデータ受信 result = read(dev, receivebuf, sizeof(receivebuf)); if (result < 0){ printf("I2C recive error : %d\n", result); return; } if (result == 0){ printf("I2C recive Buffer empty\n"); return; } receivebuf[receivebuf[0]] = 0; // printfするためのストッパーセット printf("I2C received : %s\n", &receivebuf[1]); while(TRUE){ // 他にデバッグしたいことがあれば・・・記述 }; }
スレーブ側プログラム
PICマイコン側(PIC16F886)。
受信したデータの先頭に「RECEIVED:」という文字列を付加して送り返している。
// // PIC16F887 // // I2Cで複数バイトのデータを送受信するテストプログラム // スレーブ側 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <xc.h> // PIC16F887 Configuration Bit Settings // CONFIG1 #pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config MCLRE = OFF // RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD) #pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled) #pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled) #pragma config BOREN = ON // Brown Out Reset Selection bits (BOR enabled) #pragma config IESO = ON // Internal External Switchover bit (Internal/External Switchover mode is enabled) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled) #pragma config LVP = OFF // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming) // CONFIG2 #pragma config BOR4V = BOR40V // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) #pragma config WRT = OFF // Flash Program Memory Self Write Enable bits (Write protection off) // クロック #define _XTAL_FREQ 8000000L // クロック 8MHz ボーレート、__delay_ms用 #define I2CADDR_P1_T_COM1 0x11// I2Cスレーブのアドレス #define I2CBUFFSIZE 20 // I2C送受信バッファサイズ //*********************************************** // グローバル変数 //*********************************************** // 送信バッファ // stopconditionが1になれば、I2C送信バッファのデータはマスターに送信完了 // unsigned char sendbuff[I2CBUFFSIZE]; // I2C送信バッファ (先頭1バイト目は自身を含むレングス) int sendlength; // 送信バッファ有効長 int sended; // 送信済データ長 int stopcondition; // Stop Condition送信済フラグ // 0:未送信 1:送信済 // 受信バッファ // receivedとreceivelengthが0以外で同じ値であれば、すべて受信済み // unsigned char receivebuff[I2CBUFFSIZE]; // I2C受信バッファ (先頭1バイト目は自身を含むレングス) int received; // 受信済データ長 int receivelength; // 受信バッファ有効長 //*********************************************** // 内部関数のプロトタイプ宣言 //*********************************************** // メイン void main(int, char**); // 割り込み処理 void __interrupt() isr(void); // 1byte送信処理 void I2CPut(void); // I2C関連初期化 void I2CInit(void); //*********************************************** // 内部関数 //*********************************************** /* * メイン */ void main(int argc, char** argv) { // 送信バッファにデバッグ用初期値設定 strcpy((char *)&sendbuff[1], "no data"); // 送信データをセット sendbuff[0] = 8; // 送信バッファ有効長をバッファ先頭にセット sendlength = 8; // 送信バッファ有効長 sended = 0; // 送信済データ長 stopcondition = 0; // Stop Condition送信済フラグ 0:未送信 // 受信バッファ初期化 received = 0; // 受信済データ長 receivelength = 0; // 受信バッファ有効長 //******************** // 初期化 //******************** OSCCONbits.IRCF =0b111; // IRCF<2:0>=111 内臓クロックは8MHz CM1CON0bits.C1ON =CM2CON0bits.C2ON = 0;// コンパレータオフ ANSEL = ANSELH = 0x00; // ポートI/Oはすべてデジタル TRISA = 0; //RAポート 全て出力 TRISB = 0; //RBポート 全て出力 TRISC = 0b00011000; //RCポート I2CのRC3,RC4以外全て出力(I2Cポートは入力にする必要があるのか否か?? TRISD = 0; //RDポート 全て出力 TRISE = 0; //REポート 全て出力 I2CInit(); //I2C関連初期化 //******************** // 割り込み初期化 //******************** SSPIF = 0; // Clear MSSP interrupt flag SSPIE = 1; // I2C interrupt enable PEIE = 1; // Enable Peripheral interrupts GIE = 1; // Enable global interrupts while(1){ if(receivelength != 0 && received == receivelength){ // receivedとreceivelengthが0以外で同じ値であれば、受信バッファにマスタからのデータ取得済 // ここに受信したデータを使った処理を記述 // 今回は受信したデータを送信バッファにコピー // 先頭のレングス格納域の後に"RECEIVED:"の文字をセット strcpy((char *)&sendbuff[1], "RECEIVED:"); // "RECEIVED:"の後に受信したデータをコピー strncpy((char *)&sendbuff[10], (char *)&receivebuff[1], received -1); // 先頭にレングスを書き込む sendbuff[0] = (unsigned char)(received + 9); sendlength = sendbuff[0]; sended = stopcondition = 0; // 処理が終わったら受信バッファ初期化 received = receivelength = 0; } } } /* * 割込み処理 */ void __interrupt() isr(void){ unsigned char skipreading; // 読み飛ばしデータ格納用 if (SSPIF == 1) { // SSP(I2C)割り込み if(SSPSTATbits.R_W == 0){ // Read/Write bit information // マスタからの送信、スレーブ(このPICマイコン)は受信 if(SSPSTATbits.D_nA == 0){ // Data/Address bit // 受信データはアドレス skipreading = SSPBUF; // アドレスデータを空読み } else { // 受信データはデータ // 前回受信したデータの処理が終わっていない場合 // 今回受信したデータを読み飛ばすか、NACKを返して受信を拒否するなどの処理が必要 // if(received == 0){ // 1バイト目のデータか? // 1バイト目のデータはレングス(AP間で取り決めた仕様) receivelength = SSPBUF; // 1回の割り込みでSSPBUFから複数回読みださないこと receivebuff[received++] = receivelength; } else { // 2バイト目以降 receivebuff[received++] = SSPBUF; // データ読出し // receivedとreceivelengthが同じ値になれば、すべてのデータの受信が完了。 // 受信バッファのデータを使った処理はmain()関数で。 } } }else{ // マスタの受信、スレーブ(このPICマイコン)は送信 if(SSPSTATbits.BF == 1){ // Buffer Full Status bit // アドレス受信後の割り込み skipreading = SSPBUF; // アドレスデータを空読み I2CPut(); // 1byte送信 } else { // データの送信後のACK受け取りによる割り込み if( (SSPCON2bits.ACKSTAT) == 0){ // ACK応答なら次のデータをセットする I2CPut(); // 1byte送信 } else { // NACKで応答された時は送信終了(処理無し) } } } SSPCONbits.CKP = 1; // SCLラインを開放(通信再開) SSPIF = 0 ; // 割込みフラグクリア } } /* * 1byte送信処理 */ void I2CPut(void) { while(SSPSTATbits.BF); // Buffer Full Status bit: 1:SSPBUF is full バッファフルの間は待ち SSPCONbits.WCOL = 0; // Write Collision Detect bit クリア if(sendlength == sended){ SSPCON2bits.PEN = 1; // Stop Condition Enable bit // 1:Initiate Stop condition on SDA and SCL pins. stopcondition = 1; // 1:Stop Condition送信済 } else { SSPBUF = sendbuff[sended++]; } while(SSPCONbits.WCOL); // 送信完了まで待ち } /* * I2C関連初期化 */ void I2CInit(void) { //SSP STATUS REGISTER SSPSTATbits.SMP = 1; //In I2 C Master or Slave mode: //1 = Slew rate control disabled for standard speed mode (100 kHz and 1 MHz) //SSP CONTROL REGISTER 1 SSPCONbits.WCOL = 1; //Slave mode:1 = The SSPBUF register is written while it is still transmitting the previous word (must be cleared in software) SSPCONbits.SSPOV = 1; //In I2 C mode:1 = A byte is received while the SSPBUF register is still holding the previous byte. SSPOV is a “don’t care” in Transmit //mode (must be cleared in software). SSPCONbits.SSPEN = 1; //1 = Enables the serial port and configures the SDA and SCL pins as the source of the serial port pins SSPCONbits.CKP = 1; //In I2 C Slave mode: //1 = Enable clock SSPCONbits.SSPM = 0b00000110; //0110 = I2C Slave mode, 7-bit address //SSP CONTROL REGISTER 2 SSPCON2bits.GCEN = 0; //0 = General call address disabled SSPCON2bits.SEN = 1; //In Slave mode: //1 = Clock stretching is enabled for both slave transmit and slave receive (stretch enabled) //クロックストレッチを有効にすると、スレーブでデータの送受信完了後、 //SSP1CONのCKPビットをセットし、SCLラインを解放しなければならない。 //SSPADD // I2Cアドレス設定 SSPADD = I2CADDR_P1_T_COM1 << 1; // 7bit address }
実行すると・・
プログラムは細部をよ~くチェックする必要があるが、とりあえず無事、送受信することができた。
課題としては、スレーブ側が受信したデータの処理が終わらないうちにマスター側が次のデータを送信してきたとき。
マスター側に「ちょっと待ってね」を返信しなくちゃならん。
余談だが、スレーブ側の電源が入っていない状態でマスター側を実行すると、wiringPiI2CSetupでエラーになる。
strerror(errno)で結果を表示すると・・
I2C send error : Remote I/O error
pi@raspberrypi-1:/var/www/html/ngauge/i2c $
Remote I/O errorが戻ってきた。