STM32 Rust halでクロックを設定してみる

  • 2022.04.17
  • RCC
STM32 Rust halでクロックを設定してみる

前回、halのバージョンを0.9から0.13に上げてUARTを動かしてみました。
halを使ったコードをいくつか紹介してきましたが、クロックは初期値のまま使ってきました。

今回は、前回のコードを少し変更してクロックを設定してみます。

開発環境とhalのバージョンは以下の通りです。

PC:Windows10 OS
Board:STM32Nucleo-F401RE
デバイス:STM32F401RE
エディタ:VSCode
言語:Rust
stm32f4xx-hal:Version 0.13

環境構築から始められる方は、前回の記事を参考になさってください。

今回はソースコードとその概要説明を書いておきます。

ソースコード

#![no_std]
#![no_main]

use embedded_hal::prelude::_embedded_hal_serial_Write;
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use cortex_m_rt::entry;
use stm32f4xx_hal::serial::{Serial, config};
use stm32f4xx_hal::gpio::{GpioExt};
use stm32f4xx_hal::rcc::RccExt;
use stm32f4xx_hal::time::Bps;

use embedded_hal::serial::Read;
use core::fmt::Write;   // (1)write!()マクロを使えるようにする
use stm32lib::uart::ErrorDetect;    // (2)追加するトレイトを使えるようにする

use stm32f4xx_hal::prelude::*;  // MHz()

use stm32f4xx_hal::pac::RCC;

const EXTERNAL_CLOCK: u16 = 8_u16;

#[entry]
fn main() -> ! {

    let dp = stm32f4xx_hal::pac::Peripherals::take().unwrap();
    let gpioa = dp.GPIOA.split();   // GPIOAのclockも有効にしてくれる (AHBENRレジスタ)
    let bps = Bps(115_200_u32); // (3)通信速度
    let seri_config = config::Config {  // (4)通信パラメーターの設定
        baudrate: bps,
        wordlength: config::WordLength::DataBits8,  // 実際には7ビット
        parity: config::Parity::ParityEven,
        stopbits: config::StopBits::STOP1,
        dma: config::DmaConfig::None,
    };

    let rcc = dp.RCC.constrain();

//    let clks = rcc.cfgr.freeze(); // 初期値でクロックを生成するコード

    let clks = rcc
        .cfgr
        .use_hse(8.MHz())   // 外部クロックを使う
        .bypass_hse_oscillator()    // 矩形波を使う(水晶振動子でなく発信器を使う)
        .sysclk(84.MHz())
        .pclk1(42.MHz())
        .pclk2(84.MHz())
        .freeze();

    let _hsebyp = unsafe { (*RCC::ptr()).cr.read().hsebyp().is_bypassed() };
    assert!(_hsebyp);

    assert!(clks.sysclk() == 84.MHz::<1_u32, 1_u32>());
    assert!(clks.pclk2() == 84.MHz::<1_u32, 1_u32>());
    assert!(clks.pclk1() == 42.MHz::<1_u32, 1_u32>());

    let mut serial = Serial::new(
        dp.USART2,
        (gpioa.pa2, gpioa.pa3),
        seri_config,
        &clks,
    ).unwrap(); // (5)Serial構造体の生成

    let sysclk = get_sysclk();    // 設定された M, N, P からクロックを計算してみる
    write!(serial, "sysclk={}MHz.\r\n", sysclk).unwrap();

    loop {
        while !serial.is_rx_not_empty() {}
        if serial.is_pe() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nParity error {}", "detected.\r\n").unwrap();
        }
        else if serial.is_fe() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nFraming error {}", "detected.\r\n").unwrap();
        }
        else if serial.is_ore() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nOver run error {}", "detected.\r\n").unwrap();
        }
        else if let Ok(c) = serial.read() {
            while !serial.is_tx_empty() {}
            serial.write(c).unwrap();
        }
    }
}

fn get_sysclk() -> u16 {

    let m = unsafe { (*RCC::ptr()).pllcfgr.read().pllm().bits() };
    let n = unsafe { (*RCC::ptr()).pllcfgr.read().plln().bits() };
    let p = unsafe { (*RCC::ptr()).pllcfgr.read().pllp().bits() };

    let cp = (p + 1) * 2;   // Pだけは変換が必要

    EXTERNAL_CLOCK * n / (m as u16 * cp as u16)    // 8M * N * 1/(M * P) = system clock

}

コード概要

stm32f4xx_hal::rcc::CFGR 構造体があります。

クレートのドキュメントは こちら を参照してください。

use_hse()メソッドで外部クロックを使う設定にします。
このメソッドはSelfが返るので、その戻り値を使って更に次の設定を行っています。

bypass_hse_oscillator()メソッドは水晶発振器を使う場合(矩形波を入力する場合)に使います。

手持ちの基板では水晶振動子ではなくデバッグ用のマイコンからクロックを供給しているので、このメソッドを使います。

このメソッドを使わない場合にどうなるのか試したところ、なんと動いてしまいました。

これについては勉強不足で、理解できていません。

(手持ちの基板には水晶振動子が実装されていないので、そちらの動作確認は行っていません)

その後、sysclk(),pclk1(),pclk2()メソッドでシステム、ペリフェラル1、ペリフェラル2用のクロックを設定します。

最後に freeze()メソッドを呼びます。
このメソッドは Clocks が返るので、clksに束縛して Serial::new()の引数として使います。

get_sysclk()関数で、設定された M, N, P の値を読んで システムクロックを計算して、loop()前にUARTで出力してみました。

sysclk=84MHz. と表示されれば正しくクロックが設定されていると考えてよいでしょう。

ビルドエラー対策

原因が良くわかっていないのですが、こちらの環境では stm32f401xx-hal のバージョンを0.13に上げてビルドするとエラーが出るようになってしまいました。
たまたま バージョン0.13 でうまく動作した stm32f401-i2c3 プロジェクトがあるので、そのプロジェクトの Cargo.lock ファイルを本プロジェクトのルートディレクトリにコピーしたところエラーが解消されました。

クレート間の相性があるようです。
プロジェクトを構成する側に問題があるように思いますが、ひとまずこれを回避策としておきます。
ご自身で書いたソースコードに関係のないエラーが出る場合には試してみてください。

(Cargo.lockはテキストファイルになっていて、プロジェクト名ごとコピーされます。エラーは出ませんが気になる方はプロジェクト名をコピー元の stm32f401-xxxx から stm32f401-uart4 に変更しておくと良いでしょう)

PC側の設定

PCのソフトには Tera Term を使いますのでダウンロードしてインストールしておきます。

新しい接続で、シリアルを選択し、Virtual COM Port のあるポートを選択します。

次に、設定-シリアルポートから通信パラメーターを以下のとおりに設定します。

続いて、設定-端末でローカルエコーにチェックを入れます。
これで送信した文字が表示されます。

USBケーブルでPCとボートを接続しておきます。

動作させてみる

それでは F5キーでプログラムを起動した後、entry で停止したらもう一度F5キーを押してプログラムを動作させてみます。

デバッグモードは OpenOCD に設定する必要があるので、エラーが出る場合には確認してみてください。

起動後 sysclk=84MHz. と表示されます。

PCのTera Termで何かキーを押し、ボートから戻ってくれば成功です。
正常に戻ってくれば、2文字ずつTera Termに表示されます。

いかがでしたか?
皆さんの環境では、うまく動作しましたか?

今回のプロジェクトを stm32f401-uart4 におきましたので、よろしければ参考になさってください。

お疲れさまでした。

RCCカテゴリの最新記事