授業でRaspberry Pi Pico Wを使う機会があったのですが、一身上の都合でC言語やMicroPythonを使うことが出来ないので、Rustで頑張ってみます。
環境
- Raspberry Pi Pico W
- Rust 1.83.0
依存関係のインストール
Raspberry Pi Pico Wに搭載されたRP2040はARM Cortex-M0+なので、クロスコンパイラが必要です。
rustup target add thumbv6m-none-eabi
その他にリンカーやランナーもインストールします。
cargo install flip-link elf2uf2-rs
プロジェクトの作成
cargo init
ライブラリの追加
以下の2つのライブラリを/lib
以下にsubmoduleとして追加します。
mkdir lib
git submodule add https://github.com/embassy-rs/embassy.git lib/embassy
git submodule add https://github.com/embassy-rs/trouble.git lib/trouble
Cargo.toml
に以下の設定を追加します。
embassy-time-driver
は頑張ってもバージョンがコンフリクトしたので、patchで解決しました。
[dependencies]
embassy-sync = { version = "0.6", path = "lib/embassy/embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.7", path = "lib/embassy/embassy-executor", default-features = false, features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] }
embassy-time = { version = "0.4.0", path = "lib/embassy/embassy-time", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] }
embassy-rp = { version = "0.3.0", path = "lib/embassy/embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] }
embassy-usb = { version = "0.3.0", path = "lib/embassy/embassy-usb", features = ["defmt"] }
embassy-usb-logger = { version = "0.2.0", path = "lib/embassy/embassy-usb-logger" }
embassy-futures = { version = "0.1.1", path = "lib/embassy/embassy-futures" }
trouble-host = { path = "lib/trouble/host", features = ["scan"] }
bt-hci = { version = "0.2", default-features = false, features = ["defmt"] }
futures = { version = "0.3", default-features = false, features = ["async-await"]}
cyw43 = { version = "0.3.0", path = "lib/embassy/cyw43", features = ["defmt", "firmware-logs", "bluetooth"] }
cyw43-pio = { version = "0.3.0", path = "lib/embassy/cyw43-pio", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4.0"
cortex-m = { version = "0.7.6" }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
static_cell = "2"
portable-atomic = { version = "1.5", features = ["critical-section"] }
log = "0.4"
[patch.crates-io]
embassy-time-driver = { path = "lib/embassy/embassy-time-driver" }
その他に必要そうなファイルと設定
cp lib/embassy/examples/rp/build.rs .
cp lib/embassy/examples/rp/memory.x .
cp -r lib/embassy/examples/rp/.cargo .
cp -r lib/embassy/cyw43-firmware lib
.cargo/config.toml
のrunner
は、elf2uf2-rs
を使うように設定します。
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip RP2040"
runner = "elf2uf2-rs -d"
[build]
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
[env]
DEFMT_LOG = "debug"
ここまでで以下のようになっているはずです。
.
├── .cargo
│ └── config.toml
├── .gitignore
├── .gitmodules
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── lib
│ ├── cyw43-firmware
│ ├── embassy
│ └── trouble
├── memory.x
└── src
└── main.rs
Hello World
PicoはUSB経由でシリアル通信が出来るので、Hello, World!
を送信してみます。
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::USB;
use embassy_rp::usb::{Driver, InterruptHandler};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
spawner.spawn(logger_task(driver)).unwrap();
loop {
log::info!("Hello, world!");
Timer::after_secs(1).await;
}
}
BOOTSELを押しながら接続し、cargo run
でビルドして書き込みます。
cargo run --release
うまくいけば、Hello, world!
が1秒毎に表示されます。
BLEのスキャン
まずはBluetoothにアクセスするためのセットアップを行います。
#![no_std]
#![no_main]
use bt_hci::controller::ExternalController;
use cyw43_pio::PioSpi;
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::gpio::{Level, Output};
use embassy_rp::peripherals::{DMA_CH0, PIO0, USB};
use embassy_rp::pio::{InterruptHandler as PioInterruptHandler, Pio};
use embassy_rp::usb::{Driver, InterruptHandler as UsbInterruptHandler};
use static_cell::StaticCell;
use {defmt_rtt as _, embassy_time as _, panic_probe as _};
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => UsbInterruptHandler<USB>;
PIO0_IRQ_0 => PioInterruptHandler<PIO0>;
});
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
}
#[embassy_executor::task]
async fn cyw43_task(
runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>,
) -> ! {
runner.run().await
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
spawner.spawn(logger_task(driver)).unwrap();
let fw = include_bytes!("../lib/cyw43-firmware/43439A0.bin");
let clm = include_bytes!("../lib/cyw43-firmware/43439A0_clm.bin");
let btfw = include_bytes!("../lib/cyw43-firmware/43439A0_btfw.bin");
let pwr = Output::new(p.PIN_23, Level::Low);
let cs = Output::new(p.PIN_25, Level::High);
let mut pio = Pio::new(p.PIO0, Irqs);
let spi = PioSpi::new(
&mut pio.common,
pio.sm0,
cyw43_pio::DEFAULT_CLOCK_DIVIDER,
pio.irq0,
cs,
p.PIN_24,
p.PIN_29,
p.DMA_CH0,
);
static STATE: StaticCell<cyw43::State> = StaticCell::new();
let state = STATE.init(cyw43::State::new());
let (_net_device, bt_device, mut control, runner) =
cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await;
unwrap!(spawner.spawn(cyw43_task(runner)));
control.init(clm).await;
let controller: ExternalController<_, 10> = ExternalController::new(bt_device);
}
次にスキャナーを追加します。
use bt_hci::cmd::le::{LeSetScanEnable, LeSetScanParams};
use bt_hci::controller::ControllerCmdSync;
use embassy_futures::join::join;
use trouble_host::prelude::*;
/// Size of L2CAP packets
const L2CAP_MTU: usize = 128;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
type Resources<C> = HostResources<C, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU>;
pub async fn scan<C>(controller: C)
where
C: Controller + ControllerCmdSync<LeSetScanParams> + ControllerCmdSync<LeSetScanEnable>,
{
let mut resources = Resources::new(PacketQos::None);
let (_stack, _, mut central, mut runner) =
trouble_host::new(controller, &mut resources).build();
let config = ScanConfig {
..Default::default()
};
log::info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
loop {
let result = central.scan(&config).await.unwrap();
for report in result.iter() {
report
.map(|report| {
log::info!("Peripheral found: {:?} {:?}", report.addr, report.addr_kind);
})
.ok();
}
}
})
.await;
}
mod scanner;
// ...
#[embassy_executor::main]
async fn main(spawner: Spawner) {
// ...
let controller: ExternalController<_, 10> = ExternalController::new(bt_device);
scanner::scan(controller).await;
}
これで、BLEのスキャンが出来るはずです。
cargo run --release