公開日:2021/08/28
title.png

17th Season


#17-7 I2Cスレーブ通信

 前回、Moscovium+STWSERユニットを使ってI2Cマスタ通信用のソフトウェアを作り、実機動作確認しました。今回はI2Cスレーブ通信を実機確認して行きます。

 前回に引き続き、使用するデザインはMCOC115TW0408です。以下リンクから必要なプロジェクトファイルをダウンロードして、お使いください。手順はこちらです。

btn_moscovium.png

 I/O制約ファイルは、MCOC_full.adcです。

マスタ通信(後だし)

 スレーブ通信に移る前に、後出しのマスタ通信で追加実験をしておきます。前回12ビットDA変換器モジュールMCP4725を使ったプログラム(sin_wave.asm)では、実はI2Cライトサイクル(マスタからスレーブへのデータ送信)しか実行していませんでした。
 ここでは加速度・ジャイロ・磁気センサMPU9250(AK8963)(下図)を使い、I2Cリードサイクル(スレーブからのデータ受信)のテストを実施しておきます。

ar9a_mpu9250.jpg

 MPU9250(AK8963)のI2Cバスサイクル🚲は以下の通りとなります。

  ライトサイクル: S [ADR,W] [reg_addr] [reg_data] P
  リードサイクル: S [ADR,W] [reg_addr] R [ADR,R] [[RCV]](N) P

※I2Cバスサイクルの表記方法についてはこちらを参照してください。

 ↑は、SMBusのバイトリード/ライトですね(下図)。

ar81_smbus.png


 こちらのSTWSERマスタドライバを使うためのI2Cフレームは以下となります。

  I2Cフレーム:

 完成したプログラムを以下にリンクしておきます。

  完成版プログラム:test_mpu9250.asm

 アセンブルしてFPGAボードに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。


 FPGAボードとMPU9250(AK8963)モジュール、FTDIを以下表の様に接続します。

FTDI 接続 FPGAボード 接続 MPU9250(AK8963)
VCC(ジャンパ:3.3V側) ×    
GND GND
TXD R7(uart_rxd)
RXD T7(uart_txd)
  3.3V VCC
GND GND
P2(stws_scl) SCL
R2(stws_sda) SDA
  × EDA
× ECL
GND AD0
× INT
VCC NCS
GND FSYNC

 以下図に実験中の様子を示します。

a177_expr1.jpg


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【ard1,00】
  ERR: could not communicate mpu9250(ak8963): 0002
    …… AK8963にアクセスできませんでした。
        → 先にMPU9250の設定が必要(↓)。

  【mwr1,37,02】
  OK: wr1[0x37]=0x02
    …… AK8963へ直接アクセスできるように設定しました。

  【ard1,00】
  OK: rd1[0x00]=0x48
    …… 今度は、AK8963のWIAレジスタ(0x00)からIDコード0x48が読み出せました

  【mrd1,75】
  OK: rd1[0x75]=0x71
    …… MPU9250のWHO_AM_Iレジスタ(0x75)から、IDコード0x71が読み出せました

 MPU9250とAK8963のIDレジスタを読み出して、各データシートに記載された通りのIDコードが読み出せました。マスタドライバソフトウェアを使って、I2C機器からのリードアクセスを検証したかったので、これでOKです。一応、内部レジスタの読み/書きができるようにしてあるので、手動制御して各種センサ値を読み出すことも可能だと思います。

 次は本題のスレーブ通信に移ります。

スレーブドライバ

 I2Cのスレーブ動作は、常にI2Cバス信号線(SCL,SDA)を監視していて、STARTコンディション検出後最初のデータ(アドレスバイト)を受信すると、自分のアドレスと比較して、以下の動作となります。

 STWSERスレーブユニットの初期設定としては、最低限、以下の設定が必要となります。

  // STWSERスレーブユニットの初期設定
  ldwi r0,slave_addr  // 自分のスレーブアドレス
  ldwi r7,stwsadr
  stw [r7],r0


