Rustという言語でSTM32F3Discoveryというマイコンを動かし,4桁7セグメントLEDをダイナミック点灯方式で光らせるっていう記事です.
この記事を読み終わるころには完全に理解したって言ってもらえるくらい丁寧に書いたつもり.
成果物はこちら.↓
概要編では,主に4桁7セグメントLEDをダイナミック点灯方式で光らせる方法について,概要を説明しました.
今回は,実際にマイコンに書き込むプログラムのRustでの書き方について,実際のコードなども見せながらなるべく詳しく説明していきます.
筆者も組み込み初心者であるため,説明が行き届かないところもあるかもしれませんが,ご容赦ください.
誤情報だけは書かないように注意を払ってますので,ご安心ください.
はじめに
コードでは,Rustのstm32f30x
というクレートを用いていますが,本記事では,このクレートをただ利用するだけではなく,使用する構造体や関数が,どのような役目を果たしているかや,本当はどんなことをしているかなど,なるべく低レベルに降りて説明するようにしています.
多少自分に言い聞かせるように書いてる部分もあり,鬱陶しく感じることがあるかもしれません.そう感じたところはどうぞ読み飛ばしちゃってくださいませ.
では本題に入ります.
ここからはリファレンスマニュアルを横に並べて読むことをお勧めします.見るべきページは,適宜示します.
ペリフェラル
マイコンには,CPUやメモリなどが搭載されている.
ROMにプログラムを書き込めば,CPUがそれを実行してくれるわけだが,ただこれだけでは,演算を行うだけで,今回のようにLEDを光らせたり信号を出力したりということはできない.
そこで,信号の入出力を行ったりする際には,***ペリフェラル(周辺回路)***を利用する.
ペリフェラルとは,ある特定のことを行う機能を持った回路.
というよりは,そのことを行うことだけが目的の回路.
マイコンには,様々なペリフェラルが搭載されている.
例:
GPIO
,USART
,CAN
,I2C
,TIM
,etc.
これらペリフェラルの機能は,マイコンの各ピンに割り当てられている.
しかし,電力を節約するため,初期状態では,ほとんどのペリフェラルには,電源が投入されていない.つまり動作していない.
今回使うペリフェラルは*GPIOA
,GPIOD
,SPI1
,TIM2
の4つなので,まずはこれらに電源を投入*するのがファーストステップ.
GPIO
*GPIO
は,General-purpose Input/Output(汎用入出力)つまり,デジタルの入出力を行うためのペリフェラル.
今回は,概要編でも述べたように,7ピンをデジタル出力として使うので,GPIO
*ペリフェラルに電源を投入する必要がある.
stm32f3discoveryには,GPIOx
($x=A,B,...,F$)の6個の*GPIO
*ペリフェラルがあるが,機能は同じ.
今回はPA4(ポートAの4番ピン),PA5,PA7,PD1,PD2,PD3,PD4の7ピンを使うので,*GPIOA
とGPIOD
*を使う.
PD1〜PD4は7セグLEDの桁の選択,つまりコモン端子への入力に使う.
PA4,PA5,PA7はSPIバスに使う.
PD1〜PD4,PA4に割り当てたのは,配線の都合だが,PA5,PA7に割り当てるのには理由がある.
*SPI1
ペリフェラルのSCK
線とMOSI
*線として使えるピンは限られているからだ.詳しくは後程説明する.
電源の投入とリセット
*GPIOA
とGPIOD
に電源を投入し,リセットするためには,どうすればいいんだろうか.その答えは,リファレンスマニュアルのp.148〜とp.158〜*にある.
use stm32f30x::RCC;
pub fn activate_gpioa() {
let rcc = unsafe { &*RCC::ptr() }; // 1073876992
rcc.ahbenr.modify(|_, w| w.iopaen().enabled());
rcc.ahbrstr.modify(|_, w| w.ioparst().set_bit());
rcc.ahbrstr.modify(|_, w| w.ioparst().clear_bit());
}
そのまんま!
stm32f30x
クレートのおかげで,抽象化されてわかりやすくなってるわけですね.何bit目とか気にしなくても,簡単にコードが書けちゃいます!
stm32f30x
クレートの中にある構造体とか関数とかが中でどんな実装になっているかは,ライブラリ編(執筆中)で説明します.
ピンごとにGPIOペリフェラルの設定
使う7つのピン全てに*GPIO
*ペリフェラルを繋ぐが,それぞれのピンで,使う機能(モード)が違う.
push-pull outputモード
PD1〜PD4,PA4は,*push-pull output
*モードで使う.
デジタル出力には*
open drain output
*というモードもあるのですが,この違いについてはここでは述べません.知りたい方は,こちらの記事がわかりやすいです.
PD1〜PD4,PA4を,*push-pull output
モードで使うためにはどうすればいいのだろうか.答えはp.237〜*にある.
このyっていうのは,何番のピンかってこと.
それぞれ2bitでモードを指定するようだ.x番ピンの設定をするには,2*x
bit〜2*x+1
bitを0b01にすれば良さそうだ.



