鉄道模型、プラモデル展示台用の音源としてPICマイコンで作成しているWAV再生ボード。ちょっと小さめに作ってみた。
I2C通信の確認プログラム
ラズベリーパイとPICマイコン間でI2C通信するための電圧レベル変換モジュールを基板の上にとりつけた。
続いては、実際に通信できるか試験用プログラムを作って確認。
NゲージのレイアウトでPICマイコンを使ってポイントマシンを動作させたり信号の点灯制御を行うためのもの。
Raspberry Pi3がマスターとなって全体を制御し、実際にポイントマシンを転換するPICマイコンなどがスレーブとなる。
環境
環境としては次のようなものになる。
マスターとなるRaspberry Pi3と電圧レベル変換モジュールがある基盤は数センチのケーブルで繋ぐが、そこから先のスレーブの基盤は2~3メートルから数メートル程度のLANケーブルで接続する。
動作確認のフェーズ
I2Cのプログラムを作るのは初めて。
他人様のHPを参考に一気呵成に環境を作ってプログラムをコピペして動けば、それに越したことはないが、世の中そんなに甘くない。
今回のようにマスター側のプログラム、スレーブ側のプログラム、マスターとスレーブをつなぐ電圧レベル変換モジュールなどが絡んでくると切り分けが大変そう。
ということで、少しずつ動作確認を行ってきている。
今一度振り返ると。
Raspberry Pi3の設定
Raspberry Pi3でI2C通信を使用できるようにするためには設定を行っておく必要がある。
これは、ラズベリーパイ3・I2C通信の準備のような手順で実施。
電圧レベル変換モジュールと疎通確認
Raspberry Pi3側が3.3V、PICマイコン側が3.3V、5V混在という予定なので、中間に電圧レベル変換モジュールが必要になる。
そのために、電圧レベル変換モジュールを取り付けた基板を作った。
その際に、スレーブとなるPICマイコンでI2C通信を担うMSSPモジュールを有効にする設定のみを行ったプログラムを作成した。
これは単にPICマイコン側のクロックの設定が正しいか、MSSPモジュールが有効になっているかの確認のみなので、実際に実行する部分としてはLEDの点滅のみの簡単なもの。
マスター側のRaspberry Pi3で「i2cdetect」コマンドを実行し、スレーブのアドレスが表示されれば電圧レベル変換モジュールを経由してPICマイコンも所定のクロックでI2Cスレーブとして動作していることの疎通確認ができるというものだった。
実際にスレーブ側からデータを送信し、マスター側で受け取る
今回の確認はここ。
Raspberry Pi用のGPIOインターフェイスライブラリ「wiringPi」の使い方も含めた検証なので、単純に1バイトのデータの送受信のみ。
試験にあたっては、スレーブ側のPICマイコンはブレッドボード上に置いたが、実際に使用する電圧レベル変換モジュールを取り付けた基板とは2メートルほどのLANケーブルで接続し、実環境に近づけた。
よく他人様のHPでみかけるような奇麗に整理された環境とは程遠いゴチャゴチャの環境。
ちなみに、Raspberry Pi3は透明のプラケースに入れ、他の基盤の裏面にはプラダンなどの絶縁物のシートをネジ止めして絶縁しているので、テキトーな位置にポっと置いてもショートすることはない・・ハズ。
でも、念のため畳の上でテスト。
通信テストプログラム
当初、スレーブ側から送信した1バイトのデータをマスター側で表示させるというプログラムを作成して動作の検証を行っていた。
その確認ができれば、次のフェーズでマスター側からデータを送信し、スレーブ側で受信できるか、という形に改修しようとしていた。
しかし、意外に簡単に改修できたので、ソースプログラムはこのページの末尾に記載しなおします。
トラブルの切り分け
前項のプログラムは最終的に動作確認ができたものであるが、実際にはすんなり動かなかった。
さて、マスター側のプログラムなのか、スレーブ側のプログラムなのか、はたまたハード上の問題なのか?
マスター側プログラムの切り分け
他人様のHPを拝見すると温度計や加速度計のサンプルプログラムではwiringPiの「wiringPiI2CReadReg8()」などで相手方のレジストリを直に取得する?ような感じ。
しかし、アタイはスレーブ側から送られた値を「wiringPiI2CRead()」という関数で取得したい。
なので、試しに温度計のモジュールを購入して実際に接続してみて・・という方法では切り分けられない。
プログラムを動作させたところ、何がしかの値は取得できているようだったが、スレーブが送信したはずの値ではなかった。
現時点で確認できることといえば・・
・存在しないスレーブのアドレスを指定するとwiringPiI2CSetup()がエラーを戻すか。
・動作中にスレーブの電源を切ったら、値の表示が止まるか。
確認したところ、存在しないアドレスだとエラーになるし、動作中にスレーブの電源を切るとwiringPiI2CRead()のエラー値(0xff)を表示するようになった。
この時点で、マスター側プログラムは正しく動作しているものと思えた。
スレーブ側プログラムの切り分け
MPLAB X IDEとPickit3を使えばデバッグも可能であるが、割り込みルーチンが絡んでくるとどうもうまくデバッグすることができない。
実環境で最低限どこまで進んでいるのかを確認するのにLEDを使用した。
最初にLEDを点灯させておき、
RC2 = 0; // 確認用LED消灯
というのを書く位置を変えながら動作を確認してみる。
・・最初にLEDを消灯させておき、指定の位置にきたらLEDを点灯・・
そんな方法はポートの入出力の設定がちゃんとできていないとLEDは点灯しないし、LEDの極性を間違えて接続していても点灯しないのでお勧めできません(^^;
スレーブ側モジュールを変更してみる
スレーブ側のモジュールを他のものに変更してみる。
しかし、前述のように今回は温度計や加速度計のモジュールは使用できない。
となると用意できるのは「Arduino Uno」
参考にさせてもらったのは「Arduino同士でI2C通信する方法」という記事。
それによれば、
#include <Wire.h>
byte b=0;
void setup() {
Wire.begin(8);// Slave ID #8
Wire.onRequest(requestEvent);
}void loop() {
}void requestEvent() {
Wire.write(b++);
}
という記述だけで、マスターから呼び出される都度1ずつインクリメントした値を返すプログラム。
スレーブのアドレスを自分の環境に合わせて変更してテスト環境に組み込んで実行したら、無事マスター側に値が表示された。
つまり、PICマイコン側のプログラムに問題があることが分かった。
実行結果
結局PICマイコン側の割り込みプログラムのSSPSTATbits.R_Wの判定等に問題点があることが判明し、前述のとおりのソースプログラムにすることで動作が確認できた。
次の確認項目は、マスター側からデータを送信し、スレーブ側で受信できるか。
=追記=
意外にマスター側から送信したデータをスレーブ側で受信するように改修するもの簡単にできたので、ソースプログラムを等をここに記載しときます。
通信テストプログラム改修版
マスター側から1バイトのデータを送信、スレーブ側でその値を1インクリメントしてマスター側に戻す。
マスター側はその値をコンソールに表示し、さらに1インクリメントしてスレーブ側に送信する。
という繰り返し。
マスター
マスター側はRaspberry Pi3。
プログラムはC言語でgccでコンパイル。
Raspberry Pi用のGPIOインターフェイスライブラリ「wiringPi」を使用するので事前にインストールしておく。
ちなみにアタイはトラブルがありながらもインストール完了。
i2ctest.c
//コンパイル // gcc -o i2ctest i2ctest.c -lwiringPi // //実行 // ./i2ctest // //停止 // コンソールからctrl+cで停止 #include #include #include #define SLAVE_ADDRESS 0x10 //スレーブアドレス static int dev; static unsigned char data=0; int init_dev(void); unsigned char writeData(void); unsigned char readData(void); void main(int, char **); int init_dev(void){ return ((dev = wiringPiI2CSetup(SLAVE_ADDRESS)) == -1) ? FALSE: TRUE; } //******************* // スレーブに1バイト送信 //******************* unsigned char writeData(){ return (wiringPiI2CWrite(dev, data) == -1) ? FALSE: TRUE; } //******************* // スレーブから1バイト取得 //******************* unsigned char readData(){ // wiringPiI2CReadの戻り値-1はエラー。 // 実際に使用するAPではスレーブ側から-1を戻さないこと。 return(wiringPiI2CRead(dev)); } //******************* // ・スレーブ側に1バイト送信 // ・スレーブ側から1バイト受信 // ・受信したデータをインクリメントして送信処理に戻る //******************* void main(int argc, char **argv){ if(init_dev()==FALSE){ printf("error init_dev()\n"); return; } while(TRUE){ delay(1000); // 1秒待ち if(writeData()){ printf("PUT :[0x%02x]\n",data); } else { printf("error writeData()\n"); return; } data = readData(); printf("GET :[0x%02x]\n\n",data); data++; } }
コンパイルと実行はソースプログラムのあるディレクトリで次のコマンドを実行。
gcc -o i2ctest i2ctest.c -lwiringPi
//実行
./i2ctest
スレーブ側
スレーブ側はPIC16F886。
プログラムはC言語でxc8でコンパイル。
//*************************** // PIC16F886 // I2C テスト // PIC16F886がスレーブとなって、マスター側から受信したデータをインクリメントして送り返す //*************************** // ピンアサイン // #1 MCLR // #2 RA0 // #3 RA1 // #4 RA2 // #5 RA3 // #6 RA4 // #7 RA5 // #8 VSS (GND) // #9 RA7 // #10 RA6 // #11 RC0 // #12 RC1 // #13 RC2 動作確認LED -> 抵抗 -> GND // #14 RC3/SCK/SC I2C clock // #15 RC4/SDI/SDA I2C data // #16 RC5 // #17 RC6 // #18 RC7 // #19 VSS (GND) // #20 VDD +2.0V ~ +5.5V // #21 RB0 // #22 RB1 // #23 RB2 // #24 RB3 // #25 RB4 // #26 RB5 // #27 RB6/ICSPCLK // #28 RB7/ICSPDAT // コンパイラ: XC8 #include #include #include // PIC16F886 Configuration Bit Settings // 'C' source line config statements // 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 = ON // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR) #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 = ON // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled) #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 I2C_ADDRESS 0x10 unsigned char data = 0; void __interrupt() isr(void); void I2CPut(void); void main(int, char**); /* * メイン処理 */ void main(int argc, char** argv) { //********** // 初期化 //********** OSCCONbits.IRCF =0b111; // IRCF<2:0>=111 内臓クロックは8MHz CM1CON0bits.C1ON =CM2CON0bits.C2ON = 0;// コンパレータオフ ANSEL = ANSELH = 0x00; // ポートI/Oはすべてデジタル TRISA = 0x00; //RAポート 全て出力 (0:出力 1:入力) TRISB = 0x00; //RBポート 全て出力 (0:出力 1:入力) TRISC = 0b00011000; //RCポート RC3,RC4入力、他は出力 (0:出力 1:入力) //********** // I2C関連初期化 //********** //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 SSPADD = I2C_ADDRESS << 1; // 7bit address //********** // 割り込み初期化 //********** SSPIF = 0; // Clear MSSP interrupt flag SSPIE = 1; // I2C interrupt enable PEIE = 1; // Enable Peripheral interrupts GIE = 1; // Enable global interrupts RC2 = 1; // 確認用LED点灯 while(1) { } } /* * 割込み処理 */ void __interrupt() isr( void ){ if (SSPIF == 1) { // SSP(I2C)割り込み if(SSPSTATbits.R_W == 1){ // Read/Write bit informationはマスタからのRead unsigned char dummy = SSPBUF; // 空読み I2CPut(); // 1byte送信 }else{ // Read/Write bit informationはマスタからのWrite data = SSPBUF; // データ読出し } SSPCONbits.CKP = 1; // SCLラインを開放(通信再開) SSPIF = 0 ; // 割込みフラグクリア } } /* * 1byte送信処理 * 変数dataの値をインクリメントして送信する */ void I2CPut() { while(SSPSTATbits.BF){}; // バッファフルの間は待ち do { SSPCONbits.WCOL = 0; // Write Collision Detect bit クリア SSPBUF = ++data; } while(SSPCONbits.WCOL); // 送信完了まで待ち }
このプログラムはあくまでRaspberry PiとPICマイコンが電圧レベル変換モジュールを介して通信ができることを確認するための簡易に作成した物です。
実際に使用するアプリを作るときにはSSPSTATなどを詳細に判定して処理を振り分けるなどの見直しが必要です。
SSPSTAT、SSPCON、SSPIFなどのレジスタはPICマイコンによってはSSP1STAT、SSP1CON、SSP1IFなどと異なっている場合があります。
使用するPICマイコンのデータシートを見て確認する必要があります。
実行結果は次のような感じ。