※必要に応じて、スレーブアドレスマスクレジスタstwsmskも設定してください(オプション)。

 また、自分の意志でアクセスを開始/終了できるマスタ通信と異なり、スレーブ通信は外部マスタからの非同期なアクセス要求に応える形で通信が成立するので、割り込みを使って制御するのが順当です(↓)。

  // STWSERスレーブユニットの割り込み設定
  ldwi r0,stws_srae  // スレーブ受信割り込み(SRAR)を許可
  ldwi r7,stwsctl
  stw [r7],r0


 割り込みを受ける以上、適切なスタックポインタspを設定して、割り込みベクタivにISRアドレスを設定する事、また、CPUの割り込み許可ビットの設定が必要となります(↓)。

  // Moscovium割り込み設定
  ldc sp,ramtop+0x100  // 各自適切なスタックポインタを設定の事
  ldc iv,lab_mcvm_wire_isr  // スレーブドライバのISRアドレスラベル
  seie  // CPUの割り込み許可


 Ivレジスタに設定したlab_mcvm_wire_isrは、これから説明するスレーブドライバの割り込みサービスルーチンISRアドレスです。もし、ご自身の割り込みを処理して、独自のISRアドレスをIvレジスタに設定するなら、そのISR内で以下の様にSRAR要因ビットを確認して、mcvm_wire_isrに分岐するようにしてください。

  // 各自のISRを使う場合
    :
    : 各自の処理
    :
  // 最後にSRAR割り込み要因をチェック
  ldwi r7,intfct
  ldw r0,[r7]
  ldwi r7,intmsk
  ldw r1,[r7]
  not r1
  and r0,r1
  andi r0,intc_srar
  beq pcnt+6
  jmpr7 mcvm_wire_isr  // mcvm_wire_isrからはrti命令で抜けるので、ここはjmp命令
  rti


※汎用レジスタの退避・回復コードは省略してあります。適切に処理するよう変更してください。


 以下、スレーブドライバの仕様です。

  スレーブドライバ:

 次は、STWSERスレーブユニットの割り込みタイミングを解説して、イベントの発生タイミングとの対応を明確にします。

割り込みタイミング

 以下にSTWSERスレーブユニットの割り込み発生タイミングについて説明します。

  STWSERスレーブユニットでの割り込みタイミング:

 いよいよ、スレーブドライバ(mcvm_lib_wire_s.incl)を使って、アプリケーションを作って見ます。

I2Cシリアルメモリ(256バイトRAM)

 アプリケーションは、I2Cシリアルメモリ(256バイトRAM)です。昔、Pi ZeroWとArduino UNOを使って、I2C通信を試した時のプログラムをMoscoviumアセンブラに移植します(↓)。

  「#8-1 Pi ZeroWのI2C通信

 移植するのは、当時UNOで動作させたプログラム(test_smbus.ino)の方です。Pi ZeroWで使ったプログラム(test_smbus.cc)の方はそのまま使います。

 Pi ZeroW側のtest_smbus.ccが実行するI2Cバスアクセスは、SMBusのバイトリード/ライトです。これを受けて動作する、旧UNO側のtest_smbus.inoはI2Cスレーブとしてアクセスに応えます。test_smbus.inoはSMBusバイトリード/ライト以外のアクセスは発行されないことを前提としています。

  SMBusバイトライト: S [ADR,W] [CMD] [DAT] P
  SMBusバイトリード: S [ADR,W] [CMD] R [ADR,R] [[RCV]](N) P

 test_smbus.inoの動作をMoscoviumのスレーブドライバ(mcvm_lib_wire_s.incl)を使って記述します。4つのイベントにおいて、以下の様に動作させると、SMBusバイトリード/ライトに従ってアクセスが可能となります。

 完成したプログラムを以下にリンクしておきます。

  完成版プログラム:test_smbus.asm

