概要
M5 Japan Tour 2024 Autumn 東京 にて発表された M5UnitUnified について当日のスライド では語れなかった事柄や、詳細について書いていきます。
なお改めて申し上げますが、各ユニットの M5UnitUnfied 対応ライブラリは新規に書き起こしされています。
従来の内製ライブラリや、依存している外部ライブラリでは対応していない機能の追加や、バグ修正、挙動のデータシートへの準拠を目標に作成しています。
さらにまだアルファ版であることにご留意ください。
以下の構成や API は今後大幅に変更される可能性があります。
またご意見、ご要望、ご感想他フィードバックやレスポンスに大変飢えておりますので、色々不備もあるとは思いますが、お試しいただければ幸いです。
ユニット対応状況
現時点( 2024/12/13 現在)で公開されているユニットについて見ていきましょう。
同系列なユニットはできる限り同じリポジトリに収める方針です。
公開済み
M5Unit-ENV
こちらは既存のライブラリに M5UnitUnified 関連のファイルが追加された形となっています。
ENV 系列のユニットに関するライブラリです。
- 対応ユニット
- 近日追加予定
M5Unit-METER
新規リポジトリ。
Meter 系列のユニットに関するライブラリです。
- 対応ユニット
M5Unit-HUB
新規リポジトリ。
Hub 系列のユニットに関するライブラリです。
- 対応ユニット
- 追加予定(時期未定)
M5Unit-GESTURE
新規リポジトリ。
- 対応ユニット
M5Unit-HEART
新規リポジトリ。
- 対応ユニット
M5Unit-TOF
新規リポジトリ。
ToF 系列のユニットに関するライブラリです。
- 対応ユニット
M5Unit-WEIGHT
新規リポジトリ。
Weight 系列のユニットに関するライブラリです。
- 対応ユニット
近日公開予定
M5Unit-RFID
新規リポジトリ。
RFID 系列のユニットに関するライブラリです。
- 対応ユニット
M5Unit-ANADIG
新規リポジトリ。
ADC,DAC 系列のユニットに関するライブラリです。
基本的な使用の流れ
- ユニットの所属するライブラリをインストール
ArduinoIDE ならライブラリマネージャを使用
PlatformIO なら platformio.ini に記述
; ToF の場合
lib_deps = m5stack/M5Unit-TOF
(依存する M5UnitUnified M5Utility M5HAL も自動でインストールされるはずですが、されない場合は手動でお願いします)
- M5UnitUnified とユニットのインクルードファイルをインクルードします
さらに M5Unified と組合せる事で、ソースが共通化できます。
// ToF 場合
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedTOF.h>
namespace {
m5::unit::UnitUnified Units; // Unit 管理
m5::unit::UnitToF unit; // ToF ユニット
} // namespace
void setup()
{
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
if (!Units.add(unit, Wire) || !Units.begin()) { // 管理クラスに Add して開始!
M5_LOGE("Failed to begin");
while (true) {
m5::utility::delay(10000);
}
}
}
void loop()
{
M5.update();
Units.update(); // ユニットの更新(定期計測機構があれば計測して蓄積)
if (unit.updated()) { // 更新されたら
M5_LOGI("\n>Range:%d\nStatus:%u", unit.range(), unit.range_status()); // データを使用
}
}
各ユニットのリポジトリの examples も参照してください
Unit クラスで共通している事柄
さて、既存の値取得系ライブラリの使用例では、以下のような loop() の書き方をよく見ます
void loop() {
if (foo.update()) { // 計測して値を取得 (取れるまでブロックする実装もある)
printf("%f\n", foo.anyValue()); // 表示
}
delay(1000); // 適当な delay または delay なし
}
計測チップのほとんどには定期計測の為の機構があります。
定期計測機構を使用する場合、大抵のユニットではインターバル時の消費電力も少なくなり、バッテリー駆動時の稼働時間を増やせるのではないでしょうか?
M5UnitUnified ではその機構を積極的に使用し、更新関数では計測タイミングでなければ即時リターンする挙動をとります。
delay に関しては必要に応じてユーザー側で行うことができます。
- delay をしたい場合 (A)
void loop() {
Units.update();
// Units.update() では計測タイミングでなければ即リターン
if(unit.updated()) {
printf("%f\n", unit.anyValue());
m5::utility::delay(unit.interval()); // 値が取れたら次の計測予想タイミングまで
}
}
- delay をしたい場合 (B)
void loop() {
Units.update();
// Units.update() では計測タイミングでなければ即リターン
if(unit.updated()) {
printf("%f\n", unit.anyValue());
}
m5::utility::delay(1); // 最小 delay を常時
}
クラス関数
使用感や、API のできる限りの統一を目指すという事で、各 Unit クラスは共通の基底クラス m5::unit::Component から派生されています。
またデータ蓄積機能を持つクラスの場合は、 m5::unit::PeriodicMeasurementAdapter も基底となります(インターフェイスクラス)
基底にあるクラス関数は派生された Unit クラスにおいて、もちろん使用できます。
各 Unit クラスにある API では補えない処理を独自に行いたい場合など、これらを使用すれば解決するかも知れません。
基底など M5UnitUnified 関連のドキュメントはこちらです。
基本ルール
- component_config 関数によって共通の設定の取得、設定が行える
- config 関数によってユニット独自の設定の取得、設定が行える
定期計測機構がある場合の初期値は、典型的な設定で定期計測が開始される状態
よって Units.begin() によって定期計測がスタートする - readFoo はユニットにアクセスして値を取得する
- writeFoo はユニットにアクセスして値を書き込む
- startPeriodicMeasurement(ユニット毎に異なる引数) は定期計測を開始する (機構が存在すれば)
- stopPeriodicMeasurement() は定期計測を停止する (機構が存在すれば)
- measureSingleshot(ユニット毎に異なる引数) シングルショット計測による計測値の取得 (機構が存在すれば)
- 定期計測を持つユニットでは、任意の要素数を環状バッファに蓄積できます
初期値は 1 なので、常に最新のデータのみにアクセスします
component_config で要素数を変更可能です
Component の関数(一部)
プロパティ
- const char* deviceName() const;
ユニット名称 - m5::unit::types::uid_t identifier() const;
一意なユニットクラス ID - uint8_t address() const;
使用している I2C アドレス
定期計測関連
- bool inPeriodic() const;
定期計測中か? - bool updated() const;
直近の更新によって値が取得できたか? - m5::unit::types::elapsed_time_t updatedMillis() const;
値が取得できた時の時刻(ms) - m5::unit::types::elapsed_time_t interval() const;
定期計測の間隔(ms)
Read
template によって 8/16 bit レジスタを識別
- m5::hal::error::error/_t readWithTransaction(uint8_t* data, const size_t len);
読み込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2,std::nullptr\_t>::type = nullptr> bool readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,const bool stop = true);
レジスタ読み込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop = true);
レジスタ 8bit 読み込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool readRegister16(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true);
レジスタ 16bit 読み込み
Write
template によって 8/16 bit レジスタを識別
- m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const bool stop = true);
書き込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> m5::hal::error::error_t writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len, const bool stop = true);
レジスタ書き込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister(const Reg reg, const uint8_t* buf = nullptr, const size_t len = 0U, const bool stop = true);
レジスタ書き込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister8(const Reg reg, const uint8_t value, const bool stop = true);
レジスタ 8bit 書き込み - template ::value && std::is_unsigned::value && sizeof(Reg) <= 2, std::nullptr\_t>::type = nullptr> bool writeRegister16(const Reg reg, const uint16_t value, const bool stop = true);
レジスタ 16bit 書き込み
bool generalCall(const uint8_t* data, const size_t len);
ジェネラルコール書き込み
PeriodicMeasurementAdapter の関数(一部)
蓄積されたデータに対する操作
- size_t available() const;
蓄積されているデータ数 - bool empty() const;
蓄積データは空か? - bool full() const;
蓄積データは環状バッファの最大に達しているか? - ユニット毎の計測データ型 oldest() const;
蓄積されている中で最も古いデータ - ユニット毎の計測データ型 latest() const;
蓄積されている中で最も新しいデータ - void discard();
蓄積されている中で最も古いデータを削除 - void flush();
全ての蓄積データを削除
蓄積データが複数の場合のアクセスの例
void any_task(void*)
{
for(;;) {
Units.update();
while(unit.available()) { // 蓄積データがある限り
copy_data_function(unit.oldest().anyValue()); // 最も古い物をどこかへコピー
unit.discard(); // 最も古い物を棄却 [これがないと永久ループ注意!]
}
}
}
計測データ
ユニット毎に独自のデータ型を持ちます。
生のデータと、生データからの値取得の関数を搭載しています。
// ToF の場合
struct Data {
std::array<uint8_t, 17> raw{}; //!< RAW data
RangeStatus range_status() const;
inline bool valid() const
{
return raw[0] == 9;
}
inline int16_t range() const
{
return valid() ? m5::types::big_uint16_t(raw[13], raw[14]).get() : -1;
}
};
またユニット側には最も古いデータに対してのクイックアクセス API を搭載しています。
// ToF の場合
class Unit UnitVL53L1X {
// ... 前略
inline int16_t range() const
{
return !empty() ? oldest().range() : -1;
}
inline bool valid() const
{
return !empty() && oldest().valid();
}
inline vl53l1x::RangeStatus range_status() const
{
return !empty() ? oldest().range_status() : vl53l1x::RangeStatus::Unknown255;
}
// ... 後略
};
その他ユニット毎のドキュメントは各リポジトリの README に GitHub Pages へのリンクがありますのでご参照ください。
今後
ユニット対応を進行しつつ、定期的に更新していく予定です。
M5HAL との統合時期は未定ですので、当分は TwoWire を直接使用する現状の形になると思われます。
GPIO / UART 接続ユニットについては 同様に Serial 等で代替する形で進行する予定です。
ご意見、ご要望、ご感想他フィードバックやレスポンスに大変飢えておりますので、色々不備もあるとは思いますが、お試しいただければ幸いです。