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


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

セミナ開催します。
"実習・MAX10 NiosIIプロセッサ活用超入門”
2018年1月16日(火)、CQ出版社セミナルーム
DE10-Liteを使ってMAX10上でNisoIIを動かします.
詳しくはこちらへ


第7回 SPIで色々なICと接続してみる

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


左から、
FT2232D(RS232C<->USB変換,FPGAコンフィグレーション)
MFPGA_SPAR3E(FPGAボード)
AT93C86(EEPROM)
MCP4992(DA変換器)
MCP3002(AD変換器)

SPIのインターフェース回路(spi_m_if)のソース(zip圧縮してあります)
 spi_m_if.zip
 spi_m_if.v i2cマスタインターフェイス回路Verilo-HDLソース
 spi_ap.v FPGAのトップ階層のVerilo-HDLソース
 spi_at93c86.v EEPROM(24FCXXX)用のコントロールモジュール
 spi_mcp3002_4922.v AD変換器(MCP3002)、DA変換器(MCP4922)用のコントロールモジュール
 fifo.v メッセージ保持用FIFO(第8回で紹介予定)
 ram.v メッセージ保持用FIFO用RAM(第8回で紹介予定)
 rs232c_txrx.v RS232Cインターフェースモジュール(第3回で紹介)
 spi_m_ap_test.v テストベンチ
 spi_ap.ucf FPGAボードにマルツパーツのMFPGA_SPAR3Eを使用した場合のPIN配置指定
 ※ソースコードが長いのでダウンロードして中身を見てください。

仕様説明(spi_m_if.v)

 動作概要
  SPIのマスタデバイスとして動作します。デバイス指定、通信クロック数、書き込みデータを設定し
  SPI通信を行う。通信完了後に読み出しデータを出力する。

 入力信号
 clk:クロック入力/今回使用してたボードは40MHzでした。
 rstb:リセット入力 0でリセット
 miso:SPIのデータ入力
 dev_sel[1:0]:接続するデバイス指定、指定された番号のCS信号を使う
 start:通信開始,1クロック幅の1でアクセス開始、busy==1の時は通信中なのでstart=1にできない。
 clk_end[4:0]:通信クロック数、1回の通信に使うsclkのクロック数指定、通信対象のデバイスの指定に合わせます。
 wr_data[31:0]:書き込みデータ、start==1の時にデータを取り込み、wr_data[31]から送信

 出力信号
 led_0:LED制御信号、動作中は点滅します。
 sclk:SPIのクロック信号
 ss0:dev_sel==00の時に使用するSPIのCS信号
 ss1:dev_sel==01の時に使用するSPIのCS信号
 ss2:dev_sel==10の時に使用するSPIのCS信号
 ss3:dev_sel==11の時に使用するSPIのCS信号
 mosi:SPIのデータ出力、モジュール外部でsda_o==1の時にSDA=Hi-Z、sda_o==0の時はSDA=0とする。 
 rd_data[31:0]:読み込みデータ、rd_data_en==1の時に有効、最後の読み込みデータがrd_data[0]に保存されている。
          通信対象のデバイスが出力するbit数より大きいMSB側データは無効データになります。
 rd_data_en:読み込みデータイネーブルう、I2Cから受信完了に1クロック幅で1になる。 読み出しデータが必要ないときは無視してください。
 busy:通信中ステータス、通信中に1

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

 詳細動作
 start=1とするとclk_end,wr_dataを取り込んで、SPIの通信が開始します。
 通信が開始するとbusy=1となります。
  通信開始時の波形
 

 SPI通信が開始されるとdev_sel==00なのでss0が1となります。mosiからwr_data[31],[30],[29]...の順でデータが出力されます。
 clk_endで指定した数のクロックがsclkから出力されると、ss0が0となって通信を完了します。
 受信データは最終受信bitがrd_data[0]に保存されて、[1],[2],[3]...の順で保存されています。
 
  SPI通信の波形
 



