”FPGAで遊んでみる”では、セミナー用に作成したサンプル回路などを整理して少しづつ紹介していきます。


セミナ開催します
手ぶらでOK!実習・Raspberry Pi PicoW×MicroPython I/Oデバイス「時短」開発入門
-- 133MHz ARM Cortex-M0マイコンをPythonでプログラミング 
2024年12月6日(金)CQ出版社セミナルーム CQ出版社セミナ・ルーム
詳しくはこちらへ

ZYBOの本
FPGAパソコンZYBOで作るLinux I/Oミニコンピュータ CQ出版 発売中

Papilioの本
FPGA版Arduino!!Papilioで作るディジタル・ガジェット CQ出版 発売中


第6回 I2Cで色々なICと接続してみる

 今回は、I2Cのマスタ・インターフェース回路を紹介します。このインタフェース回路にコントロール用モジュール、
RS232Cインターフェースモジュールを追加してI2Cインターフェースを持つEEPROM、温度センサ、加速度センサへ
アクセスしてアクセス結果をRS232Cへ出力してみます。

左から、
FT2232D(RS232C<->USB変換,FPGAコンフィグレーション)
MFPGA_SPAR3E(FPGAボード)
24FC512(EEPROM)
LM73(温度センサ)
AE_KXP84(3軸加速度センサ)

I2Cのインターフェース回路(i2c_m_if)のソース(zip圧縮してあります)
 i2c_m_if.zip
 i2c_m_if.v i2cマスタインターフェイス回路Verilo-HDLソース
 i2c_ap.v FPGAのトップ階層のVerilo-HDLソース
 i2c_fc23x_ctrl.v EEPROM(24FCXXX)用のコントロールモジュール
 i2c_lm73_ctrl.v 温度センサ(LM73)用のコントロールモジュール
 i2c_kxp84_ctrl.v 3軸加速センサ(KXP84)用のコントロールモジュール
 fifo.v メッセージ保持用FIFO(第8回で紹介予定)
 ram.v メッセージ保持用FIFO用RAM(第8回で紹介予定)
 ic2_m_ap_test.v テストベンチ
 i2c_ap.ucf FPGAボードにマルツパーツのMFPGA_SPAR3Eを使用した場合のPIN配置指定
 rs232c_txrx.v RS232Cインターフェースモジュール(第3回で紹介)
 ※ソースコードが長いのでダウンロードして中身を見てください。

