dsPIC33CK sin演算スピード・・・ドタバタ顛末記

カテゴリー: PIC,ソフトウェア  タグ: /

dsPIC30F/33EPからdsPIC33CKへ移行しようとしています。「sinテーブルを演算させるルーチンで意外なほど演算時間がかかった」、という投稿を最初にしたものの、完全に自分のミスであることが判明したので、投稿を全面改版し、ドタバタ顛末記として再投稿です。

事の発端は、パワーオン時にsinテーブルの計算をさせたこと

これは「パワーオンですぐに動作させる回路」プログラミング時、冒頭で、

void calc_sintbl(void) {
    double dval0;
    int i;
    for(i=0;i<SINDIV;i++) {
        dval0 = sin((double)i/SINDIV*3.1415926*2.0);
        sinbuf[i] = (int)(dval0*10000.0);
    }
}

という関数でRAM上に1サイクルsinカーブのint配列を作らせています。SINDIV:200で1サイクル200個のデータです。

これを50MIPSのdsPIC33CK256MP503に実装してみたところ、なんと300ms近くもかかった、ということです。

目的の機器はスイッチを押して動作開始に300msタイムラグがあるので、なんとも「もっさり」した体感になってしまいます。

原因は、クロック初期化の前にグローバル変数初期化してたこと

その中に、sinテーブル作成が入っていたのである。

PICは、PLL有効化等を、コンフィグレーションではなくプログラム起動後に行うことになっています。今回は内蔵8MHzFRCを×12.5して50MIPSにしています。maxの100MIPSにしないのは、そこまで必要ないのと消費電流が半分ぐらいだからです。

int main(void) {
    initIO();
    initParam(); // この中でsinテーブル作成を呼んでいた
    initCLK();
    ・
    ・

したがって、このクロック初期化の前の処理は4MIPSで実行されることになり、sinテーブル計算もその速度になっていた、というのが原因です。

<<<判明した経緯>>>

まず初期化をいじらず、sinテーブル演算ルーチンでパルスを出しオシロで観測。メインループでもsinテーブル演算を繰り返したら、なんと最初の一回目だけ長いのが判明した。・・・なぜだ?。思い込みもありコンフィグレーションをいじるなど迷走しながらここから数時間悩んでしまった。そして解決。

改善して実測したら実行時間は19.3msだった!

「とにかくsinテーブル作成を先頭でやらせよう」という思いが強かったための単純ミスです。まあ、これまでも「ポート設定だけは最初にしたい」だの、「変数初期化だけは先頭で・・・」となりがちなところが確かにありました。これからは何があってもクロック初期化再優先で行きます。

原因が判明する前やったこと、armと比較

昨今ベクトル制御で20KHzで当たり前のように三角関数演算する時代ですが、それでもsin演算って意外とかかるものだなぁ、と思いながらarmと比較するため実測してみました

 ・STM32F446RE 180MHz(Nucleo-F446RE):2.99ms

 ・STM32F308K8 72MHz(Nucleo-F303K8):10.14ms

環境はmbedオンライン開発環境です。カリカリに最適化されたコンパイラです。

実行時間の差は単純にクロック差と、M4とM3の違いのようです。F446REはFPU持っていますが単精度なのでmbed開発環境でFPUは使用していないと思われます。

この時は、クロックの違いあれど、armは早いなぁ、と勝手に思い込んでいました。

原因特定前の解決策→ROMテーブル化

ROMテーブルにして解決。dsPIC33CK256MP503は256KBもフラッシュ持つので余裕です。ただ、波形を台形や矩形にアレンジしたいときにテーブルを用意しなければならないのが欠点といえば欠点です。

結局、今回のアプリでは解決後もRAMテーブル方式に戻さずROMテーブル方式で解決、ということにしてしまいました。

sinテーブル

ちなみに、Excelで計算した値をCソースに埋め込むには、

  • Excelのその行を選択してクリップボードへコピー
  • 秀丸エディタに貼りつけ。改行で区切られた数値が並ぶ。
  • 正規表現で「\r」を「,」に全置換
  • 20とかで改行するようにキーマクロで操作。例えば”,”を検索しておきカーソルを先頭に持っていく→マクロ記録開始→F3を20回→右カーソル”→”を一回→改行→マクロ記録終了。つぎにShift+F2を必要回数押す。です。

数百データ程度なら、この方法でやっています。

IF誌の「軽量sin計算アルゴリズム」を試してみる

せっかくなので、高速sinアルゴリズムを試して今回の顛末とします。2017.8号のIF誌の三上直樹氏の記事、ミニマックス多項式近似のサンプルをつかわせていただき、dsPIC33CKへ実装。

// 三上氏作成のミニマックス多項式近似によるsin計算式をつかわせてもらう int32_t labs(int32_t x) {
    if (x<0) return -x; else return x;
}

