1.はじめに
数十チャネルのAD変換を同時におこないたいってことはありませんか?
ということで、シリアルIFをもつ小型ADコンバータデバイスADS7046からデータ取得するためのカスタムSPI IPを作成してみます。
AMDの開発環境(VIDAVO)のもAXI SPIのIPはあるが、一つのSPI IFしか対応しておらず、これを数十個並べるにはAXIバスの制約なあまりに冗長な機能があるのでもっとシンプルかつ今後の機能拡張を想定して自作カスタムSPI IF IPを作成することにする。さらに複数のADS7046からのデータを同時に取得できるようにMISOを任意に増やせる機能を追加する。
2.目指す構成~SPI_MISOの複数化~
最初に基本的なSPI IF IPを作成して、最終的には任意にMISOポートを増やせるようなIPにする。そうしたら必要なだけads7046を配置して同時にAD変換できるような想定系が構築できる。マイコンでは多くてもSPIポートが7個くらいなので、Figure 2‑1に示すようにFPGAであればGPIOポートがあるだけ増やせるので柔軟なシステム構成が構築できる。

SWからの使い方としてはMISOデータ受信レジスタを並べるので、配置した分の受信データレジスタからメモリイメージでデータ取得することができる。配線的にはSPI_CLKとSPI_CSを共有することが可能だが、扱う周波数が高いとポートのドライブ能力と配線マッチングの影響が出てくるので実体配線についてはいくつかのバッファが必要になるかもしれない。
FPGA内部ブロックとしてはロジックリソース内での合成ができればSPI_MISOのポートを増やすことができるが、SPIの安定動作には基板上の配線やレイアウト、SPI_CLK周波数が影響するため実運用的にどこまでSPI_CLKを共有できるかは不明である。
3.開発環境
3.1. 使用するデバイス
XC7Z020-CLG484-1 AMD Zynq™ 7000 SoC ZC702 評価キット
• 1GB DDR3 SODIM メモリで高度なメモリインターフェイス
• USB OTG、UART、IIC、CAN バスとのシリアル接続を有効にする
• デュアル Arm Cortex-A9 コア プロセッサでエンベデッド プロセッシングをサポート
• 10-100-1000 Mbps イーサネット (GMII, RGMII および SGMII) でネットワーク アプリケーションを開発
• HDMI 出力付きビデオ ディスプレイ アプリケーションをインプリメント
• FPGA メザニン カード (FMC) インターフェイスで I/O を拡張
ロジック セル (K) | 85 |
DSP スライス | 220 |
ブロック RAM (Mb) | 4.9 |
最大 I/O ピン数 | 200 |
3.2. 使用した開発環境
AMD VIVADO / Vitis 2025.1版
動作PC:windows11
プロセッサ AMD Ryzen 9 4900H with Radeon Graphics 3.30 GHz
実装 RAM 48.0 GB (47.4 GB 使用可能)
4.SPI IP 仮仕様
項目 | 内容 | 備考 |
---|---|---|
ビット長 | 16it | MSB First |
転送クロック | 60MHz以下 | 本評価実験では33MHz@clk_2x=70MHz AXI4-Lightバスクロックよりも低く設定しておくほうがいい。 |
SPIモード | mode 0 | 下記の図参照 |
その他補助機能 | read時に,レジスタにて更新されたデータかどうか、およびBusyフラグビットによるフラグあり。 | |
GPO 2ch | 2本 | レジスタにてGPOポート出力できるようにしているが、デバッグ用として使用していた名残”(-“”-)” |
SPIにはいくつか動作モードがいくつかありますが、今回は一般的に使用されることが多いSPI mode 0を実装することにします。(必要であれば後でモードを追加すればいいことだし・・・)

