製作モジュール一覧

ラベル mbed OS 5 の投稿を表示しています。 すべての投稿を表示
ラベル mbed OS 5 の投稿を表示しています。 すべての投稿を表示

2018年9月28日金曜日

mbed OS 5のThread

mbed OS 5のThreadについて以下の記事を参考にしました。

Docs › Reference › RTOS › RTOS overview
https://os.mbed.com/docs/v5.9/reference/rtos.html

Docs › Reference › RTOS › Thread
https://os.mbed.com/docs/v5.9/reference/thread.html

Mbed OS 5のRTOSはMutexやSemaphoなど小難しいものがありますが、Thread間でメモリなどのリソースを競合せずに共有するための仕組みで、使わなくて済むならそれに越したことはありません。

というわけで、まずはThreadだけ試してみました。

Docs › Reference › RTOS › Thread」のExampleをNucleo F446RE用に多少書き換えました。

ブレッドボード配線図

Thread example


#include "mbed.h"
 
DigitalOut led1(D2);
DigitalOut led2(D3);
Thread thread;
 
void led2_thread() {
    while (true) {
        led2 = !led2;
        wait(1);
    }
}
 
int main() {
    thread.start(led2_thread);
    
    while (true) {
        led1 = !led1;
        wait(0.5);
    }
}

led1の点滅はメイン・ループで行われ、led2の点滅は生成したスレッドで行われます。おおざっぱにいうと、メインループとは独立して、生成したスレッドが勝手にled2を点滅させているイメージです。

Thread example with callbacks

引数を1つ取るコールバック関数を使った例です。引数によってThreadの開始の仕方(State)を変えられます。この例ではどのLEDを点滅させるかを、Threadの開始時に指定しています。


#include "mbed.h"

Thread thread;
DigitalOut led1(D2);
DigitalOut led2(D3);

volatile bool running = true;

// Blink function toggles the led in a long running loop
void blink(DigitalOut *led) {
    while (running) {
        *led = !*led;
        wait(1);
    }
}

// Spawns a thread to run blink for 5 seconds
int main() {
    thread.start(callback(blink, &led1));
    wait(10);
    running = false;
    thread.join();
 
    thread.start(callback(blink, &led2));
    wait(10);
    running = false;
    thread.join();
 
}

LED1が10秒間(5回)点滅したあと、LED2が10秒間(5回)点滅し、プログラムは終了します。thred.join()はLEDを点滅させるスレッドが終了するのを待ちます。

OLED表示とADC読み取りをThread化


実験中のNucleo DCOでやっているOLED表示とADC読み取りをThread化し、DAC出力はTickerのままでテストしました。

DAC出力は波形生成を行っていてタイミングにシビアで、RTOSを使った場合ミリ秒単位(最大1kHz)でしか制御できないため、マイクロ秒単位で制御できるTickerを使用しています。

ブレッドボード配線図

メインループでのポーリング(mbed-cli)
https://github.com/ryood/Mbed_OS_5_Thread/tree/master/mbed/Thread_Test03

メインループ内でADC読み取りとOLED表示を逐次処理して、TickerでDACからサイン波を出力しています。

ADC読み取りとOLED表示をThread化(mbed-cli)
https://github.com/ryood/Mbed_OS_5_Thread/tree/master/mbed/Thread_Test04

ADC読み取りとOLED表示をThread化し、Thread::set_priority()でADC読み取りの優先順位を高、OLED表示を低に設定しました。

優先順位は以下の通りです。

0 波形生成  Ticker
1 ADC読み取り Thread(priority:高)
2 メインループ Thread(priority:普通) LED点灯
3 OLED表示 Thread(priority:低)

ADC読み取りとOLED表示のタイミング

メインループでのポーリング

ch1:D3(OLED表示) ch2:D4(ADC読み取り)

それぞれの処理の最初と最後でピンの出力をH/Lしています。OLED表示(赤色)に0.133秒かかってその後にADC読み取り(黄色)が一瞬行われています。

この場合ADC読み取りはOLED表示の完了を待つため、サンプリング周期が長くなり、POTを回すと出音が段階的な変化になりました。口でいうと「ポ、ポ、ポ、ポ・・・」という感じ。

Thread化

