6
2

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](実装編2)〜SPIペリフェラル〜

Last updated at Posted at 2020-05-05

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

成果物はこちら.↓

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

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

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

はじめに

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

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

SPI1

SPIを用いて通信するため,SPI1ペリフェラルを使う.
前回までで,
GPIOペリフェラルに対して,PA5PA7
でそれぞれ*AF5を使うよっていう設定までした.しかし,これだけではまだSPIは使えない.
AF5を使うことにしただけで,その実態はまだないわけです.
じゃあどうすれば
SPI*を使うことができるのだろうか.

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

電源の投入とリセット

じゃあというわけでまずは電源とリセット.
はい,教科書p.150〜
電源
スクリーンショット 2020-05-04 17.06.05.png
スクリーンショット 2020-05-04 17.06.40.png
スクリーンショット 2020-05-04 17.07.24.png
リセット
スクリーンショット 2020-05-04 17.12.24.png
スクリーンショット 2020-05-04 17.12.51.png
スクリーンショット 2020-05-04 17.13.33.png
*GPIO*のときも似たようなことをしたので,説明は省く.
コードはこちら↓

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

    rcc.apb2enr.modify(|_, w| w.spi1en().enabled());
    rcc.apb2rstr.modify(|_, w| w.spi1rst().set_bit());
    rcc.apb2rstr.modify(|_, w| w.spi1rst().clear_bit());

SPI1ペリフェラルの設定

*GPIOペリフェラルのときはピンごとに設定していたが,SPI1*ペリフェラルの場合は複数のピンで1つの機能を成すので,それぞれのピンで別々に設定するということはしない.

どんな設定にすればいいかは概要編でほとんど話したので,端折り気味で説明する.

*SPI1*ペリフェラルの設定を行うレジスタは2つある.

SPI1_CR1(SPI control register 1)

1つ目は*SPI1_CR1.(p.998〜
スクリーンショット 2020-05-04 22.20.08.png
スクリーンショット 2020-05-04 22.20.44.png
このレジスタで設定すべきなのは,
下から順に
CPHACPOLMSTRBRSPELSBFIRSTSSISSMBIDIMODE*の9個.
順番に説明していきます.

CPHACPOLについては概要編で話した通り0.↓の画像で思い出せない人は概要編に戻ってみてください.

MSTRは,このマイコンをSPIバスのマスターとして使うかどうかってことなので,もちろん1

*BR*は少しややこしいので後回し.

SPESPI1ペリフェラルを有効化するかどうかってことなので,もちろん1

LSBFIRSTは,8bitのデータを,先頭から送るか末尾から送るか.これはプログラムをこの設定に合わせればいい話なのでどっちでもいいが,どっちかには決めとかないとプログラムが書けないので好みで0MSB first)か1LSB first)にする.僕のプログラムではMSB firstになっている.

SSIは,SPIバスにおいて,マスターモードのときのSS線の出力を設定しておけばいい?はず?
次に示す,
SSM1
のときのみしか機能しない.
(すみません,このbitに関してはあまり理解していません.)
1にします.

SSMは,SS線の出力の制御を,自分で別に書く場合は1
今回は1にする.詳しくはあとで触れる.

BIDIMODEっていうのは,SPIバスで用いる通信方式を選択するためのbit.
実は
SPIバスには一般的な全二重3線式の他にも,半二重とかいう方式もあるらしい.詳しく知りたい方はこちらの記事がわかりやすかった.
今回使うのは普通のやつなので,全二重3線式
というわけでこちらは
0

最後に,後回しにしていた*BR
これは,つまりは
ボーレートのことで,簡単にいうなら通信速度.
単位は
bpsで,1秒間に何bitの情報を送れるかということを表す.
SPI通信では,クロックの立ち上がりでデータがサンプルされるため,クロックの周波数と,この
ボーレート*の値は一致する.
がしかし,どんな周波数でも設定できるわけではない.
設定できる周波数は以下のようになっている.
スクリーンショット 2020-05-05 10.24.05.png

pclk1の分周で設定するようだ.

通信相手の74HC595は,SCKに設定できるのは,最大で50MHz程度まで.
そもそもこのマイコンではpclk1の最大値が64MHzなので,BRには32MHzまでしか設定できない.なのでこれは気にしなくて良さそう.

