こんにちは、M5StackでいよいよRustで書いたプログラムを動かせたので記録として残しておきます
環境
- M1MacMini(Ventura)
- rustc 1.73.0
- M5Stack FIRE V2.7
ビルドできるように準備
rustup toolchain install nightly --component rust-src
cargo install cargo-generate
cargo generate esp-rs/esp-idf-template cargo
# プロンプトには以下のように回答
# ✔ 🤷 Which MCU to target? · esp32
# ✔ 🤷 Configure advanced template options? · false
# プログラムをM5Stackに送るためのコマンド。
# espflashではなくcargo-espflashをインストールするとうまく動かなかった
cargo install espflash
cargo install espup
cargo install ldproxy
. ~/export-esp.sh
cargo build
これで動いた!その後、似たような記事を見つけたので、std環境でしかできない実装をしてノリを理解した
Embedded Rust on Espressif
その後、Espressif
が公開してたRustでESP32のコードをstd環境で書いて動作させるサイトを見つけたのでこれをひたすらやってた(途中でやめたけど)
途中でESP32の内部温度を取得してそれをESP32がHTTPサーバーとしてHTMLを公開する、という課題があったが、M5StackのESP32は内部温度を取得できない?みたいで、代わりにジャイロセンサーの値を読み取ることにした
今思うとこれがこんなにしんどいと思わなかった🤢
ArdinoIDEだったら、公式サイトなどで紹介されているように簡単にジャイロの値が取れるらしい。こんな感じでめちゃくちゃ楽
float gyroX, gyroY, gyroZ;
M5.IMU.getGyroData(&gyroX,&gyroY,&gyroZ);
同じようなAPIが都合よくesp_idf_sys
に映えてないか探したけどなかった…
よく考えれば当たり前で、ジャイロセンサはESP32とは全く別の部品なのでEspressif社が用意してるわけない🙄
どうやらM5Stackに入っているジャイロセンサはMPU6886
というらしい。
MPU6886の公式サイトがあってそこに載ってる資料見ればわかるか?と思って探したらデータシートなるものが公開されてた。(データシートという言葉を聞いてラズパイとLCDディスプレイ(AQM0802)で遊んでみた時の記憶が若干蘇った)
正直全く意味わからん..
ということで結局、M5StackがArdinio用に提供するライブラリの中身を見て実装することにした…
コードジャンプをビュンビュンんしたかったのでgithub.devがおおいに役だった
https://github.dev/m5stack/M5Stack/blob/master/src/utility/MPU6886.cpp
このコードのMPU6886::Init
とMPU6886::getGyroAdc
で行ってるI2C通信をRustで再現したらついに値を取得できた!!😂
この作業で合計4hほど溶けてしまって辛かったのと、何かセンサーを読み取りたいとか操作したいときに毎回こんな苦悩して実装しないといけないのにつらみを感じてここでやめた
書いたコード
誰かのためになればええんですけどね🥹
/*
Copyright 2023 morimorikochan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright 2022 Ferrous Systems GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://github.com/esp-rs/std-training/blob/main/LICENSE-MIT.txt
*/
use anyhow::Result;
use core::str;
use esp_idf_hal::{
i2c::{I2cConfig, I2cDriver},
prelude::*, delay::BLOCK,
};
use esp_idf_svc::{
eventloop::EspSystemEventLoop, http::server::{EspHttpServer, Configuration},
};
use std::{
sync::{Arc, Mutex},
thread::sleep,
time::Duration,
};
use embedded_svc::http::Method;
use wifi::wifi;
use esp_idf_sys as _;
#[toml_cfg::toml_config]
pub struct Config {
#[default("")]
wifi_ssid: &'static str,
#[default("")]
wifi_psk: &'static str,
}
fn main() -> Result<()> {
esp_idf_sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();
let peripherals = Peripherals::take().unwrap();
let sysloop = EspSystemEventLoop::take()?;
// Initialize temperature sensor
let sda = peripherals.pins.gpio21;
let scl = peripherals.pins.gpio22;
let i2c = peripherals.i2c0;
let config = I2cConfig::new().baudrate(100.kHz().into());
let mut i2c = I2cDriver::new(i2c, sda, scl, &config)?;
// The constant `CONFIG` is auto-generated by `toml_config`.
let app_config = CONFIG;
// Connect to the Wi-Fi network
let _wifi = wifi(
app_config.wifi_ssid,
app_config.wifi_psk,
peripherals.modem,
sysloop,
)?;
let MPU6886_ADDRESS = 0x68;
i2c.write(MPU6886_ADDRESS, &[0x6B, 0x00], BLOCK)?;
sleep(Duration::from_millis(10));
i2c.write(MPU6886_ADDRESS, &[0x6B, (0x01 << 7)], BLOCK)?;
sleep(Duration::from_millis(10));
i2c.write(MPU6886_ADDRESS, &[0x6B, 0x01], BLOCK)?;
sleep(Duration::from_millis(10));
// +- 8g
i2c.write(MPU6886_ADDRESS, &[0x1C, 0x10], BLOCK)?;
sleep(Duration::from_millis(1));
// +- 2000 dps
i2c.write(MPU6886_ADDRESS, &[0x1B, 0x18], BLOCK)?;
sleep(Duration::from_millis(1));
// 1khz output
i2c.write(MPU6886_ADDRESS, &[0x1A, 0x01], BLOCK)?;
sleep(Duration::from_millis(1));
// 2 div, FIFO 500hz out
i2c.write(MPU6886_ADDRESS, &[0x19, 0x01], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x38, 0x00], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x1D, 0x00], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x6A, 0x00], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x23, 0x00], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x37, 0x22], BLOCK)?;
sleep(Duration::from_millis(1));
i2c.write(MPU6886_ADDRESS, &[0x38, 0x01], BLOCK)?;
sleep(Duration::from_millis(10));
let convert = |a: u8, b: u8| {
let mut result = (a as u16) << 8;
result += b as u16;
result as i16
};
let mut _buffer = Arc::new(Mutex::new([0u8; 6]));
println!("Server preparing..");
let mut server: EspHttpServer= EspHttpServer::new(&Configuration::default())?;
let _buffer_cloned = Arc::clone(&_buffer);
let _convert_cloned = convert.clone();
server.fn_handler("/", Method::Get, move |request| {
let mut response = request.into_ok_response()?;
let buffer = _buffer_cloned.lock().expect("cannot get lock...");
response.write(templated(format!("<li><ul>x: {}</ul><ul>y: {}</ul><ul>z: {}</ul></li>", _convert_cloned(buffer[0], buffer[1]),_convert_cloned(buffer[2], buffer[3]), _convert_cloned(buffer[4], buffer[5]))).as_bytes())?;
response.flush()?;
Ok(())
})?;
println!("Server awaiting connection");
loop {
i2c.write(0x68, &[0x3B], BLOCK).unwrap();
sleep(Duration::from_millis(10));
let mut buffer = _buffer.lock().expect("cannot get lock on write data...");
i2c.read(MPU6886_ADDRESS, &mut *buffer, BLOCK)?;
// for n in 0..6 {
// print!("[{:x}]",buffer[n]);
// }
println!("{},{},{}", convert(buffer[0], buffer[1]),convert(buffer[2], buffer[3]), convert(buffer[4], buffer[5]));
}
}
fn templated(content: impl AsRef<str>) -> String {
format!(
r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>esp-rs web server</title>
</head>
<body>
{}
</body>
</html>
"#,
content.as_ref()
)
}
fn index_html() -> String {
templated("Hello from mcu!")
}
fn temperature(val: f32) -> String {
templated(format!("chip temperature: {:.2}°C", val))
}
遭遇したトラブル
M5StackからWifiに繋がらない
ESP32は5GHzのWifiには繋がらないらしい。
2.4HGzのWifiにすると繋がった
M5Stack起動時にエラー bad load address range
E (137) esp_image: Segment 0 0x3c050020-0x3c05fff8 invalid: bad load address range
これは、テンプレートプロジェクトを作成する際に、esp32c3
にしていたからだった
esp32
を選ぶと問題なくコンパイルが通るようになった
VSCodeでrust-analyzer
がエラーを吐く
~/export-esp.sh
で展開されている環境変数がVSCodeでは読み取られないためだった
.zshrc
などにコピーした後再起動するとうまく動いた
ビルドできない
自分の環境だと.cargo/config.toml
の設定値が以下の通りじゃないと動かなかった。
調べた感じはターゲットがxtensa-esp32-espidf
とriscv32imc-esp-espidf
の2種類あるらしいけど何が違うのか正直よくわからん。全てノリでやっている
[build]
target = "xtensa-esp32-espidf"
[target.xtensa-esp32-espidf]
linker = "ldproxy"
runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
[unstable]
build-std = ["std", "panic_abort"]
[env]
# Note: these variables are not used when using pio builder (`cargo build --features pio`)
ESP_IDF_VERSION = "v4.4.6"
先に書いた、Embedded Rust on Espressifの初期値が全く違ったけどこれに書き換えたらうまく行った。
所感
- ググって出てくるRustで組み込みのプログラムをビルドする方法がno_stdばっかりで辛かった
- 似たようなことやっていたブログが見つかって歓喜した
- https://tomo-wait-for-it-yuki.hatenablog.com/entry/embedded-std-rust-on-m5stamp-c3-mate
- このブログにも載ってたけど、標準入力が表示されないissueが公式で挙げられてた。
- 大変参考になった
- ジャイロセンサの数値の範囲が全くわからん
- データシート読むのだるすぎる
esp-idf-svc
モジュールに色々な機能がありそうなのでここにある範囲で遊ぶのであれば楽しそう。