ARMプロセッサを搭載したシングルボードコンピュータ・Raspberry Pi(ラズベリー パイ)。3になってWi-FiやBluetoothも使えるようになった。Nケージレイアウトの制御用に導入してみた。・・ということでレイアウト関連の作業はまたまた脱線。
I2C通信・・複数バイトの受信終了がわからない・・
ラズベリーパイとPICマイコン間でのI2Cプログラムを作成中。
だが、スレーブ側で複数バイト受信したときの終了タイミング、判定方法がよくわからん。
マスター側はラズベリーパイ、スレーブ側はPICマイコン(PIC16F886)。
マスター側はc言語でGPIOインターフェイスライブラリ「wiringPi」を使用。
スレーブ側もc言語。
I2C通信の確認プログラムに掲載したような内容の1バイトのデータをマスター側とスレーブ側で交互に送受信するプログラムを作ってみた。
これは動作したが、実際に使用するには数バイトのデータのやり取りが必要になりそう、ということで複数バイトの送受信をやってみることにした。
複数バイトの送信
「wiringPi」には任意の複数バイトのデータを送信する関数はなさそう。
では、どうするか・・
単純にwrite();関数で送信できた。
#include char buf[5] = {0x30,0x31,0x32,0x33,0x00}; dev = wiringPiI2CSetup(SLAVE_ADDRESS); int result = write(dev,buf,sizeof(buf));
スレーブ側では割り込みの都度1バイトずつ取得できることが確認できた。
受信終了の判定
「I2C通信の確認プログラム」でテストしたプログラムは1バイトずつ交互に送受信していた。
データは1バイトと分かっているので、データを受信したときの割り込みがかかれば、そのデータを元に処理を行うロジックを作り込むことができる。
ただ、バイト数が任意のサイズのデータの受信完了はどうやって判定するのだろう?
ラズベリーパイ側とPICマイコン側の双方での受信があるが、ひとまずPICマイコン側で受信・・ということをやってみた。
PICマイコンのデータシートやら他人様のHPを拝見すると、
・アドレスデータが来る
・データが来る
・データが来る
・:
・ストップビットが立つ
という流れになるらしい。
とすればPIC16F886では、次のような形でストップビットが判定できる・・ハズ。
で、試してみた。
void __interrupt() isr( void ){ if (SSPIF == 1) { if(SSPSTATbits.R_W == 1){ // スレーブからの送信モード if(SSPSTATbits.P == 1){ //ここでならストップビットが立っている・・ことがある //~以降省略~ }else{ // SSPSTATbits.R_W == 0 // マスターからの受信モード if(SSPSTATbits.P == 1){ //ここではストップビットは立っていない //~以降省略~
マスター側からデータの送信(wiringPiI2CWrite)が終わり、データの受信(readData())命令が発行されたとき、スレーブ側はSSPSTATbits.R_W == 1になる。
このとき、ストップビットは立っているようだ。
が、この状態は・・・「SSPSTATbits.R_W == 1」を判定すれば送受信が切り替わったことが分かるので、特にストップビットを判定する必要はない。
今回のテストプログラムはマスター1個、スレーブ1個。
しかも、マスター側はスレーブ側にデータを送信したあと、直ちにスレーブ側からの受信命令(readData())を発行している。
これが、複数のスレーブがあって、マスターが他のスレーブと通信を始めたり、マスター側が別処理に移行し直ちにこのスレーブに対して受信命令を発行しなければ、スレーブ側では送受信の切り換わりが判定できないことになる。
ダメじゃん・・
「STOP Condition」以外にも「Repeated Start Condition」というものがあり、ストップビットが立たず別のビットが立つのか?と思ったが、どうも違うみたい。
ネットで色々調べたが、どうも複数バイトの受信終了を判定する例がほんの少ししか見当たらない。
あいにく、それらでは解決に至らなかった。
自分でなんとかせにゃならん。
・・このままではNゲージのポイントマシンが動かん。
スレーブモード波形図を見てみる
PIC16F886のデータシートにあるスレーブモードの波形図を見てみた。
アドレスやデータなど1バイト分の受信し、ACKを返送後にSSPIF(割り込みフラグ)が立っている。
右端にある肝心のストップビットのところはオーバーフロービットが立っているのでSSPIFが立つ瞬間は表示されていない。
ただし、そこまでの流れを見ると、ACKを返送後に赤線の部分でSSPIFが立った後に、オレンジ色の部分でストップビットが立つようになるみたい。
つまり、データをして受信してSSPIFが立って割り込みルーチン(__interrupt() isr())に飛び込んだ時にはストップビットは立っていない。
Stop Condition Enable bit
PIC16F886では、SSP CONTROL REGISTER 2(SSPCON2)に「PEN」というビットがある。
「1 = Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.」
I2Cモジュール使用開始時にこのビットを立ててりゃ、ストップビットが立った時に割り込んでくれるのかい?とも思ったが、 (in I2C Master mode only) と書かれている。
スレーブ側なれど、試しにビットを立て、割り込みルーチンで見てみたが・・何も起こらなかった。
データの中身で判定しよう
このまま試していても時間がたつのみ。
ひとまず、この状態でデータの終了を判定することを考えることにした。
まぁ、順当な方法としてはデータの中身をみて、その値で判断することか。
つまり、複数バイトのデータの末尾にヌル(0x00)をつけて送り出し、受信側ではそれを見つけたらデータ終了と判定する。
どうしてもヌルを送りたい要件があるのなら、まずレングスのデータを1バイト送り、その後データを送る。
ちなみに、マスター側のwiringPiの「wiringPiI2CRead()」関数は-1をエラーの戻り値として使用している。
つまり、スレーブ側から-1(0xff)を1バイトのみ送信する仕様も併用すると、マスター側のAPはエラーの戻値との判別がつかない。
データが0x00はダメ、0xffはダメなどとするとどんどん制限だらけになってしまう。
自分自身の環境のなかで使うものなので・・・なんとでも好きなように決めることができるが。
ひとまず、次のような形で決着しよう。
先頭1バイトはデータのレングス、それに続けて複数バイトのデータ
つまり、こんな感じ。
レングス 割り込み データ 割り込み データ 割り込み データ 割り込み
受信を始めて一番最初の割り込みで受信したデータが今回受信するデータの長さ。
例えば上記の例では3。
次からの割り込みでは受信したデータを繋げていき、3回目の割り込みで受信したデータを末尾に繋げは受信終了。
ちゃんと割り込みなどで判定できるようになったら・・
そのときは・・・かなりプログラムを作り込んでいたらアタイは放置するかも知れない横着者です。
その後の状況
2020/02
こちらのサイトによると、Raspberry Pi の I2C ドライバ(i2c_bcm2708)はデフォルトでは Repeated Start Conditionに対応していないらしい。
つまり、ACK の後に STOP コンディションにせず,続けて START を発行するとのこと。
で、Raspberry Pi ので I2C の Repeated Start Condition を有効化するには、
- 「/etc/modprobe.d/i2c.conf」というファイルを作成
内容↓
「options i2c_bcm2708 combined=1」
とのこと。
やってみたが、ストップビットは立たないみたい・・・
送信側のプログラムはc言語、データを書き込んだのはwrite命令。
どうやら、送信側のプログラムでストップビットを立てるにはどうやったらいいのか?というのが鍵になるっぽい・・と思う。
;
しかし、しかし・・さらに調べてたら。
こちらのサイトによるとRaspberry Piでストップビットを自在に操るのは中々困難な模様。
既製品の市販スレーブモジュールを使う場合、マスター側もその仕様に準拠する必要があるが、マスター側(今回はRaspberry Pi)もスレーブ側(今回はPICマイコン)もAPを自作するのであれば、無駄なあがきはせず、安直な方法で回避した方が良いかも。
と、いうことでアタイは「データの中身で判定しよう」に書いたとおり、「最初のデータにこれから送信するデータ長を含めておき、指定されたバイト数のデータを受け取ったら受信終了」という方法にします・・
2020/02/22
で、その仕様で動作するテストプログラムを作成してみた。
ソースプログラムは「I2C通信・・複数バイトの送受信テスト・・」のページで。