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

17th Season


#17-6 I2Cマスタ通信

 前回設計したSTWSER(Synchronous Two Wire Serial)ユニットをMoscoviumのソフトウェアで制御して、実際のI2C通信機器と通信してみたいと思います。
 今回のテーマはI2Cのマスタ通信となります。スレーブ通信の方は、また次回に譲ります。

I2Cバスサイクルの理解

 STWSERマスタユニットのレジスタに値を設定すると、基本的には指定した通りに動作します。たとえその動作がI2C仕様にそぐわない動作であったとしても、です。従って、ソフトウェア(ドライバ)を設計するプログラマが、I2C仕様を理解してI2C通信プロトコルに則ったサイクルを発生させなければなりません。以下、不正なバスサイクルの例を挙げます。

※本ページでは、こちらで示した記法でI2Cバスサイクルを表記します。

 この様なアクセスを実行すると、I2C仕様に準じたモジュールは誤った解釈をして、誤動作する場合があります。

 上に挙げた例の様な不正なバスサイクルも、全てSTWSERマスタユニットから生成可能です。STWSERユニットは、言われた通りに、指示された通りに、動作します。ソフトウェアが間違っていれば、いくらでも不正なバスサイクルを発生できるようになっています。

 ソフトウェアのプログラマはまず、I2C仕様の理解が必要となります。

マスタドライバ

 STWSERユニットを使ってI2C通信するには、STWSERユニットのI/Oレジスタを叩いて所望の動作をさせる必要があります。
 「難しい、難解、わかり難い」のは、嫌いなので、それほど難しくしたつもりはないのですが、STWSERユニットのレジスタ数は非常に多く、設定・確認すべきビット数も相当数に及んでいます。元々のI2C仕様が複雑なため、この程度で抑え込むのがやっとでした。

 そこで、ソフトウェアの作成を少しでも容易にするため、プログラマ(ユーザ)からSTWSERユニットのレジスタを隠ぺいして、簡単にコーディングが可能な様に、ドライバソフトウェアを作っておこうと思います。

 I2C機器との通信を実行したいユーザは、以下に示す「I2Cフレーム」をRAM上に作成し、I2Cフレームの先頭アドレスを汎用レジスタr1に代入して、サブルーチンmcvm_wireをコール(jal命令)します。

  I2Cフレーム仕様:

 サブルーチン内では、I2Cバス仕様に関するエラーチェック等は実行していないので、I2Cバス仕様に準拠したプロトコルでI2Cフレームを構成するのはユーザ責任となります。


 以下、サブルーチンmcvm_wire内の処理フロウを示しておきます。

  サブルーチンmcvm_wire内の処理フロウ:

 サブルーチンmcvm_wireは、I2Cとの通信を実行する間、処理をブロックします。割り込みを使ってリアルタイム処理に対応したい場合など、STWSERユニットのレジスタを直接叩いて制御したいケースでは、サブルーチンmcvm_wireによる制御(レジスタの設定手順等)を参考に、各自対応下さい。

※ArduinoのWireライブラリでは、Wire.endTransmission(stop=false)で、STOPコンディションを発行せずに次のbeginTransmission()で反復STARTコンディションを発行できるので、間に別処理を挟める仕様です。ただ、一連の送信動作と受信動作は処理をブロックしているので、使い勝手はそんなに大きく変わらないはずです。

