yoshiyuki's blog

Arduino/Teensy/Raspberry pi pico を利用した I2C, SPI 通信アプリを紹介します

Teensy 4.0 / I2C ができるまで (6)

前回までで I2C Write/Read 共に成功した、と思ったら落とし穴に落ちたお話です。
テンプレですが、I2C通信をやりたいだけなら Wireライブラリを使った方が簡単、確実です。

最終的には I2C 関数の自作に至ります。
ysin1128.hatenablog.com

さらに、I2C に加えて SPI と GPIO も制御する Sketch とツールも作成しました。
ysin1128.hatenablog.com

Master-Slave間の接続

以前と同じ。

Burst-Read

Burst-Read ができるまでの話です。 Burst-Read とは Slave device から一気に複数-byte の値を読み出すことを言います。Slave device 側も Burst-Read に対応していないと使えませんが、I2C を扱うようになったここ5年以上で対応していない Device に出会ったことが無いので、その辺の Device を拾ってきてもまず対応していると思われます。もしかすると、機能は同じでも呼び名は Burst-Read ではないかもしれません。

Burst-Read に失敗する

1-byte の Read が上手く動作したので Burst-Read もすぐにできるだろうと思って作成した Sketch がこちらです。setup の中身は前回までと同じです。

int cntDAT;
int i;
byte inDAT;
byte outDAT;
uint32_t pageTMP;

IMXRT_LPI2C_t *port_i2c;
IMXRT_REGISTER32_t *port_iomuxc;
IMXRT_REGISTER32_t *port_iomuxc_b;

void setup() {
  Serial.begin(9600);

  port_i2c = &IMXRT_LPI2C1; //0x403F0000
  port_iomuxc = &IMXRT_IOMUXC;  //0x401F8000
  port_iomuxc_b = &IMXRT_IOMUXC_b;  //0x401F8400

  // #19 = SCL, GPIO_AD_B1_00
  port_iomuxc->offset0FC = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SCL)
  port_iomuxc->offset2EC = 0x0000F861; // Pull-up = Enable, PUS = 11 (22kohm), PUE = 1, PKE = 0, ODE = 1, SPEED = 01, DSE = 100, SRE = 1
  port_iomuxc_b->offset0CC = 0x00000001; // DAISY = 1

  // #18 = SDA, GPIO_AD_B1_01
  port_iomuxc->offset100 = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SDA)
  port_iomuxc->offset2F0 = 0x0000F861; // Pull-up = Enable                                                                                                                                                                                                     
  port_iomuxc_b->offset0D0 = 0x00000001; // DAISY = 1

  // I2C Clock
  CCM_CSCDR2 = (CCM_CSCDR2 & 0xFE03FFFF) | 0x00040000;
  CCM_CCGR2 |= 0x000000C0; // CG3 = 11, enable LPI2C1 clock

  // LPI2C1 Setting
  port_i2c->MCFGR1 = 0x00000001;
  port_i2c->MCFGR2 = 0x05050BB8; // FILTSDA = 5 (0.2 us), FILTSCL = 5 (0.2 us), BUSIDLE = 3000 (250 us)
  port_i2c->MCCR0 = 0x1928373B; // DATAVD = 25 (2.2 us), SETHOLD = 40 (3.4 us), CLKHI = 55 (4.7 us), CLKLO = 59 (5 us)

  port_i2c->MCR = 0x00000001;
}

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){    
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;

    for(i=0;i<(inDAT - 0x30);i++){
      port_i2c->MTDR = 0x00000100;

      while((port_i2c->MFSR & 0x00070000) == 0){
        delayMicroseconds(100); 
      }

      outDAT = port_i2c->MRDR & 0xFF;
      Serial.print(outDAT);        

      Serial.print("-");      
    }

    port_i2c->MTDR = 0x00000200;
    Serial.print("\n");
  }

  delay(1000);
}

シリアルモニタから 1 から 9 までの数字を受け取ると Teensy は Slave device の Register address = 0x00 を先頭に (受け取った数字)-byte の値を読み出してシリアルモニタに返す、という内容です。例えばシリアルモニタから 4 を送ると Slave device の Register address = 0x00 から 0x03 までの 4-byte 分の値を読み出してそれをシリアルモニタに返すはずです。それに対して実際にシリアルモニタから 4 を送った結果が以下になります。

f:id:ysin1128:20210303150432p:plain