仕様説明(i2c_m_if.v)

 動作概要
  I2Cのマスタデバイスとして動作する。スレーブアドレス、リード/ライト、書き込みバイト数、書き込みデータ、読み出しバイト数を設定し
 I2C通信を行う。リード動作では、アクセス完了後に読み出しデータを出力する。

 入力信号
 clk:クロック入力/今回使用してたボードは40MHzでした。
 rstb:リセット入力 0でリセット
 sda_i:I2CのSDA信号の入力
 adr[6:0]:接続するI2Cデバイスのスレーブアドレス指定
 wr:I2Cで書込みアクセス,1クロック幅の1でアクセス開始、busy==1の時は通信中なのでwr=1にできない。
 rd:I2Cで読出しアクセス,1クロック幅の1でアクセス開始、busy==1の時は通信中なのでrd=1にできない。
 wr_data[31:0]:書き込みデータ、wr==1の時にデータを取り込み、wr_data[31:24]から送信
 wr_bytes[2:0]:書き込みバイト数指定、wr==1の時に取り込み。1〜4の範囲で設定、1の場合はwr_data[31:24]を送信
          2の場合はwr_data[31:16]を送信、3の場合はwr_data[31:8]を送信、4の場合はwr_data[31:0]を送信する
 rd_bytes[2:0]:読み出しデータバイト数指定、rd==1の時に取り込み。1〜4の範囲で設定、1の場合はrd_data[31:24]に受信
          2の場合はrd_data[31:16]に受信、3の場合はrd_data[31:8]に受信、4の場合はrd_data[31:0]に受信する

 出力信号
 led_0:LED制御信号、動作中は点滅します。
 scl:ISCのSCL信号
 sda_o:I2CのSDA信号の出力、モジュール外部でsda_o==1の時にSDA=Hi-Z、sda_o==0の時はSDA=0とする。
 rd_data[31:0]:読み込みデータ、rd_data_en==1の時に有効、読み込みデータの先頭は、wr_[31:24]に保存
 rd_data_en:読み込みデータイネーブルう、I2Cから受信完了に1クロック幅で1になる。 

 パラメータ
 p_1bit_cnt:SCLに1クロック周期指定、入力clkのクロック数で指定
        今回のボードは、clkは40MHzなのでp_1bit_cnt=100として、SCLを400KHzとしてあります。
        使用するボードのクロック周波数に合わせて変更してください。
 p_sda_chg:SDAの変化タイミング指定、SDAを変化させるタイミングをSCL立下りからclkのクロック数で指定
 

 詳細動作
 wr=1とするとadr,wr_bytes,wr_dataを取り込んで、I2Cの書み込み通信が開始します。
 通信が開始するとbusy=1となります。
  書み込み通信開始時の波形
 

 I2C書み込み通信が開始されるとsda_oからスタートビット(0)、スレーブアドレス(adr[6:0])、r/wビット(書込みなので0)、
 スレーブからack受信(1)、wr_data[31:24]、 スレーブからack受信(1)、wr_data[23:16]、スレーブからack受信(1)、wr_data[15:8]、
 スレーブからack受信(1)、wr_data[7:0]、 スレーブからack受信(1)、ストップビット(0)の順で出力する。
  I2C書込み通信の波形
 

 
 rd=1とするとadr,rd_bytesを取り込んで、I2Cの読み出し通信が開始します。
 通信が開始するとbusy=1となります。
  読み出し通信開始時の波形
 

 I2C読み出し通信が開始されるとsda_oからスタートビット(0)、スレーブアドレス(adr[6:0])、r/wビット(読み出しので1)、
 スレーブからack受信(1)、8bitデータ受信(1)、 ack送信(0)、8bitデータ受信(1)、ack送信(0)、8bitデータ受信(1)、
 ack送信(0)、8bitデータ受信(1),nack送信(1)、ストップビット(0)の順で出力する。
 8bitデータ受信間はsda_iからデータを受信してrd_bytesで指定したバイト数を受信すると受信データをrd_dataに出力して
 1クロック間rd_data_en=1とする。
  I2C読み出し通信の波形
 


FPGAでの動作
 FPGAトップモジュール(i2c_ap.v)
  i2c_m_if単体ではインターフェース回路しかなので、コマンドを発行とリードデータを表示するコントローラと
 表示データをPCへ転送するRS232C通信回路を追加してトップモジュールを作成します。
 また、トップモジュールでは、SCL、SDAの出力を0とHi−Zに変換する記述も入れます。

   トップモジュール(i2c_ap.v)のブロック図
 
  i2c_mif:i2cマスタインターフェース
  i2c_24fcx_ctrl:EEPROM(2FCXX)用コントロールモジュール
  i2c_lm73_ctrl:温度センサ(LM73)用コントロールモジュール
  i2c_kxp84_ctrl:3軸加速センサ(KXP842)用コントロールモジュール
  ※i2c_24fcx_ctrl、i2c_lm73_ctrl、i2c_kxp84_ctrlのどれか一つを選んでインプリメントします。
  fifo      表示データの一時保存
  ram      FIFO内部で使用するRAM、DFFで作ってあるので、セルを節約する場合はツールで作ったRAMモジュールにr付け替えしてください。
  rs232c_tx_rx RS232C通信モジュール 

  コントロールモジュールの選択

  接続するI2Cデバイスによってコントロールする方法が違っていますので、それぞれのI2Cデバイス用コントロールモジュールを用意しています。
  各コントロールモジュールはピンコンパチになっているので、使用するモジュール以外のモジュール名をコメントアウトすることで選択します。

  コントロールモジュール選択部分の記述

