🐡

STM32G4 ベアメタルRustでI2C通信してみた

2023/07/05に公開

ベアメタルRustでSTM32G4チップのI2C通信を実装したので、記録として残しておきます。
 STM32G4でLLやHALを使って実装した例やSTM32F4でベアメタルRustでI2Cを実装した例は世の中にありましたが、そのものはありませんでした。特にSTM32G4からはI2Cのレジスタが変更になっています。

開発環境:

  • Windows10
  • STM32G431C6T6
  • rustc 1.68.0 (2c8cc3432 2023-03-06)

今回はマイコン側がマスタの通信を実装しセンサデータを読み取っています。

I2C通信とは

スレーブとなるデバイス

スレーブとなるデバイスとして今回はBNO055を使用しました。
使用したモジュールはこちらの秋月電子で販売されている
BNO055使用 9軸センサーフュージョンモジュールキットです。
I2CまたはUARTで通信できます。

接続

詳細は省略しますが、今回は
マイコン側のI2C3のピンをデバイスのSDA, SCLにそれぞれ接続しています。
また、電圧はマイコンと同じ3.3Vとしています。

I2Cの通信方法

I2Cでの通信方法はこちらのBOSCHのページからたどることのできるデータシートに記載されています。

最初にスレーブアドレスと読み込み始めるレジスタを送った後、
再スタートし、スレーブアドレスを送るとそこから先のデータが順次送られてきます。

使用しているRustのクレート Cargo.toml

今回のプログラムではこのあたりのプログラムを使用しています

Cargo.toml
[dependencies]
embedded-hal = "0.2"
nb = "1"
cortex-m = "0.6.0"
cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0"

[dependencies.stm32g4]
features = ["stm32g431", "rt"]
version = "0.14.0"

STM32G4でのプログラム

注意事項

  • マスタ側が返すACKはCR2内のNBYTEの値に対応するので、読み込みたいデータ数の数だけREADの開始前に設定しておく必要がある。このByte数はスレーブアドレスの送信は含まれない
  • 送信時も同様に送信するデータ数をWriteの開始前に設定しておく必要がある。
  • 各バイトの送信完了はISRのTXISを確認して待つ
  • 受信はISRのRXNEにデータが入っているかで判定する
  • AUTOENDを設定する場合TCRレジスタで通信終了を確認する。
  • AUTOENDを設定しない場合、ISRのTCレジスタを確認することで通信終了を確認する。今回実装したのはこちら

以下のプログラムでは
BNO055 CHIP IDのレジスタを読み値が0xA0(固定値)になっていることを確認し、通信できたと判断しています。

I2C通信を行うコードは以下の通りです。
この処理の前にクロックの初期化を行っていますが、参考の記事などを参照していただけると幸いです。
今回はI2Cに供給されるクロックが140MHzとなるように設定します。

// GPIOポートの電源投入(クロックの有効化)
perip.RCC.ahb2enr.modify(|_, w| w.gpioaen().set_bit());
perip.RCC.ahb2enr.modify(|_, w| w.gpioben().set_bit());

// gpioモード変更
// I2C
let gpioa = &perip.GPIOA;
gpioa.otyper.modify(|_, w| w.ot8().open_drain());
gpioa.ospeedr.modify(|_, w| w.ospeedr8().very_high_speed());
gpioa.moder.modify(|_, w| w.moder8().alternate());
gpioa.afrh.modify(|_, w| w.afrh8().af2());
let gpiob = &perip.GPIOB;
gpiob.otyper.modify(|_, w| w.ot5().open_drain());
gpiob.ospeedr.modify(|_, w| w.ospeedr5().very_high_speed());
gpiob.moder.modify(|_, w| w.moder5().alternate());
gpiob.afrl.modify(|_, w| w.afrl5().af8());

// I2Cのクロック設定
perip.RCC.ccipr.modify(|_, w| w.i2c3sel().pclk());
perip.RCC.apb1enr1.modify(|_, w| w.i2c3en().enabled());

// I2C設定
let i2c = &perip.I2C3;
i2c.cr1.modify(|_, w| w.pe().clear_bit() );

i2c.cr1.modify(|_, w| w.anfoff().disabled() );
i2c.cr1.modify(|_, w| w.dnf().no_filter() );
// 140MHz, presc:14-1->10MHz, t=100ns
i2c.timingr.modify(|_, w| w.presc().bits(140-1) );
i2c.timingr.modify(|_, w| w.scll().bits(50-1) ); // t_SCLL 5000ns
i2c.timingr.modify(|_, w| w.sclh().bits(40-1) ); // t_SCLH 4000ns
i2c.timingr.modify(|_, w| w.sdadel().bits(5) ); // 500ns
i2c.timingr.modify(|_, w| w.scldel().bits(12-1) );   // 1200ns

i2c.cr1.modify(|_, w| w.nostretch().disabled() );

// Peripheral enable
i2c.cr1.modify(|_, w| w.pe().set_bit() );

// 読み込みたいアドレスの送信
i2c.cr2.modify(|_, w| w.nbytes().bits(1) );
// Address
i2c.cr2.modify(|_, w| w.sadd().bits(0x28 << 1) );
i2c.cr2.modify(|_, w| w.add10().bit7());
// Transfer direction
i2c.cr2.modify(|_, w| w.rd_wrn().write() );
i2c.cr2.modify(|_, w| w.autoend().clear_bit() );
i2c.cr2.modify(|_, w| w.reload().clear_bit() );

while i2c.cr2.read().start().bit_is_set() {}
i2c.cr2.modify(|_, w| w.start().set_bit() );
while i2c.isr.read().txis().bit_is_clear() {
}
i2c.txdr.modify(|_, w| w.txdata().bits(0x00) ); // BNO055 CHIP IDのアドレスは0x00

// データの読み取り
i2c.cr2.modify(|_, w| w.nbytes().bits(1) );
// Transfer direction
i2c.cr2.modify(|_, w| w.rd_wrn().read() );
while i2c.cr2.read().start().bit_is_set() {}
i2c.cr2.modify(|_, w| w.start().set_bit() );
// hprintln!("start read").unwrap();

while i2c.isr.read().rxne().is_empty() {
}
let rd = i2c.rxdr.read().rxdata().bits();
hprintln!("rd: {}", rd).unwrap();

while i2c.isr.read().tc().bit_is_clear() {
    hprintln!("is_busy: {}", i2c.isr.read().busy().is_busy()).unwrap();
    hprintln!("nbytes: {}", i2c.cr2.read().nbytes().bits()).unwrap();
}
i2c.cr2.modify(|_, w| w.stop().stop() );
hprintln!("stop").unwrap();
hprintln!("loop").unwrap();

その他

今回はBNO055を使用した例を記載しましたが、STSPIN32G4の内部通信でも似たような設定で使用できることを確認しています。

参考文献

Discussion