皆さん こんにちは。
ポンコツRustacean の moon です。
今回はADT7410を実装している温度センサーモジュールとラズピコをI2Cでつないでみました。
この記事は開発環境を構築することを前提にしています。
環境構築について知りたい方は こちらの記事 をご覧になってください。
このサイトは 書籍 基礎から学ぶ組込みRust を参考にしています。
I2Cとは
主にプリント基板上でマイコンとデバイスを接続するのにつかわれる通信方式です。
・フィリップス(現NXP)セミコンダクターズが開発した通信方式
・SCLとSDAの2本の通信ラインを使って双方向で通信が可能
・通信速度は100k, 1Mbits/secなどいくつか選択肢がある
・マスタとスレーブが存在し、マルチマスタでの構築も可能
I2Cの詳しい仕様については こちら を見て頂ければと思います。
I2Cモジュール
I2Cの温度センサーモジュールは こちら を使いました。
デバイスの資料は ADT7410 からご確認ください。
接続
下表の通りに接続します。
| 電源・信号 | 温度センサーモジュール | ラズピコ |
|---|---|---|
| 電源(3.3V) | VDD | 36番ピン |
| SCL | SCL | 22番ピン(GPIO16) I2C0 SCL |
| SDA | SDA | 21番ピン(GPIO17) I2C0 SDA |
| GND | GND | 38番ピン |
温度センサーモジュールのジャンパーは以下のように設定しました。
J1:短絡(SCLを10kでプルアップ)
J2:短絡(SDAを10kでプルアップ)
J3:開放(デバイスのアドレスピンA0を10kでプルダウン)
J4:開放(デバイスのアドレスピンA0を10kでプルダウン)
J3とJ4を開放することによりデバイスのアドレスは 0x48 になります。
詳しくはモジュールのマニュアルをご覧になってください。
パッケージの複製を作成する
まず私のGitHubリポジトリにあるRustのひな形をベースに別名でパッケージをつくります。
ひな形はデバッグ環境用のものですから、その複製もデバッグできるようになります。
新しいパッケージ名を rp2040-i2c として git clone します。
xxxxは皆さんのユーザー名です。
カレントディレクトリを rp2040-i2c に移し、VSCodeを起動します。
C:\Users\xxxx>cd pprp
C:\Users\xxxx\pprp>git clone https://github.com/moons3925/rp2040.git rp2040-i2c
C:\Users\xxxx\pprp>cd rp2040-i2c
C:\Users\xxxx\pprp\rp2040-i2c>code .
launch.jsonを編集する
executable: の名称を rp2040 から rp2040-i2c に変更します。
"executable": "./target/thumbv6m-none-eabi/debug/rp2040-i2c",
Cargo.tomlを編集する
[dependencies]を以下の通りに編集します。
[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.3"
embedded-hal = "0.2.7"
panic-halt = "0.2.0"
rp-pico = "0.8.0"
rp2040-hal = "0.9"
fugit = "0.3.7"
それから [package] の name を以下の通りにします。
name = "rp2040-i2c"
main.rsを編集する
main.rsを以下の通りに編集します。
#![no_std]
#![no_main]
use fugit::RateExtU32;
use hal::pac;
use hal::uart::{DataBits, StopBits, UartConfig};
use rp2040_hal::Clock;
use rp_pico::entry;
use embedded_hal::digital::v2::OutputPin;
use panic_halt as _;
use rp2040_hal as hal;
use embedded_hal::prelude::_embedded_hal_blocking_i2c_WriteRead;
use crate::pac::I2C0;
use crate::pac::UART0;
use rp2040_hal::gpio::bank0::*;
use rp2040_hal::gpio::*;
use rp2040_hal::uart::Enabled;
use rp2040_hal::uart::UartPeripheral;
use rp2040_hal::I2C;
const DEVICE_ADRS: u8 = 0x48;
#[entry]
fn main() -> ! {
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
let clocks = hal::clocks::init_clocks_and_plls(
rp_pico::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
let sio = hal::Sio::new(pac.SIO);
let pins = rp_pico::Pins::new(
// (8-6-7)
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
let uart_pins = (pins.gpio0.into_function(), pins.gpio1.into_function());
let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS)
.enable(
UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One),
clocks.peripheral_clock.freq(),
)
.unwrap();
uart.write_full_blocking(b"i2c & uart_tx example started...\r\n");
let mut led_pin = pins.led.into_push_pull_output();
let mut i2c = hal::I2C::i2c0(
// (1)
pac.I2C0,
pins.gpio16.into_function(), // sda
pins.gpio17.into_function(), // scl
400.kHz(),
&mut pac.RESETS,
125_000_000.Hz(),
);
loop {
led_pin.set_high().unwrap();
delay.delay_ms(100);
led_pin.set_low().unwrap();
delay.delay_ms(1000);
read_temperature(&mut i2c, &mut uart); // (2)
}
}
fn read_temperature(
i2c: &mut I2C<
I2C0,
(
Pin<Gpio16, FunctionI2c, PullDown>,
Pin<Gpio17, FunctionI2c, PullDown>,
),
>,
uart: &mut UartPeripheral<
Enabled,
UART0,
(
Pin<Gpio0, FunctionUart, PullDown>,
Pin<Gpio1, FunctionUart, PullDown>,
),
>,
) {
let mut readbuf: [u8; 2] = [0; 2]; // (3)
i2c.write_read(DEVICE_ADRS, &[0], &mut readbuf).unwrap(); // (4)
let mut temp: u16 = (readbuf[0] as u16) << 8; // (5)
temp += readbuf[1] as u16; // (5)
temp >>= 3; // (5)
let mut f_temp = temp as f64 / 16.0; // (5)
f_temp += 0.05; // (6)
let i_temp = (f_temp * 10.0) as u8; // (7)
let u100: u8 = (i_temp / 100) as u8; // (8)
let u10: u8 = i_temp.wrapping_sub(u100 * 100) / 10;
let u1: u8 = i_temp.wrapping_sub(u100 * 100).wrapping_sub(u10 * 10);
let mut buf = [0u8; 9];
buf[0] = u100 + 48;
buf[1] = u10 + 48;
buf[2] = b'.';
buf[3] = u1 + 48;
buf[4] = 0xe2; // e2 84 83 = ℃の文字コード(utf-8)
buf[5] = 0x84;
buf[6] = 0x83;
buf[7] = 0x0d; // CR
buf[8] = 0x0a; // LF
uart.write_full_blocking(&buf);
}
PC側の準備
通信の確認用として Tera Term を使います。
インストール後、 Tera Term を起動し、シリアルポートでUSBシリアル変換モジュールのCOMポートを選択します。
もしCOMポートが複数ある場合には、USBシリアル変換モジュールのケーブルをはずしてデバイスマネージャーでCOMポート番号を確認しておきます。

起動後、設定メニューのシリアルポートから以下の設定を行います。
スピート : 9600
データ : 8 bit
パリティ : なし
ストップビット : 1
フロー制御 : none
これらの値は送受信する相手と合わせておく必要があります。

ビルドして実行する
Run – Start Debugging (F5) からプログラムを実行します。
Tera Termで温度が表示されれば成功です。

コード解説
今回は I2C に関わる部分について解説します。
(1)hal::I2C::i2c0()関数
中がマクロになっていてわかりにくいのですが、I2C::new_controller()という関数でインスタンスがつくられます。
初期化の話ですからサンプルコードを使えば中を深く考える必要はないと思います。
I2Cに限らずラズピコ関連のサンプルは以下のフォルダから確認することができます。
クレートの中にいろいろなサンプルコードが含まれているので助かります。
C:\Users\xxxx\.cargo\registry\src\index.crates.io-xxxxxxxxxxxxxxxx\rp2040-hal-0.9.1\examples
C:\Users\xxxx\.cargo\registry\src\index.crates.io-xxxxxxxxxxxxxxxx\rp-pico-0.8.0\examples
xxxx の部分は皆さんのユーザー名です。
xxxxxxxxxxxxxxxx の部分はランダム?な16桁の英数字です。
(2)モジュールから温度データを読みUARTで出力する関数です。
(3)ADT7410から温度データを読むためのリードバッファです。
(4)write_read()はembedded_halのトレイトWriteReadのメソッドです。
第2引数で &[0] を指定することにより、値0の1バイトを書き込んだ後、readbufに2バイト読み込みます。
embedded_halのトレイトを使うことで抽象化することができ、上位層は下位層を意識せずにインターフェースすることができます。
(5)この4行でf64型の温度データが求まります。
(6)小数点第2位を四捨五入します。
(7)小数点第1位までの値を10倍して整数にします。
(8)この3行で ab.c の数値を3つの文字に分解し a を u10 , b を u1 , c を u0_1 の変数に代入します。
(9)UARTの送信バッファを用意し、分解した文字と小数点をバッファにセットして送信します。
単位℃をつけるために、Tera Term がデフォルトで認識する文字コードである utf-8 形式の 0xe2 0x84 0x83 も付加し、最後に改行コードをつけて送信します。
GitHubにアップ
GitHubの rp2040-i2c にプロジェクトをアップしましたので参考になさってください。
お疲れさまでした。