i2C(Wire)通信で教えてください。

GR-SAKURAです。

スレーブの受信で、受信ハンドラーのEventReceive関数を呼ばれるのですが、loop関数に戻ってこない。

マスターから1文字送ると受信文字数available()で0を読みます。この時loop関数から割り込みでWireの割り込みになっていると思います。

もう一度1文字送るとavailable()は1をだして、送った文字がreadで読めます。

この事からEventReceive関数は受信のたびに呼ばれていることはわかります。

しかしloop関数に戻ってこない。

間違いが分かる方いたら教えてください。

以下ソースです。

/* GR-SAKURA Sketch Template V2.24 */
#include <Arduino.h>
#include <wire.h>

void requestEvent( void )
{

    Wire8.write( "Hello ") ;
    Serial.println("I2c Request");

}

char I2cRcvData[32] ;
char iat;                                      // I2C 1 Byte Read
int bufc ;

void EventReceive( int numbyte )
{
    digitalWrite(PIN_LED0,1);                    // LED1 ON

    bufc = 0 ;
    while( Wire8.available() ) {
           digitalWrite(PIN_LED1,1);                    // LED OFF!

        bufc++ ;
        iat = Wire8.read() ;                                 //  1バイトの読込み        bufc++ ;
        I2cRcvData[bufc] = iat ;
        if( iat == 0x0a ) {
            I2cRcvData[0] = bufc ;                         // 受信受信文字数
            I2cRcvData[bufc+1] = '\0' ;
        }
        Serial.print( iat );                                   // 受信文字
    }
}

void setup()
{

    Serial.begin(57600);

    pinMode(PIN_LED0,OUTPUT);
    pinMode(PIN_LED1,OUTPUT);
    pinMode(PIN_LED2,OUTPUT);
    pinMode(PIN_LED3,OUTPUT);

}