ch1:D3(OLED表示) ch2:D4(ADC読み取り)

Thread化した場合、OLED表示(赤色)がHになっている間にもADC読み取り(黄色)が行われています。こちらはPOTを回すとなめらかに変化しました。口でいうと「プーーーーーーン」という感じ。

波形生成Tickerのタイミング


メインループでのポーリング

ch1:D2(波形生成) ch2:D4(ADC読み取り)

Thread化

ch1:D2(波形生成) ch2:D4(ADC読み取り)

Thread化した場合、ADC読み取り(黄色)の間隔が詰まっています。どちらの場合も波形生成のTicker割り込み(赤色)が優先されていて、50kHzのサンプリングレートがキープできています。

DAC出力波形


メインループでのポーリング

ch1:D2(波形生成) ch2:A2(出力波形)

Thread化

ch1:D2(波形生成) ch2:A2(出力波形)

ch1のデジタル波形がオシロ内で干渉してサイン波の表示が汚くなっていますが、1kHzのサイン波が出力できています。

メモ:


Tickerの様に使えるRtosTimerは非推奨になり、EventQueueが推奨されています。←機能が多いのでちょっとめんどくさそう。

https://os.mbed.com/docs/v5.9/reference/rtostimer.html

2018年9月23日日曜日

mbed OS 5でCallbackを使う。

mbed OS 5ではコールバック関数にFunctionPointer(参考「mbedのコールバック FunctionPointerを使うテスト」)ではなくCallbackクラスが推奨されています。以下の記事を参考にして少し調べました。

Callbackについて
https://os.mbed.com/docs/v5.8/reference/platform.html#callbacks

状態(State)の重要性
https://os.mbed.com/docs/v5.8/reference/platform.html#the-importance-of-state

APIドキュメント
https://os.mbed.com/docs/v5.8/reference/callback.html

「[em] bed ded」さんの「mbed OS5 の Callback

Tickerのコールバック


NucleoF446REでTickerのサンプルプログラムを動作させました。

参考「https://os.mbed.com/docs/latest/reference/ticker.html

ブレッドボード配線図

drivers/Ticker.hでコールバック関数の登録は以下のように定義されています。

void attach (Callback< void()> func, float t)

サンプルプログラム

#include "mbed.h"
 
Ticker flipper;

DigitalOut led1(D2);
DigitalOut led2(D3);
 
void flip() {
    led2 = !led2;
}
 
int main() {
    led2 = 1;
    flipper.attach(&flip, 2.0);
 
    while(1) {
        led1 = !led1;
        wait(0.2);
    }
}

普通の関数の場合、見た目はCallbackクラスを使わないmbed 2のときと変わりありません。

クラスの中でコールバック関数を登録する場合は以下のように定義されています。

template<typename T , typename M >
void attach (T *obj, M method, float t)

サンプルプログラム

#include "mbed.h"
 
// LEDチカチカクラス
class Flipper {
public:
    Flipper(PinName pin) : _pin(pin) {
        _pin = 0;
    }
    void flip() {
        _pin = !_pin;
    }
private:
    DigitalOut _pin;
};
 
DigitalOut led1(D2);
Flipper f(D3);
Ticker t;
 
int main() {
    // mbed 2のときは
    // t.attach(&f, &Flipper::flip, 1.0);
    t.attach(callback(&f, &Flipper::flip), 1.0); 
 
    while(1) {
        led1 = !led1;
        wait(0.1);
    }
}

RotaryEncoder LibraryをCallbackに対応させる


以上を踏まえて、Callbackを使うように警告が出ていたRotaryEncoderクラスを修正しました。

RotaryEncoder Library
https://os.mbed.com/users/ryood/code/RotaryEncoder/#cfc8f362bce6

テストプログラム
https://os.mbed.com/users/ryood/code/RotaryEncoder_Test/

RotaryEncoderクラスで内部的に使っている、Ticker::attach_us()の書き方が

ticker.attach_us(this, &RotaryEncoder::func_ticker, t);

のとき、以下のような警告が出ていましたが、


Warning: Function "mbed::Ticker::attach_us(T *, M, us_timestamp_t) [with T=RotaryEncoder, M=void (RotaryEncoder::*)()]" (declared at line 144 of "/extras/mbed-os.lib/drivers/Ticker.h") was declared "deprecated" in "RotaryEncoder/RotaryEncoder.h", Line: 98, Col: 17

