PR

CMSIS_DSPのFFTが失敗するときの対処法

FFT失敗するんだけど?

こうした悩みを解決します。

立プロ

新卒でメーカーに入り、10年間組み込みの現場で設計を行う。
今は個人事業主として自作の組み込み機器開発や、エージェント様に紹介いただき業務委託を行っています。
C,C#,JavaScript, Vue, PHP, VBA, GAS, Kotlinなど、扱う言語が増えゆく日々。

立プロをフォローする

CMSISとは

CMSISライブラリとは、Armが提供するCortex-Mプロセッサー用の共通のソフトウェアインターフェースのことです。
CMSISライブラリを使うと、デバイスに依存しないハードウェア抽象化や、DSPやニューラルネットワークなどの高度な機能を簡単に利用できます。

とくにCMSIS_DSPは、三角関数や行列演算、フィルタやFFTなどの機能があります。関数群はこちらをどうぞ。
https://arm-software.github.io/CMSIS-DSP/v1.12.0/group__RealFFT.html

で、私は今回FFTをやりたくて、CMSIS_DSPを使ったわけですが、動かないのです。。。

その原因と対策についてまとめます。

海外で質問に出ていたこの件に似ています。

arm_rfft_fast_f32() fails with 128 points but works with 256 or 512?
...

エラーの原因

float_t wave[256] = ~~~;
int length = 256;

arm_rfft_fast_instance_f32* instance = (arm_rfft_fast_instance_f32*)malloc(sizeof(arm_rfft_fast_instance_f32));
arm_rfft_fast_init_f32(instance, length);
arm_rfft_fast_f32(instance, wave, wave, 0);

のように、arm_rfft_fast_init_f32()の第一引数にFFTインスタンスを表す構造体、lengthにはFFTの長さ(2のべき乗)が入ります。

ここでやってるのはFFTインスタンスの初期化です。

つづいて、arm_rfft_fast_f32()は第一引数にFFTインスタンス、第2引数は入力データ、第3は出力データ、第4は実数か複素数かの選択。

要するにここでFFTが実行されて、第3引数の出力データに値が格納されるというもの。

で、arm_rfft_fast_f32()を実行するとエラーで固まるんですよね。

入力波形が悪いのか?入力と出力に同じ配列の先頭アドレスを入れてる(=同じ配列が入出力を兼用)のが悪いのか?

いろいろやってみた結果、分かりました。

標準状態では128データの長さにしか対応していない!!

私は256データでFFTをしたかったのですが、CMSIS_DSPは標準では128データ長のFFTインスタンスしか作れないようになっていたのです。

つまり、arm_rfft_fast_init_f32()で長さ128以外を選択するとFFTインスタンスが生成されておらず、空っぽのFFTインスタンスをarm_rfft_fast_f32()に流すとエラーになって固まっていたという状況でした。

ちなみに、私が使っているのはmodustoolboxという開発環境(PSoC6マイコンに対応)です、さきほど上で紹介した英語の質問ではSTM32マイコンを使った開発でも起こっていたので、マイコンや開発環境ではなく、CMSIS_DSPライブラリ固有の問題だと思います。

FFTインスタンスが生成されたか確認できます

    if(arm_rfft_fast_init_f32(rfft, 256) == ARM_MATH_ARGUMENT_ERROR){
    	printf("error"); //init失敗したらエラーを返す
    }

このようにインスタンス初期化の戻り値がARM_MATH_ARGUMENT_ERRORだったら失敗しています。

成功していたらFFTインスタンスが返ります。

解決方法

デバッグしながらarm_rfft_fast_init_f32()やarm_rfft_fast_f32()を見ていると、128以外のFFTインスタンスの生成や処理ができるよう関数は存在するのですが、define使ってはじいていました。

arm_status arm_rfft_fast_init_f32(
  arm_rfft_fast_instance_f32 * S,
  uint16_t fftLen)
{
  typedef arm_status(*fft_init_ptr)( arm_rfft_fast_instance_f32 *);
  fft_init_ptr fptr = 0x0;

  switch (fftLen)
  {
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_2048) && defined(ARM_TABLE_BITREVIDX_FLT_2048) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_4096))
  case 4096U:
    fptr = arm_rfft_4096_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_1024) && defined(ARM_TABLE_BITREVIDX_FLT_1024) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_2048))
  case 2048U:
    fptr = arm_rfft_2048_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_512) && defined(ARM_TABLE_BITREVIDX_FLT_512) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_1024))
  case 1024U:
    fptr = arm_rfft_1024_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_256) && defined(ARM_TABLE_BITREVIDX_FLT_256) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_512))
  case 512U:
    fptr = arm_rfft_512_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_128) && defined(ARM_TABLE_BITREVIDX_FLT_128) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_256))
  case 256U:
    fptr = arm_rfft_256_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_64) && defined(ARM_TABLE_BITREVIDX_FLT_64) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_128))
  case 128U:
    fptr = arm_rfft_128_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_32) && defined(ARM_TABLE_BITREVIDX_FLT_32) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_64))
  case 64U:
    fptr = arm_rfft_64_fast_init_f32;
    break;
