💡

CH32V003J4M6マイコンでRustでLチカをしてみた

2024/01/15に公開

最近マイコンで遊んでいたら、会社の同僚が、CH32V003 っていう32ビットRISC-Vマイコンが小さくて楽しそうだよ、とオススメしてきたので Rust で Lチカをしてみました。


(LED自体に抵抗が入っています。あと、変換基板に差してあるマイコンの上下が逆ですいません)

秋月で買ったもの

CH32V003 対応の Rust のコンパイル

Rust が CH32V003 自体に(まだ)対応していないため、2023年3月ころに書かれた、Rust on the CH32V003 という記事を参考に、頑張る必要があります。

というわけで、CH32 シリーズ向けの Rust 対応を進めているグループが提供している CH3200V3 向けの Rust をコンパイルします。

具体的な手順は、前述の Rust on the CH32V003 の Part 1: Custom Rust toolchain を参考にしました。

$ git clone https://github.com/ch32-rs/rust.git
$ cd rust
$ git submodule update --init --recursive
$ vi config.toml
# copied from config.example.toml
change-id = 118703

# Use defaults for codegen affecting custom builds
profile = "codegen"

[llvm]
# Use our own LLVM build instead of downloading from CI
download-ci-llvm = false

[rust]
# Enable building LLD for our target as well
lld = true
$ python x.py build
...
Build completed successfully in 0:07:57
$ rustup toolchain link custom-rv32ec $PWD/build/host/stage1

Rust のプロジェクトを作成して、CH32V003 向けにコンパイルしてみる

プロジェクトの作成と設定

$ cargo new
ch32v003j4m6-led
$ cd ch32v003j4m6-led

最低限のコードを書きます。

main.rs
#![no_std]
#![no_main]

use riscv_rt::entry;
use panic_halt as _;

#[entry]
fn main() -> ! {
    loop {}
}

Part 2: Boot and blink を参考に、ビルドが通るようにします。

Cargo.toml
[package]
name = "ch32v003j4m6-led"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[patch.crates-io]
riscv-rt = { git = "https://github.com/9names/riscv-rt", branch = "rv32e" }

[dependencies]
panic-halt = "0.2.0"
riscv-rt = "0.11.0"

[profile.dev]
opt-level = "s"
.cargo/config.toml
[build]
target = "riscv32ec-unknown-none-elf"

[unstable]
build-std = ["core", "compiler_builtins"]
memory.x
PROVIDE(_hart_stack_size = 1K);

MEMORY
{
	FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16K
	RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 2K
}

REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);
build.rs
fn main() {
    // Tell rustc to pass linker scripts to LLD
    println!("cargo:rustc-link-arg=-Tmemory.x");
    println!("cargo:rustc-link-arg=-Tlink.x");

    // Rerun this script only when necesary
    println!("cargo:rerun-if-changed=memory.x");
    println!("cargo:rerun-if-changed=build.rs");
}

ビルド

$ cargo +custom-rv32ec build

実機にコピーするバイナリを作成します。

$ llvm-objcopy -O binary target/riscv32ec-unknown-none-elf/debug/ch32v003j4m6-led out.bin

実機に書き込む

WCH-LinkE の準備


WCH LinkE と CH32V002 を接続します。

  • 3V3 -> マイコンの4番ピン(左下)
  • GND -> マイコンの2番ピン(左上から2番目)
  • SWDIO -> マイコンの8番ピン(右上)

書き込みのソフトウェアの準備

ch32v003fun が提供している minichlink を使いました。

$ git clone https://github.com/cnlohr/ch32v003fun.git
$ cd minichlink
$ make
$ ./minichlink
Warning: found at least one WCH-LinkE in ARM programming mode. To use it with minichlink, you need to change it to RISC-V mode as per https://github.com/cnlohr/ch32v003fun/issues/227
Error: Could not initialize any supported programmers
Error: Could not initialize any supported programmers

WCH-LinkE のモードの変更

PC に WCH-LinkE を差すと、青い LED が光っていたので、ドリルで横穴を開けて、ModeS とかかれているボタンを押しながら USB を差す作業をしました。
ケースを開けてやるべきっぽいのですが、開けられませんでした。

https://github.com/cnlohr/ch32v003fun/issues/227

CH32V003 に、プログラムを書き込む

$ ./minichlink -w ...../out.bin flash -b
Found WCH Link
WCH Programmer is LinkE version 2.9
Chip Type: 003
Setup success
Flash Storage: 16 kB
Part UUID    : bb-71-ab-cd-23-99-bc-5b
PFlags       : ff-ff-ff-ff
Part Type (B): 07-13-bb-91
Read protection: disabled
Interface Setup
Image written.

Lチカをする

Part 2: Boot and blink を参考に、Lチカのプログラミングをします。

src/main.rs
#![no_std]
#![no_main]

use riscv_rt::entry;
use panic_halt as _;

#[entry]
fn main() -> ! {
    let RCC_APB2PCENR: *mut u32 = 0x4002_1018 as _;
    let GPIOC_CFGLR: *mut u32 = 0x4001_1000 as _;
    let GPIOC_OUTDR: *mut u32 = 0x4001_100C as _;

    unsafe {
        // Enable clocks to the GPIOC bank
        RCC_APB2PCENR.write_volatile(0b1_0000);
        // Set pin 1 to output
        GPIOC_CFGLR.write_volatile(0b0001_0000);

        loop {
            // Set pin 1 to high
            GPIOC_OUTDR.write_volatile(0b1_0);

            for _ in 0..1_000_000 {
                core::hint::black_box(()); // Do nothing, but keep the loop
            }

            // Set pin 1 to low
            GPIOC_OUTDR.write_volatile(0b0_0);

            for _ in 0..1_000_000 {
                core::hint::black_box(()); // Do nothing, but keep the loop
            }
        }
    }
}

配線して電源ON

LED を マイコンPC1(右下)から GND に向けてつなぎます。
ボタン電池をマイコンの VDD から GND に向けてつなぎます。

おわり

CH32V003 というとても小さな RISC-V マイコンで、Rust で Lチカをしてみました。
かなり試行錯誤をして苦労しましたが、正解手順だけにまとめるととてもすっきりしましたね。

ソースコードは以下に置いてあります。
https://github.com/task-jp/ch32v003j4m6-led/tree/custom-rv32ec

なにか楽しいものが作れるといいなー。

続きを書きました。
https://zenn.dev/tasuku/articles/e6db210375ba24

Discussion