話を戻して,仮に1MHzに設定したいとしたら,どうすればいいだろうか.
まず,BRには,pclk1の分周という形で設定するため,丁度1MHzに設定できるかどうかはpclk1の周波数次第ということになる.
pclk1の値さえわかれば,*BR*の値は,$$\frac{pclk1}{1_\rm{MHz}}$$の値によって決められる.
コードだとこんな感じ↓だ.

spi.rs
    let freq = 1000_000; // = 1MHz

    let br = match pclk1 / freq {
        0 => unreachable!(),
        1..=2 => 0b000,
        3..=5 => 0b001,
        6..=11 => 0b010,
        12..=23 => 0b011,
        24..=39 => 0b100,
        40..=95 => 0b101,
        96..=191 => 0b110,
        _ => 0b111,
    };

さて,残る疑問はただ1つ.
pclk1の値はどうやって得られるのか.
教科書p.126
スクリーンショット 2020-05-05 12.03.04.png

ふむふむ,8MHzHSIとやらを$\frac{1}{2}$倍してそれをPLLMUL倍したものがSYSCLKで.
それをAHB prescalerhpre)で割ったものがHCLK
んでそれをAPB1 prescalerppre1)で割ったものがpclk1か!

ところで,このpllmulとかhpreとかppre1ってどこからきてるんだろうか.
教科書p.138
スクリーンショット 2020-05-05 12.32.45.png
スクリーンショット 2020-05-05 12.33.25.png

スクリーンショット 2020-05-05 12.35.13.pngスクリーンショット 2020-05-05 12.36.13.png
何度も同じことをやってるので説明は省略します. コードはこんな感じ↓になる.
spi.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;

*SPI1_CR1*についての説明はこれで終わり.
コードはこんな感じ↓.

spi.rs
    spi.cr1.write(|w| unsafe {
        w.cpha()
            .bit(false)
            .cpol()
            .bit(false)
            .mstr()
            .set_bit()
            .br()
            .bits(br)
            .spe()
            .set_bit()
            .lsbfirst()
            .clear_bit()
            .ssi()
            .set_bit()
            .ssm()
            .set_bit()
            .bidimode()
            .clear_bit()
    });

SPI1_CR2(SPI control register 2)

2つ目は*SPI1_CR2.(p.998〜
スクリーンショット 2020-05-05 12.48.59.png
スクリーンショット 2020-05-05 12.50.05.png
このレジスタで設定すべきなのは,
下から順に
SSOEDS*の2個.

スクリーンショット 2020-05-05 12.57.08.pngスクリーンショット 2020-05-05 12.58.20.png

順番に説明していきます.

SSOEは,SS線の出力を有効にするかどうか.
今回は
SS線の出力の制御は,自分で別に書くので無効にする.
というわけで
0

*DS*は,データサイズ.
今回は8bitなので,0b0111

*SPI1_CR2*についての説明はこれで終わり.
コードはこんな感じ↓.

spi.rs
    spi.cr2.write(|w| unsafe {
        w.ds().bits(0b0111).ssoe().clear_bit()
    });

SPI1ペリフェラルを使ってデータを送信

設定が終わったので,あとは実際にシフトレジスタにデータを送信するだけです.
p.1004
スクリーンショット 2020-05-05 16.46.01.png
スクリーンショット 2020-05-05 16.46.46.png
この*SPI1_DR*というレジスタに送信したいデータを書き込むだけ.

でも忘れてはいけないのが,SS線(シフトレジスタのRCK).
この信号は自分で別に書くことにしていたので,それも忘れずに.
前回PA4に割り当てた*GPIOpush-pull outputSS*線の制御を行う.
なのでコードはこんな感じ↓.

const NUMBERS: [u8; 11] = [
    0b11101011, // 0
    0b00101000, // 1
    0b10110011, // 2
    0b10111010, // 3
    0b01111000, // 4
    0b11011010, // 5
    0b11011011, // 6
    0b11101000, // 7
    0b11111011, // 8
    0b11111010, // 9
    0b00000000  // OFF
];

... // 省略

    pub fn display_num(&self, number: usize) {
        self.rck.set_low();
        unsafe { ptr::write_volatile(&(*SPI1::ptr()).dr as *const _ as *mut u8, NUMBERS[number]) };
        self.rck.set_high();
    }

これで*SPI1*ペリフェラルについての説明は終わりです.

あとは,7セグの,表示する桁とLEDを一定時間ごとに切り替えるだけです!
そのために,今度は*TIM2っていうペリフェラル*を使います.

次回

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

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?