STM32マイコンのレジスタを叩いてLチカしようとしたらどハマりした

お前ほんまどハマりしてばっかりやな

##概要
ゴールデンウィークで時間あるので、前から興味あったけど手をつけられていなかった「Rustでベアメタルプログラミング・組込みOS作成」に手をつけました。
以下のサイトにすごく細かく手順が書いてあったので、それをみながら勉強し、実装を進めました。
[Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html)


ある程度実装が進んでからそういえばLチカしてへんなと思い、レジスタを叩くLチカに挑戦しました。
マニュアルを読む練習を兼ねて手順をググらず、またボードのクレートも使わず車輪の再開発的にやろうとしたんですが、結果どハマりして無限に時間を溶かしました。
最終的には解決しLチカできたので、~~こんなアホな時間の溶かし方をする人が減るよう~~、間違った手順で出た挙動、およびその直し方をまとめます。

##環境・使用したマニュアル
上述の通り、以下のサイトを見て実装を進めました。
[Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html)

このサイトの「4.割り込み制御」までは実装が完了していました。
（Systick内でLEDをON/OFFすればblinkできるだろうと考えたのがこの試みのきっかけです）

開発に使用した環境はだいたい以下の通りです。

* パソコン：MacOS Mojave 10.14.6
* 使用言語：Rust
    * cargo 1.45.0-nightly (f534844c2 2020-05-06)
    * rustc 1.45.0-nightly (7ebd87a7a 2020-05-08)
    * rustup 1.21.1 (7832b2ebe 2019-12-20)
* ボード：STM32 NUCLEO-F446RE
* エディタ:VSCode
* デバッグ環境: VSCode（Cortex-Debugプラグイン導入）
* 背景知識：やや組込み初心者（基礎の基礎は知ってるものの基礎以上はわからない）

上サイトではNUCLEO-F429ZIボードが使われていましたが、そのボードを持っていませんでした。
STM32の他のマイコンでも大丈夫だろうと書いてあったこともあり、家にあったNUCLEO-F446REボードを使いました。￥

このため、参考にしたマニュアル類がサイトに書いてあるものとは異なりました。
サイトにないもので使用したものは以下のものです。

