LedDelayの抽象化

これから、LEDルーレットアプリケーションを実装するための、2つの高レベルな抽象化を紹介します。

補助クレートのaux5は、initという初期化関数を公開しています。 この関数を呼び出すと、DelayLedsの値からなるタプルが返ってきます。

Delayは、ミリ秒単位で指定された時間の間、プログラムをブロックします。

Ledsは、8個のLedからなる配列です。各Ledは、F3ボード上のLEDの1つを表しています。 そして、onoffという2つのメソッドを公開しており、それぞれ、LEDをオンまたはオフにします。

スターターコードを次のように修正して、2つの抽象化を試してみましょう。

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use aux5::{entry, prelude::*, Delay, Leds};

#[entry]
fn main() -> ! {
    let (mut delay, mut leds): (Delay, Leds) = aux5::init();

    let half_period = 500_u16;

    loop {
        leds[0].on();
        delay.delay_ms(half_period);

        leds[0].off();
        delay.delay_ms(half_period);
    }
}

ビルドします。

$ cargo build --target thumbv7em-none-eabihf

注記 GDBセッションを開始するに、プログラムを再ビルドすることを忘れることがあります。このうっかりミスは、非常に混乱するデバッグセッションを作り上げます。 この問題を避けるために、cargo buildではなくcargo runを呼び出すことができます。 cargo runは、ビルドデバッグセッションの開始を行い、プログラムの再コンパイル忘れが起きないようにしてくれます。

次に、前のセクションで行ったとおり、Flashへの書き込み手順を繰り返します。

$ # これはプログラムのGDBセッションを開始します。バイナリのパスを指定する必要はありません。
$ arm-none-eabi-gdb -q target/thumbv7em-none-eabihf/debug/led-roulette
Reading symbols from target/thumbv7em-none-eabihf/debug/led-roulette...done.
(gdb) target remote :3333
Remote debugging using :3333
(..)

(gdb) load
Loading section .vector_table, size 0x188 lma 0x8000000
Loading section .text, size 0x3fc6 lma 0x8000188
Loading section .rodata, size 0xa0c lma 0x8004150
Start address 0x8000188, load size 19290
Transfer rate: 19 KB/sec, 4822 bytes/write.

(gdb) break main
Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 9.

(gdb) continue
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9
9           let (mut delay, mut leds): (Delay, Leds) = aux5::init();

では、コードをステップ実行していきましょう。今回は、stepの代わりに、nextコマンドを使います。 nextは、関数呼び出し時、関数内に入らずに、ステップオーバーします。

(gdb) next
11          let half_period = 500_u16;

