4
1

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 5 years have passed since last update.

RustでSTM32ベアメタル [4桁7セグメントLED](実装編3)<最終回>〜TIMペリフェラル〜

Last updated at Posted at 2020-05-05

Rustという言語でSTM32F3Discoveryというマイコンを動かし,4桁7セグメントLEDダイナミック点灯方式で光らせるっていう記事の4編目です.
細かく書いてるのもあって長くなってます.ここまで読んでいただいてる方はもしかしたら少ないかもしれませんが,まだまだ丁寧に書いていきます.

この記事を読み終わるころには完全に理解したって言ってもらえるはず!

成果物はこちら.↓

概要編では,主に4桁7セグメントLEDダイナミック点灯方式で光らせる方法について,概要を説明しました.
実装編1では,ペリフェラル全般とか,*GPIOペリフェラルについて説明しました.
実装編2では,
SPI*ペリフェラルについて説明しました.

今回は,前回の続きで,*TIM*ペリフェラルのところから始めていきたいと思います.

筆者も組み込み初心者であるため,説明が行き届かないところもあるかもしれませんが,ご容赦ください.
誤情報だけは書かないように注意を払ってますので,ご安心ください.

はじめに

コードでは,Ruststm32f30xというクレートを用いていますが,本記事では,このクレートをただ利用するだけではなく,使用する構造体や関数が,どのような役目を果たしているかや,本当はどんなことをしているかなど,なるべく低レベルに降りて説明するようにしています.
多少自分に言い聞かせるように書いてる部分もあり,鬱陶しく感じることがあるかもしれません.そう感じたところはどうぞ読み飛ばしちゃってくださいませ.

では本題に入ります.
ここからはリファレンスマニュアルを横に並べて読むことをお勧めします.見るべきページは,適宜示します.

TIM2

7セグメントLEDの,表示する桁とLEDを一定時間ごとに切り替えるために,TIM2っていうペリフェラル**を使います.

電源の投入とリセット

まずは電源とリセット.
教科書p.152〜
電源
スクリーンショット 2020-05-05 15.43.21.png
スクリーンショット 2020-05-05 15.44.41.png
スクリーンショット 2020-05-05 15.45.34.png
リセット
スクリーンショット 2020-05-05 15.47.18.png
スクリーンショット 2020-05-05 15.48.03.png
スクリーンショット 2020-05-05 15.49.03.png
*GPIOSPI*のときも似たようなことをしたので,説明は省く.
コードはこちら↓

timer.rs
    let rcc = unsafe { &*RCC::ptr() };

    rcc.apb1enr.modify(|_, w| w.tim2en().set_bit());
    rcc.apb1rstr.modify(|_, w| w.tim2rst().set_bit());
    rcc.apb1rstr.modify(|_, w| w.tim2rst().clear_bit());

TIM2ペリフェラルの使い方

*TIM2ペリフェラルの設定を行うために扱うレジスタは4つある.
が,その前にまずはこの
TIM2*の使い方から説明します.

TIM2のカウンタを有効にすると,TIM2_CNTレジスタの値がTIM2_ARRレジスタの値と等しくなるまで一定時間ごとにインクリメントされ,等しくなるとTIM2_SRレジスタの0bit目(uif)が1になる.
というような動作をするので,TIM2_SRuifを監視することにより,時間を測ることができる.
これを利用して,7セグメントLEDの,表示する桁とLEDを
一定時間ごとに**切り替えるわけだ.

TIM2ペリフェラルの設定

というわけで,設定する必要があるのは,*TIM2_CNTレジスタの値がインクリメントされる間隔と,TIM2_ARR*レジスタの値だ.

なんか記事が長くなって,無意識に説明が雑になってそうで怖い

まずは*TIM2のカウンタを無効にして,さらにカウンタの値も0*にリセットします.
p.647〜p.663〜
スクリーンショット 2020-05-05 18.42.40.png
スクリーンショット 2020-05-05 18.43.17.png
スクリーンショット 2020-05-05 18.43.52.png

TIM2_CR1の0bit目(CEN)を0にして,

スクリーンショット 2020-05-05 18.47.33.png スクリーンショット 2020-05-05 18.48.12.png >*`TIM2_CNT`*の全ビットを*0*にする.

コードは下↓のようになる.

timer.rs
    let tim = unsafe { &*TIM2::ptr() };
    tim.cr1.modify(|_, w| w.cen().clear_bit());
    tim.cnt.reset();

次に,*TIM2_PSCTIM2_ARR*レジスタの値を設定する.
*TIM2_ARRについては先ほど少し説明したが,TIM2_PSC*との関連性が少し大事なので改めて説明する.

ここで,とりあえず*TIM2に入力されるクロックの周波数をclk*とおく.
TIM2_CNTレジスタの値は,毎秒$$\frac{clk}{{\tiny TIM2}PSC+1}$$という周波数で増加する.
TIM2_CNTの値がTIM2_ARRの値まで達すると,イベントが発生する(TIM2_SRレジスタの0bit目(uif)が
1
になる).

