7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

STM32マイコンのベアメタルRUST(その3)【TIM割り込み】

Last updated at Posted at 2020-04-08

 その2で、出来たらいいなと書いた、割り込みバージョンを作成しました。
 ちょうど、このへんで、Lチカの最終回ということで、区切りをつけておきます。

#タイマー割り込み

割り込み処理概要

 STM32F401のタイマーは、カウントアップした時にハードウェア割り込みを発生してくれる機能が有ります。(まぁ、STM32に限らず、マイコンのCPUのタイマーには大概ついてます。)
 この割り込みが発生すると、自動的に指定された関数へ制御が移り、この関数が終了すると、また元の実行していた関数に戻ります。

 この機能を使えば、タイマーがカウントアップしたかずっと調べ続けていなくても、カウントアップの処理が出来ます。
 今回は、メインの関数と割り込み関数での情報共有のテストを兼ねて、割り込み処理関数では、単純にフラグを立てるだけにし、そのフラグをメイン関数で見てLEDを点滅させてみます。(LEDの点滅処理の部分を全部割り込み処理関数に移動すれば、main関数内のループは何も処理なしにすることも可能です。)

割り込み処理の初期設定

 割り込み処理をTIM11から発生させるためには、TIM11の回路で割り込みを発生するように設定する必要が有ります。
 ここで発生した割り込みは、NVICという割り込みコントローラーに渡されます。割り込みコントローラーでは、割り込み一つ一つに対して、その割り込みを許可するかしないかを設定することが出来、デフォルトでは許可しないようになっているので、これを許可に変更する必要が有ります。
NVICでの割り込みの割付に関しては、リファレンスの「ネスト化されたベクタ割込みコントローラ(NVIC)」の章にある表に掲載されています。TIM11関係をここから調べると、位置26のところに、TIM_TRG_COM_TIM11と言う名前で登録されています。よって、割り込みコントローラーの26番割り込みを有効化することになります。その右に書いてある、「0x0000_00A8」というアドレスは、割り込み時に飛ぶ関数(割り込みハンドラーといいます。)のアドレスを登録してあるアドレスになります。割り込み発生時にはCPUはここのアドレスを見て、その内容のアドレスにジャンプするわけです。
 また、cortex_m及び、stm32f401クレートを使って割り込みを設定するときには、stm32f401::interruptという列挙型をリファレンスで調べてください。ここに、全ての割り込みの名前が列挙されています。この名前は、割り込みの名前であり、割り込みハンドラーの関数名でも有ります。
 

 レジスタの設定手順は、2つです。

  • TIM11の割り込みON (TIM11_DIERのUIEフラグをセット)
  • NVICの割り込み許可設定 (NVIC_ISER0のSETENA23をセット)

割り込みハンドラーの定義

 割り込みが発生した時の割り込みハンドラーを定義する必要が有ります。
 割り込みハンドラーに関しては、次のように定義します。

