1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

家庭用サイクルトレーナーにmicro:bitをつなぐ(その3)- STEP信号からケイデンスとスピードを求めるモジュールをドライバー(アイドル・コンポーネント)として作成する

Last updated at Posted at 2021-02-10

家庭用サイクルトレーナーにmicro:bitをつなぐ(その3)

前回までの記事(その1その2)では、家庭用サイクルトレーナーのワイヤレス運動測量計とmicro:bitをつなぎ、STEP信号からケイデンスとスピードを算出・推定することができるようになりました。

リンク

  1. 家庭用サイクルトレーナーにmicro:bitをつなぐ(その1)
    ワイヤレス運動測量計から出力されるクランク回転センサーの信号をモニターする
  2. 家庭用サイクルトレーナーにmicro:bitをつなぐ(その2)
    STEP信号からワイヤレス運動測量計に表示されているスピードをmicro:bitで推定する

今回、MicroBitComponentを継承し、ワイヤレス運動測量計のSTEP信号からケイデンスとスピードを求めるモジュールをドライバー化した**MicroBitIndoorBikeStepSensorクラス**を作成しました。

IMG_20210211_082808.jpg

MicroBitIndoorBikeStepSensorクラスの使用例

MicroBitIndoorBikeStepSensorクラスは、MicroBitComponentを継承したドライバーです。次のmain.cppで、その使い方の実装例を示しています。

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を定義し、stepSensoruBit.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を継承しています。

MicroBitCustomComponent.h
#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つあります。

  1. イベント・バスIDを取得したい為
  2. イベント・バスIDのユーザー定義値において、標準の定義値との重複を避けたい為

1つ目の理由は、今回の使用例では、使用していませんが、そのインスタンス変数から保持しているイベント・バスIDを取得したい場合があるためです。例えば、インスタンス変数を定義する際に、コンストラクタへ動的にイベント・バスIDを割り当てるような実装をしたい場合が考えられます。その場合は、uBit.messageBus.listenメソッドの第1引数には、そのインスタンスのイベント・バスIDを取得する為に、stepSensor.getId()を与えます。

2つ目の理由は、継承とは関係ありませんが、ユーザー定義されるイベント・バスIDの範囲を標準化しています(32768以上の値)。

ケイデンスとスピードの計算

前回までの記事で、ケイデンスとスピードの計算式を求めましたが、それらの計算式を使ったcalcIndoorBikeDataメソッドで算出しています。
STEP信号の間隔(単位:マイクロ秒) crankIntervalTime から、ケイデンス(単位:0.5rpm)cadence2とスピード(単位:0.01km/h)speed100に結果を返します。

MicroBitIndoorBikeStepSensor.cpp
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つを実装しています。

  1. メンバ変数の初期化
  2. STEP信号の取得イベントハンドラの設定
  3. エッジイベントの有効化
MicroBitIndoorBikeStepSensor.cpp
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状態と判断する最大値を実験的に求めた値です。

MicroBitIndoorBikeStepSensor.cpp
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メソッドが頻繁に呼び出されます。

例えば、次の標準コンポーネントがアイドル・コンポーネントとして利用できます。

  1. MicroBitBLEManager
  2. MicroBitEventService
  3. MicroBitIOPinService
  4. MicroBitAccelerometer
  5. MicroBitCompass
  6. MicroBitThermometer.

システム・コンポーネント

uBit.addSystemComponentメソッドなどでMicroBitランタイムに登録すると、タイマー割り込みからsystemTickメソッドがTick間隔で呼び出されます。尚、タイマー・コンテキストからの呼び出しであることに注意してください。

例えば、次の標準コンポーネントがシステム・コンポーネントとして利用できます。

  1. MicroBitSystemTimerCallback
  2. MicroBitButton
  3. MicroBitDisplay

ケイデンスとスピードの算出とイベント生成

MicroBitIndoorBikeStepSensorクラスは、アイドル・コンポーネントとして実装してあります。

MicroBitIndoorBikeStepSensor.cpp
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信号の検出がない場合は、停止状態と判断し、ケイデンスとスピードがゼロになるようにしています。

MicroBitIndoorBikeStepSensor.cpp
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)を実装します。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?