はじめに
普段はQtの話ばかりな底辺系フリーランスのhermit4です。ごきげんよう。
このたび合同会社シグナルスロット様のご厚意で、次週11/20から11/22までみなとみらいで行われるEdge Tech+2024でのSlintの展示と紹介をお手伝いさせていただけることになりました。会場へお越しになる方はぜひ遊びにきてください。
前回に続いて次はM5 Stack向けのものを・・・とあれこあれやってたのですが難航中でして。イベントまであと3日程度、間に合うのか!
というわけで、気分転換に別件をということで。Raspberry pi picoの上で動くSlintアプリの開発について記載しておきます。今回のお題は、Pico(ベアメタル)でのSlint開発についてです。
Raspberry Pi Picoとは
Raspberry Piはイギリスで学校でのコンピュータサイエンスの教育・研究を促進することを目的として設立された慈善団体のラズベリーパイ財団が開発した安価なボードコンピュータとして名前が売れています・・・・が、Picoはもっと安価な”マイコン”学習用ボードになっています。
どれぐらい安価かというと、Raspberry Pi5が1万5千円を超えているのに対し、Raspberry Pi Picoは千円未満という感じです。その代わりスペックは当然低く
- Dual Core ARM Cortex M0+ (max 133MHz)
- SRAM 264KB
- Flash : 2MB
というスペックです。当然Linuxを動かせるスペックではなく、組込向けのOSかベアメタルで利用することになります。
Slintとは
先日の記事にも書いたのですが、この記事から触る人のためにも繰り返します。
SlintはドイツのSixtyFPS GmbH社の作った宣言型UI言語・フレームワークです。Rustで開発されており、Rustはもちろん、C++, Python, JavaScriptなどから呼び出しが可能です。対応するプラットフォームとしてLinux, MacOS, Windowsはもちろん、AndroidやZephyr、マイナーどころだとRedoxでも利用可能で、そのほかベアメタルでも動かすことができ、上述のようなpicoのスペックでも簡単なUIなら動作させることが可能です。
簡単なチュートリアルは @task_jp さんが書いた「GUI フレームワーク Slint の紹介」をご確認ください。
Slint+Picoの組み合わせ
こちらは、実は公式がデモとして利用しています。利用されているのは
で、2年ほど前に @tkhshmsy さんがデモを動かす記事を公開されています。
まぁ、僕もこの記事を読んで学ばせていただきました。今回の環境としてはこちらを利用します。
開発環境について
開発環境の構築は、7月頃記事にしたので、そちらを参考にしてください。なおこの記事執筆段階では、probe-rsのビルドが通らない事態になっていてそのままの手順だとエラーがでたので、先ほどひっそり修正しておきました。
なぜこの記事を書いたのか
デモを動かすところまでは記事があるのですが、Picoで自分のアプリを書くための記事がなかったのでそこを埋めようかと思います。でも動かしましたで終わってしまうと勿体無いので。
アプリケーションを作ってみよう
まずは公式のテンプレートがあるのでそちらを拾ってきます。
cargo generate --git https://github.com/slint-ui/slint-rust-template \
--name slint-hello; cd $_
このまま以下のコマンドを打つとホスト上で動きます。
cargo run
作成されるコードは以下の通りです
import { Button, VerticalBox } from "std-widgets.slint";
export component AppWindow inherits Window {
in-out property <int> counter: 42;
callback request-increase-value();
VerticalBox {
Text {
text: "Counter: \{root.counter}";
}
Button {
text: "Increase value";
clicked => {
root.request-increase-value();
}
}
}
}
このファイルがSlint独自の言語で、UIと簡単なロジックを定義しています。
fn main() {
slint_build::compile("ui/app-window.slint").expect("Slint build failed");
}
画面の定義をbuild.rsを通じてコンパイルしています。
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::error::Error;
slint::include_modules!();
fn main() -> Result<(), Box<dyn Error>> {
let ui = AppWindow::new()?;
ui.on_request_increase_value({
let ui_handle = ui.as_weak();
move || {
let ui = ui_handle.unwrap();
ui.set_counter(ui.get_counter() + 1);
}
});
ui.run()?;
Ok(())
}
そしてメインロジックはRustで記載しています。
slint::include_modules!();
というマクロでビルド結果を取り入れ利用できるようにしています。
[package]
name = "slint-rust-template"
version = "0.1.0"
edition = "2021"
[dependencies]
slint = "1.8.0"
[build-dependencies]
slint-build = "1.8.0"
Cargo設定で必要としたのはslintとslint-buildになっています。
ベアメタルで動かすために
このテンプレートは、基本的にWindows/Linux/macOSなどOS上で動かすことを前提にstdを使っています。しかし、ベアメタル環境ではstdは使えません。また、main関数を呼び出すのもOSがおこなってくれる部分で、このままでは利用できないため、ベアメタル向けに修正していきます。
まずは、Slintコンパイラに組込向けであることを示す必要があります。
fn main() {
let config = slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer);
slint_build::compile_with_config("ui/app-window.slint", config).unwrap();
slint_build::print_rustc_flags().unwrap();
}
次にmain.rsです。
#![no_std]
#![no_main]
slint::include_modules!();
#[mcu_board_support::entry]
fn main() -> ! {
mcu_board_support::init();
let ui = AppWindow::new().unwrap();
ui.on_request_increase_value({
let ui_handle = ui.as_weak();
move || {
let ui = ui_handle.unwrap();
ui.set_counter(ui.get_counter() + 1);
}
});
ui.run().unwrap();
panic!("The MCU demo should not quit")
}
Picoと今回の環境はデモのためにSlintにボードサポートサンプルが用意されています。そのため、こちらを使ってさっくりと動かしてみます。
- no_std, no_mainで、stdやmain関数を利用しないことを宣言します
- mainが値を返しても処理するOSはいないので返却型を変えます
- mcu_board_support::init()を使ってボード側の初期化設定を行います
- エラーを返さない用にunwrap()を使うようにします
- run()が終わることは想定していないので最後panicを入れておきます
[package]
name = "slint-hello"
version = "0.1.0"
edition = "2021"
[dependencies]
slint = { git = "https://github.com/slint-ui/slint", rev="refs/tags/v1.8.0", version="1.8.0" , default-features = false, features = ["compat-1-2"] }
mcu-board-support = {git = "https://github.com/slint-ui/slint", rev="refs/tags/v1.8.0", version="1.8.0", features=["pico-st7789"]}
[build-dependencies]
slint-build = {git = "https://github.com/slint-ui/slint", rev="refs/tags/v1.8.0", version="1.8.0"}
[dev-dependencies]
divan = "0.1.14"
Cargoについては、mcu-board-supportを使うように指定しなくてはなりませんが、こちらを取得するためにgitを指定するように変更しています。また、nameを修正しています。
ビルドは以下の通りです。
cargo build --target=thumbv6m-none-eabi --release
あとは、BootselボタンをおしながらUSBケーブルを接続するとLinux側にディスクとして認識されます。
dmesgなどを確認してどのデバイスか確認後、以下のコマンドを書き換えて実行してください。
udisksctl mount -b /dev/[Your device file]
elf2uf2-rs -d target/thumbv6m-none-eabi/release/slint-hello
じつは、昨夜はCargo.tomlの設定を一部ミスっていて動かない、ビルドできないで一人夜中困り果ててました。何がだめなんだって、自分がポンコツでした。歳はとりたくないものです。
まとめ
せっかくPicoでSlintが動かせるのだから、デモではなくて自分のアプリを動かしたい!という人向けに、簡単なテンプレートを動かす手順を書いてみました。ぜひ楽しんでみてください
なお、実際に上記手順で作ったアプリの動作動画です。
動画撮って入れてよってことでしたので、入れておきます。
こんなんで良さそう? pic.twitter.com/iYrl8IbVqg
— 緑野翁 (@hermit4) November 17, 2024