4 を送ったのに 4-byte どころか最初の 1-byte しか返ってきません。失敗です。

原因を考える

Burst-Read に失敗した原因を調べます。まずヒントですが、実は先の失敗したシリアルモニタの画面においてさらに数字を送ってみたのですが、さっきは返ってきた "0-" すらも返って来なくなっていました。また、Teensy の Sketch を書き換えようとすると Arduino IDE から「書き込めないから Teensy をリセットしろ」というようなことを言われます。つまり、Teensy が応答しない状態になっていたのです。
Teensy が応答しない原因なら先の Sketch の以下の部分で発生する可能性があります。

    for(i=0;i<(inDAT - 0x30);i++){
      port_i2c->MTDR = 0x00000100;

      while((port_i2c->MFSR & 0x00070000) == 0){
        delayMicroseconds(100); 
      }

      outDAT = port_i2c->MRDR & 0xFF;
      Serial.print(outDAT);        

      Serial.print("-");      
    }

for の中では、まず MTDR に Command = 0x1 (I2C read) を書き込んで Read が完了するのを while で MFSR を見張りながら待ちます。MFSR の 18-16 bit が I2C通信の受信FIFOに蓄えられたデータ数を示すため、これが 0 の間は Read が終わっていないことを意味します。
受信FIFO に Read したデータが蓄えられたら while を抜け、MRDR を通して FIFO の中のデータを読み出してシリアルモニタに送ります。
これを for で inDAT の回数だけ繰り返すことで連続したレジスタの値を inDAT で指定した数だけ一気に読み出すことができます。(for の条件の inDAT から 0x30 を引いているのは ascii code の "1" を数値の 1 に変換するため)

この中で while は一般的に無限ループの元で、Teensy が応答しなくなる原因になり得ます。というわけで、まずはここを疑ってタイムアウトを仕込んでみます。

int cntDAT;
int i;
int cnt_timeout;
byte inDAT;
byte outDAT;
uint32_t pageTMP;

IMXRT_LPI2C_t *port_i2c;
IMXRT_REGISTER32_t *port_iomuxc;
IMXRT_REGISTER32_t *port_iomuxc_b;

void setup() {
省略
}

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){    
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;

    for(i=0;i<(inDAT - 0x30);i++){
      port_i2c->MTDR = 0x00000100;

      cnt_timeout = 0;

      while((port_i2c->MFSR & 0x00070000) == 0){
        delayMicroseconds(100); 

        cnt_timeout++;

        if(cnt_timeout == 10000){
          break;
        }
      }

      if(cnt_timeout == 10000){
        Serial.print("Timeout");            
      }            
      else{
        outDAT = port_i2c->MRDR & 0xFF;
        Serial.print(outDAT);        
      }
      Serial.print("-");      
    }

    port_i2c->MTDR = 0x00000200;
    Serial.print("\n");
  }

  delay(1000);
}

100 us x 10000 = 1秒待っても受信FIFOにデータが入ってこない場合はタイムアウトと判定し、タイムアウトと判定された場合は MRDR から読み出した値ではなく Timeout という文字列をシリアルモニタに返すようにしました。以下が結果です。送った数字は 4 です。

f:id:ysin1128:20210303160555p:plain

最初の 0 は MRDR から読み出した、つまり、Slave device から I2C で読み出した値であり、後は Timeout が 3回という結果になっています。この結果から、Teensy はここで while の無限ループに陥っていたことが確認できました。

MSR (Master Status Register)

次の疑問は、どうしてここで無限ループに陥ってしまうのかという点です。while の直前に MTDR に Read コマンドを書き込んでいるので、待っているだけで受信FIFO に読み出した値が入ってきて while を抜けることができるはずです。つまり、Read コマンドを指示しているにも関わらず Teensy が Read を実行しないという状況にあるようです。何らかのエラーが発生して、それにより Teensy が止まっている可能性が考えられます。

というわけで先ほどのタイムアウトを加えた Skech において、タイムアウト発生時にシリアルモニタに返すものを Timeout の文字列ではなく MSR というレジスタの値に変更してみました。

      if(cnt_timeout == 10000){
        // Serial.print("Timeout");            
        Serial.print(port_i2c->MSR);
      }            
      else{
        outDAT = port_i2c->MRDR & 0xFF;
        Serial.print(outDAT);        
      }

以下はその結果です。送った値は 4 です。

f:id:ysin1128:20210303162159p:plain