信号名 | 方向 | 説明 |
---|---|---|
S00_AXI | 双方向 | AXI4-light バス |
s00_axi_aclk | IN | AXI4-light バスクロック。本評価では150MHzにしている |
s00_axi_aresetn | IN | AXI4-light バスリセット |
clk_2x | IN | spi_clkクロックの2倍の周波数を設定する。ただしs00_axi_aclkよりは小さい値のほうがいい。本評価では70MHzに設定している。 |
rst_n | IN | 内部SPIブロックのリセット。s00_axi_aresetnと同じでもいいかも |
spi_mosi | out | spi mosiポート |
spi_cs | out | spi cs ポート |
spi_miso[n] | out | spi_misoポート 合成時にポートの数を指定する。default=4 |
spi_clk | out | spi clkポート。 |
gport[1:0] | out | GPOポート2本。 |
dv_sync_d | out | 内部データラッチタイミグ。デバッグ用。通常は未使用 |
オフセットアドレス (4byte単位) | レジスタ名 | Access type | 説明 |
---|---|---|---|
0x0000 | MOSI_W | R/W | SPI MOSIデータの設定と送出。書き込みと同時に送出する W: [15:0] write data R: [16]busy flag 1:busy, 0:idle |
0x0004 | MISO_R | R | SPI MISOデータの読み出し [15:0] MISOデータ [30] busy flag 1:busy(送受信中), 0:idle [31] New Data flag (1=新着データあり。読みだしたら”0”になる。) [29:16]予約 |
0x0008 | GPO | W/R | [1:0] 2本のGPOの設定。ポートはOUT方向のみ |

実はこのブロックを作成するのにゴニョゴニョと時間を費やした!やはりはまったのはクロックドメインが異なることからくる信号伝送のところ。今回はこの説明は長くなるので省略する。(*^-^*)



5.SPI IP動作確認
address +0に何らかの何らかのspi_mosiデータ。ここではインクリメントカウンタ(16bit)を書き込むとStart Triggerを生成してspi動作を開始する。

またS/W視点からAXI4-Lihg バスの動作もsILAを用いて観測してみます。
以下評価SWのSPI IPレジスタアクセスしている部分の一部抜粋
MYSPI_WRITE_TXDATA(BASEADDR, count);
rx_regdata1 = SPIMULTRX; //一回目
rx_regdata2 = SPIMULTRX; //二回目
printf("TX:0x%04X ", count);
printf(" .RX(1):0x%08X", rx_regdata1);
printf(" ,RX(2):0x%08X\n",rx_regdata2);

AXI4-LightのBUS Clockが142.85MHz。SPI MSI送信レジスタwriteしてすぐにSPI受信レジスタを2回連続でreadしてターミナルでログをみたとこ。Read#1は”0xc000xxxx”となっており、”New flag”と”Busy” Flagが同時に決着しているところ状態。
“0x4000xxxx”はBusy(送受信中)を示している。

正しいアクセス方法としては以下の手順で行う
- 送信開始
BASE+0x08 を読み、BUSY==0 を確認。
BASE+0x00 に 16bit データを書き込む(自動的に送信開始)。 - 受信確認
BASE+0x08 を読み、RX_NEW==1 を確認。
[15:0] が受信データ。
読み出し後 1クロックで RX_NEW がクリアされる。 - GPIOCTL (0x04)
独立した GPIO 制御機能として使用可能。
SPI動作確認用のサンプルコード(主要部を抜粋)
while (1) { /* Set the LED to High */ XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, (count & 0xFFFF)); count++; /* Wait a small amount of time so the LED is visible */ // 1) GPIO 出力を設定 (例: count値 を出力) MYSPI_WRITE_GPIO(BASEADDR, count & 0x03); // 2) SPI 転送開始 MYSPI_WRITE_TXDATA(BASEADDR, count); // 3) 各チャネルの受信完了を確認 temp = 0; do { rx_reg = MYSPI_READ_RXDATA(BASEADDR, ch); temp++; } while (!MYSPI_RXDATA_GET_NEW(rx_reg)); // rx_new が立つまで待機 printf("TX:0x%04X RX:0x%08X\n", count, MYSPI_RXDATA_GET_DATA(rx_reg)); }

