STM32 Rust halでI2Cを使ってみる

  • 2022.04.08
  • I2C
STM32 Rust halでI2Cを使ってみる

今回はhalを使ってI2Cを操作してみます。

前回ベアメタルでEEPROMと接続しましたが、今回はhalを使ってみます。
I2C、接続するデバイス、接続方法などについては 前回の記事 を参照してください。

開発環境は以下の通りです。

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

ボードの情報は こちら からご覧いただけます。
環境構築については こちら をご覧になってください。

halとは

halとは Hardware Abstraction Layer の略で日本語では「ハードウエァ抽象化層」です。
halはハードウェアごとの仕様の違いを吸収し、ユーザーが同じ方法で取り扱えるようにする役割をもつ階層になります。

ベアメタルでレジスタに書く方法に比べると抽象化されているので、コードを書きやすく見やすいので保守の面でも良さそうです。

embedded_hal と stm32f4xx_hal

組み込み用のhalクレートして embedded_hal が基礎としてあり、その上に各メーカー等の hal があります。
こちらで今使っているマイコンに対しては stm32f4xx_hal を使います。

プロジェクトをつくる

コマンドプロンプトを起動し、cargo generate と git を使ってSTM32F401用のプロジェクトを作成します。

cargo generate –git https://github.com/rust-embedded/cortex-m-quickstart.git と入力しプロジェクト名を聞いてくるので stm32f401-i2c2 と入力します。

cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart.git
 Unable to load config file: C:\Users\xxxxx\.cargo\cargo-general.toml
 Project Name : stm32f401-i2c2

C:\Users\xxxxx\stm32f401-i2c2 というフォルダができていることを確認します。

xxxxx は皆さまのユーザー名です。

VSCodeを起動する

以下のようにコマンドを入力することで、stm32f401-i2c2をカレントディレクトリとしてエディタ(VSCode)が起動します。

cd stm32f401-i2c2
code .

C:\Users\xxxxx>cd stm32f401-i2c2
C:\Users\xxxxx\stm32f401-i2c2>code .

config.tomlの編集

gdbの指定と Cortex-M4F をターゲットに指定します。

ツリーの >.cargo の >部分をクリックすると V .cargo となり、そのツリー下に config.toml というファイルが見えるのでこれをクリックして開きます。

(1)こちらの環境では 8行目に # runner = “arm-none-eabi-gdb -q -x openocd.gdb” があるので、先頭の # をはずします。
(2)こちらの環境では35行目に target = “thumbv7m-none-eabi” # Cortex-M3 があるので、先頭に # をつけます。
前後の行に合わせて # target = … のように記述しておきます。
(3)こちらの環境では37行目に # target = “thumbv7me-none-eabihf” # Cortex-M4F and Cortex-M7F (with FPU) があるので、先頭の # をはずします。

# のある行はコメント(無効)になります。

編集が終わったら Ctrl + s で保存しておきます。

launch.jsonの編集

ここではデバイスの指定を行います。
ツリーの >.vscode の >部分をクリックすると V .vscode となり、そのツリー下に launch.json というファイルが見えるのでこれをクリックして開きます。

“configurations”: の中に {},{} で2つの項目が区切られています。
上の方は “name” が “Debug (QEMU)” となっていて、下の “name” が “Debug (OpenOCD)” です。
QEMUはエミュレーターで、今回は OpenOCD による Debug を行いますので、下の {} 内だけを編集します。

(1)こちらの環境では35行目に #device”: “STM32F303VCT6” とあるので “STM32F401RET6” に変更します。
(2)こちらの環境では38行目に “target/stm32f3x.cfg” とあるので “target/stm32f4x.cfg” に変更します。
(3)こちらの環境では40行目に “svdFile”: “${workspaceRoot}/.vscode/STM32F303.svd” とあるので最後の 303 を 401 に変更します。

編集が終わったら Ctrl + s で保存しておきます。

Cargo.tomlの編集

ツリーの下の方に Cargo.toml があります。

23~25行を以下のように編集します。
dependencies は依存関係の意味で、こういうクレートの、このバージョンのものを使いますよ とRustに教えてあげます。

今回は hal を使うので stm32f4xx_halクレートを指定します。
新しいバージョンではエラーが出ることがあるので今回は “0.9” を選択しました。

[dependencies.stm32f4xx-hal]
features = ["stm32f401", "rt"]
version = "0.9"

編集が終わったら Ctrl + s で保存しておきます。

memory.xの編集

ツリーの下の方に memory.x があります。
デバイスによってFlashメモリーとRAMの容量が異なるので、ここで指定します。

6, 7行目を以下のように編集します。

  FLASH : ORIGIN = 0x08000000, LENGTH = 512K
  RAM : ORIGIN = 0x20000000, LENGTH = 96K

編集が終わったら Ctrl + s で保存しておきます。

svdファイル

svdファイルはデバッグに必要なファイルです。

svdファイルは こちら からダウンロードできます。

解凍して、data\STMicroの中にある STM32F401.svd を C:\Users\xxxxx\stm32f401-i2c2\.vscode に保存します。

ソースコード

#![no_std]
#![no_main]

use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use cortex_m_rt::entry;
use stm32f4xx_hal::gpio::{GpioExt};
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::rcc::RccExt;
use stm32f4xx_hal::time;
use stm32f4xx_hal::i2c::*;

const DEVICE_ADRS_FOR_WRITE: u8 = 0xa0;

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

    let dp = stm32f4xx_hal::pac::Peripherals::take().unwrap();
    let gpiob = dp.GPIOB.split();   // GPIOBのclockも有効にしてくれる (AHBENRレジスタ)
    let scl = gpiob.pb8.into_alternate_af4_open_drain();   // afrh, modeレジスタを設定してくれる (I2Cはオープンドレイン指定する)
    let sda = gpiob.pb9.into_alternate_af4_open_drain();   // afrh, modeレジスタを設定してくれる (I2Cはオープンドレイン指定する)

    let rcc = dp.RCC.constrain();       // RCCの取得
    let clks = rcc.cfgr.freeze();   // 各clockの設定
    let kilohz = time::KiloHertz(100u32);  // I2Cのクロック 100kHz

    let mut i2c = I2c::new(
        dp.I2C1,
        (scl, sda),
        kilohz,
        clks,
    );  // I2Cの生成

    let wbuf = [0, 1, 18, 52, 86];
    // 0, 1 : 書き込むメモリーの先頭アドレス (0x0001 番地)
    // 18, 52, 86 はメモリーに書く値(数値に意味はない)

    let adrs = DEVICE_ADRS_FOR_WRITE >> 1;  // 中で左シフトされるので右シフトしておく
    let _ = i2c.write(adrs, &wbuf);

    let mut rbuf = [0; 3];

    let _ = i2c.write_read(adrs, &wbuf[..2], &mut rbuf);    // 第2引数はスライスを使って最初の2バイトのみ書くように指示する

    loop {
    }
}

コード概要

要所にコメントを入れたので参考にしてください。
メモリーの1番地から3バイト適当な値を書いて、読み出してみました。
rbufの配列に読んだ値が入ります。

ベアメタルで書いたコードより、遥かに少ない量で見通しも良いですね。

動作させてみる

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

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

こちらでは書いた値3バイトを正しく読むことができました。

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

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

お疲れさまでした。

I2Cカテゴリの最新記事