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


コメント