Callbackを使い、

ticker.attach_us(callback(this, &RotaryEncoder::func_ticker), t);

とすればワーニングは出なくなりました。

C++のテンプレートは難解でよくわかっていませんが、かいつまんで言えば、普通のコールバック関数の登録は特に変わらず、クラス内でコールバック関数を使う場合は、

attach(オブジェクトへのポインタ, オブジェクトのメンバ関数へのポインタ, 引数)

と書いていていたところを、callback()関数を使って

attach(callback(オブジェクトへのポインタ, オブジェクトのメンバ関数へのポインタ), 引数)

と書けば良いようです。

callback()関数はplatform/Callback.hで定義されています。

/** Create a callback class with type infered from the arguments
 *
 *  @param func     Static function to attach
 *  @return         Callback with infered type
 */
template <typename R>
Callback<R()> callback(R(*func)() = 0)
{
    return Callback<R()>(func);
}

/** Create a callback class with type infered from the arguments
 *
 *  @param obj      Optional pointer to object to bind to function
 *  @param method   Member function to attach
 *  @return         Callback with infered type
 */
template<typename T, typename U, typename R>
Callback<R()> callback(U *obj, R(T::*method)())
{
    return Callback<R()>(obj, method);
}


/** Create a callback class with type infered from the arguments
 *
 *  @param func     Static function to attach
 *  @param arg      Pointer argument to function
 *  @return         Callback with infered type
 */
template <typename T, typename U, typename R>
Callback<R()> callback(R(*func)(T *), U *arg)
{
    return Callback<R()>(func, arg);
}

など。

2018年9月16日日曜日

Nucleo F767ZIでmbed 2とmbed OS 5の浮動小数点数演算の速度を比較

Nucleo F767ZIでオンラインコンパイラのmbed 2と、mbed-cliのmbed OS 5の浮動小数点数演算の処理時間を測定しました。

テストプログラム
https://github.com/ryood/Nucleo_DCO/tree/master/mbed/FloatingPoint_Test01

測定結果

青とオレンジがfloat型の演算結果で、グレーと黄色がdouble型の演算結果です。

add, sub, mul, divはそれぞれ加算、減算、乗算、除算で圧倒的にfloat型が高速です。

ところが、なぜだがか三角関数や指数関数のsin, cos, exp, logは、float型とdouble型の差があまりありません。

DDSのチューニングワードを計算する
// calc tuning word
    t.reset();
    t.start();
    for (int i = 0; i < LOOP_N; i++) {
        buffer[i] = pow(2.0, 32) * (double)i / 100000.0;
    }
    t.stop();
    elapse = t.read_us();
    pc.printf("tword\t%d\t%f\r\n", elapse, (float)elapse / LOOP_N);
という演算では、むしろmbed OS 5のdouble型(グラフではtwordのグレイ)が最速となっています。「pow(2.0, 32)」という項は定数なので、この場合最適化されているのかもしれません。

Nucleo F767ZIをmbedで使った場合に限りますが、浮動小数点数演算の四則演算はfloat型でやったほうが速く、三角関数や指数関数(特にDDSがらみの演算)はdouble型でやってもそれほど遅くないと言えると思います。

測定データ

op OS 5 float Mbed 2 float OS 5 double Mbed 2 double
add 0.03708 0.02549 2.23522 1.64085
sub 0.07417 0.04866 2.29228 1.684
mul 0.11125 0.07416 2.36179 1.7396
div 0.21784 0.16682 2.54717 1.90871
sin 0.55664 0.37082 0.88683 0.93309
cos 0.59404 0.35549 0.95088 1.11853
exp 0.76539 0.53734 1.16889 1.04258
log 0.82766 0.48837 1.2759 1.36975
sqrt 1.31143 0.22935 3.23866 0.37065
pow32 2.28002 1.59155 0.6069 3.92082
tword 2.1812 1.60074 0.21782 3.48061

<追記:2018.09.16>

mbed-cliのOS 5とオンラインコンパイラのmbed 2で分けてグラフ化しました。

Mbed OS 5

Mbed 2

</追記>