というわけでコードは↓のようになる.(*GPIOA
*の場合も同様)
use stm32f30x::GPIOD;
// port D
pub struct PDxL {
x: u8, // pin number
}
impl PDxL {
pub fn new(x: u8) -> PDxL {
PDxL {
x: x,
}
}
pub fn mode_push_pull_output(&self) {
let gpiod = unsafe { &*GPIOD::ptr() };
// general purpose output mode
let mode = 0b01;
let offset = 2 * self.x;
gpiod.moder.modify(|r, w| unsafe {
w.bits((r.bits() & !(0b11 << offset)) | (mode << offset))
});
// push pull output
let mode = 0b0;
let offset = self.x;
gpiod.otyper.modify(|r, w| unsafe {
w.bits(r.bits() & !(0b1 << self.x))
});
}
}
(*PAxL{ x: 1 }
*で,PA1を指します.)
ビット演算に慣れていない方はちょっとわかりづらいかもしれませんね.
*MODER
*の方を例に,少し説明します.
(r.bits() & !(0b11 << offset)) | (mode << offset)
レジスタの指定したbit以外は,変えたくないわけです.
そこで,まず指定したbitだけを0にする.
それが(r.bits() & !(0b11 << offset))
っていうところ.r.bits()
は,MODER
レジスタの全32bitです.
そのあとで,(mode << offset)
とORをとれば,指定したbitだけをmode
に変えられる.
実際にピンをLow/Highにするには*GPIOx_BSRR
*というレジスタを使う.
p.240.
x番ピンをHighにするためには下位16bitのx番目を1にし,Lowにするためには上位16bitのx番目を1にする.
コードは↓のようになる.
pub fn set_high(&self) {
unsafe { (*GPIOD::ptr()).bsrr.write(|w| w.bits(1 << self.x)) }
}
pub fn set_low(&self) {
unsafe { (*GPIOD::ptr()).bsrr.write(|w| w.bits(1 << (16 + self.x))) }
}
alternate functionモード
PA5,PA7は,*alternate function
モードで使う.
PA5,PA7をalternate function
モードで使うためにはどうすればいいのだろうか.答えはp.237〜とp.241〜*にある.
それぞれ2bitでモードを指定するようだ.x番ピンの設定をするには,
2*x
bit〜2*x+1
bitを0b10にすれば良さそうだ.


ところで,AF
にも番号があるようだが,PA5,PA7ではどのAF
を使えばいいのだろうか.
その答えはデータシート(リファレンスマニュアルとは別の資料)のp.45〜にある.
概要編でも述べたように,今回はMISO
線は使わないので,PA5とPA7でそれぞれ*AF5
*を指定すればいいということがわかる.
というわけでコードは↓のようになる.
use stm32f30x::GPIOA;
// port A
pub struct PAxL {
x: u8, // pin number
}
impl PAxL {
pub fn new(x: u8) -> PAxL {
PAxL {
x: x,
}
}
/// Configures the pin to serve as alternate function 5 (AF5)
pub fn mode_af5(&self) {
let gpioa = unsafe { &*GPIOA::ptr() };
let offset = 2 * self.x;
// alternate function mode
let mode = 0b10;
gpioa.moder.modify(|r, w| unsafe {
w.bits((r.bits() & !(0b11 << offset)) | (mode << offset))
});
let af = 5; // = 0b0101
let offset = 4 * (self.x % 8);
gpioa.afrl.modify(|r, w| unsafe {
w.bits((r.bits() & !(0b1111 << offset)) | (af << offset))
});
}
}
さっきとあまり変わらないので,コードの説明は省く.
GPIO
ペリフェラルに対して,PA5とPA7でそれぞれ*AF5
を使うよっていう設定までした.しかし,これだけではまだSPIは使えない.
AF5を使うことにしただけで,その実態はまだないわけです.
じゃあどうすればSPI*を使うことができるのだろうか.
GPIO
ペリフェラルには,SPI1
ペリフェラルが接続されている.このSPI1
ペリフェラルを有効化することで初めてPA5とPA7でSPIバスが使えるようになるのだ.
長くなってきたので,今回はここで終わり.
次回
というわけで,次回はこの*SPI
*ペリフェラルの有効化〜設定について書きます.
拙い文章ですが,ここまで読んでいただきありがとうございました.