* [ユーザーズマニュアル](https://www.stmcu.jp/design/document/users_manual/52239/)
* [リファレンスマニュアル](https://www.st.com/resource/en/reference_manual/dm00135183-stm32f446xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)

##コード実装&失敗

###実装

マニュアルを辿り、

* LEDはGPIOのA5ピンに接続されている
* GPIOAのベースアドレスは0x4002_0000にマッピングされている
* GPIAの入出力切り替えはオフセット0x00のレジスタに書き込んで行う
* outputポートからの出力値の設定はオフセット0x04のレジスタに書き込んで行う

ということが判明しました。
これをもとに、だいたい以下のような感じにコードを書きました。（解説サイトと重複しているところは省略します）

まず、ledを操作するモジュールを`led.rs`として新たに作成します。

```rust
use core::ptr::{write_volatile};

const GPIO_A_ADDR : usize = 0x4002_0000;
const GPIO_MODER_A_DEFAULT: usize = 0xA800_0000;
const GPIO_MODER_OFFSET : usize = 0x00;
const GPIO_ODR_OFFSET: usize = 0x14;
const LED_PIN : usize = 5;



pub fn init() {
    unsafe{
        let writingValue : u32 = (GPIO_MODER_A_DEFAULT | (0x01 << (LED_PIN * 2))) as u32;
        write_volatile((GPIO_A_ADDR + GPIO_MODER_OFFSET) as *mut u32,  writingValue);
    }
}

pub fn turn_on() {
    unsafe {
        let reg_value : u32 = read_volatile((GPIO_A_ADDR + GPIO_ODR_OFFSET) as *mut u32);
        let writing_value : u32 = reg_value | (0x1 << (LED_PIN));
        write_volatile(
            (GPIO_A_ADDR + GPIO_ODR_OFFSET) as *mut u32,
            writing_value
        );
    }
} 

```

ボード依存のライブラリは使用しないと縛ったので、アドレス類は全てconstで定義して利用しました。
`init()`メソッドでポートAのMODERレジスタのうち、５ピンの入出力設定に対応する位置に値を書いてA5ピンをOUTPUTに設定します。
`turn_on()`および`turn_off()`では、アウトデータレジスタであるODRレジスタのうち、5番ピンに対応するところに値を書いています。

※このコードは誤っており、このままやと５番ピン以外のレジスタ値を全部０で書き潰してしまいます。noobかな？
後述するコードはこの点も修正しています。

あとは、Reset関数の中で`led::init()`と`led::turn_on()`を呼び出します

```rust
...
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
...
    hprintln!("Hello World").unwrap();
    systick::init();
    led::init();
    led::turn_on();
    loop {}
}
...

```


###失敗・発生した現象

これでLEDが点灯はするやろー（ヘラヘラ）と思ってたのですが、LEDはうんともすんとも言いません。
仕方がないので`GPIOA`のレジスタに関し、書き込む値や書き込んだ後のレジスタの値を読み出してデバッグしてみました。
なおマニュアルより、

* `GPIO_MODER`は、0xa800_0000、`GPIO_ODR_ADDR`は0x0000_0000で初期化される
* `GPIO_MODER`には0xa800_0400が書き込まれないといけない

ということはわかっていました。

デバッグの結果、

1. 書き込もうとしている値は確かに0x0000_0400になっている
1. 書き込む前に`GPIO_MODER`のアドレスから読み出す値は確かに0xa800_0000になっている
1. **書き込んだ後も`GPIO_MODER`から読み出す値が0xa800_0000のままになっている**
1. **`GPIO_ODR`のアドレスを読み出しても0xa800_0000が返ってくる**

となりました。
4.が一番不自然だったので、他のアドレスにもアクセスして値を確かめました。すると

1. GPIOAのアドレス範囲(0x4002_0000-0x4002_03FF)の**どのアドレスから読み出しても0xa800_0000が返ってくる**
1. GPIOBのアドレス範囲（0x4002_0400-0x4002_0800)の**どのアドレスから読み出しても0x0000_0280が返ってくる**  
(0x0000_0280はGPIOBの`GPIO_MODER`の初期値です）

うーん、なんでなんやろうとマニュアルとにらめっこしながら無限時間を溶かしいろいろ試しました

## 原因・解決

マニュアルだけから解決するんだ！と意気込んでましたが、諦めてググりました。
以下のブログ様の内容がそのものズバリでした。

[STM32のレジスタを叩く。 #1 ~GPIO編~ - エビデイエビナイ電子工作](https://rappy.hatenablog.com/entry/2019/11/17/221651)

ブログ様によると
> STM32では省電力のため、ペリフェラルへのクロック供給がデフォルトで無効になっています。

なるほどそうだったのか。~~電源入れたら全部準備できてると思うなよってことか~~

メモリ読み書きで操作しているのですっかり忘れていたのですが、あくまで「ペリフェラルの操作インターフェースがメモリ空間にマッピングされている」のであり実際にメモリがあってそこに読み書きしているわけではない（であってる？？）ので
ペリフェラルが動作していなければ想定する値を返すわけがないということですね
（当たり前かも知れませんがすっかり抜け落ちていました）

というわけで`led.rs`のプログラムを以下のように修正しました。

```rust
...
const RCC_ADDR : usize = 0x4002_3800;
const RCC_AHB1ENR_OFFSET : usize = 0x30;

pub fn init() {
    hprintln!("led init").unwrap();
    unsafe{
        write_volatile((RCC_ADDR + RCC_AHB1ENR_OFFSET) as *mut u32, 1);
        let writingValue : u32 = (GPIO_MODER_A_DEFAULT | (0x01 << (LED_PIN * 2))) as u32;
        write_volatile((GPIO_A_ADDR + GPIO_MODER_OFFSET) as *mut u32,  writingValue);
...
```
GPIOAへのクロック供給を行うよう、`RCC_AHB1ENR`への書き込みを行っています。
このコードを実行したところ無事LEDが光りました。めでたしめでたし。

## 参考ページ
1. [Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html)
2. [STM32のレジスタを叩く。 #1 ~GPIO編~ - エビデイエビナイ電子工作](https://rappy.hatenablog.com/entry/2019/11/17/221651)