21
19

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

RustでSTM32ベアメタル [4桁7セグメントLED](実装編1)〜ペリフェラル,GPIO〜

Last updated at Posted at 2020-05-04

Rustという言語でSTM32F3Discoveryというマイコンを動かし,4桁7セグメントLEDダイナミック点灯方式で光らせるっていう記事です.
この記事を読み終わるころには完全に理解したって言ってもらえるくらい丁寧に書いたつもり.

成果物はこちら.↓

概要編では,主に4桁7セグメントLEDダイナミック点灯方式で光らせる方法について,概要を説明しました.

今回は,実際にマイコンに書き込むプログラムのRustでの書き方について,実際のコードなども見せながらなるべく詳しく説明していきます.

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

はじめに

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

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

ペリフェラル

マイコンには,CPUメモリなどが搭載されている.
ROMにプログラムを書き込めば,CPUがそれを実行してくれるわけだが,ただこれだけでは,演算を行うだけで,今回のようにLEDを光らせたり信号を出力したりということはできない.

そこで,信号の入出力を行ったりする際には,***ペリフェラル(周辺回路)***を利用する.
ペリフェラルとは,ある特定のことを行う機能を持った回路.
というよりは,そのことを行うことだけが目的の回路.

マイコンには,様々なペリフェラルが搭載されている.

例: GPIOUSARTCANI2CTIM,etc.

これらペリフェラルの機能は,マイコンの各ピンに割り当てられている.
しかし,電力を節約するため,初期状態では,ほとんどのペリフェラルには,電源が投入されていない.つまり動作していない.

今回使うペリフェラルは*GPIOAGPIODSPI1TIM2の4つなので,まずはこれらに電源を投入*するのがファーストステップ.

GPIO

*GPIOは,General-purpose Input/Output(汎用入出力)つまり,デジタルの入出力を行うためのペリフェラル
今回は,概要編でも述べたように,7ピンをデジタル出力として使うので,
GPIO*ペリフェラルに電源を投入する必要がある.

stm32f3discoveryには,GPIOx($x=A,B,...,F$)の6個の*GPIO*ペリフェラルがあるが,機能は同じ.

今回はPA4(ポートAの4番ピン),PA5PA7PD1PD2PD3PD4の7ピンを使うので,*GPIOAGPIOD*を使う.

PD1PD4は7セグLEDの桁の選択,つまりコモン端子への入力に使う.
PA4PA5PA7SPIバスに使う.
PD1〜PD4,PA4に割り当てたのは,配線の都合だが,PA5PA7に割り当てるのには理由がある.
*SPI1ペリフェラルSCK線とMOSI*線として使えるピンは限られているからだ.詳しくは後程説明する.

電源の投入とリセット

*GPIOAGPIODに電源を投入し,リセットするためには,どうすればいいんだろうか.その答えは,リファレンスマニュアルp.148〜p.158〜*にある.

`電源` スクリーンショット 2020-05-04 13.02.47.png スクリーンショット 2020-05-04 13.02.11.png スクリーンショット 2020-05-04 12.11.01.pngスクリーンショット 2020-05-04 12.17.55.png `リセット` スクリーンショット 2020-05-04 13.03.52.png スクリーンショット 2020-05-04 13.04.34.png スクリーンショット 2020-05-04 12.19.46.pngスクリーンショット 2020-05-04 12.20.18.png
*`GPIOA`*ペリフェラルを使うためには,*`RCC`*ペリフェラルの*`AHBENR`*っていうレジスタの,17bit目を*1*にし,*`AHBRSTR`*っていうレジスタの,17bit目を*1*にすればいいというわけですね. (リセットの方は*0*に戻さないとリセットされ続ける.) コードはこちら↓
gpio.rs
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〜*にある.
スクリーンショット 2020-05-04 13.44.38.png
スクリーンショット 2020-05-04 13.48.38.png

このyっていうのは,何番のピンかってこと.
それぞれ2bitでモードを指定するようだ.x番ピンの設定をするには,2*xbit〜2*x+1bitを0b01にすれば良さそうだ.

スクリーンショット 2020-05-04 13.46.03.png スクリーンショット 2020-05-04 13.49.17.png スクリーンショット 2020-05-04 13.50.04.png >それぞれ1bitでモードを指定するようだ.*x*番ピンの設定をするには,`x`bit目を***0b0***にすれば良さそうだ.

というわけでコードは↓のようになる.(*GPIOA*の場合も同様)

pin.rs
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
スクリーンショット 2020-05-05 16.59.46.png
スクリーンショット 2020-05-05 17.01.39.png

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モード

PA5PA7は,*alternate functionモードで使う.
PA5PA7
alternate functionモードで使うためにはどうすればいいのだろうか.答えはp.237〜p.241〜*にある.
スクリーンショット 2020-05-04 13.44.38.png
スクリーンショット 2020-05-04 13.48.38.png

それぞれ2bitでモードを指定するようだ.x番ピンの設定をするには,2*xbit〜2*x+1bitを0b10にすれば良さそうだ.

スクリーンショット 2020-05-04 15.23.05.png スクリーンショット 2020-05-04 15.25.45.png >それぞれ4bitでモードを指定するようだ.*x*番ピンの設定をするには,`4*x`bit〜`4*x+3`bitをいじれば良さそうだ. でもよく見ると*7*番ピンまでの設定しかできない感じになっているぞ. 実は,*0〜7*番ピンの*`AF`*の設定はこの*`AFRL`*レジスタで行うが,*8〜15*番ピンの*`AF`*の設定は,*`AFRH`*レジスタで行うようになっている. レジスタのサイズは32bitで,1つのピンの設定に4bit使うから,2つのレジスタに分けてあるのだ.

ところで,AFにも番号があるようだが,PA5PA7ではどのAFを使えばいいのだろうか.
その答えはデータシートリファレンスマニュアルとは別の資料)の
p.45〜にある.
スクリーンショット 2020-05-04 16.04.30.png
スクリーンショット 2020-05-04 15.58.50.png
概要編でも述べたように,今回は
MISO線は使わないので,PA5PA7
でそれぞれ*AF5*を指定すればいいということがわかる.

というわけでコードは↓のようになる.

shift.rs
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ペリフェラルに対して,PA5PA7でそれぞれ*AF5を使うよっていう設定までした.しかし,これだけではまだSPIは使えない.
AF5を使うことにしただけで,その実態はまだないわけです.
じゃあどうすれば
SPI*を使うことができるのだろうか.

GPIOペリフェラルには,SPI1ペリフェラルが接続されている.このSPI1ペリフェラルを有効化することで初めてPA5PA7SPIバスが使えるようになるのだ.

長くなってきたので,今回はここで終わり.

次回

というわけで,次回はこの*SPI*ペリフェラルの有効化〜設定について書きます.
拙い文章ですが,ここまで読んでいただきありがとうございました.

21
19
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
21
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?