ここで気づいて欲しいのが,このイベントが発生するまでの時間は,$${\tiny TIM2}ARR\times({\tiny TIM2}PSC+1)$$で決まるということだ.
つまり,それぞれの値はそれほど大事ではなく,2つを掛け合わせた値が大事ということだ.この性質をあとで利用するので,頭の片隅に置いておいて欲しい.

では,仮に,freqという周波数でこのイベントを発生させたいとしよう.
このとき,*TIM2_PSCTIM2_ARR*レジスタの値はどのように設定すれば良いのだろうか.
すぐに,
$$\frac{1}{freq} == \frac{{\tiny TIM2}ARR}{\frac{clk}{{\tiny TIM2}PSC+1}}$$
が成り立てばいいということがわかる.
式変形して,
$$\frac{clk}{freq} == {\tiny TIM2}ARR\times({\tiny TIM2}PSC+1)$$
頭に置いといたやつが出てきた.
*TIM2_PSCTIM2_ARR*のどちらかの値を決めれば,他方も決まるというわけだ.

しかし,適当に決めればいいというものでもない.
こちらを見ていただきたい.
スクリーンショット 2020-05-05 22.18.53.png
スクリーンショット 2020-05-05 22.28.45.png
TIM2_PSC16bitなのだ!(TIM2_ARRTIM2じゃない時は16bit**.)
だから何?って思った方もいるかもしれないのでちゃんと説明すると,TIM2入力されるクロックの周波数freqは,最大で64MHz(多分).
これを2進数で表すと,26bitになる.
つまり,適切な値にしなければ,TIM2_PSCレジスタのサイズに収まらなくなってしまう.
今回は,より汎用性の高いコードにしたかったので,
TIM2_PSCTIM2_ARRがどちらも
16bit**に収まるようなコードにした.それを以下に示す.

timer.rs
    let ticks = clk / freq;

    let psc = u16((ticks - 1) >> 16).unwrap();
    tim.psc.write(|w| unsafe { w.psc().bits(psc) });

    let arr = u16(ticks / u32(psc + 1)).unwrap();
    tim.arr.write(|w| unsafe { w.bits(u32(arr)) });

TIM2_PSCの値を,ticks-1上位16bitにして,TIM2_ARRはそれに合わせて決める.
これなら確かにどちらも
16bit**に収まりそう.
こちらのコードを参考にしています.)

あとは,TIM2_CEN1にしてカウンタを有効にするだけ.

timer.rs
    tim.cr1.modify(|_, w| w.cen().set_bit());

なのだが,1つだけ忘れていることがある.それは,TIM2に入力されるクロックclkの周波数だ.
p.125
スクリーンショット 2020-05-05 12.03.04のコピー.png
SPIのときにも見たやつですね.
ただ1つ注意するのは,APB1 prescalerppre1)が
1
のとき以外は,最後に2倍されるということ.

*SPI*のときにもほとんど同じことをしたので,説明は省略して,いきなりコードを示す.

timer.rs
const HSI: u32 = 8_000_000;

... // 関係ないので省略

    let rcc = unsafe { &*RCC::ptr() };

    let pllmul_bits = rcc.cfgr.read().pllmul().bits();
    let pllmul: u32 = u32(pllmul_bits + 2);

    let ppre1_bits = rcc.cfgr.read().ppre1().bits();
    let ppre1: u32 = if ppre1_bits & 0b100 == 0 { 1 } else { 1 << (ppre1_bits - 0b011) };
    
    let hpre_bits = rcc.cfgr.read().hpre().bits();
    let hpre: u32 = if hpre_bits & 0b1000 == 0 { 1 } else { 1 << (hpre_bits - 0b0111) };
    
    let sysclk = pllmul * HSI / 2;
    let hclk = sysclk / hpre;
    let pclk1 = hclk / ppre1;

    let clk = pclk1 * if ppre1 == 1 { 1 } else { 2 }; // Don't forget!

これで設定の説明は終わり.

使ってみる

実際に,イベントが発生するまで待つっていうだけの関数を書くとこんな感じになる.

pub fn wait() {
    let tim = unsafe { &*TIM2::ptr() };
    while tim.sr.read().uif().bit_is_clear() {}

    tim.sr.modify(|_, w| w.uif().clear_bit()); // Don't forget!
}

TIM2_SRレジスタのuifビットを0に戻すことで,この関数を同じ機能で何回も使えるようにする.

*TIM2*のイベントを発生させる周波数を変更するような関数を作っておけば,この↓ように切り替える速さを徐々に速くしたりすることができる.

おわりに

こんだけのことをするのに長々4編も書いてしまいました.
でも結構詳しく書いたので,皆さんの役に立てれば幸いです.

全体をまとめたコードは記事には載せていませんが,あとは4編分の内容を組み合わせるだけです.
一応冒頭にgithubのリンクも貼ってますから,参考にしたい方はどうぞm(_ _)m.

拙い文章をこんなところまで読んでいただき本当にありがとうございました.

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?