//コントロールモジュールの選択
//ピンコンパチになっているのでインスタンスするモジュール名を変更することで、モジュールを選択できます。
//使用するモジュール以外はコメントアウトしてください。

//24FCX用コントロールモジュール                    
i2c_24fcx_ctrl i2c_dev_ctrl( 

//LM84用コントロールモジュール     
//i2c_lm73_ctrl i2c_dev_ctrl(

//KXP84用コントロールモジュール                    
//i2c_kxp84_ctrl i2c_dev_ctrl( 
  .clk(   clk),
  .rstb(  rstb),
  .led_0( led_0),
  .wr(    wr),
  .rd(    rd),
  .adr(   adr),
  .wr_data(wr_data),
  .wr_bytes(wr_bytes),
  .rd_data(rd_data),
  .rd_data_en(rd_data_en),
  .rd_bytes(rd_bytes),
  .tx_fifo_data_en    (tx_fifo_data_en),   
  .tx_fifo_data   (tx_fifo_data)
);

 I2C用の信号ドライブ
   I2C用の信号の出力値は、0またはHi-z(ハイインピーダンス)に決められています。この処理はブロック図のSCL,SDAが接続している
  トライステートバッファの部分になります。Verilog-HDLソースでは、トップモジュールに記述してあります。

  I2C用の信号ドライブとi2cインターフェースモジュールのインスタンスの記述

