【備忘録】STM32 × RustでLチカしてみよう【SysTick使ってみた】

# はじめに

以前[【備忘録】STM32 × RustでLチカしてみよう【IDEはEclipse】](https://qiita.com/yagisawa/items/a38a36f504eb998d919f)という記事を書いたのですが、その時「次回は割込みに挑戦しようと思います」ということを書きました。
この記事は上記作業の備忘録となります。備忘録なので記載が雑な部分がありますがご容赦ください。

本記事の環境は以下にまとめてあります。

https://github.com/kei-yagisawa/nucleo_blinky_startup/releases/tag/20220905

# 環境
* OS
    * Windows 10 Home 21H2(64bit)
* rustc・cargo
    * 1.65.0-nightly
* rustup
    * 1.25.1
* GNU Arm Embedded Toolchain
    * Version 10-2021-10
* ボード
    * [NUCLEO-F401RE](https://www.st.com/ja/evaluation-tools/nucleo-f401re.html)

# コーディング
以下のようなコードを書きました。

```Rust:main.rs
#![no_main]
#![no_std]

use core::ptr::{copy_nonoverlapping, read_volatile, write_bytes, write_volatile};

extern crate panic_halt;

#[link_section = ".vector_table.reset_vector"]
#[no_mangle]
static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;

#[link_section = ".vector_table.exceptions"]
#[no_mangle]
static EXCEPTIONS: [Option<unsafe extern "C" fn()>; 14] = [
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    None,
    None,
    None,
    None,
    Some(DefaultExceptionHandler),
    None,
    None,
    Some(DefaultExceptionHandler),
    Some(SysTick),
];

#[no_mangle]
extern "C" fn DefaultExceptionHandler() {
    loop {}
}

#[no_mangle]
unsafe extern "C" fn Reset() -> ! {
    extern "C" {
        static mut _sbss  : u8;
        static     _ebss  : u8;
        static     _sidata: u8;
        static mut _sdata : u8;
        static     _edata : u8;
    }

    // 初期値なし変数の初期化
    let count = ((&_ebss as *const u8) as usize) - ((&_sbss as *const u8) as usize);
    write_bytes(&mut _sbss as *mut u8, 0, count);

    // 初期値付き変数の初期化
    let count = ((&_edata as *const u8) as usize) - ((&_sdata as *const u8) as usize);
    copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count);

    init_gpio();
    init_systick();

    loop {}
}

#[no_mangle]
extern "C" fn SysTick() {
    static mut ON_OFF: bool = false;

    let gpioa_odr = 0x4002_0014 as *mut usize;
    unsafe {
        ON_OFF = !ON_OFF;
        set_bit(gpioa_odr, 5, ON_OFF);
    }
}

fn init_gpio() {
    let rcc_ahb1enr = 0x4002_3830 as *mut usize;
    let gpioa_moder = 0x4002_0000 as *mut usize;

    set_bit(rcc_ahb1enr, 0, true);
    set_bit(gpioa_moder, 10, true);
}

fn init_systick() {
    let stk_ctrl_addr  = 0xE000_E010 as *mut usize;
    let stk_load_addr  = 0xE000_E014 as *mut usize;
    let stk_val_addr   = 0xE000_E018 as *mut usize;

    unsafe {
        write_volatile(stk_val_addr, 0);
        write_volatile(stk_load_addr, 1_000_000);
        write_volatile(stk_ctrl_addr, 0x3);
    }
}

fn set_bit(addr: *mut usize, bit: usize, is_set: bool) {
    unsafe {
        let val = read_volatile(addr);
        match is_set {
            true  => write_volatile(addr, val |  (1 << bit)),
            false => write_volatile(addr, val & !(1 << bit))
        }
    }
}
```

## 変更点

前回と違うところをいくつか。

### `#![feature(llvm_asm)]`の削除

割込みでLチカしているので不要となり削除しました。

### `crate panic_halt`の追加

パニック処理をよろしくやってくれるようなのでこちらに任せました。

### ベクタテーブルの追加

SysTick割込みを発生させるために以下を追加しました。

```Rust
#[link_section = ".vector_table.exceptions"]
#[no_mangle]
static EXCEPTIONS: [Option<unsafe extern "C" fn()>; 14] = [
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    Some(DefaultExceptionHandler),
    None,
    None,
    None,
    None,
    Some(DefaultExceptionHandler),
    None,
    None,
    Some(DefaultExceptionHandler),
    Some(SysTick),
];
```
STM32F401のベクタテーブルは以下のようになっているので、それに合わせてNMI～SysTickの関数テーブルを作ってあげる感じです。`SysTick`以外は今回は使用しないため`DefaultExceptionHandler`でまとめています。
![vector_table.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469259/9d2f5619-2079-7a52-a1bc-018fc4df786a.png)
基本的には以下の記事を参考にしていますが、色々調べて(Option等)ちょっとオリジナリティを出しています。

https://tomoyuki-nakabayashi.github.io/embedonomicon/exceptions.html

### 初期値付き変数の初期化

SysTick割込み内で初期値付きのstatic変数を使っているので、初期値付き変数の初期化処理を追加しました。組込みエンジニアじゃないとおそらく意識しないところですね…

```Rust
    extern "C" {
        static mut _sbss  : u8;
        static     _ebss  : u8;
        static     _sidata: u8;
        static mut _sdata : u8;
        static     _edata : u8;
    }

    // 初期値なし変数の初期化
    let count = ((&_ebss as *const u8) as usize) - ((&_sbss as *const u8) as usize);
    write_bytes(&mut _sbss as *mut u8, 0, count);

    // 初期値付き変数の初期化
    let count = ((&_edata as *const u8) as usize) - ((&_sdata as *const u8) as usize);
    copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count);
```

`_sbss`とかは後ほど[リンカ・スクリプトの作成](#リンカスクリプトの作成)で説明します。

### SysTickの初期化

以下のコードでSysTickを初期化しています。

```Rust
fn init_systick() {
    let stk_ctrl_addr  = 0xE000_E010 as *mut usize;
    let stk_load_addr  = 0xE000_E014 as *mut usize;
    let stk_val_addr   = 0xE000_E018 as *mut usize;

    unsafe {
        write_volatile(stk_val_addr, 0);
        write_volatile(stk_load_addr, 1_000_000);
        write_volatile(stk_ctrl_addr, 0x3);
    }
}
```

クロックの初期化を何もやっていないので、デフォルトでHSIクロック(16MHz)が選択されており8分周した2MHzがCortexシステムタイマに流れ込みます。そのためSysTickのリロード値を1,000,000にすることで1秒周期で点滅します。
![clock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469259/673771c2-2711-9eb2-26e3-bcb1a7eb2f4b.png)

# ビルド

## リンカ・スクリプトの作成

`linker.ld`の中身はこんな感じになります。

```
MEMORY {
	FLASH : ORIGIN = 0x08000000, LENGTH = 512K
	RAM   : ORIGIN = 0x20000000, LENGTH =  96K
}

ENTRY(Reset);
EXTERN(RESET_VECTOR);

SECTIONS {
	.vector_table ORIGIN(FLASH) : {
		LONG(ORIGIN(RAM) + LENGTH(RAM));     /* スタックポインタ   */
		KEEP(*(.vector_table.reset_vector)); /* リセットベクタ     */
		KEEP(*(.vector_table.exceptions));   /* 例外ベクタテーブル */
	} > FLASH

	.text : {
		*(.text .text.*);
	} > FLASH

	.rodata : {
		*(.rodata .rodata.*);
		*(.data.rel.ro);
	} > FLASH

	.bss (NOLOAD): {
		_sbss = .;
		*(.bss .bss.*);
		_ebss = .;
	} > RAM

	.data : AT(ADDR(.rodata) + SIZEOF(.rodata)) {
		_sdata = .;
		*(.data .data.*);
		_edata = .;
	} > RAM
	
	_sidata = LOADADDR(.data);

	/DISCARD/ : {
		*(ARM.exidx .ARM.exidx.*);
	}
}
```

リセットベクタの後ろに例外ベクタ用のセクションを配置しています。
また初期値付き変数・初期値なし変数用に`.bss`セクションと`.data`セクションを作成しました。先程の`_sbss`等はこれらのセクションの開始アドレスと終了アドレス(正確には_sbss等の**ポインタ**が)となります。

# あとは

ビルドして書込んで実行するだけです。
代わり映えしないので今回はLチカ画像は省略です。

# おわりに

またもや以前の記事から1年近く経ってしまいましたがようやく割込みを使った組込みらしいLチカが完成しました。
割込みも使ったことだし、これはもう立派な組込みRustと言っていいでしょう！
次回はメモリマップドレジスタへのアクセスをもう少しいい感じにする…かもしれません？