---
---
dsPICを使用すると、以前は高嶺の花であったスペクトラムアナライザが簡単に実現できます。FFTを使用しdsPICの魅力と実力を示す良い例題です。 プログラムの主な仕様は以下の通りです。
下の写真では、デジタル周波数シンセサイザ基板を使用して、1KHzの正弦波をスピーカーから出力しています。スピーカの前に置いたマイクで音を拾いSterter Kitに入力します。音声入力にコンデンサーマイクを使用していますが、Line in を使用し直接入力してもかまいません。 LCDには、FFTにより入力音が周波数軸に変換され表示されています。現在 64サンプルでFFTを実施していますが、128や 256サンプルとすることで、表示の分解能jをあげることができます。
*注意*
このこのプログラムを動作させるには、Starter Kitの改造が必要です。
写真の実験セットで、スピーカーからの出力周波数を変更したときのLCD画面の表示例です。

1KHzを入力

2KHzを入力

3KHzを入力(S1を押す)
プロジェクトファイル dsP03_FFT.zip |
// *********************************************************************
// 64ポイントFFT
// ADCサンプリング8kHz 周波数分解能8k/64=125Hz
//
// シリアルLCD表示
// 表示レンジ 0-2KHz:1.5-3.5KHz
//
// Processor: dsPIC33FJ256GP506
// Compiler: MPLABョ C30 v3.00 or higher
//
// Copyright (c) 2012 iwamoto All Rights Reserved
// **********************************************************************
#include "..\h\p33FJ256GP506.h"
#include "..\h\sask.h"
#include "..\h\ADCChannelDrv.h"
#include <dsp.h>
#include "..\h\L_sLCD.h"
#include <stdio.h>
// コンフィグレーション
_FGS(GWRP_OFF & GCP_OFF);
_FOSCSEL(FNOSC_FRC);
_FOSC(FCKSM_CSECMD & OSCIOFNC_ON & POSCMD_NONE);
_FWDT(FWDTEN_OFF);
// FRAME_SIZE -
#define powerOf2 6 // 64ポイントFFT
#define FRAME_SIZE (1<<powerOf2) // ADCサンプリング8kHzなので
// 周波数分解能8k/64=125Hz
// 回転因子の定義
const fractcomplex twiddleFactors[] __attribute__
((space(auto_psv), aligned (FRAME_SIZE * 2))) = {
0x7FFF, 0x0000, 0x7F62, 0xF374, 0x7D8A, 0xE707, 0x7A7D, 0xDAD8,
0x7642, 0xCF04, 0x70E3, 0xC3A9, 0x6A6E, 0xB8E3, 0x62F2, 0xAECC,
0x5A82, 0xA57E, 0x5134, 0x9D0E, 0x471D, 0x9592, 0x3C57, 0x8F1D,
0x30FC, 0x89BE, 0x2528, 0x8583, 0x18F9, 0x8276, 0x0C8C, 0x809E,
0x0000, 0x8000, 0xF374, 0x809E, 0xE707, 0x8276, 0xDAD8, 0x8583,
0xCF04, 0x89BE, 0xC3A9, 0x8F1D, 0xB8E3, 0x9592, 0xAECC, 0x9D0E,
0xA57D, 0xA57D, 0x9D0E, 0xAECC, 0x9592, 0xB8E3, 0x8F1D, 0xC3A9,
0x89BE, 0xCF04, 0x8583, 0xDAD8, 0x8276, 0xE707, 0x809E, 0xF374
} ;
// スペクトル(複素数)
fractcomplex fftData[ FRAME_SIZE ] __attribute__
((section (".ybss, bss, ymemory"), aligned (FRAME_SIZE * 2 * 2)));
// パワースペクトル(実数)
fractional powerSpec[ FRAME_SIZE/2 ];
int lcdData[20];
// データバッファ
int adcBuffer[ADC_CHANNEL_DMA_BUFSIZE] __attribute__((space(dma)));
int samples[FRAME_SIZE];
// AD変換インスタンスの設定
ADCChannelHandle adcChannelHandle;
// AD変換ハンドルの設定
ADCChannelHandle *pADCChannelHandle = &adcChannelHandle;
int main(void){
int i;
int val;
// 棒グラフ用の外字コードを高さ順に定義
char topChar[] = {32,32,32,32,32,32,32,32,
32,32,32,32,32,32,32,32,
16,17,18,19,20,21,22,23 };
char midChar[] = {32,32,32,32,32,32,32,32,
16,17,18,19,20,21,22,23,
23,23,23,23,23,23,23,23 };
char btmChar[] = {16,17,18,19,20,21,22,23,
23,23,23,23,23,23,23,23,
23,23,23,23,23,23,23,23 };
char topbar[20];
char midbar[20];
char btmbar[20];
// --------------- PLL動作 ------------------------------------------
// 内部発振をPLLで 80Mhz(Fosc) とし 40MHz(Fcy)動作させる
// Fosc= Fin*M/(N1*N2), Fcy=Fosc/2
// Fosc= 7.37M*43/(2*2)=80Mhz 7.37M は内部発振周波数
PLLFBD=41; // M=43
CLKDIVbits.PLLPOST=0; // N1=2
CLKDIVbits.PLLPRE=0; // N2=2
OSCTUN=0;
__builtin_write_OSCCONH(0x01); // クロックを FRC PLL に切り替える
__builtin_write_OSCCONL(0x01);
while (OSCCONbits.COSC != 0b01); // PLLの安定を待つ
while(!OSCCONbits.LOCK);
// 初期化 -----------------------------------------------------------
SASKInit(); // LED/SW/電源初期化
ADCChannelInit (pADCChannelHandle,adcBuffer); // ADC初期化
LCD_int(); // LCD初期化
ADCChannelStart (pADCChannelHandle); // AD変換開始
// 入出力の繰返しループ ----------------------------------------
while(1){
// AD変換結果を待ち、そのデータを samples に保存する
while(ADCChannelIsBusy(pADCChannelHandle));
ADCChannelRead (pADCChannelHandle,samples,FRAME_SIZE);
// 複素数の実数部にデータを入れる、虚数部は0
for(i = 0; i < FRAME_SIZE; i++) {
fftData[i].real = samples[i];
fftData[i].imag = 0;
}
// FFTの実行(DSP関数)
FFTComplexIP( powerOf2, fftData, (fractcomplex *)twiddleFactors,
(int) __builtin_psvpage(&twiddleFactors[0]));
// ビット逆順ソート(DSP関数)
BitReverseComplex( powerOf2, fftData );
// 複素数の絶対値を取る(DSP関数)
SquareMagnitudeCplx( FRAME_SIZE/2, fftData, powerSpec );
// 表示データの選択
LCD_posyx(3, 0); // 横軸目盛位置
if(SWITCH_S2){ // SW が離れている
VectorCopy(20, lcdData, powerSpec); // 0~2kHzまで表示
LCD_str("0 . 1 . 2kHz"); // 横軸目盛を表示
}else{ // SW が押されている
VectorCopy(20, lcdData, powerSpec + 12); // 2~3kHzまで表示
LCD_str(". 2 . 3 .kHz"); // 横軸目盛を表示
}
// 棒グラフの作成
for(i = 0; i < 20; i++) {
val = lcdData[i] / 10;
if(val > 23) val = 23;
topbar[i] = topChar[val];
midbar[i] = midChar[val];
btmbar[i] = btmChar[val];
}
// 棒グラフ表示
LCD_posyx(0, 0); LCD_strNum(topbar, 20);
LCD_posyx(1, 0); LCD_strNum(midbar, 20);
LCD_posyx(2, 0); LCD_strNum(btmbar, 20);
}
}