// IO driver      
assign sda = (sda_o==1'b0)?1'b0:1'bz;
assign sda_i = sda;
assign scl = (scl_drv==1'b0)?1'b0:1'bz;

// i2c master    
i2c_m_if i2c_m_if(
  .clk(   clk),
  .rstb(  rstb),
  .scl(  scl_drv),
  .sda_o(  sda_o),
  .sda_i(  sda_i),
  .wr(    wr),
  .rd(    rd),
  .adr(   adr),
  .wr_data(wr_data),
  .wr_bytes(wr_bytes),
  .rd_data(rd_data),
  .rd_data_en(rd_data_en),
  .rd_bytes(rd_bytes),
  .busy(busy) 
  );

 EEPROM(24FC512)との接続
  MICROCHIP社のI2C用EEPROM(24FC512:データシート)と接続してみました。
  コントロール回路(i2c_24fcx_ctrl)での動作
   40MHZでインクリメントするmain_cntのカウンタ値によりコマンド制御しています。
   メモリ内アドレス2バイト、データ2バイトを4回書き込み、
   読み出し用のメモリアドレス2バイト書き込み、
   4バイトのデータ読み出しを2回実行しています。

  制御部分の記述  

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0) begin
    wr <= 1'b0;
    wr_data <=32'h00000000;
    wr_bytes <= 3'd0; 
    adr <= 7'b1010000;  //EEPROM 24FC512 
    mem_adr <= 16'h0000; 
    mem_dat <= 16'h1000; 
    end
  else
    if ((main_cnt == 26'd500000)||
        (main_cnt ==26'd1000000)||
        (main_cnt ==26'd1500000)||
        (main_cnt ==26'd2000000))
      begin
        //データ書き込み
        wr <= 1'b1;
        //[31:16]には書込みアドレス,[15:0]には書込みデータ
        wr_data <= {mem_adr,mem_dat[15:8],mem_dat[7:0]};
        adr <= adr ;
        wr_bytes <= 3'd4;
        //書き込み後にアドレスを+2,データを-1
        mem_adr <= mem_adr + 16'h02;
        mem_dat <= mem_dat - 16'h01; 
      end
    else  if (main_cnt ==26'd2500000)
      begin
        //読み込み用アドレス書込み
        wr <= 1'b1;
        wr_data <= {(mem_adr-16'h008),16'h0000};
        adr <= adr;
        wr_bytes <= 2'd2; 
      end
    else
      begin
        wr <= 1'b0;
        wr_data <= wr_data;
        adr <= adr;
        wr_bytes <= wr_bytes; 
      end

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0)
    begin
      rd <= 1'b0; 
      rd_bytes <= 3'd0; 
    end   
  else   
    if (main_cnt ==26'd3000000)
      begin
        //データ読み出し 1回目
        rd <= 1'b1;
        rd_bytes <= 3'd4; 
      end
    else if (main_cnt ==26'd3500000)
      begin
        //データ読み出し2回目
        rd <= 1'b1;
        rd_bytes <= 3'd4; 
      end
    else
      begin   
        rd <= 1'b0;
        rd_bytes <= rd_bytes; 
      end   


  FPGAボード(MFPGA_SPAR3E)との接続例
 

 ログ出力
  TXDをFT232D経由でPCのパソコンへ接続(第3回を参照)して、データを表示してみます。
 ログにファーマットは以下の様になっています。
 [R/W]_adr[6:0][read_bit]_data[31:24]_data[23:16]_data[15:8]_data[7:0]
 R:リードアクセス
 W:ライトアクセス
 ADR[6:0]:スレーブアドレス
 read_bit:1でリード、0でライト
 data[31:24]:ライトアクセスでは1バイト目のライトデータ、リードでは1バイト目のリードデータ
 data[23:16]:ライトアクセスでは2バイト目のライトデータ、リードでは2バイト目のリードデータ
 data[15:8]:ライトアクセスでは3バイト目のライトデータ、リードでは3バイト目のリードデータ
 data[7:0]:ライトアクセスでは4バイト目のライトデータ、リードでは4バイト目のリードデータ
 
 アドレス0x0000から2バイトデータの0x1000をデクリメントしながら4回書き込みして
 その後に4バイトデータを2回読み出しています。
 書き込んだデータ読み出されています。


 温度センサ(LM73)との接続
  次は、NatinalSemiconductor社の温度センサ(LM73:データシート)に接続してみます。
  接続するデバイスはエレキジャックNo.8のおまけに付いていた物を使用しています。

  コントロール回路(i2c_lm73_ctrl)での動作
   40MHZでインクリメントするmain_cntのカウンタ値によりコマンド制御しています。
   アクセス対象のレジスタを指定するため、温度データのレジスタポンインタ(0x00)を書き込み、
   その後に2バイト分の温度データを読み出します。
   読み出したデータは、バーナリーコードデシマル変換してRS232C経由で表示します。

  制御部分の記述  

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0) begin
    wr <= 1'b0;
    wr_data <=32'h00000000;
    wr_bytes <= 4'd0; 
    adr <= 7'b1001101;    //LM73
    end
  else
    //LM73の内部レジスタ指定の書き込み
    if (main_cnt == 26'd500000)
      begin
        wr <= 1'b1;
        //温度データレジスタ・ポインタを設定
        wr_data <= {32'h00000000};
        adr <= adr ;
        wr_bytes <= 3'd1;
      end
    else
      begin
        wr <= 1'b0;
        wr_data <= wr_data;
        adr <= adr;
        wr_bytes <= wr_bytes; 
      end

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0)
    begin
      rd <= 1'b0; 
      rd_bytes <= 3'd0; 
    end   
  else 
    //温度の読み出し  
    if (main_cnt ==26'd3000000)
      begin
        rd <= 1'b1;
        rd_bytes <= 3'd2; 
      end
    else
      begin   
        rd <= 1'b0;
        rd_bytes <= rd_bytes; 
      end   


  FPGAボード(MFPGA_SPAR3E)との接続
 

 ログ出力
 TXDをFT232D経由でPCのパソコンへ接続(第3回を参照)して、データを表示してみます。 
 ログにファーマットは以下の様になっています。
 先頭がR,Wの場合
  [R/W]_adr[6:0][read_bit]_data[31:24]_data[23:16]_data[15:8]_data[7:0]
  R:リードアクセス
  W:ライトアクセス
  T:温度表示
  ADR[6:0]:スレーブアドレス
  read_bit:1でリード、0でライト
  data[31:24]:ライトアクセスでは1バイト目のライトデータ、リードでは1バイト目のリードデータ
  data[23:16]:ライトアクセスでは2バイト目のライトデータ、リードでは2バイト目のリードデータ
  data[15:8]:ライトアクセスでは3バイト目のライトデータ、リードでは3バイト目のリードデータ
  data[7:0]:ライトアクセスでは4バイト目のライトデータ、リードでは4バイト目のリードデータ
 先頭がTの場合
   T____温度

 

 LM73をドラヤーで温めている時にのログです。表示温度が上昇してきてるのが分かります。


 3軸加速度センサ
 次はkionix社の3軸加速度センサ(KXP84:データシート)に接続してみます。
 3軸加速度センサは秋月電子でモジュール化したAE_KXP84を使いました。

  コントロール回路(i2c_kxp84_ctrl)の動作
   40MHZでインクリメントするmain_cntのカウンタ値によりコマンド制御しています。
   アクセス順序
   X方向の加速度データレジスタを指定する書き込み
   X方向の加速度データの読み出し
   Y方向の加速度データレジスタを指定する書き込み
   Y方向の加速度データの読み出し
   Z方向の加速度データレジスタを指定する書き込み
   Z方向の加速度データの読み出し
   各方向の加速度データをRS232C経由で表示します。

  制御部分の記述  

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0) begin
    wr <= 1'b0;
    wr_data <=32'h00000000;
    wr_bytes <= 4'd0; 
    adr <= 7'b0011000;    //KXP84
    end
  else
    //X方向の加速度データレジスタのアドレスを設定
    if (main_cnt == 26'd500000)
      begin
        wr <= 1'b1;
        wr_data <= {32'h00000000};
        adr <= adr ;
        wr_bytes <= 3'd1;
      end
    //Y方向の加速度データレジスタのアドレスを設定
    else if (main_cnt == 26'd2000000)
      begin
        wr <= 1'b1;
        wr_data <= {32'h02000000};
        adr <= adr ;
        wr_bytes <= 3'd1;
      end
    //Z方向の加速度データレジスタのアドレスを設定
    else if (main_cnt == 26'd3500000)
      begin
        wr <= 1'b1;
        wr_data <= {32'h04000000};
        adr <= adr ;
        wr_bytes <= 3'd1;
      end
    else
      begin
        wr <= 1'b0;
        wr_data <= wr_data;
        adr <= adr;
        wr_bytes <= wr_bytes; 
      end

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0)
    begin
      rd <= 1'b0; 
      rd_bytes <= 3'd0; 
    end   
  else   
    //加速度データの読み出し
    if ((main_cnt ==26'd1000000)||(main_cnt ==26'd2500000)||(main_cnt ==26'd4000000))
      begin
        rd <= 1'b1;
        rd_bytes <= 3'd2; 
      end
    else
      begin   
        rd <= 1'b0;
        rd_bytes <= rd_bytes; 
      end   


  FPGAボード(MFPGA_SPAR3E)との接続例
 
  AE_KXP84にプルアップ抵抗が内蔵されているのでプルアップ抵抗は付けていません。


 ログ出力
 TXDをFT232D経由でPCのパソコンへ接続(第2回を参照)して、データを表示してみます。 
 ログにファーマットは以下の様になっています。
  [X/Y/Z]____加速度データ
  X:X方向の加速度
  Y:Y方向の加速度
  Y:Y方向の加速度
  加速度データ;KXP84から読み出したデータ、2048dec(0x800)で加速度0G、1Gの時に2867(0xB33)になる

 
 
  Z方向を上にした状態からY方向が上にになるよう動かしている状態のログです。
  初めはZ方向の加速度が約1Gですが、その後にY方向が上になるように傾けることによって、
  Z方向の加速度が減少し、Y方向の加速度が増加して来るのが分かりります。
 
紹介した回路を試す場合は、自己責任でお願いします。
次回は、”SPIで色々なICと接続してみる”を紹介する予定です。

リンクフリーです。
リンクされた場合にご連絡をいただけると嬉しです。
メール 


変更履歴
(2010/10/16) 初版
(2019/10/27) ブロック図のモジュール名の誤記修正


TOPへ戻る