タイムアウト時には MSR の値として 4864 = 0001_0011_0000_0000 となっているようです。
MSR は LPI2C モジュールの中の状態やエラーを教えてくれるレジスタです。で、ここでいくつか 1 が出ている bit の意味は以下の通りです。

13 bit: Start Condition の生成無しで MTDR に Write や Read のコマンドを書き込むと 1 になる
9 bit: Stop Condition が生成されると 1 になる
8 bit: Repeated Start Condition または Stop Condition が生成されると 1 になる

Repeated Start Condition は MTDR = 0x00000448 の後に Stop Condition を挟まずに MTDR = 0x00000449 で Start Condition を生成させたことを指すので 8 bit が 1 になっているのは動作の通りです。
13 bit について、8 bit が 1 になっているということは Start Condition の生成が 2 回とも認識されているということなので、Start Condition 無しで MTDR = 0x00000100 か何かを書き込んだと判定される原因が分かりません。しかし、もし予期しない Stop Condition がどこかで生成されていると仮定すると、その後の MTDR = 0x00000100 の書き込みでこの 13 bit が 1 になることも、また、Stop Condition 生成そのものを示す 9 bit が 1 になっていることも、どちらも説明が付きます。
というわけで、状況証拠からとなりますが、この予期しない Stop Condition の可能性を追いかけてみます。

Stop Condition の出所

Stop Condition の謎を追って Teensy の本体である IMXRT1062 の Reference Manual を読み返してみたところ、Teensy には Stop Condition の自動生成という機能があることが分かりました。この機能は I2C の制御において、MTDR に書き込んだコマンドがいったん送信 FIFO に蓄えられて順次、処理されていき、この送信 FIFO に蓄えられたコマンドが全て処理されて空になると I2C 通信が終わったものと判断して自動的に Stop Condition を生成するというものです。ただし、この機能は MCFGR1 の 8 bit 目 に 1 を書き込まないと有効にならないので MCFGR1 = 0x00000001 を書き込んでいる今回の場合は関係無さそうです。
・・・無さそうなのですが、気にはなるので確認することにしました。そのための Sketch が以下です。

int cntDAT;
int i;
byte inDAT;
byte outDAT;
uint32_t pageTMP;

IMXRT_LPI2C_t *port_i2c;
IMXRT_REGISTER32_t *port_iomuxc;
IMXRT_REGISTER32_t *port_iomuxc_b;

void setup() {
  Serial.begin(9600);

  port_i2c = &IMXRT_LPI2C1; //0x403F0000
  port_iomuxc = &IMXRT_IOMUXC;  //0x401F8000
  port_iomuxc_b = &IMXRT_IOMUXC_b;  //0x401F8400

  // #19 = SCL, GPIO_AD_B1_00
  port_iomuxc->offset0FC = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SCL)
  port_iomuxc->offset2EC = 0x0000F861; // Pull-up = Enable, PUS = 11 (22kohm), PUE = 1, PKE = 0, ODE = 1, SPEED = 01, DSE = 100, SRE = 1
  port_iomuxc_b->offset0CC = 0x00000001; // DAISY = 1

  // #18 = SDA, GPIO_AD_B1_01
  port_iomuxc->offset100 = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SDA)
  port_iomuxc->offset2F0 = 0x0000F861; // Pull-up = Enable                                                                                                                                                                                                     
  port_iomuxc_b->offset0D0 = 0x00000001; // DAISY = 1

  // I2C Clock
  CCM_CSCDR2 = (CCM_CSCDR2 & 0xFE03FFFF) | 0x00040000;
  CCM_CCGR2 |= 0x000000C0; // CG3 = 11, enable LPI2C1 clock

  // LPI2C1 Setting
  port_i2c->MCFGR1 = 0x00000001;
  port_i2c->MCFGR2 = 0x05050BB8; // FILTSDA = 5 (0.2 us), FILTSCL = 5 (0.2 us), BUSIDLE = 3000 (250 us)
  port_i2c->MCCR0 = 0x1928373B; // DATAVD = 25 (2.2 us), SETHOLD = 40 (3.4 us), CLKHI = 55 (4.7 us), CLKLO = 59 (5 us)

  port_i2c->MCR = 0x00000001;
}

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){    
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;
    port_i2c->MTDR = 0x00000100;

    for(i=0;i<12;i++){
      delayMicroseconds(50); 

      Serial.print(port_i2c->MCFGR1);
      Serial.print("-");      
      Serial.print(port_i2c->MFSR & 0x00000007);
      Serial.print("-");      
      Serial.print((port_i2c->MFSR & 0x00070000)>>16);
      Serial.print("-");      
      Serial.print(port_i2c->MSR & 0xFFFF);
      Serial.print("\n");      
    }

    Serial.print("\n");      

    port_i2c->MTDR = 0x00000100;
    port_i2c->MTDR = 0x00000100;

    for(i=0;i<10;i++){
      delayMicroseconds(50); 

      Serial.print(port_i2c->MCFGR1);
      Serial.print("-");      
      Serial.print(port_i2c->MFSR & 0x00000007);
      Serial.print("-");      
      Serial.print((port_i2c->MFSR & 0x00070000)>>16);
      Serial.print("-");      
      Serial.print(port_i2c->MSR & 0xFFFF);
      Serial.print("\n");      
    }
  
  }

  delay(1000);
}

