Rustという言語でSTM32F3Discoveryというマイコンを動かし,4桁7セグメントLEDをダイナミック点灯方式で光らせるっていう記事の4編目です.
細かく書いてるのもあって長くなってます.ここまで読んでいただいてる方はもしかしたら少ないかもしれませんが,まだまだ丁寧に書いていきます.
この記事を読み終わるころには完全に理解したって言ってもらえるはず!
成果物はこちら.↓
概要編では,主に4桁7セグメントLEDをダイナミック点灯方式で光らせる方法について,概要を説明しました.
実装編1では,ペリフェラル全般とか,*GPIO
ペリフェラルについて説明しました.
実装編2では,SPI
*ペリフェラルについて説明しました.
今回は,前回の続きで,*TIM
*ペリフェラルのところから始めていきたいと思います.
筆者も組み込み初心者であるため,説明が行き届かないところもあるかもしれませんが,ご容赦ください.
誤情報だけは書かないように注意を払ってますので,ご安心ください.
はじめに
コードでは,Rustのstm32f30x
というクレートを用いていますが,本記事では,このクレートをただ利用するだけではなく,使用する構造体や関数が,どのような役目を果たしているかや,本当はどんなことをしているかなど,なるべく低レベルに降りて説明するようにしています.
多少自分に言い聞かせるように書いてる部分もあり,鬱陶しく感じることがあるかもしれません.そう感じたところはどうぞ読み飛ばしちゃってくださいませ.
では本題に入ります.
ここからはリファレンスマニュアルを横に並べて読むことをお勧めします.見るべきページは,適宜示します.
TIM2
7セグメントLEDの,表示する桁とLEDを一定時間ごとに切り替えるために,TIM2
っていうペリフェラル**を使います.
電源の投入とリセット
まずは電源とリセット.
教科書p.152〜.
電源
リセット
*GPIO
やSPI
*のときも似たようなことをしたので,説明は省く.
コードはこちら↓
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_SR
のuif
を監視することにより,時間を測ることができる.
これを利用して,7セグメントLEDの,表示する桁とLEDを一定時間ごとに**切り替えるわけだ.
TIM2ペリフェラルの設定
というわけで,設定する必要があるのは,*TIM2_CNT
レジスタの値がインクリメントされる間隔と,TIM2_ARR
*レジスタの値だ.
なんか記事が長くなって,無意識に説明が雑になってそうで怖い
まずは*TIM2
のカウンタを無効にして,さらにカウンタの値も0*にリセットします.
p.647〜とp.663〜
TIM2_CR1
の0bit目(CEN
)を0にして,


コードは下↓のようになる.
let tim = unsafe { &*TIM2::ptr() };
tim.cr1.modify(|_, w| w.cen().clear_bit());
tim.cnt.reset();
次に,*TIM2_PSC
とTIM2_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_PSC
とTIM2_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_PSC
とTIM2_ARR
*のどちらかの値を決めれば,他方も決まるというわけだ.
しかし,適当に決めればいいというものでもない.
こちらを見ていただきたい.
TIM2_PSC
は16bitなのだ!(TIM2_ARR
もTIM2
じゃない時は16bit**.)
だから何?って思った方もいるかもしれないのでちゃんと説明すると,TIM2
入力されるクロックの周波数freqは,最大で64MHz(多分).
これを2進数で表すと,26bitになる.
つまり,適切な値にしなければ,TIM2_PSC
レジスタのサイズに収まらなくなってしまう.
今回は,より汎用性の高いコードにしたかったので,TIM2_PSC
とTIM2_ARR
がどちらも16bit**に収まるようなコードにした.それを以下に示す.
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_CEN
を1にしてカウンタを有効にするだけ.
tim.cr1.modify(|_, w| w.cen().set_bit());
なのだが,1つだけ忘れていることがある.それは,TIM2
に入力されるクロックclkの周波数だ.
p.125
SPI
のときにも見たやつですね.
ただ1つ注意するのは,APB1 prescaler(ppre1)が1のとき以外は,最後に2倍されるということ.
*SPI
*のときにもほとんど同じことをしたので,説明は省略して,いきなりコードを示す.
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.
拙い文章をこんなところまで読んでいただき本当にありがとうございました.