勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

マイコンと加速度センサでタコメータを作る - 10. 加速度ログ取得変更

RXタコメータの続きです。
加速度ログに、加速度センサからの割り込み間隔を追加するようにしていきたいと思います。

方針

まずはPCを使いながら、加速度センサの割り込み発生間隔を調べてみたいと思います。 その結果を見て、加速度ログや実際の回転数推定時ににどのように測定間隔の補正値を記録していくか、補正を行うか、検討したいと思います。

まず調査

まず、加速度センサの割り込み発生間隔を色々と見てみたいと思います。
気になる点としては、

  • 割り込み発生間隔の平均値
  • 割り込み発生間隔の変動具合(分散を見てみるかと)
  • もしかしたら温度でも変化する?

といったところがあります。

割り込み発生間隔測定

前の投稿では、加速度センサの測定は1600Hzに設定していました。
RXタコメータでは、測定レートは1600Hzまたは3200Hzにしようと思っています。
前の回転数-速度のデータから、回転数は最大で10000rpm=1000Hz程度確保しておきたいためです。(1600Hzだとちょっと足りないですが、実際そこまで回転数は行かないので大丈夫かと思います。)

いずれにしても1msより短く、前回の1ms割り込みタイマでは測定できません。

CMTモジュールで測定

1 ms割り込みタイマはRXマイコンのCMTモジュールを使っていました。これはシステムクロック(の分周)でカウントアップする16ビットカウンタですが、このカウント値を直接使えばより細かく測定できるので、これでやってみます。

やり方としては、

  • カウンタクリアのカウント値を0xFFFFにして、16ビットフリーランカウンタとして動かす
  • 加速度センサ割り込みごとにカウント値を読み出し、前のカウント値との差を計算する
  • UART経由で定期的にこれを表示する

といった感じです。

CMTモジュール構成

RX220には、CMTモジュールが2ユニット、1ユニットに2チャネルが含まれていて、計4チャネルのタイマが存在します。

f:id:nokixa:20201102010137p:plain

ユニットの単位の意義があまりなさそうですが、以下の「コンペアマッチタイマスタートレジスタ」がユニットごとにまとめられています。
これによって同じユニット内のタイマの動作タイミングを揃えられる、という意義がありそうです。

f:id:nokixa:20201102010553p:plain

今回使うタイマはずっと動かしっぱなしにしようと思います。前回の1ms割り込みを作っているタイマも同様なので、同じユニット内のチャネルを使おうと思います。

コード

前回のCMT0モジュール初期化の部分で、以下のコードを追加します。

    //// 1ms timer (CMT0) setting ////
    CMT0.CMCR.BIT.CKS = 0x0;   // count source : PCLK / 8
    CMT0.CMCR.BIT.CMIE = 1;        // interrupt enable
    CMT0.CMCOR = 2499;             // interrupt cycle : PCLK(=20MHz) / 8 / 2499 = 1ms

    CMT.CMSTR0.BIT.STR0 = 1; // CMT0 count start

    //// 16bit freerun counter (CMT1) setting ////
    CMT1.CMCR.BIT.CKS = 0x0;   // count source : PCLK / 8
    CMT1.CMCR.BIT.CMIE = 0;        // interrupt disable
    CMT1.CMCOR = 0xFFFF;       // 16bit freerun counter

    CMT.CMSTR0.BIT.STR1 = 1; // CMT1 count start
  • できるだけ細かく測定したいので、最も周波数の高い分周クロック(PCLK/8)をクロック源として選択します。
  • 特にCMT0とタイミングを揃える必要はないので、カウント開始は別のタイミングとしています。
  • 周辺モジュールクロック(PCLK)は、前回の設定により、大元の水晶の発振を分周なしで使用するので、20MHzとなります。
  • 1クロックあたり8/20MHz = 400ns
  • カウンタ1周あたり400ns x 65536 = 26.2144ms (カウントできる最大値)

カウンタ値を示すレジスタは一度#defineでリネームしておきます。

#define ADXL345_INT_CNTR CMT1.CMCNT

次に、割り込み発生時のカウント値を残す変数です。
割り込みが発生したことを示すフラグも用意します。

static struct{

    ... 省略 ...

    uint16_t       g_int_intvl;
    uint16_t       g_int_cnt_last;
    uint8_t            g_int_update;

    ... 省略 ...

}vars;

次、加速度センサの割り込みルーチンです。
関数の最初に割り込み間隔測定の処理を追加します。
main()関数で表示しきるまでは更新しないようにするため、フラグ(vars.g_int_update)がクリアされているかまず確認します。