シリアルモニタから何かを送ると動作します。
最初の Burst-Read を試そうとして上手く動作しなかった Sketch と同様のコマンドを送った際の送信 FIFO に蓄えられたコマンドの数、受信FIFO に蓄えられた読み出しデータの数、MSR の値の推移を見張ります。ついでに MCFGR1 の値も見張ります。
以下が結果です。

f:id:ysin1128:20210303171502p:plain

各行とも値は以下の順番になっています。

(MCFGR1 の値)-(送信 FIFO に蓄えられたコマンドの数)-(受信 FIFO に蓄えらえた読み出しデータの数)-(MSR の値)

MCFGR1 の値はずっと 1 で、Stop Condition を自動生成する機能は無効のままとなっていることが確認できました。
送信FIFO からは、実際に送信 FIFO の値が 0 になっても MSR の値は 257 = 0000_0001_0000_0001 または 259 = 0000_0001_0000_0011 となっていて、Stop Condition が生成されたことを示す 9 bit は 1 になっていない、つまり、Stop Condition が生成されていないことが確認できます。
MSR の 8 bit が 1 になっているのは MTDR = 0x00000449 のコマンドが実行されるタイミングなので、Repeated Start Condition の生成によるものと判断できます。
MSR の 2 bit は 受信 FIFO にデータが蓄えらえれると 1 になり、1 bit は送信 FIFO に蓄えられたコマンドが空になると 1 になりますが、ここでは重要ではないので無視します。

さて、肝心の MSR の 13 bit と 9 bit が 1 になるタイミングですが、明らかに後半の MTDR = 0x00000100 を 2 回書き込んだ後となっています。この結果から、送信 FIFO が空になった後に MTDR に Read コマンドを書き込んだことで Stop Condition が生成されたと推測されます。

というわけで、ひとつ実験をしてみました。

Loop 以外は前の Sketch と同じなので省略

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){    
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;
    port_i2c->MTDR = 0x00000100;

    for(i=0;i<12;i++){
      delayMicroseconds(50); 

      Serial.print(port_i2c->MCFGR1);
      Serial.print("-");      
      Serial.print(port_i2c->MFSR & 0x00000007);
      Serial.print("-");      
      Serial.print((port_i2c->MFSR & 0x00070000)>>16);
      Serial.print("-");      
      Serial.print(port_i2c->MSR & 0xFFFF);
      Serial.print("\n");

      if(i == 0){
        port_i2c->MTDR = 0x00000100;
      }
    }

    Serial.print("\n");      

    port_i2c->MTDR = 0x00000100;
    port_i2c->MTDR = 0x00000100;

    for(i=0;i<10;i++){
      delayMicroseconds(50); 

      Serial.print(port_i2c->MCFGR1);
      Serial.print("-");      
      Serial.print(port_i2c->MFSR & 0x00000007);
      Serial.print("-");      
      Serial.print((port_i2c->MFSR & 0x00070000)>>16);
      Serial.print("-");      
      Serial.print(port_i2c->MSR & 0xFFFF);
      Serial.print("\n");      
    }
  
  }

  delay(1000);
}

送信FIFO が空になる前に MTDR = 0x00000100 を 1個だけ書き込む記述を追加しました。結果は以下の通りです。

f:id:ysin1128:20210303173625p:plain

送信FIFO が空になる前に書き込めば Read コマンドは正常に処理されているようで、MSR の 13 bit や 9 bit は 0 のままで、受信 FIFO に 2個目の受信データが蓄えられています。一方、送信 FIFO が空になった後の MTDR への書き込みに対する結果は同じです。