Moscovium Wireライブラリ

 以下に、Moscovium Wireライブラリのマスタドライバファイルをリンクしておきます。

  Moscovium Wireライブラリ:
    ヘッダファイル:mcvm_lib_wire_h.incl
    マスタドライバ:mcvm_lib_wire_m.incl

 ヘッダファイルはアセンブラソースの先頭付近で読み込んで(incl疑似命令)下さい。マスタドライバは、アセンブラソースの末尾付近など、通常、処理の届かない場所で読み込んでください。

 所望のI2CフレームをRAM上に構築したら、汎用レジスタr1に先頭アドレスを格納して、サブルーチンmcvm_wireへジャンプします(↓)。

  // STWSERマスタユニットの初期設定
  ldwi r0,fcpu/400000-1  // scl speed=400kHz
  ldwi r7,stwmbaud
  stw [r7],r0

  // I2Cフレームを作成する
    :
    :
  ldwi r1,i2c_frame  // 先頭アドレスをr1にセット
  jalwr7 mcvm_wire


 サブルーチンmcvm_wireをコールする場合の初期設定は、上のコード例に示した通り、マスタ通信速度レジスタstwmbaudに所望の通信周波数を設定するだけです。

※ArduinoのWireライブラリの様に、ユーザからI2Cの通信仕様まで隠ぺいするためには、更にもう一枚皮を被せないとなりません。本ページでは「ここまで」、としておきます。

割り込みのタイミング

 用意したサブルーチンmcvm_wireでは割り込みを使っていませんが、使う場合、どのタイミングで割り込みがかかるかを説明しておきます。

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

 スレーブユニットの割り込みタイミングについては、次回解説します。

I2C検出

 I2Cバス上に接続されている機器をスキャンして検出する場合、以下の様な短いバスサイクルをアドレスADR毎にキャストして、毎回スレーブからのアクノリッジ(A)またはノットアクノリッジ(N)応答を確認します。

  I2C検出バスサイクル(凡例): S [ADR,W](A/N) P

 アドレスバイト[ADR,W]を受信して、スレーブアドレスADRが自身のアドレスと一致する機器は、[ADR,W]に対してアクノリッジ(A)を返します。アドレスが一致しない場合または、I2Cバスに機器が接続されていない場合、ノットアクノリッジ(N)が返ります。
 全7ビットアドレスADR=0x08〜0x77をスキャンして、アドレスバイト[ADR,W]に対するアクノリッジ状態を確認すると、機器の有無を判定することが可能です。

 マスタドライバを使う前提でプログラムを組んで行きます。まず、I2CフレームをRAM上に作成します。

 とても短いバスサイクルなので、以上2ワードで終了です。I2Cフレーム先頭のアドレスをr1に設定して、「jalwr7 mcvm_wire」で終了です。返り値r0を確認して、機器の有無を判定します。


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

  I2C検出プログラム:i2c_detect.asm

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

 Moscoviumシリアルモニタから、以下のコマンドを受け付けるようになっています。


 ここからは、前回作成したデザインMCOC115TW0408を使って、FPGAボード(Sipeed Lichee Tang Primer)で動作を確認して行きます。必要なデザインファイルを以下リンクよりダウンロードしたら、コンパイル(論理合成・配置配線)してFPGAボードに転送してください。手順はこちらです。

btn_moscovium.png

 この時、プログラム(i2c_detect.asm)をアセンブルして完成したMIFファイルを、ROM用RAMマクロの所定のディレクトリに格納しておくことをお忘れなき様。


  実験サンプル:【 】内はMoscoviumシリアルモニタからの送信データ
  【scan】
  OK+ start scan       -- -- -- -- -- -- -- --
  10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  30 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40 -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
  50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70 -- -- -- -- -- -- -- --
  OK: i2c detect


 I2C機器からノットアクノリッジ(N)が返ったアドレスには「--」が表示され、アクノリッジ(A)が返ったアドレスには、その16進数値が表示されます。
 この実験で積極的に接続したのは、次節で実験に使用する12ビットDA変換器モジュールMCP4725だけで、アドレスは0x60です。ちゃんと0x60が検出できていますね。よしよし。……と、何か居ますね?アドレス0x48に。

 プログラムまたはSTWSERユニットのバグでごみが見えたのか?と思いましたが、ずいぶんと安定して見えています。何回やり直してみても、毎回0x48が検出されます。FPGAボードの回路図を入手して確認したところ、SCL,SDA端子に指定したP2,R2端子には、FPGAボード上のタッチスクリーンコントローラNS2009が接続されているようです。

  NS2009のデータシートを検索する(別ウインドウが開きます)

 データシートによると、NS2009のスレーブアドレスは0x48と言う事で、ビンゴです。今のところ使い道は無いのですが、FPGAボード上に搭載されたI2C機器もちゃんと検出できました。OKです。