#endif
#if !defined(ARM_DSP_CONFIG_TABLES) || defined(ARM_ALL_FFT_TABLES) || (defined(ARM_TABLE_TWIDDLECOEF_F32_16) && defined(ARM_TABLE_BITREVIDX_FLT_16) && defined(ARM_TABLE_TWIDDLECOEF_RFFT_F32_32))
  case 32U:
    fptr = arm_rfft_32_fast_init_f32;
    break;
#endif
  default:
    break;
  }

  if( ! fptr ) return ARM_MATH_ARGUMENT_ERROR;
  return fptr( S );

}

なので、該当するdefineを1にしてやればよいですね。

どうやればいいか、CMSIS_DSPのreadmeを読むと下記の記載が。

Compilation symbols for tables

Some new compilations symbols have been introduced to avoid including all the tables if they are not needed.

If no new symbol is defined, everything will behave as usual. If ARM_DSP_CONFIG_TABLES is defined then the new symbols will be taken into account.

It is strongly suggested to use the new Python script cmsisdspconfig.py to generate the -D options to use on the compiler command line.

pip install streamlit
streamlit run cmsisdspconfig.py

If you use cmake, it is also easy since high level options are defined and they will select the right compilation symbols.

For instance, if you want to use the arm_rfft_fast_f32, infft.cmake you’ll see an option RFFT_FAST_F32_32.

If you don’t use cmake nor the Python script, you can just look at fft.cmake or interpol.cmake in Source to see which compilation symbols are needed.

We see, for arm_rfft_fast_f32, that the following symbols need to be enabled :

  • ARM_TABLE_TWIDDLECOEF_F32_16
  • ARM_TABLE_BITREVIDX_FLT_16
  • ARM_TABLE_TWIDDLECOEF_RFFT_F32_32
  • ARM_TABLE_TWIDDLECOEF_F32_16

In addition to that, ARM_DSP_CONFIG_TABLES must be enabled and finally ARM_FFT_ALLOW_TABLES must also be defined.

This last symbol is required because if no transform functions are included in the build, then by default all flags related to FFT tables are ignored.

まあ要するに、fft.cmakeに必要な定義が一覧で載ってるから、それ探してmakefileに追加して、と言ってます。

ということで追加しましょう。

プロジェクトのトップディレクトリにmakefileあると思いますので、そのDEFINEに足します。

今回256データ長に対応させるためには1つの定義追加(赤文字)でよかったです。

DEFINES=CY_RETARGET_IO_CONVERT_LF_TO_CRLF CY_RTOS_AWARE \
        ARM_DSP_CONFIG_TABLES ARM_FAST_ALLOW_TABLES ARM_FFT_ALLOW_TABLES \
        ARM_TABLE_TWIDDLECOEF_F32_128 ARM_TABLE_BITREVIDX_FLT_128 \
        ARM_TABLE_TWIDDLECOEF_F32_64 ARM_TABLE_BITREVIDX_FLT_64 \
        ARM_TABLE_TWIDDLECOEF_RFFT_F32_128 ARM_TABLE_TWIDDLECOEF_RFFT_F32_256 ARM_ALL_FAST_TABLES \
		ARM_MATH_LOOPUNROLL

ちなみに追加定義を先頭に入れたら、うまく動きませんでした(DEFINEって順番ある?)。

これでビルドし直して実行したら問題なく動作しました!よかった!!

まとめ

CMSIS_DSPライブラリは128データ長しか標準で対応していない

CMSIS_DSPライブラリの入っているディレクトリの中にfft.cmakeファイルがあるので、必要な定義を見つけたらmakefileのDEFINEに足せばOK!

どんな長さにでも対応しといてくれよ!解決に2日要したぞ。

参考

Using the CMSIS DSP Library in a ModusToolbox Project
...

コメント

タイトルとURLをコピーしました