”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) ブロック図のモジュール名の誤記修正