LoginSignup
14
10

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-09

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

概要

ゴールデンウィークで時間あるので、前から興味あったけど手をつけられていなかった「Rustでベアメタルプログラミング・組込みOS作成」に手をつけました。
以下のサイトにすごく細かく手順が書いてあったので、それをみながら勉強し、実装を進めました。
Rustで始める自作組込みOS入門

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

環境・使用したマニュアル

上述の通り、以下のサイトを見て実装を進めました。
Rustで始める自作組込みOS入門

このサイトの「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ボードを使いました。¥

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

コード実装&失敗

実装

マニュアルを辿り、

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

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

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

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レジスタのうち、5ピンの入出力設定に対応する位置に値を書いてA5ピンをOUTPUTに設定します。
turn_on()およびturn_off()では、アウトデータレジスタであるODRレジスタのうち、5番ピンに対応するところに値を書いています。

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

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

...
#[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になっている
  2. 書き込む前にGPIO_MODERのアドレスから読み出す値は確かに0xa800_0000になっている
  3. 書き込んだ後もGPIO_MODERから読み出す値が0xa800_0000のままになっている
  4. GPIO_ODRのアドレスを読み出しても0xa800_0000が返ってくる

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

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

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

原因・解決

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

STM32のレジスタを叩く。 #1 ~GPIO編~ - エビデイエビナイ電子工作

ブログ様によると

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

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

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

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

...
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入門
  2. STM32のレジスタを叩く。 #1 ~GPIO編~ - エビデイエビナイ電子工作
14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10