FPGAを使った多チャネルSPI(MISO)機能

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ポートがあるだけ増やせるので柔軟なシステム構成が構築できる。

Figure 2 -1 SPIブロックの拡張イメージ


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
ちょっと大きめのPSOCって感じでしょうか(^-^)

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 仮仕様

項目内容備考
ビット長16itMSB First
転送クロック60MHz以下本評価実験では33MHz@clk_2x=70MHz AXI4-Lightバスクロックよりも低く設定しておくほうがいい。
SPIモードmode 0下記の図参照
その他補助機能 read時に,レジスタにて更新されたデータかどうか、およびBusyフラグビットによるフラグあり。
GPO 2ch2本レジスタにてGPOポート出力できるようにしているが、デバッグ用として使用していた名残”(-“”-)”
SPI IF概要

SPIにはいくつか動作モードがいくつかありますが、今回は一般的に使用されることが多いSPI mode 0を実装することにします。(必要であれば後でモードを追加すればいいことだし・・・)

信号名方向説明
S00_AXI双方向AXI4-light バス
s00_axi_aclkINAXI4-light バスクロック。本評価では150MHzにしている
s00_axi_aresetnINAXI4-light バスリセット
clk_2xINspi_clkクロックの2倍の周波数を設定する。ただしs00_axi_aclkよりは小さい値のほうがいい。本評価では70MHzに設定している。
rst_nIN内部SPIブロックのリセット。s00_axi_aresetnと同じでもいいかも
spi_mosioutspi mosiポート
spi_csoutspi cs ポート
spi_miso[n]outspi_misoポート 合成時にポートの数を指定する。default=4
spi_clkoutspi clkポート。
gport[1:0]outGPOポート2本。
dv_sync_dout内部データラッチタイミグ。デバッグ用。通常は未使用
カスタムSPI IP masterブロックIF仕様(内部信号の定義)
オフセットアドレス (4byte単位)レジスタ名Access type説明
0x0000MOSI_WR/WSPI MOSIデータの設定と送出。書き込みと同時に送出する W: [15:0] write data R: [16]busy flag 1:busy, 0:idle
0x0004MISO_RRSPI MISOデータの読み出し [15:0] MISOデータ [30] busy flag 1:busy(送受信中), 0:idle [31] New Data flag (1=新着データあり。読みだしたら”0”になる。) [29:16]予約
0x0008GPOW/R[1:0] 2本のGPOの設定。ポートはOUT方向のみ
S/Wレジスタ仕様
SPI IPの全体概念図

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

Multi SPIに実際に作成したIP Block。赤枠のところでMISOポート数を設定する。
カスタム SPI IPの評価ブロック
AXIバスおよびPL部のクロックの設定

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); 
連続して[MOSI Write]→[MISO Read#1] →[MISO Read#2]を行いステータスフラグ動作を確認する。正常に動作しているのがわかる。

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

write直後にRX(1),RX(2)でレジスタを見てもSPIはBUSY(送受信中)なので正しく値を読むことができない。正常な動作。

正しいアクセス方法としては以下の手順で行う

  1. 送信開始
    BASE+0x08 を読み、BUSY==0 を確認。
    BASE+0x00 に 16bit データを書き込む(自動的に送信開始)。
  2. 受信確認
    BASE+0x08 を読み、RX_NEW==1 を確認。
    [15:0] が受信データ。
    読み出し後 1クロックで RX_NEW がクリアされる。
  3. 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));
}
データ折り返し動作で評価したものを示す。 ちなみにrx_new flagが立つまでの待ちループ回数は約temp=3であった。 AXI4-Lightバスクロック:142.85MHz SPI_2xクロック:71.428MHz (SPI_CLK=35.7MHz) 条件下。 1usecくらい待てばいいくらいの感じ。

7.ADS7046を複数(2個)からデータ取得するサンプル動作例

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

ADS7046サブ基板回路図

ADS7046サブ基板ASSY
完成したADS7046サブ基板

MISO(入力)を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動作確認

2ch AD変換を行っている波形

CLK_2xの半周期(35.7MHz、0.028usec)で1カウントのメモリスケールとなっている。この波形よりStart間隔の時間は128cycleなので128*0.028usec=3.5usec周期のカウントとなり約285kHzのサンプリング周波数となっている。取得するチャネルを増やすと、複数同時サンプリングは変わらないが、レジスタから読み出す時間がかかるので、2ch接続のこの条件時の3.5usecのサンプリング周期は長くなる。

Analog Discovery IIから信号を入力
wave genで正弦波、三角波を生成
ch0正弦波、ch1三角波

ch0正弦波、ch1三角波出力された信号をみると波形ピークの位相がちょっとずれているように見える。
そこで三角波の同じ信号をch0,ch1に入力してみる。

ch0,1に同じ三角波を入力する。一見すると分かりにくい。波形が1個しかないように見えるが、実際は2本が重なっている状態。

同じに信号をch0,ch1に入力すると同じ波形になることから、「ch0正弦波、ch1三角波」
の波形ピークの位相が少しずれている問題はAnalog DiscoveryIIの問題である可能性が高い。
ということで、AD変換タイミグにずれがないことがわかった。

まとめ

今回FPGAのカスタムIPの「複数SPI MISO」を作成して無事にデータを取得することができた。
FPGAの開発ツールはバージョンアップされてもいつもどこかにバグが潜んでいる状態なので、それが「バグ」なのか「仕様」なのか「使い方が悪い」のかを見極めるのが難しい。ここは永遠の課題かな。
今後どの程度安定してパラレルでAD変換できるか評価できる機会があればおこないたい。
では!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です