Burst-Read 用の Read コマンド

ここまでの実験結果から送信FIFO を空にしてしまうのが良くないのではないかという推測が立ちます。そこで思い出したのが Burst-Read時の I2C 通信の仕様です。
I2C通信で Burst-Read を行う際、Master は Slave に対して連続でデータの読み出しを行うことを伝える必要があります。そのために、Master が Slave から 1-byte 分のデータを読み出した際に、もし続けて次の 1-byte も読み出すのであれば Master は Slave に対して ACK を発行してそれを伝えます。この ACK の発行が無い場合、Slave device は読み出しが完了したものと認識して、続けて Master から SCL が送られてきてもデータを送り返したりはしません。
つまり、Burst-Read を行う場合は ACK を発行するよう Teensy に指示する必要があるのですが、Teensy で I2C 通信をやろうと思い立ってからここまでそれを全く意識していなかったのでそういう設定も指示も行った覚えがありません。なので、それが原因になっているのではないかと IMXRT1062 の Reference Manual を読み返して、それらしい答えを見付けるに至りました。

MTDR = 0x00000100 は、送信FIFO において次に処理されるコマンドも MTDR = 0x00000100 であった場合は ACK を発行し、違う場合は ACK を発行しません。

つまり、Burst-Read を行う場合は ACK を発行させるためには 送信 FIFO に MTDR = 0x00000100 を書き込み続ける必要があり、それが間に合わず送信 FIFO が MTDR = 0x00000100 を処理した後でいったん空になると Master は Slave に対して ACK を発行しなかった、つまり、続けての読み出しは行わないと伝えた後ということになり、その後の MTDR = 0x00000100 の書き込みは正しくない指示なのでエラーになる、という話だったようです。

Burst-Read 再挑戦

送信 FIFO を空にしないように変更した Sketch が以下になります。

int cntDAT;
int i;
byte inDAT;
int cntINDAT;
int cntOUTDAT;
byte outDAT[9];
uint32_t pageTMP;

IMXRT_LPI2C_t *port_i2c;
IMXRT_REGISTER32_t *port_iomuxc;
IMXRT_REGISTER32_t *port_iomuxc_b;

void setup() {
  Serial.begin(9600);

  port_i2c = &IMXRT_LPI2C1; //0x403F0000
  port_iomuxc = &IMXRT_IOMUXC;  //0x401F8000
  port_iomuxc_b = &IMXRT_IOMUXC_b;  //0x401F8400

  // #19 = SCL, GPIO_AD_B1_00
  port_iomuxc->offset0FC = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SCL)
  port_iomuxc->offset2EC = 0x0000F861; // Pull-up = Enable, PUS = 11 (22kohm), PUE = 1, PKE = 0, ODE = 1, SPEED = 01, DSE = 100, SRE = 1
  port_iomuxc_b->offset0CC = 0x00000001; // DAISY = 1

  // #18 = SDA, GPIO_AD_B1_01
  port_iomuxc->offset100 = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SDA)
  port_iomuxc->offset2F0 = 0x0000F861; // Pull-up = Enable                                                                                                                                                                                                     
  port_iomuxc_b->offset0D0 = 0x00000001; // DAISY = 1

  // I2C Clock
  CCM_CSCDR2 = (CCM_CSCDR2 & 0xFE03FFFF) | 0x00040000;
  CCM_CCGR2 |= 0x000000C0; // CG3 = 11, enable LPI2C1 clock

  // LPI2C1 Setting
  port_i2c->MCFGR1 = 0x00000001;
  port_i2c->MCFGR2 = 0x05050BB8; // FILTSDA = 5 (0.2 us), FILTSCL = 5 (0.2 us), BUSIDLE = 3000 (250 us)
  port_i2c->MCCR0 = 0x1928373B; // DATAVD = 25 (2.2 us), SETHOLD = 40 (3.4 us), CLKHI = 55 (4.7 us), CLKLO = 59 (5 us)

  port_i2c->MCR = 0x00000001;
}

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;

    cntINDAT = 0;
    cntOUTDAT = 0;

    while(1){
      if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT < (inDAT - 0x30))){
        port_i2c->MTDR = 0x00000100;
        cntINDAT++;
      }
      else if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT == (inDAT - 0x30))){
        port_i2c->MTDR = 0x00000200;
        cntINDAT++;
      }

      if((port_i2c->MFSR & 0x00070000) > 0){
        outDAT[cntOUTDAT] = port_i2c->MRDR & 0xFF;
        cntOUTDAT++;
      }

      if(cntOUTDAT == (inDAT - 0x30)){
        break;
      }
    }

    for(i=0;i<cntOUTDAT;i++){
      Serial.print(outDAT[i]);
      Serial.print("-");
    }
    
    Serial.print("\n");
  }

  delay(1000);
}