void loop()
{

    Serial.println("I2c INIT");

    Wire8.begin(0x40) ;
    Wire8.onRequest(requestEvent);              // 送信要求がMasterから来たら呼ばれる
    Wire8.onReceive(EventReceive);              // Masuterからデータ受信したら呼ばれる

    while( 1 ) {
        digitalWrite(PIN_LED2,1);                    // LED2 ON Ether ON
        delay(500) ;
        digitalWrite(PIN_LED2,0);                    // LED2 ON Ether OFF!
        delay(500) ;
    }
}

  • Arduino Wireライブラリを使ったことはないですが、Arduinoのloopは基本的にちゃんと定期的に戻りが必要だったはずです(がじぇっとルネサスもかは確証なし)。まずはwhile(1)というのはやめましょう。loopの呼び出し元がloopの前後で色々やってるはず。

    Wireの使い方は以下をご参考に

    https://deepbluembedded.com/arduino-i2c-slave/

    なぜloop()で止まってダメなのかは実際のloop呼び出しコードをみてみればわかりますが、古いコードの時の場合、loop呼び出し戻ってきた時にシリアルバッファ処理が実行されてます。while(1)でブロックしてしまうと非同期な処理が止まってしまいます。

    https://forum.arduino.cc/t/void-loop/297970/8

  • 回答ありがとうございます。

    loop()に戻すようにしてみましたが結果は同じでした。
    マスターから1Byte 送るとスレーブのLEDの点滅が止まってしまう同じ結果でした。
    void setup()
    {

        Serial.begin(57600);

        pinMode(PIN_LED0,OUTPUT);
        pinMode(PIN_LED1,OUTPUT);
        pinMode(PIN_LED2,OUTPUT);
        pinMode(PIN_LED3,OUTPUT);

        Wire8.begin(0x40) ;
        Wire8.onRequest(requestEvent);              // 送信要求がMasterから来たら呼ばれる
        Wire8.onReceive(EventReceive);              // Masuterからデータ受信したら呼ばれる

    }

    void loop()
    {

        digitalWrite(PIN_LED2,1);                    // LED2 ON Ether ON 接続!
        delay(500) ;
        digitalWrite(PIN_LED2,0);                    // LED2 ON Ether ON 接続!
        delay(500) ;
     
    }

  • EventReceiveはwhileから抜けてますか?

    Wire8.read()はデータがない場合、ブロックする仕様だったはず

    void EventReceive( int numbyte )
    {
        digitalWrite(PIN_LED0,1);                    // LED1 ON

        bufc = 0 ;
        while( numbyte ) {
               digitalWrite(PIN_LED1,1);                    // LED OFF!

            bufc++ ;
            iat = Wire8.read() ;                                 //  1バイトの読込み        bufc++ ;
            I2cRcvData[bufc] = iat ;
            if( iat == 0x0a ) {
                I2cRcvData[0] = bufc ;                         // 受信受信文字数
                I2cRcvData[bufc+1] = '\0' ;
            }
            Serial.print( iat );                                   // 受信文字

            numbyte --;
        }
    }

    こうだとどうですか?これで解決ならWire8.available() の実装がおかしい可能性もありますね。

  • read()ですが確認したらStreamの派生だったので戻り値はintでデータない時は-1を返す仕様でした。ブロックはないです。Wire8.read()の戻り値でwhile終了判定をしてもいいかもしれませんね。

    github.com/.../Wire.h

  • 回答ありがとうございます。

    numbyteをfor文にして実行しましたが、結果は同じでした。

    もっと簡単に以下にして1文字を送信したところ、1回目にはLED0が点灯しない。

    EventReceiveが呼ばれていない状態で、loop()の点滅も止まっている状態です。

    2文字目を送ると、EventReceiveが呼ばれて、LED1,LED2が点灯します。

    loop()の点滅も止まっている状態です。

    このことから、1文字目送信のどこかのハードステータスを読んでいる所でループしている?

    void EventReceive( int numbyte )
    {
        digitalWrite(PIN_LED0,1);           // LED ON
        iat = Wire8.read() ;                      // 1バイトの読込み
        digitalWrite(PIN_LED1,1);           // LED ON
    }

  • これは実際の信号波形やマスタ側のI2Cコードの確認も必要な気がします。マスタ側もGR-SAKURAでWireライブラリを使っていますか?

    ぶっちゃけ、マイコン間通信はUARTでいいような気がします。そういう意味でWireのスレーブモードは使ったことがありません。

  • 調べました。

    どうも割り込みハンドラに問題ありそうなので、RX63Nのマニュアルをみてスレーブ受信動作についてi2c通信のスレーブ受信のフローチャート例と、スレーブ受信の動作タイミングを見ると、INTハンドラーの中でフロー通りになっていない。

    これだと1度目の送信では、データが出ないが、2度目の送信で2度目の割り込みになり、STOPフラグが1になっているので、

    前に送った電文がバッファから読める。

    LOOPには戻ってこないが、同じ電文を送り続けるとまるで正しく動いているように見えるが、実際はRDEFフラグを見るところで永遠にループしている。

    twi_rx.c

    void INT_Excep_RIIC0_RXI0(void){

        if(RIIC0.ICCR2.BIT.MST == 0){ // slave mode
            twi_rxBufferIndex = 0;
         char b = RIIC0.ICDRR; // dummy read
        while(!RIIC0.ICSR2.BIT.STOP){ // detect stop condition

    // ↓ここのRDRFフラグを1になるのをループしているが、0ではフローでSTOPフラグを見るように戻っている

    // つまり電文の終了を検知できない状態で最後の文字を取った後ここでループしている。

            while(!RIIC0.ICSR2.BIT.RDRF); // wait for data
                  if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
                        // put byte in buffer and ack
                        twi_rxBuffer[twi_rxBufferIndex++] = RIIC0.ICDRR;
                   }
             }
             RIIC0.ICSR2.BIT.STOP = 0;
             // put a null char after data if there's room
             if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
                     twi_rxBuffer[twi_rxBufferIndex] = '\0';
             }
             // callback to user defined callback
             twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
             // since we submit rx buffer to "wire" library, we can reset it
             twi_rxBufferIndex = 0;
        }
    }

    修正した内容(私は動きましたが、使う時は自己責任でお願いします)

    void INT_Excep_RIIC0_RXI0(void){

    if(RIIC0.ICCR2.BIT.MST == 0){ // slave mode

    if (RIIC0.ICSR2.BIT.STOP){ // detect stop condition

    RIIC0.ICSR2.BIT.STOP = 0;

    return ;

    }

    twi_rxBufferIndex = 0;

    char b = RIIC0.ICDRR; // dummy read

    while(!RIIC0.ICSR2.BIT.STOP){ // detect stop condition

    if(!RIIC0.ICSR2.BIT.RDRF) // wait for data

    continue;

    if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){

    // put byte in buffer and ack

    twi_rxBuffer[twi_rxBufferIndex++] = RIIC0.ICDRR;

    }

    }

    if(RIIC0.ICSR2.BIT.RDRF) // wait for data

    twi_rxBuffer[twi_rxBufferIndex++] = RIIC0.ICDRR;

    // RIIC0.ICSR2.BIT.STOP = 0;

    // put a null char after data if there's room

    if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){

    twi_rxBuffer[twi_rxBufferIndex] = '\0';

    }

    // callback to user defined callback

    twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);

    // since we submit rx buffer to "wire" library, we can reset it

    twi_rxBufferIndex = 0;

    }

    }