// 丸めのために使う
inline float ForRound(float x) {
    return (x > 0) ? 0.5f : -0.5f;
}

// float 型の引数を int16_t 型の Q14 に変換する
inline int16_t ToFixed14(float x) {
    return (int16_t)(x*16384.0f + ForRound(x));
}

// float 型の引数を int16_t 型の Q15 に変換する
inline int16_t ToFixed15(float x) {
    return (int16_t)(x*32768.0f + ForRound(x));
}

// float 型の引数を int16_t 型の Q29 に変換する
inline int32_t ToFixed29(float x) {
    return (int32_t)(x*32768.0f*16384.0f);
}

// 下位ビットを丸めて右に 14 ビットシフトする
inline int32_t Round14(int32_t x) {
    return (x + 0x2000) >> 14;
}

// 下位ビットを丸めて右に 154 ビットシフトする
inline int32_t Round15(int32_t x) {
    return (x + 0x4000) >> 15;
}

inline int32_t Sin16(int32_t x) {
    int32_t SIN_A1_ = ToFixed29( 0.57079101); // a1 - 1
    int32_t SIN_A3_ = ToFixed29(-0.64589285); // a3
    int32_t SIN_A5_ = ToFixed29( 0.07943434); // a5
    int16_t SIN_A7_ = ToFixed15(-0.00433310); // a7
    if (labs(x) > 0x4000) x = 0x8000 - x; // x ← 2 - x ※1
    int32_t x2 = Round14(x*x);
    int32_t acc = Round14(Round14(Round14 (SIN_A7_*x2 + SIN_A5_)*x2 + SIN_A3_)*x2 + SIN_A1_)*x;
    return Round15(acc) + x;
}

void calc_sintbl_fix(void) {
    int i,ival;
    long lval;
    long th;
    th=0; for(i=0;i<SINDIV;i++) {
        lval = Sin16(th);
        sinbuf_f[i] = lval*28508/32768; // 87% th += 327L;
    }
}

変数のレンジをそのまま使ったので2πが65536です。16ビットMCUなのでint16_tの挙動でうまくいかず※1でオーバーフロー起こすので、やむなく各所にint32_tを多用しました。

それでも、実行時間は約半分の8.68msに短縮されました。また誤った情報を拡散しないためにも、確認ためdsPICからUART経由でデータをPCに転送しグラフ化してsinカーブを確認ました。

厳密にいえば、1サイクル分解能65536を200分割するのは上記コードでは無理で、327を200回加算すると135ほどずれています。256にするとか、ジッタ覚悟でfloatで加算する必要があります。あくまで速度差の参考用です

まとめ

dsPIC33CKは符号付乗算器を持ち、32ビット乗算をサポートしています。コンパイラでどこまでサポートするかは未確認ですが、演算性能も上がっているようです。と、手のひらを返したように持ち上げることにします。

sin演算に限れば、dsPIC33CK はクロック相対で比較すると32ビットMCU Cortex-M3の70%ぐらいのパフォーマンスということになります。

dsPIC30F/33EPからの移行は粛々と進めることにします

以上