FPGAでの動作
 FPGAトップモジュール(spi_ap.v)
  spi_m_if単体ではインターフェース回路しかなので、コマンドの発行とリードデータを表示するコントローラと
 表示データをPCへ転送するRS232C通信回路を追加してトップモジュールを作成します。

   トップモジュール(spi_ap.v)のブロック図
 
  spi_mif:i2cマスタインターフェース
  spi_at93c86:EEPROM(at93c86)用コントロールモジュール
  spi_mcp3002_4922:AD変換器(MCP3002)、DA変換器(MCP4922)用コントロールモジュール
  ※spi_at93c86,spi_mcp3002_4922のどれか一つを選んでインプリメントします。
  fifo      表示データの一時保存
  ram      FIFO内部で使用するRAM、DFFで作ってあるので、セルを節約する場合はツールで作ったRAMモジュールに付け替えしてください。
  rs232c_tx_rx RS232C通信モジュール 

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

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

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

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

//AT93C86(EEPROM)用のコントロールモジュール
spi_at93c86 spi_device (

//MPC4922(DA変換)用のコントロールモジュール
//spi_mcp4922 spi_device (

//MPC3002(AD変換)用のコントロールモジュール
//spi_mcp3002 spi_device (

//MCP4922,MPC3002同時使用のコントロールモジュール
//spi_mcp3002_4922 spi_device (
  .clk(clk), 
  .rstb(rstb), 
  .led_0( led_0),
  .dev_sel(dev_sel),
  .start(start),
  .clk_end(clk_end),
  .wr_data(wr_data),
  .rd_data(rd_data),
  .rd_data_en(rd_data_en),
  .tx_fifo_data_en(tx_fifo_data_en),   
  .tx_fifo_data   (tx_fifo_data));

 SS信号の極性
   デバイスによってSS信号の極性が違っています。使用するデバイスの極性に合わせる記述をトップモジュールに記述してあります。
  spi_m_ifのSS信号は1で有効です。0で有効のデバイスへ接続する場合は信号値を反転させます。

  SS信号の極性の記述

//SPIインターフェースのインスタンス   
spi_m_if spi_m_if(   
  .clk(   clk),
  .rstb(  rstb),
  .sclk(  sclk),
  .ss0(   ss0_tmp),
  .ss1(   ss1_tmp),
  .ss2(   ss2_tmp),
  .ss3(   ss3_tmp),
  .mosi(  mosi),
  .miso(  miso),
  .dev_sel(dev_sel),
  .start( start),
  .busy(  busy),
  .clk_end(clk_end),
  .wr_data(wr_data),
  .rd_data(rd_data),
  .rd_data_en(rd_data_en));
   