※必要なインクルードファイルなどはこちらからダウンロードできます。

 アセンブルしてFPGAボードに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。

 ただし、上のコマンドはデバッグ用で、基本的には使いませんので、FTDIは接続しなくて良いです。次節にてPi ZeroWからI2Cシリアルメモリ(256バイトRAM)としてアクセスするテストを実行します。

実験だよ

 FPGAボード(Sipeed Lichee Tang Primer)とPi ZeroWを以下表の様に接続します。

FPGAボード 接続 Pi ZeroW
GND GND(phys#9等)
P2(stws_scl) SCL(phys#5)
R2(stws_sda) SDA(phys#3)

※FPGAボードの入力プルアップ抵抗が有効なので、外付けにプルアップ抵抗は必要ありません。


 実験中の様子を以下に示します。

a177_expr.jpg


  実験サンプル:【 】内はPi ZeroW shellウインドウでのコマンド入力
  【g++ -lwiringPi test_smbus.cc -o test_smbus】
    …… コンパイル。

  【test_smbus -c】 …… 現在の/boot/config.txt設定値を表示
  dtparam=i2c_arm=on
  dtparam=i2c_baudrate=400000
    …… I2C 400kHzの設定でテストしました。

  【gpio i2cdetect】 …… I2Cに接続されたスレーブアドレスをスキャン
    0 1 2 3 4 5 6 7 8 9 a b c d e f
  00:     -- -- -- -- -- 08 -- -- -- -- -- -- --
  10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
  50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- --

    …… Moscoviumのスレーブアドレスは0x08です(=i2c_slv_adr定数)。

※FPGAボード上のタッチスクリーンコントローラNS2009(スレーブアドレス=0x48)も、Pi ZeroWから見えています。


  【test_smbus】 …… Moscoviumの256バイトRAMをダンプ
  addr: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f : 0123456789abcdef
  0000: 68 65 6c 6c 6f 2c 20 72 61 73 70 62 69 61 6e 00 : hello, raspbian
  0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  00f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :
  DEBUG: access time=49742[us]/256[byte] freq=41.172[kHz]


 Moscovium側のプログラム(test_smbus.asm)でリセット解除後に自動的に書き込んだメッセージを、アドレス0x00〜0x0fから読み出すことが出来ています。
 最後の行は、256バイトの読み込みに要した時間と、実効通信速度freqを表示しています。実効通信速度freq=転送したデータ(DAT)ビット数/転送に要した時間と、定義しました。


  【test_smbus -m】 …… マーチングテストを実行
  Start marching test, wait a moment...
  OK: marching test passed (266433[us])

 マーチングテストも、パスしました。正しく通信して、256バイトRAMにアクセスできています。

 UNO+test_smbus.inoでは、400kHzのSMBusアクセスは出来ず、75kHzまで周波数を落とすと、アクセスできました。この時のマーチングテストが約850msでした。400kHzのアクセスが失敗する原因については、「#8-1 Pi ZeroWのI2C通信」を参照してください。

※UNOに於いて、SMBusプロトコルを使わず、ライト/リードアクセスをSTOPコンディションで分離して400kHzでテストした時は、マーチングテストが約360msでした。

 Moscovium+test_smbus.asmでは、何ら問題なく400kHzのSMBusアクセスが可能で、マーチングテストは約266msとなりました。まずまず、ではありませんか?
 MoscoviumのSMBus over I2C通信が400kHzで破綻しないのは、CPUの処理速度云々ではなくて、STWSERスレーブユニットがリードサイクルのアドレスバイト[ADR,R]を受信した時、送信データが準備できる(STAF=0となる)まで、アクノリッジサイクル(A)をストレッチしてマスタを待たせているからです(ハードウェアの処理)。


 スレーブ通信も実機動作を確認できて、ようやく一息付けました(1^^)v。I2Cは中々便利な通信方式なので、色々な活用が期待できます。さて、何に使おうか?


btn_arduino.png

btn_prev.png btn_next.png

home