ラズピコでRust(12) タイマー割り込みを使ってみる

ラズピコでRust(12) タイマー割り込みを使ってみる

皆さん こんにちは。
ポンコツRustacean の moon です。

ここまで、いろいろやってきたのですがタイマー割り込みの存在をすっかり忘れていました。
タイマー割り込みは重要な機能ですから、ここで実装してみることにしました。

この記事は開発環境を構築することを前提にしています。
環境構築について知りたい方は こちらの記事 をご覧になってください。

このサイトは 書籍 基礎から学ぶ組込みRust を参考にしています。

パッケージの複製を作成する

まず私のGitHubリポジトリにあるRustのひな形をベースに別名でパッケージをつくります。
ひな形はデバッグ環境用のものですから、その複製もデバッグできるようになります。

新しいパッケージ名を rp2040-timer-int として git clone します。
xxxxは皆さんのユーザー名です。
カレントディレクトリを rp2040-timer-int に移してから、code . で VSCode を起動します。

C:\Users\xxxx>cd pprp
C:\Users\xxxx\pprp>git clone https://github.com/moons3925/rp2040.git rp2040-timer-int
C:\Users\xxxx\pprp>cd rp2040-timer-int
C:\Users\xxxx\pprp\rp2040-timer-int>code .

launch.jsonを編集する

EXPLORER – ツリーの .vscode 下にある launch.json をクリックして編集します。

executable: の名称を rp2040 から rp2040-timer-int に変更します。

"executable": "./target/thumbv6m-none-eabi/debug/rp2040-timer-int",

Cargo.tomlを編集する

個々のバージョンが違っていたりするとエラーが出たりしてやっかいですので。。
以下をコピペしてください。

[package]
edition = “2021”
name = “rp2040-timer-int”
version = “0.1.0”
license = “MIT OR Apache-2.0”

[dependencies]

cortex-m = “0.7.7”
cortex-m-rt = “0.7.3”
embedded-hal = “0.2.7”
panic-halt = “0.2.0”
rp-pico = “0.8.0”
rp2040-hal = “0.9.2”
fugit = “0.3.7”
critical-section = “1.1.2”

main.rsを編集する

#![no_std]
#![no_main]
use panic_halt as _;
use rp2040_hal as hal;
use rp2040_hal::pac;
use rp_pico::entry;

use rp2040_hal::Clock;

use core::cell::RefCell;
use critical_section::Mutex;
use fugit::MicrosDurationU32;

use embedded_hal::digital::v2::ToggleableOutputPin;
use pac::interrupt;
use rp2040_hal::timer::Alarm;

// (1)
type LedAndAlarm = (
    hal::gpio::Pin<hal::gpio::bank0::Gpio25, hal::gpio::FunctionSioOutput, hal::gpio::PullDown>,
    hal::timer::Alarm0,
);

// (2)
static mut LED_AND_ALARM: Mutex<RefCell<Option<LedAndAlarm>>> = Mutex::new(RefCell::new(None));

// (3)
const FAST_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::millis(10);

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    let clocks = hal::clocks::init_clocks_and_plls(
        rp_pico::XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());

    let sio = hal::Sio::new(pac.SIO);

    let pins = rp_pico::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let led_pin = pins.led.into_push_pull_output();

    let mut timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); // (4)
    critical_section::with(|cs| { // (5)
        let mut alarm = timer.alarm_0().unwrap();
        let _ = alarm.schedule(FAST_BLINK_INTERVAL_US);
        alarm.enable_interrupt();
        unsafe {
            LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm)));
        }
    });
    unsafe {
        pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0); // (6)
    }

    loop {
        delay.delay_ms(100);
    }
}

#[interrupt]
fn TIMER_IRQ_0() { // (7)
    critical_section::with(|cs| {
        let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() };
        if let Some((mut led, mut alarm)) = ledalarm {
            alarm.clear_interrupt();
            let _ = alarm.schedule(FAST_BLINK_INTERVAL_US);

            unsafe {
                static mut COUNT: u8 = 0;
                COUNT += 1;
                if 9 < COUNT {
                    COUNT = 0;
                    // Blink the LED so we know we hit this interrupt
                    led.toggle().unwrap();
                }
            }

            // Return LED_AND_ALARM into our static variable
            unsafe {
                LED_AND_ALARM
                    .borrow(cs)
                    .replace_with(|_| Some((led, alarm)));
            }
        }
    });
}

ビルドして実行する

Run - Start Debugging (F5) からプログラムを実行します。

割り込みの周期自体は10msecですが、割り込みサービスルーチン内で10カウントしたらLEDのGPIO出力を反転しているので100msec周期でLEDが点滅します。

Lチカしていれば成功です。

念のためタイマー割り込み fn TIMER_IRQ_0()関数 内にブレークポイントを貼ってプログラムが来ることを確認しておきます。

プログラムの概要

(1)LEDピンとタイマーを制御するアラームをタプルにしたものを type で型にしています。
(2)その型を Mutex, RefCell, Option でラップしています。排他制御の常套手段です。
(3)MicrosDurationU32は期間を表現するのに用意された構造体です。figitクレートを使うのでCargo.tomlに登録しています。
(4)Timer構造体を生成します。
(5)排他制御するためにクリティカルセクションを使います。
(6)タイマー割り込みを使うために割り込みをイネーブルにします。
(7)割り込みサービスルーチンです。

C/C++ SDK の方ではタイマー割り込みを実現する方法が2種類あるようです。
RP2040 HAL のRustのソースを覗いてみたのですが、アラームによる1種類の方法しかないようです。
アラームの方はリスケジュールして使う必要があるので少々手間がかかるようですけど。。
まあ、仕方ないですね。

GitHubにアップ

GitHubの rp2040-timer-int にプロジェクトをアップしましたので参考になさってください。

お疲れさまでした。

Timerカテゴリの最新記事