void adxl345_int_routine()
{
    signed short d[3];
    FIX_T f;
    LOG_UNIT log_d;
    uint16_t int_cnt = ADXL345_INT_CNTR;

    if(vars.g_int_update == 0){
        vars.g_int_intvl = int_cnt - vars.g_int_cnt_last;
        vars.g_int_update = 1;
    }
    vars.g_int_cnt_last = int_cnt;

    ADXL345_get(d);

    // Put data into buffer
    if(vars.logging){

    ...

一旦変数にカウント値を取っておき、差分の計算、前回値の更新を行います。

main()関数の最初でフラグをクリアしておきます。

    vars.g_int_update = 0;

最後に、UART経由で定期的に表示する処理です。
main()関数内のwhile(1)ループ内で実施します。
フラグが立っていたら、測定値を表示してフラグをクリアします。
PRINTFは単にprintf()関数に置換されますが、これでUARTへ表示が行われます。printf()関数を使えるようにする方法はまた紹介したいと思います。

    while (1) {

        // Display ADXL345 interrupt interval
        if(vars.g_int_update == 1){
            PRINTF("%8d\r\n", vars.g_int_intvl);
            vars.g_int_update = 0;
        }

測定実施

上記の割り込み間隔測定を入れて見てみました。
いくつかの加速度センサ測定レート設定で試しています。
各10測定分ずつ以下に示します。

3200Hz設定

 810
 810
 809
 810
 810
 809
 810
 810
 810
 810

1600Hz設定

1620
1620
1620
1620
1619
1620
1619
1620
1621
1620

800Hz

3242
3242
3241
3241
3242
3242
3242
3242
3242
3241

それほど測定ごとの変動はありません。
これらはカウント値なので、実際の周期に直すと、

3200Hz設定(周期0.3125ms) : 810 x 400ns = 0.324ms = 1.04 x (設定周期)
1600Hz設定(周期0.625ms) : 1620 x 400ns = 0.648ms = 1.04 x (設定周期)
800Hz設定(周期1.25ms) : 3242 x 400ns = 1.297ms = 1.04 x (設定周期)

となり、いずれも周期が設定より4%ほど長くなっています。

前に加速度ログの解析を行ったときは加速度ログデータを引き伸ばしてGPSデータと一致させたので、それらしい感じがします。

次回へ

記事が長くなったので、続きは次回にします。
次回は、加速度ログに加速度測定間隔の測定値を追加したいと思います。

QCQI Excercise 4.6

量子コンピュータの勉強もしてみています。

教材はこれ。

www.cambridge.org

練習問題も入っていて、ネットで回答例も出ているのですが、Excercise 4.6の回答が見当たらなかったのでここで晒してみます。

回答の方針としては、「ブロッホ球上でn軸中心にΘ回転」というのを

「nに対応する状態とそれに直交する状態を基底とした表現に変換」
→ 「Θ回転する位相ゲートを作用させる」
→「元の基底(|0>, |1>)での表現に戻す」

と考えて、この行列を作ってみます。

一方で、Rn(Θ)もX, Y, Zの行列表現を使って2x2行列で書き下すことができます。
これらを比較して、一致することを確認します。

間違い等ありましたら、優しくご指摘ください。
英語は怪しいです。

f:id:nokixa:20201018025911p:plain

f:id:nokixa:20201018025946p:plain

Mathchaというオンラインの数式エディタで書きました。

www.secat-blog.net

Mathcha上のドキュメントへのリンクも貼っておきます。

https://www.mathcha.io/editor/0Q959FBpt21TzVQ37DhpQdjm4fgYzyYyFmVx2nO

以上。


マイコンと加速度センサでタコメータを作る - 9. 加速度ログ取得変更

前回の記事の続きです。
RXマイコンでの時間計測の仕組みを説明していきます。
ソースコード

GitHub - hubnoki/RXtacho: Designing tachometer using RX220

時間計測処理

RXマイコンのタイマモジュールを使って時間計測をする仕組みを用意しています。これを少し説明します。


マイコンボード回路

まず、今回使用しているRX220マイコンボードには、20MHz、32.768kHzの水晶発振子が載っています。
RXマイコンとの接続は、

  • XTAL(pin7), EXTAL(pin9)に20MHz水晶発振子 (メインクロック)
  • XCIN(pin4), XCOUT(pin5)に32.768kHz水晶発振子 (サブクロック)

現状、20MHzのほうだけ使用しています。


f:id:nokixa:20201005024940p:plain


水晶発振子の型番は特に記載はありませんが、秋月での取り扱いを見ると、50ppmの精度のものかと思います。一般的にも100ppm(=0.01%)程度の精度はあるものかと。精度がこれだけあれば十分です。

表面実装型クリスタル(水晶発振子) 20MHz (4個入): パーツ一般 秋月電子通商-電子部品・ネット通販


RXマイコン機能 -クロック構成

今回使用しているRX220の内部構成は以下のようになっています。

参照 : RX220グループ ユーザーズマニュアル ハードウェア編
以下からダウンロード可能です。

RX220 | ルネサス エレクトロニクス


f:id:nokixa:20201005030659p:plain


先ほどの水晶発振子はこの中の「クロック発生回路」に接続され、マイコンのシステムクロックを生成します。

さらに「クロック発生回路」の内部構成。


f:id:nokixa:20201005031105p:plain


  • 水晶発振子はまずそれぞれ発振回路に接続され、クロック信号を生成します。
  • 2つのクロック信号、およびマイコンが内部で持つクロック(高速オンチップオシレータ、低速オンチップオシレータ)の中から、どの信号を大元のクロックとして使用するか、セレクタで選択します。
  • 分周器に入力されます。1/1、1/2、1/4、… 1/64といった7種類の分周クロックが生成されます。
  • FlashIF用クロック、システムクロック(ICLK)、周辺モジュールクロック(PCLKB、PCLKD)それぞれでセレクタがあり、どの分周クロックを使用するか選択します。

ソースコードのhardware_ilb.cのrx220_init_setup()関数でこのレジスタ設定手順が記述されています。
このコードに至る前に、e2studioで生成されたiodefine.hがインクルードされており、この中にレジスタ定義が記載されています。

Renesas純正のコンパイラと環境を使っていればこのあたりのドライバを生成してもらえますが、今回はgccを使用しているので、自分で作る必要があります。

void rx220_init_setup()
{
    int i;

    ////// Clock settings //////
    SYSTEM.SCKCR.BIT.PCKD = 0x00; // Peripheral clock D divider
    SYSTEM.SCKCR.BIT.PCKB = 0x00; // Peripheral clock B divider
    SYSTEM.SCKCR.BIT.BCK = 0x00; // External bus clock divider
    SYSTEM.SCKCR.BIT.ICK = 0x00; // System clock divider
    SYSTEM.SCKCR.BIT.FCK = 0x00; // Flash IF clock divider

//// Register protect off ////
    SYSTEM.PRCR.WORD = 0xA503;

    //// Main clock ////
    SYSTEM.MOSCCR.BIT.MOSTP = 0x1; // Main oscillator stop
    SYSTEM.MOFCR.BIT.MODRV = 0x0; //
    SYSTEM.MOFCR.BIT.MODRV2 = 0x3; //
    SYSTEM.MOFCR.BIT.MOSEL = 0; //
    // P36 : EXTAL, P37 : XTAL Port

    PORT3.PDR.BIT.B6 = 0;
    PORT3.PDR.BIT.B7 = 0;

    SYSTEM.MOSCWTCR.BIT.MSTS = 0xC; // Main oscillator wait cycle, 0xC : 65536 cycles
    SYSTEM.MOSCCR.BIT.MOSTP = 0x0; // Main oscillator start
    while(SYSTEM.MOSCCR.BIT.MOSTP != 0x0) ; // wait for oscillator to be stable
// SYSTEM.SCKCR3.BIT.CKSEL = 0x2; // Switch clock source, 3 bits, 010 : main oscillator
    SYSTEM.SCKCR3.WORD = 0x200; // SCKCR3 ... bit access was unavailable

    //// HOCO stop ////
    SYSTEM.HOCOCR.BIT.HCSTP = 1;
    SYSTEM.HOCOPCR.BIT.HOCOPCNT = 1;

    //// LPC ////
    SYSTEM.MSTPCRA.BIT.ACSE = 0; // All module clock stop mode enable -> disable
    SYSTEM.MSTPCRA.BIT.MSTPA15 = 0; // CMT unit0 (CMT0, CMT1) enable
    SYSTEM.MSTPCRB.BIT.MSTPB30 = 0; // SCI1 enable
    SYSTEM.MSTPCRB.BIT.MSTPB26 = 0; // SCI5 enable
    SYSTEM.MSTPCRB.BIT.MSTPB25 = 0; // SCI6 enable
    SYSTEM.MSTPCRB.BIT.MSTPB4 = 0; // SCI12 enable


    //// RTC ////
#if USE_RTC == 1

    //使っていないので割愛

#endif

//// Register protect on ////
    SYSTEM.PRCR.WORD = 0xA500;

... まだ続く ...
  • システムクロックコントロールレジスタ(SCKCR)で、FlashIF、システムクロック等のセレクタの設定を行います。すべて1/1(分周なし)を選択しています。
  • クロック発生回路のレジスタ設定の前に、プロテクトレジスタ(PRCR)で書き込みを許可します。

f:id:nokixa:20201005033528p:plain


  • MOSCCR、MOFCR、MOSCWTCRといったレジスタを使用して、メインクロック発振回路を停止→設定→動作開始→安定待ちという手順を踏みます。なお、この間CKSEL(1段目のセレクタ)の設定はデフォルトの0(LOCO選択)となっており、LOCO(低速オンチップオシレータ)で動作します。

f:id:nokixa:20201005035851p:plain


f:id:nokixa:20201005035741p:plain


f:id:nokixa:20201005040015p:plain


f:id:nokixa:20201005033809p:plain


  • メインクロックの発振が安定したら、CKSELを2に設定して、クロックソースをメインクロックに切り替えます。
  • 最後に、PRCRレジスタで書き込みを禁止します。
  • 途中にちょっと別の処理(CMTモジュールイネーブル、SCIモジュールイネーブル)も入ってしまいましたが、ご容赦。

これ以降も、rx220_init_setup()関数では周辺モジュールの初期設定等が続きます。


RXマイコン機能 - CMT0モジュール

RX220には、CMT(Compare Match Timer)モジュールが2つ用意されています。
CMTモジュール1つには16ビットカウンタが2チャネルあります。
今回は、このモジュールで1msごとに割り込みを発生させ、ms単位の時間測定ができるようにしています。
上のrx220_init_setup()関数の続きで、以下のように設定しています。

    //// 1ms timer (CMT0) setting ////
    CMT0.CMCR.BIT.CKS = 0x0;   // count source : PCLK / 8
    CMT0.CMCR.BIT.CMIE = 1;        // interrupt enable
    CMT0.CMCOR = 2499;             // interrupt cycle : PCLK(=20MHz) / 8 / 2499 = 1ms

    CMT.CMSTR0.BIT.STR0 = 1; // CMT0 count start

割り込みルーチンはgenerate/inthandler.cの以下の部分で登録しています。ここで呼んでいる関数については次で説明しますが、これがCMTモジュールの割り込み周期(=1ms)ごとに呼ばれます。

#include "timer_soft.h"
// CMT0 CMI0
void INT_Excep_CMT0_CMI0(void)
{
    timer_soft_int();
}


ソフト処理

割り込みが発生するごとに、カウンタ変数をカウントアップしていき、これで時間を刻みます。
経過時間取得の手順は以下の通りです。

  1. リセット関数を呼ぶ(現在のカウンタ変数値を取得)
  2. カウント数読み出し関数を呼び、リセットからのカウント数を得る

src/timer_soft.cおよびtimer_soft.hにこのあたりを記述しています。

まずカウンタ変数。timer_soft.c内のstatic変数になっています。

static unsigned long t;

タイマのリセット関数とカウント値取得関数です。timer_soft_reset関数で現在のカウンタ変数値を保存します。ここの引数のtmを使って、後でtimer_soft_count関数でリセット実施からのカウント数を取得します。

static unsigned long get_counter();

// tm : timer variable // tm is reset void timer_soft_reset(unsigned long tm) { tm = get_counter(); }

// tm : timer variable // return : count from resetting tm unsigned long timer_soft_count(unsigned long tm) { return get_counter() - tm; }

上記に出てくるget_counter関数は、単にカウンタ変数値を返す関数です。このソースコードを8bitマイコンで使用したことがあり、そのときは単純にはできなかったので、関数化しています。
コメントアウト部にそのコードが残っていますが、割愛します。

前に出てきた、割り込み時に呼ばれるtimer_soft_int関数では、カウンタ変数を無条件でインクリメントしています。0xFFFFFFFFまで行くと、次は0に戻ります。32bitのフリーランカウンタとなります。

static unsigned long get_counter()
{
    return t;
}

ここがちょっとしたポイントで、32bitフリーランカウンタにしておくことで、リセット関数を呼んだ時のカウンタ変数値にかかわらず、単純な引き算だけで最大カウント数の時間計測 (1[ms] * 232 = 4294967.296 [s] ~= 1200[h]) を行うことができます。
このやり方は学生のときのバイトで教わりました。

// increment counter
// must be called by interrupt routine
void timer_soft_int()
{
    t++;
}


次回に続く

今回はここまでにします。
次回は、加速度ログに加速度センサからの割り込み間隔測定値を追加していきたいと思います。

その他

同じRX220ボードを使用されているサイトがあったので、参考でリンクを貼っておきます。

www.elec-hobbyist.com

マイコンと加速度センサでタコメータを作る - 8. 加速度ログ取得変更

前回までの解析を踏まえて、いくつかマイコンプログラムの変更を行います。

もう一度GitHubのリンクを貼っておきます。

GitHub - hubnoki/RXtacho: Designing tachometer using RX220

まずは現状のプログラムを説明してから変更内容を書いていきたいと思います。


ログ取得処理

このプログラムでは、以下のように加速度ログ取得を行っています。

  1. 加速度ログ用のバッファを用意
  2. ユーザのボタン操作でログ取得開始
  3. まずはログ記録用のファイルをSDカード上にオープン、ヘッダを書き込む
    ・ChaN氏のFatFsを使わせていただいています。
    ・FatFsオブジェクトを確保、ファイルオブジェクトも確保、オープンします。
  4. 加速度センサからの割り込みでバッファに書き込み
    ・書き込んだデータが読み出されないうちにバッファを1周してしまった場合、上書きしてオーバランフラグを立てます。
  5. main()関数内で、バッファに新しいデータが入っているのを検知したらファイルオブジェクトに書き込み
  6. ユーザのボタン操作でログ取得終了

これらの処理は、src/RXtacho.cに記述しています。

対応するコードは以下の通りです。


加速度ログバッファ

以下のvarsは内のグローバル変数になっていて、現在の状態を示す色々な変数を入れています。

typedef struct{
    signed short g;
    uint16_t flags;
} LOG_UNIT;

#define N_LOGBUF (2*sizeof(FIX_T)*N_GBUF / sizeof(LOG_UNIT))

static struct{

    ... 省略 ...

    union{
        LOG_UNIT g_buf[N_LOGBUF];
        struct{
            FIX_T g_buf_a[N_GBUF];
            FIX_T g_buf_b[N_GBUF];
        };
    };
    uint8_t        g_buf_sel; // Buffer side to save G sensor data
    uint16_t       g_buf_wp_a;
    uint16_t       g_buf_wp_b;
    uint16_t       g_buf_wcnt_a; // For logging mode
    uint16_t       g_buf_rp;
    uint16_t       g_buf_rcnt; // For logging mode
    uint8_t        g_overrun;
    uint8_t        logging;

    ... 省略 ...

}vars;
  • この中のg_bufが加速度ログ用バッファになっています。
    FFT用のバッファと共用の領域に確保しています。
    RAM容量かつかつなので…
  • 書き込みポインタと読み出しポインタ(g_buf_wp_a, g_buf_wcnt_a, g_buf_rp)を用意していて、これで書き込むべきデータがあること、オーバランが発生していることを検出します。オーバランフラグ(g_overrun)もここにあります。


ユーザのボタン操作でログ取得開始

main()関数内の無限ループでログ取得処理関数のproc_logging()を繰り返し呼んでいます。この関数の引数で、プッシュボタンの状態を知らせます。
フラグ分ける必要はなかったかな…

static void proc_logging(uint8_t spush, uint8_t lpush);

... 省略 ...

int main(void)
{
    char s[50];
    int i;
    unsigned long psw_cnt;
    uint8_t psw_spush, psw_lpush;

//---------------------------------------------------------

... 初期化処理色々 ...

    while (1) {

        //// Serial commands ////
        ... UARTコマンド受信処理 ...

        //// Check for PSW ////
        ... プッシュスイッチ状態確認 ...
        詳細は省略するが、psw_spush(短プッシュ)、psw_lpush(長プッシュ)フラグをセットする
        
        //// Process for each mode ////
        //---------
        if(vars.mode == MODE_LOGGING){
            proc_logging(psw_spush, psw_lpush);
        }
        //---------
        else if(vars.mode == MODE_FFT){

        ... まだ続く ...


ファイルオープン、ヘッダ書き込み

ログ取得を行っていないときに短プッシュされると、ログ書き込み用のファイルをオープンして、ヘッダを書き込みます。
また、加速度センサの動作を開始します。
先ほどのproc_logging()関数に記述してあります。


//-------------------------------------------------------------------------------------------
// Process for logging mode 
//-------------------------------------------------------------------------------------------
static unsigned long tm, tmax; // For measuring process time

static void proc_logging(uint8_t spush, uint8_t lpush)
{
    ... 省略 ...

    if(vars.logging == 0){
        // Start logging
        if(spush){
            fres = 0;

            fres |= get_file_sqno("adxl345_log_*", &(vars.log_sqno));
            fres |= (mount_SD(&(wk.fatfs)) != FR_OK);

            if(vars.log_format == LOG_FMT_CSV)
                sprintf(wk.fname, "adxl345_log_%03d.csv", vars.log_sqno);
            else
                sprintf(wk.fname, "adxl345_log_%03d.bin", vars.log_sqno);
            fres |= (f_open(&(wk.fl), wk.fname, FA_WRITE | FA_CREATE_NEW) != FR_OK);

            lcdc_fill_area(LCDC_BLACK, 0, LCDC_ROW-1, 0, LCDC_COL-1);
            if(fres){
                lcdc_puts("File open failed", LCDC_WHITE, 0, 0);
                lcdc_puts("Short push to start", LCDC_WHITE, 0, 10);
            }
            else{
                sprintf(wk.fname, "Logging ... (%d)", vars.log_sqno);
                lcdc_puts(wk.fname, LCDC_WHITE, 0, 0);
                lcdc_puts("Short Push to stop ", LCDC_WHITE, 0, 10);

                // Log file header
                if(vars.log_format == LOG_FMT_CSV){
                    fres = (
                        f_printf(&(wk.fl), "rate:%d, range:%d\r\n"
                            , value_list_rate[vars.cur_value_idx[STG_RATE]].val
                            , value_list_range[vars.cur_value_idx[STG_RANGE]].val
                        )
                    == EOF);
                }
                else{
                    // Header identifier
                    tmp = 'LOGH';
                    fres |= f_write(&(wk.fl), &tmp, sizeof(int), &btw);
                    // Header version
                    tmp = 1;
                    fres |= f_write(&(wk.fl), &tmp, sizeof(int), &btw);
                    // Header contents size (in bytes)
                    tmp = 8;
                    fres |= f_write(&(wk.fl), &tmp, sizeof(int), &btw);
                    // Header contents
                    // Rate setting
                    tmp = value_list_rate[vars.cur_value_idx[STG_RATE]].val;
                    fres |= f_write(&(wk.fl), &tmp, sizeof(int), &btw);
                    // Range setting
                    tmp = value_list_range[vars.cur_value_idx[STG_RANGE]].val;
                    fres |= f_write(&(wk.fl), &tmp, sizeof(int), &btw);
                }
                vars.logging = 1;
                vars.g_buf_wp_a = 0;
                vars.g_buf_wcnt_a = 0;
                vars.g_buf_rp = 0;
                vars.g_buf_rcnt = 0;
                adxl345_start();
            }

            ... 続く ...


バッファ書き込み

加速度センサからのピン割り込みでバッファへの書き込みを行います。
読み出しポインタも確認して、オーバランフラグをセットします。 ピン割り込みの使い方、加速度センサの設定はまた余裕があったら説明します。
下のadxl345_int_routine()関数は、割り込み発生時に実行されるよう設定してあります。

void adxl345_int_routine()
{
    signed short d[3];
    FIX_T f;
    LOG_UNIT log_d;

    ADXL345_get(d);

    // Put data into buffer
    if(vars.logging){

        log_d.g = d[vars.axis_sel];
        // Flags
        //     overrun - Overrun occurred, overwriting data which are not read
        log_d.flags = (vars.g_overrun = (vars.g_buf_wcnt_a - vars.g_buf_rcnt) >= N_LOGBUF-1);

        vars.g_buf[vars.g_buf_wp_a] = log_d;

        if(++vars.g_buf_wp_a >= N_LOGBUF)
            vars.g_buf_wp_a = 0;
        vars.g_buf_wcnt_a++;
    }
    else{

        ... FFTモード用の処理 ...

    }
}


ファイル書き込み

前に出たproc_logging()関数の続きです。

  • 書き込みポインタと読み出しポインタを比較して、一致していなければデータ書き込みが行われたと判断し、ファイルオブジェクトへの書き込みを行います。
  • ファイル関係のエラー処理も行っています。SDカードを使用しているので、途中で抜けてしまったりというエラーが考えられます。エラーが出たらログ記録を停止します。
… 続き…

        // Continue logging
        else{
            td = timer_soft_count(&tm);
            timer_soft_reset(&tm);
            if(td > tmax)
                tmax = td;

            if(vars.g_buf_wcnt_a != vars.g_buf_rcnt){ // There are rest of data to read
                ovrn = vars.g_overrun;
                if(vars.log_format == LOG_FMT_CSV){
                    fres = (
                            f_printf(&(wk.fl), "%d,%d\r\n"
                            , vars.g_buf[vars.g_buf_rp].g, vars.g_buf[vars.g_buf_rp].flags)
                        == EOF);
                }
                else if(vars.log_format == LOG_FMT_BIN){
                    fres = f_write(&(wk.fl), &(vars.g_buf[vars.g_buf_rp]), sizeof(LOG_UNIT), &btw);
                }

                if(ovrn == 1){
                    vars.g_buf_rp = vars.g_buf_wp_a;
                    vars.g_buf_rcnt = vars.g_buf_wcnt_a;
                    vars.g_overrun = 0;
                }
                else{
                    if(++vars.g_buf_rp >= N_LOGBUF) vars.g_buf_rp = 0;
                    vars.g_buf_rcnt++;
                }

                if(fres){ // f_write() or f_printf() failed
                    adxl345_stop();
                    vars.logging = 0;
                    f_close(&(wk.fl));
                    umount_SD();
//                 lcdc_puts("File write failed", LCDC_WHITE, 0, 0);
                    sprintf(wk.fname, "File write failed:%d", fres);
                    lcdc_puts(wk.fname, LCDC_WHITE, 0, 0);
                    lcdc_puts("Short push to start", LCDC_WHITE, 0, 10);

                }
            }
        }
    }

}


ログ取得終了

ログ取得中に短プッシュでログ取得を終了します。終了時、ファイルを閉じてSDカードをアンマウントします。

        // Stop logging
        if(spush){
            adxl345_stop();
            vars.logging = 0;
            f_close(&(wk.fl));
            umount_SD();

            PRINTF("Max process interval : %d\r\n", tmax);

            vars.mode_init = 1; // Return to the initial step
        }


次回に続く

長くなってしまったので、今回の記事はここまでにします。
次回は、現状すでに時間計測の仕組みが入っているので、これを説明したいと思います。

マイコンと加速度センサでタコメータを作る - 7. 回転数推定の検証

前回の回転数推定について、再確認してみます。

まず結論

結論から言って、前回の回転数推定がGPSデータと合っていなかったのは、私の勘違いでした…
/(^o^)\ナンテコッタイ

maxの使い方を勘違いしていました。
正しくは、以下のようにピーク周波数を出します。

>> y = [1:512]/512/gData(1,1);
>> [maxAmp iMax] = max(abs(gFFT(3:200,:)));
>> maxFreqRpm = y(iMax)*60;

ここでは、[rpm]の単位でピーク周波数列を出しました。
これを使って、前回と同様のグラフを作ってみると、

>> gear1 = maxFreqRpm * 5.01*10^-3;
>> gear2 = maxFreqRpm * 7.85*10^-3;
>> gear3 = maxFreqRpm * 1.05*10^-2;
>> gear4 = maxFreqRpm * 1.32*10^-2;
>> gear5 = maxFreqRpm * 1.61*10^-2;
>> plot(x, gear1, x, gear2, x, gear3, x, gear4, x, gear5, vel(:,1), vel(:,2), "*-");
>> axis([0 400 0 100]);
>> xlabel("t[s]");
>> ylabel("velocity[km/h]");
>> xticks([0:10:400]);

f:id:nokixa:20200927191240p:plain

点マーカ付きの線がGPSデータからの速度データです。 まだ合っているようには見えませんが、時間軸位置を調整すれば合いそうに見えます。 260[s]あたりでのGPSデータ落ち込みと320[s]あたりの加速度データ落ち込みが重なるようにしてみます。

GPSデータの260[s]近辺で極小値を取る時刻を探します。

>> find((vel(:,1) > 250) & (vel(:,1) < 270))
ans =

   210
   211
   212
   213
   214
   215
   216
   217
   218
   219
   220
   221
   222
   223
   224
   225
   226
   227
   228

>> [minVal minIdx] = min(vel(ans,2))
minVal =  21.600
minIdx =  11

>> ta_vel = vel(ans(minIdx),1)
ta_vel =  261

同様に、FFTデータからの推定速度の、320[s]付近の極小値時刻は、

>> find((x > 315) & (x < 325))
ans =

    985
    986
    987
    988
    989
    990
    991
    992
    993
    994
    995
    996
    997
    998
    999
   1000
   1001
   1002
   1003
   1004
   1005
   1006
   1007
   1008
   1009
   1010
   1011
   1012
   1013
   1014
   1015

>> [minVal minIdx] = min(gear1(ans))
minVal =  1.8788
minIdx =  11
>> ta_gear1 = x(ans(minIdx),1)
ta_gear1 =  318.40

GPSデータのほうの時刻を基準にして横軸を合わせてみたいと思います。

>> plot(x2, gear1, x2, gear2, x2, gear3, x2, gear4, x2, gear5, vel(:,1), vel(:,2), "*-");
>> axis([0 400 0 100]);
>> xticks([0:10:400]);
>> xlabel("t[s]");
>> ylabel("velocity[km/h]");

f:id:nokixa:20200928000540p:plain

合わせた部分(t = 260[s]付近)とその前の部分が合うようになってきました。
ただ、加速し始めの部分が少しずれているように見えます。 これは加速度センサの測定周波数誤差によるものではと考えられます。これを補正してみます。

Ւ>> find((vel(:,1) > 160) & (vel(:,1) < 165))
ans =

   122
   123

>> [minVal minIdx] = min(vel(ans,2))
minVal =  7.9000
minIdx =  1
>> tb_vel = vel(ans(minIdx),1)
tb_vel =  163
>> tb_gear1 = 170
tb_gear1 =  170
>> tRatio = (tb_vel - ta_vel) / (tb_gear1 - ta_vel)
tRatio =  1.0769
>> x3 = (x2 - ta_vel) * tRatio + ta_vel;
>> plot(x3, gear1/tRatio, x3, gear2/tRatio, x3, gear3/tRatio, x3, gear4/tRatio, x3, gear5/tRatio, vel(:,1), vel(:,2), "*-");
>> axis([0 400 0 100]);
>> xticks([0:10:400]);
>> xlabel("t[s]");
>> ylabel("velocity[km/h]");

f:id:nokixa:20200928004617p:plain

FFTデータでは、加速し始め部分は170[s]に見えるので、特に極小値時刻探しせずに値を入れています。
また、加速度の測定周波数に誤差がありそうということなので、FFT結果の周波数も修正が必要になります。 ここでは、tRatio(実際の測定周期/当初想定した測定周期)で割っています。

まあまあ合ってきましたが、まだ微妙なところが多いです。

  • t = 210[s]~250[s]では、4速ギアでの推定速度がよく合っています。
  • t = 170[s]~210[s]は4速ギアと5速ギアの推定のどちらが正しいのか、微妙です。
  • t = 90[s]~130[s]はどのギアも実際の速度より低い推定値になっています。
  • t = 80[s]~90[s]あたりの落ち込みではGPSデータとFFTデータが合っていません。もしかしたら周波数誤差も時間ごとに変動があるのかもしれません。

課題

やっぱり加速度センサの測定周波数誤差を補正できるようにする必要がありそう。
加速度ログに、CPUクロックで測定した測定周波数を入れておけば後で分析できるかと思っています。

一度マイコンプログラムを変更して、再度データ取りをしてみたいと思います。



以下、この間違いに気づく前に調べたことを掲載します。
今回の速度推定方法が間違っていなさそうということが確認できました。



バイク駆動系の仕組み

前回、回転数→速度の変換を計算サイトに頼りましたが、一度自分で仕組みを調べてみます。

以下がいい参考になりました。

www.virginharley.com

www.youtube.com

youtu.be

motorcycle-aroundtheworld.com

エンジンの回転からリアタイヤまでの伝達経路は以下の通りです。

  • エンジンのピストンが往復運動する
  • クランクシャフトが回転
    • この回転数(1分あたりに何回転するか)が"回転数"となると考えられます。
  • プライマリードライブギアが回転
  • クラッチが回転
    • このギア比が"1次減速比"
  • ミッションで変速される
    • ギアごとに変速比が決まっています。
  • ドライブスプロケットが回転
  • ドリブンスプロケットが回転
    • このギア比が"2次減速比"
  • リアタイヤが回転する

前回の回転数→速度の変換サイトを見ると、これらの要素が考慮に入っているようです。

ドラッグスター250の関連スペックは以下の通り。

項目
1次減速比 3.130
2次減速比 2.800
変速比(1速) 2.642
変速比(2速) 1.684
変速比(3速) 1.260
変速比(4速) 1.000
変速比(5速) 0.821
リアタイヤ断面積 130mm
リアタイヤ扁平率 90%
リアタイヤリム径 15インチ

確認してみると、

  • リアタイヤの外周長は、(15[inch] x 25.4 + 130[mm] x 0.9 x 2) x π = 1932[mm]
  • 1速の場合、速度[km/h]と回転数[rpm]の比は、
    60(/min→/h) / 3.13 / 2.642 / 2.800 x 1932 / 10^-6(mm→km) = 5.01 x 10^-3

となり、変換サイトに記載されていたものと一致します。
ということで、この変換比については問題なさそうです。

加速度波形の再確認

加速度の時間波形を確認してみたいと思います。

まず、対応が分かりやすくなるように、加速度データをFFTデータと同じ形に直します。

>> gFFTLen = size(gFFT)(2);
>> gData2 = reshape(gData(1:512*gFFTLen, 2), 512, gFFTLen);

これで、gFFT(:,i)gData2(:,i)が対応するデータ(gFFT(:,i)の元になったのはgData2(:,i))となります。

もう一度FFTグラフを掲載します。

f:id:nokixa:20200908075438p:plain

このグラフでは、t = 250[s] あたりのFFT波形が素直な形に見えます。
加速時のぎざぎざも過ぎた後なので、5速で安定して走っている状態ではないかと思われます。
ここの加速度波形を見てみます。

>> find((x > 250) & (x < 251))
ans =

   782
   783
   784

まず、t = 250[s]にあたるのは783番目あたりのデータになるようです。

>> figure(2);
>> plot(gData(1:512,1),gData2(:,783), "*-");
>> xlabel("t[s]");
>> ylabel("Accel[g]");

前のグラフを残しておくために、figure(2)でもう一つ別のグラフウィンドウを用意しました。
横軸目盛り表示のため、gDataの1列目を使用しています。
グラフは以下の通り。

f:id:nokixa:20200923021450p:plain

きれいな繰り返し波形が得られました。
この波形を見ると、およそ0.3秒の間に20個の山が見られます。すると、

周波数はおよそ60Hz
→ 回転数3600[rpm]
→ 5速ギアとすると速度は 3600 x 1.61 x 10^-2 = 58.0[km/h]

と速度推定ができます。

GPSデータで、この時刻周辺の速度を確認してみます。
(ここでも前回と同じデータを使います。時刻基準が19秒ほどずれていたので、これも考慮します。)

>> find((vel(:,1) > 240-19) & (vel(:,1) < 260-19))
ans =

   181
   182
   183
   184
   185
   186
   187
   188
   189
   190
   191
   192
   193
   194
   195
   196
   197
   198
   199

>> vel(ans,:)
ans =

   222.000    52.900
   223.000    54.000
   224.000    51.800
   225.000    51.500
   226.000    51.500
   227.000    51.100
   228.000    50.000
   229.000    51.500
   230.000    51.800
   231.000    53.600
   232.000    53.600
   233.000    54.000
   234.000    54.000
   235.000    52.900
   236.000    52.200
   237.000    51.100
   238.000    49.000
   239.000    50.400
   240.000    50.000

これを見てみると、速度推定はそこそこ合っていそうです。

FFT波形確認

時刻t = 250[s]あたりのFFT結果を確認してみます。

>> y = [1:512]/512/gData(1,1);
>> plot(y(1:200), abs(gFFT(1:200,783)), "*-");
>> xlabel("Freq[Hz]");

ここで登場したyは、以前にFFTグラフ表示を行ったときに使ったものと同じで、周波数軸方向のインデックスに対応する周波数値を示します。
グラフは以下の通り。

f:id:nokixa:20200923025330p:plain

1点目、2点目で大きな値が出ているのはDCバイアス(重力による定常的な測定値)および窓関数によるものと考えられます。
これを除くと、やはり60[Hz]あたりでピークが出ているようです。
確認してみます。

>> [m i] = max(abs(gFFT(3:end,783)))
m =  174.60
i =  20
>> y(20)
ans =  62.500

ピーク周波数62.5[Hz]と出ました。
問題なさそうです。

以上

今回の記事は以上です。
次回はマイコンプログラム変更と、できれば再データ取りになります。

マイコンと加速度センサでタコメータを作る - 6. 回転数推定

前回の加速度ログを取ったとき、一緒にGPSのログをスマホのアプリで取っていました。
エンジンの回転数は速度と比例関係にあるかと思います(クラッチを切っているときを除く)。
これを使って、回転数の推定が正しいかどうか確認してみます。

GPSログ

いつもこのアプリでツーリングのログを取っています。

ZweiteGPS

ZweiteGPS

  • SENSYUSYA
  • ナビゲーション
  • 無料
apps.apple.com

いくつか設定項目がありますが、今回大事なこととして、

  • 速度も記録する
  • 記録の間隔はできるだけ短く(最短1秒まで設定できました)

という2点があります。

f:id:nokixa:20200912194433j:plain:w300 f:id:nokixa:20200912194437j:plain:w300

速度は、GPSの位置情報さえ記録できていれば計算はできると思いますが、計算しておいてもらえればそれが楽です。

GPSログデータ

ZweiteGPSで取ったログは、いくつかの形式で出力できます。

等々。

CSV形式が一番簡単に解析できますが、出力したものを見てみると、位置情報しか残っていませんでした…

KML形式データをCSV変換するのが一番やりやすそうでした。

変換は以下のサイトを使わせてもらいました。

www.chizroid.info

こんな感じでCSVデータが得られました。

f:id:nokixa:20200912223036p:plain

ここから、時刻と速度のデータを取り出します。

f:id:nokixa:20200912223203p:plain

テキストエディタでとりあえず開きました。
<br />をカンマに置換すれば処理しやすそうです。

f:id:nokixa:20200912223912p:plain

置換後、エクセルで開きました。
ここから時刻と速度の列だけ取り出します。

色々と加工して、最終的に以下のようなCSVデータを作成しました。

t[sec],速度[km/h]
0,0
14,1.1
18,1.1
21,1.8
24,1.8
29,1.4
34,2.5
35,4
36,7.2
37,7.6
38,6.8
39,7.2
40,5
42,14.8
43,19.1
44,21.6
...

グラフにするとこんな感じ。

f:id:nokixa:20200913195354p:plain

※このGPSログは、目的地(いつも行くカフェ)からの往路全体を記録しています。前回の加速度ログは、この往路の一部だけで記録しています。

停止しているところは1秒間隔ではデータ記録していないようです。

このデータと回転数データを比較していきます。

回転数と速度の関係

エンジンが回転してから動力がホイールに伝わるまで、色々とギヤ等が間に入ってきます。タイヤの径も速度に影響してきます。
幸い、車種ごとに回転数 ⇔ 速度の変換表を出してくれるサイトがありました。

nokubi.jp

この表から、ギアごとの回転数 ⇔ 速度の関係が出てきます。

ギア 速度[km/h] / 回転数[rpm]
1速 5.01 x 10-3
2速 7.85 x 10-3
3速 1.05 x 10-2
4速 1.32 x 10-2
5速 1.61 x 10-2

グラフ作成


※9/28記載
以下のグラフ作成は間違いがありました。
読まないでください… (´;ω;`)
次回の記事で正しくグラフ作成&検証しています。


前回までのFFT結果から、時間ごとのピーク周波数を抜き出してみます。これが回転数に対応ことになるかと思います。

まずは前回と同様のデータ取り込み。

>> [gData ovrn] = read_adxl345_log_bin_2byte("adxl345_log_002.bin");
Header detected, Ver:1, Size:8
Rate:1600[Hz], Range:2[G]
>> gFFT = fft_slice(gData(:,2), 512);
>> x = gData(512:512:end,1);

次に、maxを使って、時間ごとのピーク周波数を出します。
maxは、2次元配列に使うと、列ごとの最大値を持った行ベクトルを返します。
今回で言うと、gFFT(freq, t)からgPeak(t)が生成できます。
最終的に列ベクトルにしておきたいので、.'を付けて転置します。

>> gPeak = max(abs(gFFT)).';

plotでプロットします。

>> plot(x, gPeak);
>> xlabel("t[s]");
>> ylabel("freq[Hz]");

f:id:nokixa:20200916013242p:plain

ちょっと汚いですね…
とりあえずこれで進めることとします。

これに、先ほどの回転数→速度の係数を全ギアについてかけてみます。

>> gear1 = gPeak * 60 * 5.01*10^-3;
>> gear2 = gPeak * 60 * 7.85*10^-3;
>> gear3 = gPeak * 60 * 1.05*10^-2;
>> gear4 = gPeak * 60 * 1.32*10^-2;
>> gear5 = gPeak * 60 * 1.61*10^-2;

60を掛けているのは、[Hz] → [rpm]の変換のためです。

これに速度グラフを重ねます。
まず先ほど作ったGPS速度データのcsvファイルを読み込みます。

>> vel = dlmread("20200816-154637.json_exp_2.csv", ",", 1, 0);

ギアごとの推定速度と、GPSからの速度をplotでグラフ化します。

>> plot(x, gear1, x, gear2, x, gear3, x, gear4, x, gear5, vel(:,1), vel(:,2));

f:id:nokixa:20200918000143p:plain

色々気になるところがありますが、まず時間軸範囲を加速度データがある部分だけにします。

>> axis([0 400 0 300]);

f:id:nokixa:20200918000808p:plain

時間軸の基準を特に合わせるようなことをしていないので、調整が必要です。
グラフを見たところ、なんとなく走り出しの時刻で合わせられそうな気がします。
グラフを拡大して見てみます。

>> plot(x, gear1, "*-", x, gear2, x, gear3, x, gear4, x, gear5, vel(:,1), vel(:,2), "*-");
>> axis([0 50 0 100]);
>> xticks([0:1:50]);

f:id:nokixa:20200918013021p:plain

マーカーも付けるようにしました。
また、グラフウィンドウの"グリッド"をクリックして、グリッド表示もしました。横軸刻みも変えました。
加速度データでは21秒、GPS速度データでは40秒に動き始めているようなので、GPS速度データの時刻を19秒分引いてやれば良さそうな感じです。

>> plot(x, gear1, "*-", x, gear2, x, gear3, x, gear4, x, gear5, vel(:,1) - 19, vel(:,2), "*-");
>> axis([0 400 0 300]);

f:id:nokixa:20200918013806p:plain

こんなグラフになりましたが、1速での推定からしGPS速度データを超えています。

原因として今考えられるのは、

  • 加速度センサIC(ADXL345)のタイミングで加速度サンプリングしてもらっていますが、これがどれほどの周期精度を持つのか、要確認です。
    グラフを見ても、最初のほうの時刻では加速度データとGPSデータが合っているようですが、後半の急に落ち込んでいるところの時刻がずれているように見えます。 CPUクロックはちゃんとした水晶を使っているので、これで補正することを考えます。
  • Vツインだから? ドラッグスター250のエンジンはVツインなので、ピーク周波数が実際の回転数の2倍のところに出るのかもしれません。

それでもギア5速としたときの推定速度が200km/hまで行っているのが不可解…

続く

まだ解析の余地があると思うので、同じデータで引き続き解析してみます。

マイコンと加速度センサでタコメータを作る - 5. 実データ解析

グラフ表示改善

前回のFFTグラフが見づらかったのは、どうもpcolorのエッジ表示によるものだったようです。

こちらはpcolorをそのままやったときの表示。

f:id:nokixa:20200908010919p:plain

z+ボタンをクリックして拡大してみると、

f:id:nokixa:20200908074424p:plain

ちゃんと見えます。どうも黒い境界線のせいで見えにくくなっていたように思えます。

対策

以下のようにすれば境界線を消せるようです。

Removing grid/edge lines in pcolor() figure - MATLAB Answers - MATLAB Central

>> h = pcolor(x, y(3:128), abs(gFFT(3:128,:)));
>> set(h, "edgecolor", "none");
>> colorbar

f:id:nokixa:20200908075438p:plain

ちゃんと見えるようになりました。
凡例も出しておきました。

これを見ると、やはり回転数を調べるにはピーク周波数だけ取れば良さそうに見えます。

以上

回転数の推定のほうを進めていきたいと思います。