7.ADS7046を複数(2個)からデータ取得するサンプル動作例
複数SPI MISOの機能IPブロックはできたので、ここでようやく当初の目的であるADS7046を複数接続して並列でデータを取得してみることにする。
その前の準備として ADS7046デバイスはあまりにも小さいので評価ボードに接続できるサブ基板を切削して作る。 今回使用した評価ボードでは2.54mmの汎用ピンヘッダ経由で使用できるGPIOポートが少なかったので、ADは2chにした。


AD 2ch分のデータ取得するためのサンプルプログラム。(一部抜粋)
int main() { int Status, spistat; unsigned short count = 0; uint32_t rx_reg; uint32_t rx_regdata0[SAMPLENUM],rx_regdata1[SAMPLENUM]; int temp; init_platform(); #ifndef SDT Status = UartPsConf(UART_DEVICE_ID); #else Status = UartPsConf(XUARTPS_BASEADDRESS); #endif /* Initialize the GPIO driver */ #ifndef SDT Status = XGpio_Initialize(&Gpio, GPIO_EXAMPLE_DEVICE_ID); #else Status = XGpio_Initialize(&Gpio, XGPIO_AXI_BASEADDRESS); #endif if (Status != XST_SUCCESS) { xil_printf("Gpio Initialization Failed\r\n"); return XST_FAILURE; } /* Set the direction for all signals as inputs except the LED output */ XGpio_SetDataDirection(&Gpio, LED_CHANNEL, ~LED); /* Loop forever blinking the LED */ print("=== myspi_multi sample (NUM_MISO=8) ===\n"); /* Set the LED to High */ XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, (count & 0xFFFF)); count++; /* Wait a small amount of time so the LED is visible */ // 1) GPIO 出力を設定 (例: count値 を出力) MYSPI_WRITE_GPIO(BASEADDR, count & 0x03); // データ取得ループ for(int i = 0;i < SAMPLENUM; i++){ // 2) SPI 転送開始 MYSPI_WRITE_TXDATA(BASEADDR, count); // 3) 各チャネルの受信完了を確認 temp = 0; do { rx_reg = MYSPI_READ_RXDATA(BASEADDR, 0); temp++; } while (!MYSPI_RXDATA_GET_NEW(rx_reg)); // rx_new が立つまで待機 // Ch0の有効タイミグをトリガにして接続されたch分のADデータ取得する // ADS7046出力データはフォーマット仕様により2bit右にshiftする。 rx_regdata0[i] = MYSPI_RXDATA_GET_DATA(rx_reg) >> 2; rx_reg = MYSPI_READ_RXDATA(BASEADDR, 1); rx_regdata1[i] = MYSPI_RXDATA_GET_DATA(rx_reg) >> 2; } // 取得データの表示 for(int i = 0;i < SAMPLENUM; i++){ printf("%d, %d, %d \n", i, rx_regdata0[i],rx_regdata1[i]); } cleanup_platform(); return 0; }
8.PL部のMultiSPI動作確認

CLK_2xの半周期(35.7MHz、0.028usec)で1カウントのメモリスケールとなっている。この波形よりStart間隔の時間は128cycleなので128*0.028usec=3.5usec周期のカウントとなり約285kHzのサンプリング周波数となっている。取得するチャネルを増やすと、複数同時サンプリングは変わらないが、レジスタから読み出す時間がかかるので、2ch接続のこの条件時の3.5usecのサンプリング周期は長くなる。
同じに信号をch0,ch1に入力すると同じ波形になることから、「ch0正弦波、ch1三角波」
の波形ピークの位相が少しずれている問題はAnalog DiscoveryIIの問題である可能性が高い。
ということで、AD変換タイミグにずれがないことがわかった。
まとめ
今回FPGAのカスタムIPの「複数SPI MISO」を作成して無事にデータを取得することができた。
FPGAの開発ツールはバージョンアップされてもいつもどこかにバグが潜んでいる状態なので、それが「バグ」なのか「仕様」なのか「使い方が悪い」のかを見極めるのが難しい。ここは永遠の課題かな。
今後どの程度安定してパラレルでAD変換できるか評価できる機会があればおこないたい。
では!