今回はSTM32でのDSPによるFFTについて紹介します。
環境はこれまで同様、
・STM32F303K8
+SW4STM32(System Workbench for STM32)
+STM32CubeMX(F3_1.6.0)
です。
プロジェクトのDSPライブラリ設定等はこちらを参照してください。
ちょっとしたFFT処理であれば、精度よりも速さ重視なことが多いため、
今回はあえてFloatでなくQ15でのFFTを紹介します。
q_15をf32に置き換えればFloatと同じように使えます。
FFT処理は使用するライブラリの関数によって
配列の渡し方や複素数の扱いが異なるため、
間違えて配列オーバーなどしてしまいがちです。
長さに注意しながらコーディングが必要です。
ARMのFFTコード例は下記の通りです。
一部抜粋です。#define LENGTH_SAMPLES 256 //Num of Real & Img
#define ifftFlag 0
#define doBitReverse 1
#define fftSize LENGTH_SAMPLES/2
#define doBitReverse 1
#define fftSize LENGTH_SAMPLES/2
#define FFTLen arm_cfft_sR_q15_len128
q15_t FFT_Input[LENGTH_SAMPLES];
q15_t FFT_Output[LENGTH_SAMPLES/2];
q15_t FFT_Output[LENGTH_SAMPLES/2];
enum{ WAIT,RUN};
//実部と虚部で256個でAD変換値は実部のため、サイズは128
enum{ ADC_BUFFER_LENGTH = LENGTH_SAMPLES/2 };
//DMA転送でHalfWordにつき16bit長で定義
static uint16_t g_ADCBuffer[ADC_BUFFER_LENGTH];
static uint16_t g_ADCBuffer[ADC_BUFFER_LENGTH];
//AD変換後に呼び出される
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
HAL_TIM_Base_Stop_IT(&htim2);
HAL_ADC_Stop_DMA(&hadc1);
for(int i=0;i<LENGTH_SAMPLES/2;i++){
int tmp;
tmp= g_ADCBuffer[i];
{
HAL_TIM_Base_Stop_IT(&htim2);
HAL_ADC_Stop_DMA(&hadc1);
for(int i=0;i<LENGTH_SAMPLES/2;i++){
int tmp;
tmp= g_ADCBuffer[i];
//dsPICのFFTは構造体で実部と虚部を格納するが、
//ARMでは配列に実部と虚部を交互に格納する
FFT_Input[2*i]=tmp; //Real part
FFT_Input[2*i+1]=0.0; //Img part
}
fft_status=RUN;
}
int main(void)
{
//~~初期化処理~~//
{
//~~初期化処理~~//
while (1)
{
{
if(fft_status==RUN){
arm_cfft_q15(&FFTLen, FFT_Input, ifftFlag, doBitReverse);
arm_cmplx_mag_q15(FFT_Input, FFT_Output, fftSize);
arm_max_q15(FFT_Output, fftSize, &maxValue, &testIndex);
arm_cfft_q15(&FFTLen, FFT_Input, ifftFlag, doBitReverse);
arm_cmplx_mag_q15(FFT_Input, FFT_Output, fftSize);
arm_max_q15(FFT_Output, fftSize, &maxValue, &testIndex);
//サンプル数は実部と虚部で256個
//fftsizeは128個
//FFT_Outputには0~127でFFT結果が格納されるが、
//64~127は対称の値が入っているだけ
//実際に使うのはFFT_Outputの中で0~63のデータ
for(int j=0;j<fftSize/2;j++){
printf("%d, ",FFT_Output[j]);
}
printf("%d, ",FFT_Output[j]);
}
fft_status=WAIT;
HAL_ADC_Start_DMA(&hadc1, g_ADCBuffer, ADC_BUFFER_LENGTH);
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim2);
}
else{
//なぜか何かしらの処理を入れないとwhileに入らないのでnopを入れる
__asm volatile("nop");
}
}
}
N555で35kHz前後で発振させた信号を読み込むと
下記のように35kHz前後の結果となりました。
DMA転送で設定をHalf Wordにしているにも関わらず、
g_ADCBufferバッファ変数長さを32bitにしてしまい、
AD変換結果のバッファを破壊するというバグに気付かず、
意図したFFT結果を得るのに時間が掛かってしまいました...
HalfWordではuint16_t g_ADCBufferが正解です。
FFT処理をしているとSTM32F303K8では
RAMサイズが16kB(実質12kB)のため、
STM32F303CCなどの32kBや40kBの大容量なRAMが欲しくなります。
一方でRAMサイズが大きくなるとピン数が48ピンになるため、
扱いやピン配列が面倒になります...
32ピンでRAMサイズが大きなものが出てほしいなあと思う限りです。