※凄く遅いI2C機器が接続されているかもしれないので、i2c_detect.asmのI2Cクロックスピードは10kHzに設定してあります。もっと遅い機器の検出が必要な場合、プログラム先頭付近の定数設定で調整してください。

12ビットDA変換器モジュールMCP4725

 12ビットDA変換器モジュールMCP4725(下図)を接続して、I2Cで制御してみたいと思います。

ar79_dac12.jpg

 使い方については、「#7-9 WAVファイルを再生する」をご覧ください。基本的には、I2Cを使って12ビットのデジタルデータを送信すると、送信した値に従ったアナログ電圧を出力してくれるモジュールです。従って、必要なI2Cバスサイクルは以下の通りとなります。

  MCP4725のI2Cバスサイクル(凡例): S [ADR,W] [DATH] [DATL] P

 これに従って、mcvm_wireドライバ用のI2Cフレームを作って見ましょう。

  MCP4725用I2Cフレーム:

 以上4ワードのデータをRAM上に生成し、先頭アドレスを汎用レジスタr1にセットしてから「jalwr7 mcvm_wire」です。

 MCP4725を使って、指定した周波数の正弦波を出力するプログラムを作って見ました。以下にリンクしておきます。

  正弦波発生プログラム:sin_wave.asm

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

 アセンブル後、デザインMCOC115TW0408をコンパイル(論理合成・配置配線)して、FPGAボードに転送しておいてください。手順はこちらです。

 Moscoviumのシリアルモニタから、以下のコマンドを受け付けるようになっています。


 FPGAボードとUSB⇔UART変換モジュール(FTDI)、MCP4725モジュール、正弦波観測用のSTM32(Arduino)を以下の様に配線します。

FTDI 接続 FPGAボード 接続 MCP4725モジュール 接続 STM32
GND GND GND GND
  3.3V VCC  
P2(stws_scl) SCL
R2(stws_sda) SDA
  OUT PA0(ADC0)
TXD R7(uart_rxd)    
RXD T7(uart_txd)
VCC(ジャンパ:3.3V側) ×  

※FPGAボードの端子設定(MCOC_full.adc)によって、P2端子(SCL)とR2端子(SDA)には入力プルアップ抵抗が利いています。加えて、MCP4725モジュールのボード上にもプルアップ抵抗が付いているので、外付けのプルアップ抵抗は不要です。

 本ページでは、正弦波観測用ArduinoにSTM32を使用しました。Arduino(STM32等)には、Logiscopeを書き込んでおいてください。
 以下、実験中の様子です。

a176_expr.jpg


  実験サンプル:【 】内はLogiscopeからの送信データ
  【osc,0,4,4,0】
  …… タイムアウトする前に、以下のコマンドを実行する。

  実験サンプル:【 】内はMoscoviumシリアルモニタからの送信データ
  【sin,100】
  OK: sin output finished

 Arduino(STM32)+Logiscopeで取得したアナログ波形は以下の様になりました。

a176_sin100.png

 【sin,100】コマンドによって、100Hzの正弦波を生成するよう指示しました。正弦波の山→山間隔を測定して周波数を求めたところ、約99.8Hzと、指定した通りの周波数であることが判りました。

 所望の波形を生成できていることから、STWSERユニットとMCP4725間のI2C通信は正常に実行できている、と判定します。実機検証合格です。


 正直、I2C通信プロトコルに則ったI/Oユニットを自分で作るなんて、泣きそう😰だったのですが、実機でI2C機器と通信できてほっとしています。この調子で、次回はスレーブ通信も実機検証してみます。


btn_arduino.png

btn_prev.png btn_next.png

home