Teensy はシリアルモニタから 1 から 9 の数字を受け取ると、I2C で Slave device から(受け取った数)-byte のデータを読み出してシリアルモニタに返します。なお、シリアルモニタから 1 から 9 以外の数字や文字を受け取った場合は何が起きるか分かりません。
主な処理は以下の記述で行っています。

    while(1){
      if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT < (inDAT - 0x30))){
        port_i2c->MTDR = 0x00000100;
        cntINDAT++;
      }
      else if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT == (inDAT - 0x30))){
        port_i2c->MTDR = 0x00000200;
        cntINDAT++;
      }

      if((port_i2c->MFSR & 0x00070000) > 0){
        outDAT[cntOUTDAT] = port_i2c->MRDR & 0xFF;
        cntOUTDAT++;
      }

      if(cntOUTDAT == (inDAT - 0x30)){
        break;
      }
    }

送信FIFO/受信FIFO を見張って送信FIFO に空きができたら即座に MTDR = 0x00000100 を書き込み、受信FIFO にデータが入ってきたら即座に MRDR から読み出す、という処理を while で延々と繰り返すという内容になっています。cntOUTDAT が読み出したデータの数を示していて、これが inDAT で示す数に達したら while を抜けます。inDAT にはシリアルモニタから受け取った数字が代入されていて、そこから0x30 が引き算されているのは ascii code を数値に変換するためです。
送信FIFO を空にしていたのが問題だと分かったことから、送信FIFO を何がなんでも空にするものかという意思を記述に込めました。同時に、受信FIFO の方もデータが入ったら即座に読み出すようにしています。これらの苦労は全て送信FIFO/受信FIFO が共にコマンド/データ 4個分の容量しか持っていないためです。例えばこれらがそれぞれ 256個ずつ入るのであれば、用意したコマンド全てを送信 FIFO に一気に放り込んで、コマンド処理が完了するのをゆっくり待ってから受信 FIFO のデータを読み出せば良いのですが、残念ながら Teensy はどちらも 4個ずつなので送信FIFO が空にならないように、受信FIFO が溢れないようにと気遣う必要があり、それを私なりに形にしたのがこの記述です。たぶん、やりすぎです。
ところで、無責任な while は何かあったらまた無限ループを引き起こす元になるのであまり良くないのですが、今回は実験なのでタイムアウト等は考えないことにしました。
シリアルモニタで 1 から 9 までの数字を順番に送った結果が以下です。

f:id:ysin1128:20210304145827p:plain

Slave device のレジスタ値を Register address = 0x00 から順に送った数字の数だけ Burst-Read できているので成功です。

Burst-Write

Burst-Read と同様に Slave device のレジスタに対して複数-Byte の値を一気に書き込むことを Burst-Write と呼びます。
Burst-Read で苦労した分、Burst-Write は何の苦労もせずに成功したので Sketch と結果だけを置いておきます。シリアルモニタから何でも良いので値を送ると Teensy は以下の動作を順番に行います。

  1. I2C で Slave device の Register address = 0x00-0x07 の値を読み出してシリアルモニタに返す (Burst-Read)
  2. I2C で Slave device の Register address = 0x02-0x05 に 0xAA, 0xBB, 0xCC, 0xDD の値を書き込む (Burst-Write)
  3. I2C で Slave device の Register address = 0x00-0x07 の値を読み出してシリアルモニタに返す (Burst-Read)
int cntDAT;
int i;
byte inDAT;
int cntINDAT;
int cntOUTDAT;
byte outDAT[9];
uint32_t pageTMP;
uint32_t pageWRITE[4];

IMXRT_LPI2C_t *port_i2c;
IMXRT_REGISTER32_t *port_iomuxc;
IMXRT_REGISTER32_t *port_iomuxc_b;