use cortex_m::interrupt::free;
use stm32f4::stm32f401::interrupt;
// 中略
#[interrupt]
fn TIM1_TRG_COM_TIM11() {
    free(|cs| {
        unsafe {
            let device = stm32f401::Peripherals::steal();
            device.TIM11.sr.modify(|_,w| w.uif().clear());
        }
        // 割り込み処理の内容
        WAKE_TIMER.set(cs);
    });

 割り込み処理関数そのものは、引数も戻り値もない、普通の関数です。
 #[interrupt]の指定で、先の割り込み一覧表で見た割り込みベクタテーブルにこの関数のアドレスが登録されます。
 free関数は、クリティカルセクションの定義で、この関数で指定したクロージャ内では、安全に共有変数などが操作できます。
 ここで呼んでいるstm32f401::Peripherals::steal()は、チェック無しでPeripherals構造体を返してくれるunsafeな関数です。
 本来、stm32f401::Peripherals::take()は、Peripherals構造体のインスタンスが、プログラム中に唯一、一つだけ存在することを保証するためにあるビルダ関数です。この構造体は、実際には、ただひとつしか無いハードウェアのレジスタを表しているので、この構造体があっちやこっちやに分散していくつもあると、構造体を通しての操作が矛盾する可能性が出てきます。そのため、一つしか作れなくしているのです。
 でも、割り込み処理関数にメインから引数を渡すことは出来ません。この関数はハードウェアで直接呼ばれるので、そもそも引数を渡す機会が無いのです。
 さらに、Peripherals構造体をグローバル変数にすることも出来ません。少なくとも、思いつけませんでした。
 ここで、登場するのが、steal()関数です。無チェックで、つまり、2つ目でも3つ目でもこの関数は、Peripherals構造体を発行してくれます。ただし、明らかに危険行為です。矛盾が出ないように自分で責任を持つ必要が有ります。そのためのunsafeです。
 そして、次の行の、device.TIM11.sr.modify(|_,w| w.uif().clear());が、割り込み処理関数でいの一番に実行する必要がある処理です。TIM11_SRのUIFフラグは、TIM11の割り込みが発生するとハードウェアでセットされます。そして、このフラグが立っている限り、TIM11の割り込みが発生し続けます。そのため、「割り込みをちゃんと処理した」とハードに通知するために、このフラグを自分でクリアする必要が有ります。

Rustでのグローバル変数

 Cや、C++でこの手の割り込み処理を伴うプログラムを書くときには、グローバル変数でメイン処理関数とのやり取りをするのが定番です。
 でも、今は、安全を唄うRustの中です。グローバルな変更可能な変数というのは、第一級
の危険人物扱いを受けます。関数の外に、staric mut var_name = init_value;とやれば、グローバル変数は作れますが、この変数の操作は全てunsafeとなります。実際、様々な危険が想定されます。特に、割り込み処理のように、メイン処理と関係なく勝手なタイミングで自動的に実行される関数が絡めば、なおさらです。
 危険は危険なのですが、少しでも、安全に・・・ということで、「組込みRust / The Embedded Rust Book」(初回の参考文献を参照。)で紹介されていた方法が、次の方法です。

use core::cell::UnsafeCell;

/// タイマーの起動を知らせるためのフラグ
/// グローバル変数として使用するためのクラス
struct WakeTimer(UnsafeCell<bool>);
const WAKE_TIMER_INIT: WakeTimer = WakeTimer(UnsafeCell::new(false));
impl WakeTimer {
    pub fn set(&self, _cs: &cortex_m::interrupt::CriticalSection) {
        unsafe { *self.0.get() = true };
    }
    pub fn reset(&self, _cs: &cortex_m::interrupt::CriticalSection) {
        unsafe { *self.0.get() = false };
    }
    pub fn get(&self, _cs: &cortex_m::interrupt::CriticalSection) -> bool {
        unsafe { *self.0.get() }
    }
}
unsafe impl Sync for WakeTimer {}

方法としては、UnsafeCellで、クラス内の可変変数をラッピングして、内部変更扱いにして、外側のクラスに関しては、アンミュータブルを装わせよう。というのが基本です。今回は、クラス内には、UnsafeCellでラッピングしたbool変数を一つ、メンバーとして用意しました。impl内の各メンバーで、_csは、クリティカルセクション内で実行することを強制するためにつけられています。_csは、cortex_m::interrupt::free関数のクロージャ内でしか入手できませんから、この引数があるだけで、クリティカルセクションでの実行が保証されます。
 これで、最低限、クラス内にunsafeを閉じ込め、安全弁もつけました。
グローバル変数の宣言は、

static WAKE_TIMER :WakeTimer = WAKE_TIMER_INIT;

となり、アンミュータブルなスタテック変数となります。

プログラム全体

 さて、これで、部品は一通り揃いました。
 前回まで、main関数に直接書いていた初期化処理ですが、少し長くなってきたので、別関数に分けました。
 後は、ここまでに書いたとおりです。

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

// pick a panicking behavior
extern crate panic_halt; // you can put a breakpoint on `rust_begin_unwind` to catch panics
// extern crate panic_abort; // requires nightly
// extern crate panic_itm; // logs messages over ITM; requires ITM support
// extern crate panic_semihosting; // logs messages to the host stderr; requires a debugger

//use cortex_m::asm;
use cortex_m_rt::entry;
use stm32f4::stm32f401;
use cortex_m::interrupt::free;
use stm32f4::stm32f401::interrupt;


static WAKE_TIMER :WakeTimer = WAKE_TIMER_INIT;
#[entry]
fn main() -> ! {

    let device = stm32f401::Peripherals::take().unwrap();
    init_clock(&device);
    gpio_setup(&device);

    //main loop
    let gpioc = &device.GPIOC;
    gpioc.bsrr.write(|w| w.bs8().set());
    loop {
        if free(|cs| WAKE_TIMER.get(cs)) {
            free(|cs| WAKE_TIMER.reset(cs));
            gpioc.odr.modify(|r,w| w.odr8().bit(r.odr8().is_low()));
        }
        cortex_m::asm::wfi();
    }
}

/// タイマー11グローバル割り込み関数
#[interrupt]
fn TIM1_TRG_COM_TIM11() {
    free(|cs| {
        unsafe {
            let device = stm32f401::Peripherals::steal();
            device.TIM11.sr.modify(|_,w| w.uif().clear());
        }
        WAKE_TIMER.set(cs);
    });
}

/// システムクロックの設定(48MHz)
fn init_clock(device : &stm32f401::Peripherals) {

    // PLLCFGR設定
    // hsi(16M)/8*192/8=48MHz
    {
        let pllcfgr = &device.RCC.pllcfgr;
        pllcfgr.modify(|_,w| w.pllsrc().hsi());
        pllcfgr.modify(|_,w| w.pllp().div8());
        pllcfgr.modify(|_,w| unsafe { w.plln().bits(192u16) });
        pllcfgr.modify(|_,w| unsafe { w.pllm().bits(8u8) });
    }

    // PLL起動
    device.RCC.cr.modify(|_,w| w.pllon().on());
    while device.RCC.cr.read().pllrdy().is_not_ready() {
        // PLLの安定を待つ
    }

    // フラッシュ読み出し遅延の変更
    device.FLASH.acr.modify(|_,w| unsafe {w.latency().bits(1u8)});
    // システムクロックをPLLに切り替え
    device.RCC.cfgr.modify(|_,w| w.sw().pll());
    while !device.RCC.cfgr.read().sws().is_pll() { /*wait*/ }
}

/// GPIO及び、TIM11の初期設定
fn gpio_setup(device : &stm32f401::Peripherals) {
    // GPIO 電源
    device.RCC.ahb1enr.modify(|_,w| w.gpiocen().enabled());
    // TIM11 電源
    device.RCC.apb2enr.modify(|_,w| w.tim11en().enabled());

    // GPIOC セットアップ
    let gpioc = &device.GPIOC;
    gpioc.moder.modify(|_,w| w.moder8().output());
    gpioc.moder.modify(|_,w| w.moder6().output());
    gpioc.moder.modify(|_,w| w.moder5().output());
    // TIM11 セットアップ
    let tim11 = &device.TIM11;
    tim11.psc.modify(|_,w| w.psc().bits(48_000u16 - 1)); // 1ms
    tim11.arr.modify(|_,w| unsafe { w.arr().bits(500u16) }); // 500ms
    tim11.dier.modify(|_,w| w.uie().enabled());
    unsafe {
        cortex_m::peripheral::NVIC::unmask(
            stm32f401::interrupt::TIM1_TRG_COM_TIM11);
    }
    tim11.cr1.modify(|_,w| w.cen().enabled());
}


use core::cell::UnsafeCell;

/// タイマーの起動を知らせるためのフラグ
/// グローバル変数として使用するためのクラス
struct WakeTimer(UnsafeCell<bool>);
const WAKE_TIMER_INIT: WakeTimer = WakeTimer(UnsafeCell::new(false));
impl WakeTimer {
    pub fn set(&self, _cs: &cortex_m::interrupt::CriticalSection) {
        unsafe { *self.0.get() = true };
    }
    pub fn reset(&self, _cs: &cortex_m::interrupt::CriticalSection) {
        unsafe { *self.0.get() = false };
    }
    pub fn get(&self, _cs: &cortex_m::interrupt::CriticalSection) -> bool {
        unsafe { *self.0.get() }
    }
}
unsafe impl Sync for WakeTimer {}

最後に、main関数内のLED処理の後にある、cortex_m::asm::wfi();ですが、これは、「割り込みが発生するまでCPUをスリープ状態とし処理を停止する」というcortex_mのアセンブリ命令「wfi」を実行します。
 この命令実行後は、割り込みが発生するまで停止し、割り込みが発生すると、割り込みハンドラの実行後に、このループに戻って処理を継続します。今回は、割り込みハンドラの実行を確認してLEDを反転ですね。その後、また次の割り込みまでスリープします。
 これで、常にCPUをフルパワーで回さなくても、CPUは時々起きるだけになります。

終わり

 これで、とりあえず、Lチカでやりそうなことは、ひとまず区切りかなと思います。
 次に、これで何をして遊ぶかは、まだ考え中。
 一旦、シリーズは、終了とします。

シリーズ目次

7
4
3

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
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?