(gdb) next
13          loop {

(gdb) next
14              leds[0].on();

(gdb) next
15              delay.delay_ms(half_period);

leds[0].on()ステートメント実行後、北を指し示す赤いLEDが点灯するはずです。

プログラムのステップオーバー実行を続けます。

(gdb) next
17              leds[0].off();

(gdb) next
18              delay.delay_ms(half_period);

delay_msの呼び出しは、0.5秒の間プログラムをブロックしますが、それに気づかないかもしれません。 nextコマンドの実行にいくらか時間がかかるためです。 しかし、leds[0].off()ステートメントをステップオーバーすると、赤いLEDが消灯するでしょう。

すでに、このプログラムが何をするか、予測できているでしょう。continueコマンドを使って、中断せずに実行しましょう。

(gdb) continue
Continuing.

次は、もっと面白いことをやります。GDBを使って、プログラムの動作を変更します。

まずは、Ctrl+Cを入力し、無限ループを停止します。Led::onか、Led::offdelay_ms内のどこかに居るでしょう。

Program received signal SIGINT, Interrupt.
0x080033f6 in core::ptr::read_volatile (src=0xe000e010) at /checkout/src/libcore/ptr.rs:472
472     /checkout/src/libcore/ptr.rs: No such file or directory.

私の場合、read_volatile関数の中で、プログラムの実行が停止していました。 GDBの出力は、core::ptr::read_volatile (src=0xe000e010)という面白い情報を示しています。 これは、この関数がcoreクレートから来ており、src = 0xe000e010という引数で呼び出されていることを意味します。

ご存知の通り、関数の引数を表示するための、より明確な方法は、info argsコマンドを使うことです。

(gdb) info args
src = 0xe000e010

どこでプログラムが停止したかに関わらず、backtraceコマンド(btという省略形があります)を使って、 どのようにそこに到達したか、を見ることができます。

(gdb) backtrace
#0  0x080033f6 in core::ptr::read_volatile (src=0xe000e010)
    at /checkout/src/libcore/ptr.rs:472
#1  0x08003248 in <vcell::VolatileCell<T>>::get (self=0xe000e010)
    at $REGISTRY/vcell-0.1.0/src/lib.rs:43
#2  <volatile_register::RW<T>>::read (self=0xe000e010)
    at $REGISTRY/volatile-register-0.2.0/src/lib.rs:75
#3  cortex_m::peripheral::syst::<impl cortex_m::peripheral::SYST>::has_wrapped (self=0x10001fbc)
    at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124
#4  0x08002d9c in <stm32f30x_hal::delay::Delay as embedded_hal::blocking::delay::DelayUs<u32>>::delay_us (self=0x10001fbc, us=500000)
    at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:58
#5  0x08002cce in <stm32f30x_hal::delay::Delay as embedded_hal::blocking::delay::DelayMs<u32>>::delay_ms (self=0x10001fbc, ms=500)
    at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:32
#6  0x08002d0e in <stm32f30x_hal::delay::Delay as embedded_hal::blocking::delay::DelayMs<u16>>::delay_ms (self=0x10001fbc, ms=500)
    at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:38
#7  0x080001ee in main () at src/05-led-roulette/src/main.rs:18

backtraceは、現在の関数からmainまでの関数呼び出しのトレースを表示します。

トピックに戻ります。やりたいことをやるためには、まず、main関数に戻る必要が有ります。 finishコマンドを使うことで、これができます。このコマンドは、プログラムの実行を再開し、プログラムが現在の関数から戻った直後に停止します。 複数回、このコマンドを呼び出します。

(gdb) finish
cortex_m::peripheral::syst::<impl cortex_m::peripheral::SYST>::has_wrapped (self=0x10001fbc)
    at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124
124             self.csr.read() & SYST_CSR_COUNTFLAG != 0
Value returned is $1 = 5

(gdb) finish
Run till exit from #0  cortex_m::peripheral::syst::<impl cortex_m::peripheral::SYST>::has_wrapped (
    self=0x10001fbc)
    at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124
0x08002d9c in <stm32f30x_hal::delay::Delay as embedded_hal::blocking::delay::DelayUs<u32>>::delay_us (
    self=0x10001fbc, us=500000)
    at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:58
58              while !self.syst.has_wrapped() {}
Value returned is $2 = false

(..)

(gdb) finish
Run till exit from #0  0x08002d0e in <stm32f30x_hal::delay::Delay as embedded_hal::blocking::delay::DelayMs<u16>>::delay_ms (self=0x10001fbc, ms=500)
    at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:38
0x080001ee in main () at src/05-led-roulette/src/main.rs:18
18              delay.delay_ms(half_period);

mainに戻ってきました。half_periodというローカル変数があります。

(gdb) info locals
half_period = 500
delay = (..)
leds = (..)

次に、setコマンドを使って、この変数を書き換えます。

(gdb) set half_period = 100

(gdb) print half_period
$1 = 100

continueコマンドを使って、プログラムの実行すると、LEDが前より速く点滅するはずです!

質問です!half_periodの値を下げ続けるとどうなるでしょうか? half_periodの値がいくつになると、LEDの点滅が見えなくなるでしょうか?

次は、あなたがプログラムを書く番です。