家庭用サイクルトレーナーにmicro:bitをつなぐ(その3)
前回までの記事(その1、その2)では、家庭用サイクルトレーナーのワイヤレス運動測量計とmicro:bitをつなぎ、STEP信号からケイデンスとスピードを算出・推定することができるようになりました。
リンク
- 家庭用サイクルトレーナーにmicro:bitをつなぐ(その1)
ワイヤレス運動測量計から出力されるクランク回転センサーの信号をモニターする - 家庭用サイクルトレーナーにmicro:bitをつなぐ(その2)
STEP信号からワイヤレス運動測量計に表示されているスピードをmicro:bitで推定する
今回、MicroBitComponent
を継承し、ワイヤレス運動測量計のSTEP信号からケイデンスとスピードを求めるモジュールをドライバー化した**MicroBitIndoorBikeStepSensor
クラス**を作成しました。
MicroBitIndoorBikeStepSensor
クラスの使用例
MicroBitIndoorBikeStepSensor
クラスは、MicroBitComponent
を継承したドライバーです。次のmain.cpp
で、その使い方の実装例を示しています。
#include "inttypes.h"
#include "MicroBit.h"
#include "MicroBitIndoorBikeStepSensor.h"
MicroBit uBit;
MicroBitIndoorBikeStepSensor stepSensor(uBit);
uint32_t stepCount;
void onStepSensor(MicroBitEvent e)
{
uBit.serial.printf("%" PRIu32 ", %" PRIu32 ", %" PRIu32 ", %" PRIu32 "\r\n"
, ++stepCount, (uint32_t)e.timestamp, stepSensor.getCadence2() , stepSensor.getSpeed100());
}
void setup(void)
{
uBit.serial.printf("#, Timestamp, Cadence2, Speed100\r\n");
uBit.addIdleComponent(&stepSensor);
uBit.messageBus.listen(MICROBIT_INDOORBIKE_STEP_SENSOR_ID, MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE, onStepSensor);
uBit.display.scrollAsync("GO!");
}
int main()
{
// Initialise the micro:bit runtime.
uBit.init();
// Insert your code here!
create_fiber(setup);
// If main exits, there may still be other fibers running or registered event handlers etc.
// Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
// sit in the idle task forever, in a power efficient sleep.
release_fiber();
}
説明
MicroBitIndoorBikeStepSensor
クラスのインスタンス変数stepSensor
を定義し、stepSensor
をuBit.addIdleComponent
メソッドで、uBit
インスタンスに追加します。
MicroBitIndoorBikeStepSensor
クラスのインスタンスには、イベント・バスIDに**MICROBIT_INDOORBIKE_STEP_SENSOR_ID
がデフォルトで割り当ててありますので、uBit.messageBus.listen
メソッドで、MicroBitIndoorBikeStepSensor
インスタンスが発生させるMICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE
イベント**をonStepSensor
イベントハンドラで受け取るようにします。
onStepSensor
イベントハンドラでは、**ケイデンス(単位:0.5rpm)とスピード(単位:0.01km/h)**をMicroBitIndoorBikeStepSensor
インスタンスから取得し、USBシリアル通信で、出力しています。
項目 | 内容 | 備考 |
---|---|---|
クラス | MicroBitIndoorBikeStepSensor | MicroBitComponentを継承(IdleComponent) |
イベント・バスID | MICROBIT_INDOORBIKE_STEP_SENSOR_ID | ユーザー定義 |
値更新イベント | MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE | 約1秒間隔で発生 |
接続端子 | 端子P2 | デフォルト |
取得値の単位(精度)について
取得できるケイデンスとスピードの単位(精度)の仕様は少し特殊ですので注意してください。MicroBitIndoorBikeStepSensor::getCadence2()
メソッドで取得できる値1は、0.5rpmとしており、MicroBitIndoorBikeStepSensor::getSpeed100()
メソッドで取得できる値1は、0.01km/hとしています。
例えば、getCadence2()
の取得値が140
の場合は、140×0.5rpm=70.0rpm
であり、getSpeed100()
の取得値が、2345
の場合は、2345×0.01km/h=23.45km/h
です。
項目 | メソッド | 単位(精度) | 例 |
---|---|---|---|
ケイデンス | getCadence2 | 0.5rpm | 取得値140の場合、140×0.5rpm=70.0rpm |
スピード | getSpeed100 | 0.01km/h | 取得値2345の場合、2345×0.01km/h=23.45km/h |
MicroBitIndoorBikeStepSensor
クラスの実装(説明)
実際の継承元
MicroBitIndoorBikeStepSensor
クラスは、MicroBitComponent
を継承していると説明しましたが、実際には、MicroBitComponent
を継承したMicroBitCustomComponent
を継承しています。
#include "MicroBit.h"
// The base of custom Event Bus ID.
static const uint16_t MICROBIT_CUSTOM_ID_BASE = 32768;
class MicroBitCustomComponent : public MicroBitComponent
{
public:
/**
* Event Bus ID of this component.
*/
uint16_t getId(void)
{
return this->id;
}
};
理由は2つあります。
- イベント・バスIDを取得したい為
- イベント・バスIDのユーザー定義値において、標準の定義値との重複を避けたい為
1つ目の理由は、今回の使用例では、使用していませんが、そのインスタンス変数から保持しているイベント・バスIDを取得したい場合があるためです。例えば、インスタンス変数を定義する際に、コンストラクタへ動的にイベント・バスIDを割り当てるような実装をしたい場合が考えられます。その場合は、uBit.messageBus.listen
メソッドの第1引数には、そのインスタンスのイベント・バスIDを取得する為に、stepSensor.getId()
を与えます。
2つ目の理由は、継承とは関係ありませんが、ユーザー定義されるイベント・バスIDの範囲を標準化しています(32768
以上の値)。
ケイデンスとスピードの計算
前回までの記事で、ケイデンスとスピードの計算式を求めましたが、それらの計算式を使ったcalcIndoorBikeData
メソッドで算出しています。
STEP信号の間隔(単位:マイクロ秒) crankIntervalTime
から、ケイデンス(単位:0.5rpm)cadence2
とスピード(単位:0.01km/h)speed100
に結果を返します。
void calcIndoorBikeData(uint32_t crankIntervalTime, uint32_t* cadence2, uint32_t* speed100)
{
if (crankIntervalTime==0)
{
*cadence2 = 0;
*speed100 = 0;
}
else
{
*cadence2 = (uint32_t)( (uint64_t)K_STEP_CADENCE / crankIntervalTime );
*speed100 = (uint32_t)( (uint64_t)K_STEP_SPEED / crankIntervalTime );
}
}
コンストラクタ
コンストラクタでは、主に次の3つを実装しています。
- メンバ変数の初期化
- STEP信号の取得イベントハンドラの設定
- エッジイベントの有効化
MicroBitIndoorBikeStepSensor::MicroBitIndoorBikeStepSensor(MicroBit &_uBit, MicrobitIndoorBikeStepSensorPin pin, uint16_t id)
: uBit(_uBit)
{
this->id = id;
this->lastIntervalTime=0;
this->lastCadence2=0;
this->lastSpeed100=0;
this->updateSampleTimestamp=0;
if (EventModel::defaultEventBus)
EventModel::defaultEventBus->listen(MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVENT_IDs[pin], MICROBIT_PIN_EVT_FALL
, this, &MicroBitIndoorBikeStepSensor::onStepSensor);
switch (pin)
{
case EDGE_P0:
uBit.io.P0.eventOn(MICROBIT_PIN_EVENT_ON_EDGE);
break;
case EDGE_P1:
uBit.io.P1.eventOn(MICROBIT_PIN_EVENT_ON_EDGE);
break;
default: // EDGE_P2
uBit.io.P2.eventOn(MICROBIT_PIN_EVENT_ON_EDGE);
break;
}
}
STEP信号の取得イベントハンドラ
ドライバー内では、MICROBIT_PIN_EVT_FALL
イベントが発生したe.timestamp
値をリストintervalList
に保持しています(push,pop)。保持する値は、最新の3つ(INTERVAL_LIST_SIZE
)です。つまり、最大、2回転分のSTEP信号の平均値からケイデンスやスピードを算出できます。
また、リストintervalList
が空の場合、初回から、ケイデンスとスピードを算出できるようにする為、MAX_STEPS_INTERVAL_TIME_US
マイクロ秒を引いた値を1つ余分に予め追加しています。MAX_STEPS_INTERVAL_TIME_US
マイクロ秒(2.5秒)の値は、ワイヤレス運動測量計がSTOP状態と判断する最大値を実験的に求めた値です。
void MicroBitIndoorBikeStepSensor::onStepSensor(MicroBitEvent e)
{
uint64_t currentTime = e.timestamp;
if (this->intervalList.size()==0)
{
// 初回から、ケイデンスとスピードを算出する。
this->intervalList.push(currentTime-MAX_STEPS_INTERVAL_TIME_US);
}
this->intervalList.push(currentTime);
if ((uint32_t)this->intervalList.size()>(this->INTERVAL_LIST_SIZE))
{
this->intervalList.pop();
}
}
算出とイベント生成 - アイドル・コンポーネントとシステム・コンポーネント
MicroBitComponent
派生クラスのインスタンスは、アイドル・コンポーネントやシステム・コンポーネントとして、MicroBitランタイムに登録されることにより、それぞれの方式で振る舞います(機能します)。
アイドル・コンポーネント
uBit.addIdleComponent
メソッドなどでMicroBitランタイムに登録すると、アイドルスレッドからidleTick
メソッドが頻繁に呼び出されます。
例えば、次の標準コンポーネントがアイドル・コンポーネントとして利用できます。
- MicroBitBLEManager
- MicroBitEventService
- MicroBitIOPinService
- MicroBitAccelerometer
- MicroBitCompass
- MicroBitThermometer.
システム・コンポーネント
uBit.addSystemComponent
メソッドなどでMicroBitランタイムに登録すると、タイマー割り込みからsystemTick
メソッドがTick間隔で呼び出されます。尚、タイマー・コンテキストからの呼び出しであることに注意してください。
例えば、次の標準コンポーネントがシステム・コンポーネントとして利用できます。
- MicroBitSystemTimerCallback
- MicroBitButton
- MicroBitDisplay
ケイデンスとスピードの算出とイベント生成
MicroBitIndoorBikeStepSensor
クラスは、アイドル・コンポーネントとして実装してあります。
void MicroBitIndoorBikeStepSensor::idleTick()
{
this->update();
}
そして、update
メソッドでは、SENSOR_UPDATE_PERIOD_US
マイクロ秒間隔(1秒間隔)で、ケイデンスとスピードを算出し、MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE
イベントを発生させています。
また、MAX_STEPS_INTERVAL_TIME_US
マイクロ秒(2.5秒)以上、STEP信号の検出がない場合は、停止状態と判断し、ケイデンスとスピードがゼロになるようにしています。
void MicroBitIndoorBikeStepSensor::update(void)
{
uint64_t currentTime = system_timer_current_time_us();
if(!(status & MICROBIT_INDOOR_BIKE_STEP_SENSOR_ADDED_TO_IDLE))
{
// If we're running under a fiber scheduer, register ourselves for a periodic callback to keep our data up to date.
// Otherwise, we do just do this on demand, when polled through our read() interface.
fiber_add_idle_component(this);
status |= MICROBIT_INDOOR_BIKE_STEP_SENSOR_ADDED_TO_IDLE;
}
if (currentTime >= this->updateSampleTimestamp)
{
this->updateSampleTimestamp = currentTime + this->SENSOR_UPDATE_PERIOD_US;
if ((this->intervalList.size()>0) && ((currentTime - this->intervalList.back())>=this->MAX_STEPS_INTERVAL_TIME_US))
{
while (this->intervalList.size()>0)
{
this->intervalList.pop();
}
}
if (this->intervalList.size() < 2)
{
this->lastIntervalTime = 0;
}
else
{
uint64_t intervalNum = this->intervalList.size() - 1;
uint64_t periodTime = this->intervalList.back() - this->intervalList.front();
this->lastIntervalTime = periodTime / intervalNum;
}
calcIndoorBikeData(this->lastIntervalTime, &this->lastCadence2, &this->lastSpeed100);
MicroBitEvent e(id, MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE);
}
}
おわりに
-
MicroBitIndoorBikeStepSensor
クラスを作成しました -
MicroBitIndoorBikeStepSensor
クラスの使用例を示しました -
MicroBitIndoorBikeStepSensor
クラスの実装内容について説明しました
次回(その4)は、スマホ・アプリでケイデンスやスピードを表示できるように BLEのGATTサービス(FTMP/FTMS)を実装します。