//SS信号の極性変更
//デバイスによってSS信号の極性が違うの、使用デバイスの極性に合わせてください。
//st93c86 1で有効
assign ss0 = ss0_tmp;
//mcp4922 0で有効
assign ss1 = ~ss1_tmp;
//mcp3002 0で有効
assign ss2 = ~ss2_tmp;
//
assign ss3 = ss3_tmp;   

 EEPROM(AT93C86)との接続
  ATMEL社のI2C用EEPROM(ATC93C86:データシート)と接続してみました。
  デバイスは秋月電子で購入しました。
  コントロール回路(spi_at93c86)での動作
   40MHZでインクリメントするmain_cntのカウンタ値によりコマンド制御しています。
   初め1回だけライトイネーブルコマンドの書込み、
   書き込みコマンド、メモリ内アドレス11bit、データ1バイトを2回書き込み、
   読み出しコマンド、読み出し用のメモリアドレス11bit書き込み、
   1バイトのデータ読み出しを2回実行しています。

  制御部分の記述  

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0) begin
    dev_sel <= 2'b00; 
    start <= 1'b0;
    wr_data <= 8'h00;
    we_doen <= 1'b0;
    clk_end <= 5'd0;
    end
  else
    if ((we_doen==1'b0)&&(main_cnt ==26'd100000))//write eable
      begin
        dev_sel <= 2'b00; 
        start <= 1'b1;
        we_doen <= 1'b1;
        clk_end <= 5'd30;
        wr_data <= 32'h98000000;
      end
    else if ((main_cnt ==26'd500000)||//write
             (main_cnt ==26'd600000))  
      begin
        dev_sel <= 2'b00; 
        start <= 1'b1;
        clk_end <= 5'd22;
        wr_data <= {3'b101,adr,data,10'h000} ;
        we_doen <= we_doen ;
      end
    else if ((main_cnt==26'd690000)||//read
             (main_cnt==26'd800000))
      begin
        dev_sel <= 2'b00; 
        start <= 1'b1;
        clk_end <= 5'd22;
        wr_data <= {3'b110,adr,18'h00000} ;
        we_doen <= we_doen ;
      end
    else
      begin
        dev_sel <= 2'b00; 
        start <= 1'b0;
        wr_data <= wr_data;
        we_doen <= we_doen ;
      end


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

 ログ出力
  TXDをFT232D経由でPCのパソコンへ接続(第3回を参照)して、データを表示してみます。
 ログにファーマットは以下の様になっています。
 [R/W]_adr[11:0]_data[7:0]
 R:リードアクセス
 W:ライトアクセス
 adr[10:0]:メモリ内アドレス
 data[7:0]:ライトアクセス時は書き込みデータ、リードアクセス時は読み出しデータ
 
 アドレスをインクリメント、1バイトデータをデクリメントして2回書き込みして
 その後に1バイトデータを2回読み出しています。
 書き込んだデータ読み出されています。


 DA変換器、AD変換器との接続
  次は、MICROCHIP社のDA変換器(MCP4922:データシート)とAD変換器(MCP3002:データシート)に接続してみます。
  デバイスは秋月電子で購入しました。

  コントロール回路(spi_mcp3002_4922)での動作
   40MHZでインクリメントするmain_cntのカウンタ値によりコマンド制御しています。
   初めにdev_sel=01としてMCP4922(DA変換)へチャンネル選択、ch_A/B,BUF,GAB,SHDNB,data[11:0]を書込み
   その後にdev_sel=10とstart,SGI,ODD,MSBFを書込みしてデータを読み出し
   書き込みデータ(12bit)と読み出したデータ(読み出しデータは10bitなのでLSBに2bitの0を追加)は、ASCIIコード変換してRS232C経由で表示します。

  制御部分の記述  

always @ (posedge clk or negedge rstb )
  if (rstb==1'b0) begin
    dev_sel <= 2'b01; 
    start <= 1'b0;
    wr_data <= 8'h00;
    clk_end <= 5'd0;
    end
  else
    if (main_cnt ==26'd20000)//write mcp4922
      begin
        start <= 1'b1;
        clk_end <= 5'd16;
        wr_data <= {4'b0111,data,16'h0000} ;//ch_a,buf=1,gab=1,shdnb=1,data
        dev_sel <= 2'b01; 
      end
    else  if (main_cnt ==26'd120000)//read mcp3002
      begin
        start <= 1'b1;
        clk_end <= 5'd15;
        wr_data <= {5'b1101,28'b00000000000} ;//start,SGI,ODD,MSBF
        dev_sel <= 2'b10; 
      end
    else
      begin
        start <= 1'b0;
        wr_data <= wr_data;
        clk_end <= clk_end;
        dev_sel <= dev_sel; 
      end   


  FPGAボード(MFPGA_SPAR3E)との接続
 
 動作確認のためにMCP4992のVoutAとMCP3002のCH0を接続してあります。

 ログ出力
 TXDをFT232D経由でPCのパソコンへ接続(第3回を参照)して、データを表示してみます。 
 ログにファーマットは以下の様になっています。
 先頭がR,Wの場合
  [DA/AD]_data[11:0]
  DA:DA変換器MCP4922へアクセス
  AD:AD変換器MCP3002へアクセス
  data[11:0]:DA変換器MCP4922へアクセスでは書き込みデータ
         AD変換器MCP3002へアクセスでは読み出しデータ、デバイスの出力値が10bitのため、LSB側に2bitの0を追加

 
 DA変換器には、アクセス毎に0x010を加算しています。
 DA変換器で出力した信号をAD変換器の接続してるので、
 DA変換器の設定値とほぼ同じ値がAD変換器から出力されている。

 アナログ信号の波形
 
 DA変換器に0x000から0x010づつ加算して値を設定しています。
 0xff0のあとに0x000へ戻るので鋸波になっています。

 
紹介した回路を試す場合は、自己責任でお願いします。
次回は、”第8回 FIFOとアービタでデータの流れを制御してみる”を紹介する予定です。

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


変更履歴
(2010/10/27) 初版


TOPへ戻る