void setup() {
  Serial.begin(9600);

  port_i2c = &IMXRT_LPI2C1; //0x403F0000
  port_iomuxc = &IMXRT_IOMUXC;  //0x401F8000
  port_iomuxc_b = &IMXRT_IOMUXC_b;  //0x401F8400

  // #19 = SCL, GPIO_AD_B1_00
  port_iomuxc->offset0FC = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SCL)
  port_iomuxc->offset2EC = 0x0000F861; // Pull-up = Enable, PUS = 11 (22kohm), PUE = 1, PKE = 0, ODE = 1, SPEED = 01, DSE = 100, SRE = 1
  port_iomuxc_b->offset0CC = 0x00000001; // DAISY = 1

  // #18 = SDA, GPIO_AD_B1_01
  port_iomuxc->offset100 = 0x00000013; // SION = 1, MUX_MODE = 011(LPI2C1_SDA)
  port_iomuxc->offset2F0 = 0x0000F861; // Pull-up = Enable                                                                                                                                                                                                     
  port_iomuxc_b->offset0D0 = 0x00000001; // DAISY = 1

  // I2C Clock
  CCM_CSCDR2 = (CCM_CSCDR2 & 0xFE03FFFF) | 0x00040000;
  CCM_CCGR2 |= 0x000000C0; // CG3 = 11, enable LPI2C1 clock

  // LPI2C1 Setting
  port_i2c->MCFGR1 = 0x00000001;
  port_i2c->MCFGR2 = 0x05050BB8; // FILTSDA = 5 (0.2 us), FILTSCL = 5 (0.2 us), BUSIDLE = 3000 (250 us)
  port_i2c->MCCR0 = 0x1928373B; // DATAVD = 25 (2.2 us), SETHOLD = 40 (3.4 us), CLKHI = 55 (4.7 us), CLKLO = 59 (5 us)

  port_i2c->MCR = 0x00000001;
}

void loop() {
  cntDAT = 0;

  while(Serial.available()){
    inDAT = Serial.read();
    cntDAT++;
    delay(5);
  }

  if(cntDAT > 0){
    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;

    cntINDAT = 0;
    cntOUTDAT = 0;

    while(1){
      if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT < 8)){
        port_i2c->MTDR = 0x00000100;
        cntINDAT++;
      }
      else if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT == 8)){
        port_i2c->MTDR = 0x00000200;
        cntINDAT++;
      }

      if((port_i2c->MFSR & 0x00070000) > 0){
        outDAT[cntOUTDAT] = port_i2c->MRDR & 0xFF;
        cntOUTDAT++;
      }

      if(cntOUTDAT == 8){
        break;
      }
    }

    for(i=0;i<cntOUTDAT;i++){
      Serial.print(outDAT[i]);
      Serial.print("-");
    }
    
    Serial.print("\n");

    pageWRITE[0] = 0x000000AA;
    pageWRITE[1] = 0x000000BB;
    pageWRITE[2] = 0x000000CC;
    pageWRITE[3] = 0x000000DD;

    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000002;

    cntINDAT = 0;

    while(1){
      if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT < 4)){
        port_i2c->MTDR = pageWRITE[cntINDAT];
        cntINDAT++;
      }
      else if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT == 4)){
        port_i2c->MTDR = 0x00000200;
        break;
      }
    }

    while((port_i2c->MFSR & 0x00000007) > 0){
      delay(1);
    }

    port_i2c->MTDR = 0x00000448;
    port_i2c->MTDR = 0x00000000;
    port_i2c->MTDR = 0x00000449;

    cntINDAT = 0;
    cntOUTDAT = 0;

    while(1){
      if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT < 8)){
        port_i2c->MTDR = 0x00000100;
        cntINDAT++;
      }
      else if(((port_i2c->MFSR & 0x00000007) < 4) && (cntINDAT == 8)){
        port_i2c->MTDR = 0x00000200;
        cntINDAT++;
      }

      if((port_i2c->MFSR & 0x00070000) > 0){
        outDAT[cntOUTDAT] = port_i2c->MRDR & 0xFF;
        cntOUTDAT++;
      }

      if(cntOUTDAT == 8){
        break;
      }
    }

    for(i=0;i<cntOUTDAT;i++){
      Serial.print(outDAT[i]);
      Serial.print("-");
    }
    
    Serial.print("\n");    
  }

  delay(1000);
}

f:id:ysin1128:20210304152317p:plain

2回の Read の間で Resigter address = 0x02-0x05 の値が書き換わっているので Burst-Write 成功です。

次回はまとめです。