MicroPythonのSPIライブラリでの失敗談

プログラミング

 ShrikeLiteというRP2040+FPGAが搭載されたボードで遊んでいます。MicroPythonをRP2040側で動作させて、FPGA側に組み込んだSPIのデバッグをしている時の話です。SPIを経由してFPGA側から32ビットのデータが読み出せるようにしたかったので、FPGA側ではデータ幅32ビットのSPIを作り込みました。シミュレータで動作確認したところ良い感じに動作していましたが、RP2040側でデータを読み出すと、0x01234567という固定データが読み出される筈なのに、0x23672367と読み出されるのです。何でだろう?と思って、とうとうオシロスコープまで接続して波形を確認すると、何とSCKが32でも8でもなく、16づつ出ているではありませんか!これが冒頭の画像です。MSBfirstなので、データは0x0123と読取る事ができます。

 ネットをググったりAIに聞くと、そもそもArduinoのSPIライブラリは8ビットのリードライトにしか対応していないというのです。しかし、10年程前にESP32とCycloneIIの組み合わせでGPSDOを製作した時には、SPI.transfer32というメソッドを使っていたので、てっきりSPIで32ビットデータのやり取りはできるものだと信じ込んでいました。ところが、RP2040のearlphilhowerのSPIライブラリにはSPI.transfer32というメソッドは見当たりませんでした。MicroPythonはどうかと言うと、spi.write_readintoの例では8バイトデータを送受信する例が示されているので、32ビット(4バイト)のデータ転送もできるだろうと思ったのが運の尽きでした。何故一度に16ビットのデータをバースト転送するのか理解に苦しみましたが、色々やってみたところ、次に示すSPIコンフィグレーションに問題があることが分かりました。

spi = SPI(0,
          baudrate=100_000,
          polarity=0,
          phase=0,
          bits=32,
          firstbit=SPI.MSB,
          sck=Pin(SCK),
          mosi=Pin(MOSI),
          miso=Pin(MISO))

 この中のbits=32という記述が悪さをしていたのです。bits=8と書き換えてspi.readメソッドで4バイトのデータを受信するように変更すると、期待どおりに動作するようになりました。

MPY: soft reboot
[shrike_flash] FPGA reset done 
[shrike_fpga] Starting FPGA flash...
[shrike_fpga] flashing: SPI32const.bin
[shrike_flash] FPGA programming done.
Received 00, 00, 00, 00
Received 01, 23, 45, 67
Received 01, 23, 45, 67
Received 01, 23, 45, 67
Received 01, 23, 45, 67

 この時のMicroPythonのソースコードは次の通りです。この例では受信データはbytearray型として記述していますが、32ビットデータに変換する際にはエンディアンにも注意する必要があります。

import shrike
import machine
from machine import Pin, SPI
import time
shrike.flash("SPI32const.bin")

# Reset
reset_pin = Pin(14, Pin.OUT, value=1)
reset_pin.value(0)
time.sleep(1)
reset_pin.value(1)
time.sleep(1)

# RP2040
SCK  = 2  
CS   = 1  
MOSI = 3  
MISO = 0  

# Chip Select pin
cs = Pin(CS, Pin.OUT, value=1)

# SPI configuration (MODE 0, MSB first)
spi = SPI(0,
          baudrate=100_000,
          polarity=0,
          phase=0,
          bits=8,
          firstbit=SPI.MSB,
          sck=Pin(SCK),
          mosi=Pin(MOSI),
          miso=Pin(MISO))

def spi_exchange32(byte_to_send):
    tx = bytearray(4)
    rx = bytearray(4)
    tx[3] = byte_to_send
    rx = spi.read(4,byte_to_send)
    print(f"Received {rx[0]:02x}, {rx[1]:02x}, {rx[2]:02x}, {rx[3]:02x}")


while True:
    for val in [0xAA, 0xFF]:
        cs.value(0)          # Select FPGA
        resp = spi_exchange32(val)
        cs.value(1)          # Select FPGA
        time.sleep(1)

 ついでに、FPGA側のVerilogによるソースコードを以下に示します。spi32_target.vは、ForgeFPGAworkshopのライブラリなので、割愛します。デバック(波形観測)のために、SCKとMISO信号を別のピンに出力するようにしています。

(* top *) module SPI_const ( 
	(* iopad_external_pin, clkbuf_inhibit *) input clk, 	// System Clock (50MHz) 
	(* iopad_external_pin *) output clk_en, 
	(* iopad_external_pin *) input rst_n, 			   	// System Reset (Active Low) 
	
	// Physical SPI Pins (Connect these to FPGA I/O) 
	(* iopad_external_pin *) input spi_ss_n, 
	(* iopad_external_pin *) input spi_sck, 
	(* iopad_external_pin *) input spi_mosi, 
	(* iopad_external_pin *) output spi_miso, 
	(* iopad_external_pin *) output spi_miso_en,
	
	
	(* iopad_external_pin *) output reg debug1,
	(* iopad_external_pin *) output debug1_en,
	(* iopad_external_pin *) output reg debug2,
	(* iopad_external_pin *) output debug2_en,

	// Physical LED Pins
	(* iopad_external_pin *) output reg led, 
	(* iopad_external_pin *) output led_en
);

	assign debug1_en = 1'b1;
	assign debug2_en = 1'b1;
	always @(posedge clk) begin
		if (spi_sck)
			debug1 <= 1'b1;
		else
			debug1 <= 1'b0;
	end
	
	always @(posedge clk) begin
		if (spi_miso)
			debug2 <= 1'b1;
		else
			debug2 <= 1'b0;
	end
	assign led_en = 1'b1;
	assign clk_en = 1'b1;

    wire [31:0] rx_data_wire;
    wire       rx_valid_pulse;
    reg  [31:0] tx_data_reg;
    wire tx_hold_pulse;
    

    always @(posedge clk or negedge rst_n) begin
    		if (!rst_n) begin
        		tx_data_reg <= 32'h0000_0000;
    		end else if (tx_hold_pulse) begin
        		tx_data_reg <= 32'h0123_4567;
    		end
	end  
	
    // LED Logic
	always @(posedge clk or negedge rst_n) begin
    		if (!rst_n) begin
        		led <= 1'b0;
    		end else if (rx_valid_pulse) begin
        		if (rx_data_wire[0] == 1'b1)
        			led <= 1'b1;
        		else
        			led <= 1'b0;
    		end
	end

	
    // SPI Target
    spi32_target #(
        .CPOL(1'b0),   // Standard Mode 0 (Idle Low)
        .CPHA(1'b0),   // Standard Mode 0 (Sample Rising)
        .WIDTH(32),
        .LSB(1'b0)     // MSB First (Standard)
    ) u_spi_target (
        // System Common
        .i_clk(clk),
        .i_rst_n(rst_n),
        .i_enable(1'b1),        // Enable the module permanently

        // SPI Physical Interface
        .i_ss_n(spi_ss_n),
        .i_sck(debug1),
        .i_mosi(spi_mosi),
        .o_miso(spi_miso),
        .o_miso_oe(spi_miso_en),

        // RX Interface (Data FROM MCU)
        .o_rx_data(rx_data_wire),
        .o_rx_data_valid(rx_valid_pulse),

        // TX Interface (Data TO MCU)
        .i_tx_data(tx_data_reg), 
        .o_tx_data_hold(tx_hold_pulse)        // Not needed for simple echo
    );

endmodule

コメント