Waveshare RP2350-ETHをRust/EmbassyでLチカする
前準備
Waveshare RP2350-ETHはRP2350A+CH9210の乗ったマイコンボード。
ちなみに、CH9210はSPIではなくUARTで通信するが、公式のサンプルコードを使うくらいなら素直にW5500を買う...が、自前で実装したくなったので後日やる。
公式ガイドは以下。自分は軽く目を通しただけで細部までは見ていない。
ビルドインLEDが存在するが、GPIOの番号は特に明記されていない。
類似品にWaveshare RP2040-ETHが存在する。チップがRP2040である以外の違いはなさそう。
環境
Windows10 64bit
CPU: Intel Core i7-4790
Rust: 1.84.0
IDE: VSCode
C:\Users\SPDG\.pico-sdk\picotool\2.1.0\picotool>picotool info -d
Device Information
type: RP2350
package: QFN60
chipid: 0xfa0ab64efab07055
flash devinfo: 0x0c00
current cpu: ARM
available cpus: ARM, RISC-V
default cpu: ARM
secure boot: 0
debug enable: 1
secure debug enable: 1
flash size: 4096K
Rust開発環境のインストール
省略。nightlyじゃなくてもOK。
コンパイル対象のtarget追加を実施する。
rustup target add thumbv8m.main-none-eabihf # RP235xA
rustup target add riscv32imac-unknown-none-elf # RP235xのRISC-V
rustup target add thumbv6m-none-eabi # RP2040(今回は扱わない)
Cortex-M33なのでthumbv7m-none-eabi
かと思ったが、thumbv8m.main-none-eabihf
らしい。謎。
Embassyを使ったプロジェクトのセットアップ
作成
cargo new
いい感じに。苦しみはまだまだこれからなので、説明を省略します。
.cargo/config.toml
ターゲット指定や書き込みの設定が必要。
targetを書き換えればRP2040でも動く。
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
#runner = "probe-rs run --chip RP2040"
#runner = "elf2uf2-rs -d"
runner = "picotool load -u -v -x -t elf"
[build]
target = "thumbv8m.main-none-eabihf"
[env]
DEFMT_LOG = "debug"
Cargo.toml
取り急ぎ、embassyのexampleからほぼそのまま持ってきている。
rp
がRP2040用で、rp23
がRP2350用。わかりづらい。
embassy-rp
はRP2040かRP235X系かによってfeaturesを分ける必要がある。
RP2350Aならrp235xa
, RP2350Bならrp235xb
。
RP2040はrp2040
。
必要ないクレートも多くあるので、不要なものはそぎ落とせすこと。
[package]
name = "testtest"
version = "0.1.0"
edition = "2021"
[dependencies]
embassy-embedded-hal = { version = "0.3.0", features = ["defmt"] }
embassy-sync = { version = "0.6.1", features = ["defmt"] }
embassy-executor = { version = "0.7.0", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
embassy-time = { version = "0.4.0", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-rp = { version = "0.3.0", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] }
embassy-usb = { version = "0.3.0", features = ["defmt"] }
embassy-net = { version = "0.6.0", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] }
embassy-net-wiznet = { version = "0.2.0", features = ["defmt"] }
embassy-futures = { version = "0.1.0" }
cyw43 = { version = "0.3.0", features = ["defmt", "firmware-logs"] }
cyw43-pio = { version = "0.3.0", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
fixed = "1.23.1"
fixed-macro = "1.2"
# for web request example
reqwless = { version = "0.13.0", features = ["defmt"] }
serde = { version = "1.0.203", default-features = false, features = ["derive"] }
serde-json-core = "0.5.1"
# for assign resources example
assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" }
#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
cortex-m-rt = "0.7.0"
critical-section = "1.1"
panic-probe = { version = "0.3", features = ["print-defmt"] }
display-interface-spi = "0.5.0"
mipidsi = "0.8.0"
display-interface = "0.5.0"
byte-slice-cast = { version = "1.2.0", default-features = false }
smart-leds = "0.4.0"
heapless = "0.8"
rand_core = "0.6.4"
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = "1.0"
embedded-hal-bus = { version = "0.1", features = ["async"] }
embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
embedded-storage = { version = "0.3" }
static_cell = "2.1"
portable-atomic = { version = "1.5", features = ["critical-section"] }
log = "0.4"
pio-proc = "0.2"
pio = "0.2.1"
rand = { version = "0.8.5", default-features = false }
embedded-sdmmc = "0.7.0"
[profile.release]
debug = 2
[profile.dev]
lto = true
opt-level = "z"
memory.x
RP2040と微妙に異なる。 RP2350の方が長い。
ちなみに意味は全く理解していない。
RP2040
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
EXTERN(BOOT2_FIRMWARE)
SECTIONS {
/* ### Boot loader */
.boot2 ORIGIN(BOOT2) :
{
KEEP(*(.boot2));
} > BOOT2
} INSERT BEFORE .text;
RP2350
外部フラッシュやPicotool関連の処理が追加されている。
こちらもあまりしっかり意味を理解してなくても良さそう
MEMORY {
/*
* The RP2350 has either external or internal flash.
*
* 2 MiB is a safe default here, although a Pico 2 has 4 MiB.
*/
FLASH : ORIGIN = 0x10000000, LENGTH = 2048K
/*
* RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping.
* This is usually good for performance, as it distributes load on
* those banks evenly.
*/
RAM : ORIGIN = 0x20000000, LENGTH = 512K
/*
* RAM banks 8 and 9 use a direct mapping. They can be used to have
* memory areas dedicated for some specific job, improving predictability
* of access times.
* Example: Separate stacks for core0 and core1.
*/
SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K
SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K
}
SECTIONS {
/* ### Boot ROM info
*
* Goes after .vector_table, to keep it in the first 4K of flash
* where the Boot ROM (and picotool) can find it
*/
.start_block : ALIGN(4)
{
__start_block_addr = .;
KEEP(*(.start_block));
KEEP(*(.boot_info));
} > FLASH
} INSERT AFTER .vector_table;
/* move .text to start /after/ the boot info */
_stext = ADDR(.start_block) + SIZEOF(.start_block);
SECTIONS {
/* ### Picotool 'Binary Info' Entries
*
* Picotool looks through this block (as we have pointers to it in our
* header) to find interesting information.
*/
.bi_entries : ALIGN(4)
{
/* We put this in the header */
__bi_entries_start = .;
/* Here are the entries */
KEEP(*(.bi_entries));
/* Keep this block a nice round size */
. = ALIGN(4);
/* We put this in the header */
__bi_entries_end = .;
} > FLASH
} INSERT AFTER .text;
SECTIONS {
/* ### Boot ROM extra info
*
* Goes after everything in our program, so it can contain a signature.
*/
.end_block : ALIGN(4)
{
__end_block_addr = .;
KEEP(*(.end_block));
} > FLASH
} INSERT AFTER .uninit;
PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr);
.vscode/settings.json
checkOnSaveはお好みで。
"rust-analyzer.cargo.target`は環境によって書き換える。
{
"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf",
"rust-analyzer.cargo.allTargets": false,
"rust-analyzer.checkOnSave": true
}
ビルド関連
cargo run/build
の際は--release
オプションを一応追加している。
picotoolのインストール
picotoolはPico SDKに付属、かつそのPico SDKはVSCode拡張機能としてインストール可能らしい。
と思って拡張機能でインストールしたがpicotoolは見当たらなかった。
仕方ないのでpico-setup-windowsでPico SDKをインストールしてみたところ、picotoolのバイナリは見つかったがpico-setup-windows自体がサポートするPico SDK自体がdeprecated/archivedであり、最新リリースバージョンのv1.5ではRP235xに対応していなかった。
代わりに、%USERPROFILE%\.pico-sdk\picotool\2.1.0\picotool
に存在した。
いつ追加したものなのか...VSCodeの拡張機能が入れてくれたのかも? 要調査。
ひとまずパスに追加しておく。
ソースコード
埋め込みピンの番号が不明だったので、ひとまずGPIO1に直接LEDをつないだ。
基本的にはexampleのコードの流用。
RP2040用
シンプルでわかりやすい。
//! This example test the RP Pico on board LED.
//!
//! It does not work with the RP Pico W board. See wifi_blinky.rs.
#![no_std]
#![no_main]
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::gpio;
use embassy_time::Timer;
use gpio::{Level, Output};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(p.PIN_1, Level::Low);
loop {
info!("led on!");
led.set_high();
Timer::after_secs(1).await;
info!("led off!");
led.set_low();
Timer::after_secs(1).await;
}
}
RP2350A用
リンカ関連の処理と、picotool info -a
に対応するためか、コード量が増えている。
本体のmain関数はほとんど変わらないので認知負荷は低そう。
また、picotool関連コード(PICOTOOL_ENTRIES
あたり)は削除しても問題ないらしい。
//! This example test the RP Pico on board LED.
//!
//! It does not work with the RP Pico W board. See wifi_blinky.rs.
#![no_std]
#![no_main]
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::block::ImageDef;
use embassy_rp::gpio;
use embassy_time::Timer;
use gpio::{Level, Output};
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
// Program metadata for `picotool info`.
// This isn't needed, but it's recomended to have these minimal entries.
#[link_section = ".bi_entries"]
#[used]
pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [
embassy_rp::binary_info::rp_program_name!(c"Blinky Example"),
embassy_rp::binary_info::rp_program_description!(
c"This example tests the RP Pico on board LED, connected to gpio 25"
),
embassy_rp::binary_info::rp_cargo_version!(),
embassy_rp::binary_info::rp_program_build_attribute!(),
];
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(p.PIN_1, Level::Low);
loop {
info!("led on!");
led.set_high();
Timer::after_millis(250).await;
info!("led off!");
led.set_low();
Timer::after_millis(250).await;
}
}
書き込み関連
RP2040のサンプルだとelf2uf2-rsを使用しているが、RP2350のサンプルではpicotoolを使用している。
どうやらelf2uf2-rsのRP2350対応が未完成?っぽい(embassy-rs/embassy#3241)
picotoolはRP2040もRP2350も書き込める上にELF->UF2の変換も不要なので、新規作成するプロジェクトはすべてpicotoolで良いと思う。
ただし、ちょっとインストールが面倒な気がする。
BOOTSELモードで起動
RP2350-ETHにUSBケーブルを差しこみ、RESETとBOOTを同時押しする。
RESETを先に離してからBOOTを離すとBOOTSELモードになり、USBマスストレージが現れる。
書き込みに関しては手動では行わず、cargoに任せる。
ビルド
cargo build --release
書き込み
cargo run --release
書き込み終了後、自動的に再起動する。
所感
思ったよりRP2040と手順が違ったため苦戦した。
ソースコードの変更も必要な辺り、完全なクロスプラットフォーム対応が難しい予感がする。
FPUが増えたりステートマシン載せたりRISC-Vを入れた影響だと思うが、コミュニティが大きいだけあって情報は多かった。
EmbassyはRP2040/RP2350+W5500を既にサポートしているため本当はW5500にしたかったが、安くRP2350が手に入ること、EmbassyのUART実装をやってみたかったのでしばらくこれで試してみる。
以下、picotool info -a
出力結果。
picotool info -a
Program Information
name: CH9120 Test
version: 0.1.0
description: This example tests the RP2350-ETH on board CH9120 chip.
binary start: 0x10000000
target chip: RP2350
image type: ARM Secure
Fixed Pin Information
none
Build Information
build attributes: release
Metadata Block 1
address: 0x10000114
next block address: 0x10000114
block type: image def
target chip: RP2350
image type: ARM Secure
Metadata Block 2
address: 0x10000114
next block address: 0x10000114
block type: image def
target chip: RP2350
image type: ARM Secure
Device Information
type: RP2350
revision: A2
package: QFN60
chipid: 0xfa0ab64efab07055
flash devinfo: 0x0c00
current cpu: ARM
available cpus: ARM, RISC-V
default cpu: ARM
secure boot: 0
debug enable: 1
secure debug enable: 1
boot_random: 6daccdaf:3dc62506:70ce1df3:c6206b07
boot type: bootsel
last booted partition: none
diagnostic source: slot 0
last boot diagnostics: 0x00000000
reboot param 0: 0x00000000
reboot param 1: 0x00000000
rom gitrev: 0x312e22fa
flash size: 4096K
こちらもお時間あれば読んでみてください。
Discussion
次の記事でイーサネット通信とシリアル出力見ようと思います