ZYBOのPSでI2Cを動かしてみた
ZYBOのPS(ARM Core部分)のI2Cを動かしてみました。本当は、OV7670カメラモジュールを使って画像の取り込みをやってみたかったのですが、Amazonで買ったOV7670モジュールがどうも不良品のようで、SCCB(I2Cのサブセットのカメラモジュール制御プロトコル)を使ってカメラモジュールとどうしても通信ができず、その過程で分かったIC2の使い方を書いています。
カメラモージュールの実験は、代替え品をaitendoさんに注文したので、商品が届いたら出直しです。Amazonのもaitendoさんのもカメラモジュール自体は同じものを使っていると思いますが、回路構成が若干異なり、aitendoの製品の方が使いやすと思います。理由は、Amazonで販売しているモジュールはパワーオンリセットやI2C信号線のプルアップがないためです。ペリフェラル側にプルアップがあると、ブレッドボードなどを経由してプルアップの配線をする必要がないので構成がスッキリします。
プルアップはFPGA内蔵のプルアップ機能を使う手もありますが、外付け抵抗を使ったプルアップの方が安定して動くと思います。ちなみに、今回の実験で使ったTMP102温度センサーは外付けプルアップ抵抗が必要で、FPGAのプルアップではクロックを10KHzに落としても正常に動作しませんでした。FPGAの内蔵プルアップはweak pull-upなので高速動作には使えないというようなフォーラムの書き込みがありました。
Update:FPGA内蔵のプルアップで動作しなかったのは、200Ωの保護抵抗が直列に入っているStandard Pmod (JE)にI2Cセンサーをつないだ時の場合でした。試しに保護抵抗が入っていない、Hi-Speed Pmod (JD)にFPGA内蔵のプルアップでつないでみたたらクロック100KHzでも動作しました。条件がよければFPGA内蔵のプルアップでも動作しますが、10KΩ程度の外部抵抗を使った方がより安定していると思われます。
ZYNQ PSのI2Cを使う方法
ZYBOでZYNQ PSのI2Cを使う方法は、以下の2通りのやり方があります:
- PL(FPGAブロック)を介して、ZYBOのPmodコネクタ(JB〜JE)に接続する
- PS(CPUブロック)直結のPmod MIO(JF)に接続する
ここでは、それぞれの場合について試してみます。開発環境は執筆時点で最新の、Vivado 2016.2を使用しています。
PL(FPGAブロック)を介して、ZYBOのPmodコネクタ(JB〜JE)に接続する方法
Vivadoで新規プロジェクトを作成して、IP Integrator(IPI)で新規のデザインを作成します(今回はsystemというデザイン名にしています)。図のようにZYNQ 7 Processing Sysemのみをインスタンス化してクロックの接続を手動で行います。
ZYNQ 7 Processing Systemのアイコンを右クリックして、”Customize Block..”メニューを開きます。
MIO Configurationをクリックして、IO Peripheralsのプルダウンを開き、I2C0にチェックを入れます。また、IOに「EMIO」を指定します。EMIOを指定することによって、CPUコアのI2C信号がFPGAのPL部分を通って外部に接続できるようになります。
ブロックデザインに戻って、ZYNQ 7のIIC_0を右クリックして”Make External”メニューを選択します。この操作によって、I2Cの信号をFPGAから外部に出力できるようになります。外部出力を作る方法には、”Create Interface Port.."などオプション指定ができる方法もありますが、今回はMake Externalで問題ありませんでした。
次に、Block DesignのSource画面に移って、デザイン名(今回の場合はsystem)を右クリックし、”Create HDL Wrapper..”を選択します。
以下のダイアログボックスが表示されるので、”Let Vivado manage wrapper and auto-update”を選択してOKをクリック。
Flow Navigatorから「Run Implementation」を実行。Implementationが終了すると以下のダイアログボックスが表示されるので、”Open Implemented Design”を選択。
画面下に表示される「IO Ports」タブを開くとIIC_0ポートのピンアサイン画面が表示されます。ここに信号を接続したいFPGAのピン番号を入力します。
ZYBOのReference Manualを参照して、今回は信号をPmod JE(Standard Pmod)のJE1とJE2に接続します。それぞれに対応するFPGAのピン番号V12とW16を入力、出力電圧をLVCMOS33(3.3V)に指定します。
CTL-Sキーを押すと、Constraintsを保存するダイアログボックスが表示されるのでOKをクリック。
ファイル名を指定して保存すると、SourcesにConstraints(制約)ファイルが追加されています。
続けて、Flow Navigatorから「Generate Bitstream」を実行。実行が完了すると以下のダイアログボックスが表示されるので、”Open Implementation Design”を指定してOKをクリック。
File Menu → Export → Export Hardware..を選択。Include bitstreamをチェックしてOKをクリック。
File Menu → Launch SDKを選択。SDKが立ち上がります。
SDKのFile Menu → New → Application Projectを選択。New Projectの設定画面にプロジェクト名(今回はI2C Test)を入力し、Nextをクリック。
Hello World Templateを選択します(このテンプレートにはprint文を使ってUARTにデバッグ情報を出力するために必要なファイルが含まれているため)。
プロジェクトが生成されたら、helloworld.cをリネーム。今回は、i2c_test.cにしています(これは好みですが)。
テンプレートが自動生成したソースを全部削除して、以下のコードを入力。
#include "platform.h" #include "xparameters.h" #include "sleep.h" #include "xiicps.h" #include "stdio.h" // I2C parameters #define IIC_SCLK_RATE 100000 // clock 100KHz #define TMP102_ADDRESS 0x48 // 7bit address #define IIC_DEVICE_ID XPAR_XIICPS_0_DEVICE_ID XIicPs Iic; int Init() { int Status; XIicPs_Config *Config; /**< configuration information for the device */ Config = XIicPs_LookupConfig(IIC_DEVICE_ID); if(Config == NULL){ printf("Error: XIicPs_LookupConfig()\n"); return XST_FAILURE; } Status = XIicPs_CfgInitialize(&Iic, Config, Config->BaseAddress); if(Status != XST_SUCCESS){ printf("Error: XIicPs_CfgInitialize()\n"); return XST_FAILURE; } Status = XIicPs_SelfTest(&Iic); if(Status != XST_SUCCESS){ printf("Error: XIicPs_SelfTest()\n"); return XST_FAILURE; } XIicPs_SetSClk(&Iic, IIC_SCLK_RATE); printf("I2C configuration done.\n"); return XST_SUCCESS; } int i2c_write(XIicPs *Iic, u8 command, u16 i2c_adder) { int Status; u8 buffer[4]; buffer[0] = command; Status = XIicPs_MasterSendPolled(Iic, buffer, 1, i2c_adder); if(Status != XST_SUCCESS){ return XST_FAILURE; } // Wait until bus is idle to start another transfer. while(XIicPs_BusIsBusy(Iic)){ /* NOP */ } return XST_SUCCESS; } int i2c_read(XIicPs *Iic, u8* buff, u32 len, u16 i2c_adder) { int Status; Status = XIicPs_MasterRecvPolled(Iic, buff, len, i2c_adder); if (Status == XST_SUCCESS) return XST_SUCCESS; else return -1; } int main() { init_platform(); Init(); u8 buff[4]; u16 rawdata; float temp; while(1) { i2c_write(&Iic, 0, TMP102_ADDRESS); i2c_read(&Iic, buff, 2, TMP102_ADDRESS); rawdata = ((int8_t)buff[0] << 4) | ((u8)buff[1] >> 4); temp = (float) ((float)rawdata * 0.0625); printf("Tmep: %2.1f\n", temp); usleep(1000*1000); // sleep 1sec (1000 x 1000us) } cleanup_platform(); return 0; }
ファイルをセーブすると自動的にビルドが実行されます。次に、ツールバーのProgram FPGAボタンをクリックしてFPGAのコンフィグデーター(Bitstream)をJTAGインタフェース経由で転送します(ZYBOのJP5ジャンパーピンをJTAGに設定しておくこと)。
続いて、デバッガーを起動してPS(ARM CPUコア)のプログラムを転送しますが、ちょっとコツがあります。私の環境では、Debgug Configurationをいきなり作って起動しようとするとエラーが出てプログラムの起動に失敗することが多いです。そのため、Project ExplorerのI2C_Testプロジェクトを右クリックし、Debug As → Launc on Hardware (GDB)を選択してまずプログラムを起動します。無事プログラムが起動するとmainの最初の行でプログラムがブレークします。
この段階ではDebug Configurationをしていないため、"STDIO not connected”の警告がConsoleに出力され、print文の実行結果は表示されません。ここで一旦、ツルーバーのTerminateボタンを押してプログラムを終了します。デバッグPerspectiveから一旦C/C++ Perspectiveに戻ってProject Explorer → I2C_Testプロジェクトを右クリック → Debug As → Debug Configurations..を選択。STDIO ConnectionにCOMポート番号と通信速度(115200)を設定してDebugをクリック。ツールバーのResumeボタンをクリックするとプログラムが動き出します。
TMP102から読み取った温度がConsole画面に表示されています。
今回使っているI2CドラバーライブラリのサンプルやドキュメントはSDKのインストールフォルダーの中(C:\Xilinx\SDK\2016.2\data\embeddedsw\XilinxProcessorIPLib\drivers)にあるので、コードの中身の説明は割愛します。
以上が、I2Cの信号をFPGA(PL)経由で取り出す方法です。FPGA内の配線を見ると、I2CのSDAなどI/Oの信号はinとoutの2本の独立した信号としてCPUから出ており、Tri-state Bufferを介してI/Oの信号として外部ピンに接続されています。なんだが回りくどいことをやってるんですね。
このIOBUFはVivadoのIPをWrapperの中でインスタンス化して接続していることが以下のHDLコードから分かります。
PS(CPUブロック)直結のPmod MIO(JF)に接続する方法
SDKを終了し、VivadoのBlock Designを開き、IIC_0ポートを削除します。
Customize Block..を開き、I2C0の接続先を「MIO 10..11」に変更します。
OKをクリックし、Run Implementationを実行します。実行後のI/O Portsタブを見ると、I2Cの信号が消えていることが分かります。これはI2Cの信号がFPGAブロック(PL)を経由しなくなったことを意味します。
Generate Bitstreamを実行し、終了後、Export Hardwareを実行して再度SDKを立ち上げます。SDKのProject ExplorerからI2C_Test_bspを右クリックして、Re-generate BSP Sourcesを実行。さらに、Project Menu → Clean..を実行してビルド環境をクリーンアップします。
センサーのI2CのピンをJFコネクタのMIO-10 (JF2: SCL)、MIO-11 (JF3: SDA)に繋ぎ変えてデバックを実行するとプログラムが起動します。今回はCPUから直接I2Cに出力しています。デバッグでプログラムの起動に失敗する場合は、Debgug Configurationに入って既存のデバッグエントリを一旦削除してから、最初の手順ででデバッグを際実行する(先ずはDebug Configを作らずにDebug Asから起動する)とうまくいくと思います。
ということで、PL経由・PS直結のどちらでもI2Cが動くことが確認できました。
参考情報
« ZYBOでPmodCLP(Parallel Interface LCD)を動かす | トップページ | オシロ(RIGOL DS1054Z)を購入しました »
「FPGA」カテゴリの記事
- PYNQ-Z1のOverlay読み込みとPythonからのFPGA PLの制御(2)(2017.02.04)
- PYNQ-Z1のOverlay読み込みとPythonからのFPGA PLの制御(2017.01.28)
- Vivado Constraints Wizardによるクロック・入出力制約の作成(2016.12.11)
- ZYBOでOV7670カメラモジュールのVGA画像を表示する(2016.12.04)
- Vivado Integrated Logic Analyzer(ILA)の使い方(2016.11.12)
「ZYBO」カテゴリの記事
- Vivado Constraints Wizardによるクロック・入出力制約の作成(2016.12.11)
- ZYBOでOV7670カメラモジュールのVGA画像を表示する(2016.12.04)
- Vivado Integrated Logic Analyzer(ILA)の使い方(2016.11.12)
- ZYBOでOV7670カメラモジュールの画像を表示する(2016.10.22)
- ZYBOのPSでI2Cを動かしてみた(2016.09.24)
コメント
この記事へのコメントは終了しました。
« ZYBOでPmodCLP(Parallel Interface LCD)を動かす | トップページ | オシロ(RIGOL DS1054Z)を購入しました »
お世話になります。 MTと言います。
本プロジェクトを実行したのですが、TMP102の温度測定とありますがこれはどこにあるのでしょうか? またSDKのソースコードで最初に#define IIC_DEVICE_ID XPAR_XIICPS_0_DEVICE_IDの定義がありますがどこで定義しており具体的な値はいくつでしょうか? 宜しく願います。
投稿: MT | 2018年4月15日 (日) 09時39分
MTさん
todotaniです。
TMP102の温度の読み取りは、Cソースコードmainのwhileループの中で行なっています。(89〜95行目)
TMP102のレジスタを読み込み12bitの整数値に変換し(92行目)、0.0625の乗数を掛け算すると温度が得られます。詳細はTMP102のデータシートを参照して下さい。
IIC_DEVICE_ID XPAR_XIICPS_0_DEVICE_IDは"xparameters.h"内で定義されています。IDEで参照先を調べたいパラメータを選択 → 右クリック → Open Declarationで参照先のファイルが開きます。
投稿: | 2018年4月15日 (日) 22時42分
回答ありがとうございます。 私の勘違いで本I2CプロジェクトについてはZYBO実装で動作したのですが温度センサー(TMP102)がないのでTeratermでの表示は当然ないわけですね。
実はIIC_DEVICE_ID XPAR_XIICPS_0_DEVICE_IDがNGだったのは
「ZYBOでPmodCLP(Parallel Interface LCD)を動かす」の方でした。もう一度整理してみます。
投稿: MT | 2018年4月19日 (木) 16時54分
お世話になります。上記の質問の続きということでこちらに書きますが
「ZYBOでPmodCLP(Parallel Interface LCD)を動かす」のSDKのソースコードで最初のLCDP.hと次のLCDP.cppの下にあるWriteByteで始まる555行に渡る長いコードはどこに入りますか? それともWriteByte.cppにすべきでしょうか?
投稿: MT | 2018年4月19日 (木) 17時39分
WriteByte関数以下のコードは、LCDP.cppの一部です。
編集の問題で行番号が分断されておりました。
ソースを修正しておきましたのでご確認下さい。
大変失礼しました。
投稿: todotani | 2018年4月19日 (木) 22時33分