スクロール効果
このセクションでは、1 文字に対するスクロール効果を作成します。文字は右から左へスクロールし、端の外へ消えた後、再び右側から現れます。
microbit-bsp という別の BSP クレートもあり、テキストのスクロールを組み込みでサポートしています。ただし、これは Embassy フレームワークと非同期プログラミング(async)を使用します。まだ Embassy や async の概念を導入していないため、ここではそのクレートを使わないことにします。
したがって、今のところは microbit-v2 クレートを使い、自分たちでスクロールのロジックを実装します。
ロジック
2 次元配列を使って LED マトリクスを点灯させる方法は、すでに知っています。では、その知識を使ってスクロール効果を作る方法を考えてみてください。これを実現する方法は複数あります。まずは自分なりのロジックを考えてみることをおすすめします。
以下では、その実装方法の一例を示します。ただし、これはあくまで 1 つのアプローチであり、最も効率的または洗練された解決策とは限らない点に注意してください。
全体のコード
#![no_std] #![no_main] use embedded_hal::delay::DelayNs; use microbit::{board::Board, display::blocking::Display, hal::timer::Timer}; use cortex_m_rt::entry; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } #[entry] fn main() -> ! { let board = Board::take().unwrap(); let mut timer = Timer::new(board.TIMER0); let mut display = Display::new(board.display_pins); // 'R' の 5x5 表現 let r_char = [ [1, 1, 1, 0, 0], [1, 0, 0, 1, 0], [1, 1, 1, 0, 0], [1, 0, 1, 0, 0], [1, 0, 0, 1, 0], ]; let mut offset = 0; loop { let mut frame = [[0; 5]; 5]; for row in 0..5 { for col in 0..5 { let char_col = col as isize + offset - 4; if char_col >= 0 && char_col < 5 { frame[row][col] = r_char[row][char_col as usize]; } else { frame[row][col] = 0; } } } display.show(&mut timer, frame, 500); timer.delay_ms(100); offset += 1; if offset > 8 { offset = 0; } } }
画面上に文字のどの部分を表示するかを制御するために、offset という変数を使います。offset が増えるにつれて、文字は左に移動します。
offset を使ったスクロール
この LED マトリクスは横 5 列です。文字を右から入れて、全体を表示し、そのまま左へ流して画面外に出すには、5 ステップを超える段階を考慮する必要があります。
順を追って見ていきましょう:
-
文字は最初、右側で完全に画面外にあります。
-
その後、1 列ずつ表示領域に入ってきます。
-
中央で完全に表示されます。
-
最後に、1 列ずつ左へ出ていき、見えなくなります。
この一連の流れ全体をカバーしたいわけです:
[off-screen right] --> [entering display] --> [fully visible] --> [leaving display] --> [off-screen left]
これには合計 9 ステップ(offset は 0 から 8)かかります:
| Offset | 画面上で起こること | 文字の何列が表示されるか | 表示領域に対する文字の位置 |
|---|---|---|---|
| 0 | 文字の最初の列がいちばん右に現れる | 1 | 文字の大部分は右側で画面外にある |
| 1 | 最初の2 列が現れる | 2 | 文字がさらにスライドして入ってくる |
| 2 | 最初の 3 列が現れる | 3 | 半分見えている |
| 3 | 最初の 4 列が現れる | 4 | ほぼ完全に見えている |
| 4 | 文字全体が完全に表示される | 5 | 通常どおりの見え方になる |
| 5 | 最初の列が左側から消え始める | 4 | 文字が左へ流れ始める |
| 6 | 中央と右側だけが見えている | 3 | 文字のさらに多くの部分が画面外へ出ている |
| 7 | 文字の最後の部分だけが残る | 2 | ほとんど消えかけている |
| 8 | 文字が完全に消える | 0 | 左側で完全に画面外に出ている |
フレームの作成
ディスプレイに送る新しい 5x5 のフレームを作成します。フレーム内の各 LED について:
その位置に表示すべき文字の列(r_char)を計算します。これは次の式で行います:
#![allow(unused)] fn main() { let char_col = col as isize + offset - 4; }
これにより、文字がゆっくり左へずれていきます。
char_col が有効な範囲(0 ~ 4)にあるかを確認します。範囲内であれば、そのピクセルを文字からコピーします。そうでなければ、0(LED オフ)を設定します。
ループしてアニメーションさせる
これをループの中で繰り返します:
-
現在のフレームを 500 ms 表示し、その後 100 ms 待つ
-
offset を増やす
-
9 に達したら offset を 0 に戻す
これにより、文字が右から左へスクロールして消え、再び現れるように見えます。
offset、LED マトリクスの "col"、char_col の関係
理解を深めるために、offset が増えるにつれてこれらの値がどのように変化するかを見ていきましょう。
offset = 0
R の最初の列(インデックス 0)だけが右端の列に表示されます。
char_col = col + offset - 4 = col - 4
col | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
char_col | -4 | -3 | -2 | -1 | 0 |
. . . . #
. . . . #
. . . . #
. . . . #
. . . . #
注: 0 と 1 をそのまま使うと、形がはっきり見えにくくなることがあります。そのため、この図では # 記号で LED が点灯している場所(つまり値 1)を表しています。ドット(.)は消灯している LED(値 0)を表します。
offset = 1
3 列目と 4 列目に、それぞれ文字の 0 列目と 1 列目が表示されます。
char_col = col + 1 - 4 = col - 3
col | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
char_col | -3 | -2 | -1 | 0 | 1 |
. . . # #
. . . # .
. . . # #
. . . # .
. . . # .
offset = 4
offset が 4 のケースに進みましょう。この時点では、文字全体がディスプレイに完全に表示されています。
char_col = col + 4 - 4 = col
これは、char_col と col が等しいことを意味するので、frame 配列は元の文字配列とそのまま一致します。
col | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
char_col | 0 | 1 | 2 | 3 | 4 |
# # # . .
# . . # .
# # # . .
# . # . .
# . . # .
offset = 5
char_col = col + 5 - 4 = col + 1
col | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
char_col | 1 | 2 | 3 | 4 | 5 |
# # . . .
. . # . .
# # . . .
. # . . .
. . # . .
ここで、各 char_col は対応する col より 1 大きくなります。char_col が 5 になると、範囲外になります(配列のインデックスは 0 から 4 までしかないためです)。
これを防ぐために、境界チェックを追加します。char_col が有効範囲の外にある場合は、その列を 0 で埋めます。これにより、文字がスクロールして外へ出ていくときの消えていく効果が生まれます。
if char_col >= 0 && char_col < 5 {
frame[row][col] = r_char[row][char_col as usize];
} else {
frame[row][col] = 0;
}
完全なコード
#![no_std] #![no_main] use embedded_hal::delay::DelayNs; use microbit::{board::Board, display::blocking::Display, hal::timer::Timer}; use cortex_m_rt::entry; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } #[entry] fn main() -> ! { let board = Board::take().unwrap(); let mut timer = Timer::new(board.TIMER0); let mut display = Display::new(board.display_pins); // 'R' の 5x5 表現 let r_char = [ [1, 1, 1, 0, 0], [1, 0, 0, 1, 0], [1, 1, 1, 0, 0], [1, 0, 1, 0, 0], [1, 0, 0, 1, 0], ]; let mut offset = 0; loop { let mut frame = [[0; 5]; 5]; for row in 0..5 { for col in 0..5 { let char_col = col as isize + offset - 4; if char_col >= 0 && char_col < 5 { frame[row][col] = r_char[row][char_col as usize]; } else { frame[row][col] = 0; } } } display.show(&mut timer, frame, 500); timer.delay_ms(100); offset += 1; if offset > 8 { offset = 0; } } }
既存のプロジェクトをクローンする
作成したプロジェクトをクローン(または参照)して、led-scroll フォルダーに移動することもできます。
git clone https://github.com/ImplFerris/microbit-projects
cd microbit-projects/bsp/led-scroll
フラッシュ
このプログラムを micro:bit にフラッシュできます。
cargo flash