

実装の様子 CPU 28pin DIP dsPIC33FJ128GP802 CODEC WM8510
まず、数あるPICデバイスモデルの中から、どれにするかですが、今回はdsPIC33FJ128GP802を選びました。
dsPIC3FJ128GP802はDAコンバータを内蔵しているので、何かに使えるかと購入しておいたものですが、今回はDAコンバータを使用せずにWolfsonのCODEC WM8510を使用することにしました。
CODECを使用するとADコンバータも不要となりますが、代わりにDCIが必要です。
幸いdsPIC3FJ128GP802はDCIもサポートしています。
CODECを使用すると入力は簡単なラインアンプ、出力にはヘッドホンアンプが必要になるだけで、部品数の多いアンチエリアシング・フィルタなどは省略できます。(CODECに内蔵されています)
回路は非常にシンプルで、
ラインアンプ -> CODEC入力 -> DSPフィルタ -> CODEC出力 -> ヘッドホンアンプ
という構成です。
その他の回路は3.3V LDO、5つのLEDランプ、3個のプッシュスイッチ、リセット回路、ICSP等です。
LCDで情報表示するのもよいのですが、今回はシンプルに、5つのLEDで5種類のフィルターのうち選択されているフィルターを示すことにしました。
ラインアンプは無線機のオーディオ信号を、0 - 3.3Vの信号にレベルシフトする回路です。
DSPフィルターの入出力はDMA転送です。
フィルターの選択はフィルター選択UP/DOWNスイッチで行います。
また、いつでもスルー(フィルター処理しない)にできるよう、スルースイッチも用意しました。
デバッグ用にキャラクタLCDもつなげられるようにしてあります。
フィルターは、好きなように作れるのですが、以下の5種類を標準としました。
タイプ 中心周波数 帯域幅
BPF 650Hz 550Hz
BPF 700Hz 750Hz
BPF 650Hz 500Hz
BPF 650Hz 300Hz
BPF 650Hz 200Hz
すべて64タップのFIRフィルタです。
フィルターの係数はDigital Filter Designer Liteで作成しています。

フィルター特性 中心周波数650Hz -60dB 通過帯域200HzのBPF
このPIC 33FJ128GP802の主なスペックは以下の通りです。(microchip社より引用(一部削除))
Architecture 16-bit
CPU Speed (MIPS) 40
Memory Type Flash
Program Memory (KB) 128
RAM Bytes 16,384
Temperature Range C -40 to 125
Operating Voltage Range (V) 3 to 3.6
I/O Pins 21
Pin Count 28
Internal Oscillator 7.37 MHz, 512 kHz
nanoWatt Features Fast Wake/Fast Control
Digital Communication Peripherals 2-UART
2-SPI
1-I2C
Codec Interface NO <- NOになってますがDCIはサポートしています。
Analog Peripherals 1-A/D 10x12-bit @ 500(ksps) 1-D/A 2x16-bit @ 100(ksps)
Comparators 2
Capture/Compare/PWM Peripherals 4/4
PWM Resolution bits 16
Timers 5 x 16-bit 2 x 32-bit
Hardware RTCC Yes
DMA 8
ADコンバータは12bit 500Ksps 10ch, DAコンバータは16bit 100Ksps 2chです。
DMAを8chサポートしています。
OCPWMのPWMモードをサポートしているので、PWM方式での出力も可能です。
DCIをサポートしているのでI2SインターフェースなどでCODEC等も使えます。
RTCをサポートしています。
いろいろ遊べそうなデバイス。
回路は以下のようになりました。

実際に無線機につないでCWの交信を聞いてみました。
スパッと切れるところはさすがにDSPフィルターですが、デジタルフィルター特有の音は仕方がありません。
ソースコード
main.c以外(初期化、I2C, I2C LCD、フィルター係数ファイル、ヘッダファイル等)は省略
//main.c
#include "..\h\p33FJ64GP802.h"
#include "..\h\WM8510CodecDrv.h"
#include "..\h\sask.h"
#include "dsp.h"
//Protection off
_FGS(GWRP_OFF & GCP_OFF);
//POR起動時のオシレータはFRC
_FOSCSEL(FNOSC_FRC);
//プライマリオシレータ HS 12MHz
_FOSC(FCKSM_CSECMD & OSCIOFNC_ON & POSCMD_NONE);
//ウオッチドッグタイマ off
_FWDT(FWDTEN_OFF);
//I2C Enable
_FPOR(ALTI2C_OFF);
//JTAG off, ICSP PGD2
_FICD(JTAGEN_OFF & ICS_PGD2);
// DSP Filter Struct (CW Filter)
extern FIRStruct FIR_HPF_600_550Filter;
extern FIRStruct FIR_LPF_700_750Filter;
extern FIRStruct FIR_BPF_650_500Filter;
extern FIRStruct FIR_BPF_650_300Filter;
extern FIRStruct FIR_BPF_650_200Filter;
/* FRAME_SIZE - Size of each audio frame */
#define FRAME_SIZE 128
#define FILTER_NO_MAX 5
#define FILTER1 1
#define FILTER2 2
#define FILTER3 3
#define FILTER4 4
#define FILTER5 5
/* Allocate memory for buffers and drivers */
int codecBuffer[WM8510DRV_DRV_BUFFER_SIZE];
fractional Signal_in[FRAME_SIZE];
fractional Signal_out[FRAME_SIZE];
int i;
int mode;
int filter_through;
int filter_select;
WM8510Handle codec;
WM8510Handle * codecHandle = &codec;
int main(void){
// Configure Oscillator to operate the device at 40MHz.
// Fosc= Fin*M/(N1*N2), Fcy=Fosc/2
// Fosc= 7.37M*40/(2*2)=80Mhz for 7.37M input clock
PLLFBD=41; // M=39
CLKDIVbits.PLLPOST=0; // N1=2
CLKDIVbits.PLLPRE=0; // N2=2
OSCTUN=0;
__builtin_write_OSCCONH(0x01); // Initiate Clock Switch to FRC with PLL
__builtin_write_OSCCONL(0x01);
while (OSCCONbits.COSC != 0b01); // Wait for Clock switch to occur
while(!OSCCONbits.LOCK);
// Intialize the board and the drivers
SASKInit();
i2c_init();
i2c_enable();
lcd_init();
lcd_write(0, 0, "DSP CW Filter " );
lcd_write(0, 1, "v1.00A by JF1VRR" );
mode = mode_set();
Delay_ms(1000);
// LED test
led_test();
WM8510Init(codecHandle,codecBuffer);
// Start Audio input and output function
WM8510Start(codecHandle);
// Configure codec for 8K operation
WM8510SampleRate8KConfig(codecHandle);
// Initialize FIR Filter (CW Filter)
FIRDelayInit(&FIR_BPF_650_500Filter);
FIRDelayInit(&FIR_BPF_650_300Filter);
FIRDelayInit(&FIR_BPF_650_200Filter);
FIRDelayInit(&FIR_HPF_600_550Filter);
FIRDelayInit(&FIR_LPF_700_750Filter);
filter_through = ON;
filter_select = FILTER3;
// Main processing loop. Executed for every input and output frame
while(1){
// Obtain Audio Samples
while(WM8510IsReadBusy(codecHandle));
WM8510Read(codecHandle,Signal_in,FRAME_SIZE);
FLT1LED = OFF;
FLT2LED = OFF;
FLT3LED = OFF;
FLT4LED = OFF;
FLT5LED = OFF;
case MODE_CW:
if(filter_through == OFF) {
// Processing DSP filter
switch (filter_select){
case FILTER1:
FLT1LED = ON;
lcd_write(0, 0, "FLT# 1 PB: 600Hz" );
lcd_write(0, 1, "CW HPF SB: 550Hz" );
FIR(FRAME_SIZE, &Signal_out[0], &Signal_in[0], &FIR_HPF_600_550Filter);
break;
case FILTER2:
FLT2LED = ON;
lcd_write(0, 0, "FLT# 2 PB: 700Hz" );
lcd_write(0, 1, "CW LPF SB: 750Hz" );
FIR(FRAME_SIZE, &Signal_out[0], &Signal_in[0], &FIR_LPF_700_750Filter);
break;
case FILTER3:
FLT3LED = ON;
lcd_write(0, 0, "FLT# 3 F: 650Hz" );
lcd_write(0, 1, "CW BPF W: 500Hz" );
FIR(FRAME_SIZE, &Signal_out[0], &Signal_in[0], &FIR_BPF_650_500Filter);
break;
case FILTER4:
FLT4LED = ON;
lcd_write(0, 0, "FLT# 4 F: 650Hz" );
lcd_write(0, 1, "CW BPF W: 300Hz" );
FIR(FRAME_SIZE, &Signal_out[0], &Signal_in[0], &FIR_BPF_650_300Filter);
break;
case FILTER5:
FLT5LED = ON;
lcd_write(0, 0, "FLT# 5 F: 650Hz" );
lcd_write(0, 1, "CW BPF W: 200Hz" );
FIR(FRAME_SIZE, &Signal_out[0], &Signal_in[0], &FIR_BPF_650_200Filter);
break;
} //switch filter select
} else { //Through
lcd_write(0, 0, "FLT# 0 F: ---Hz" );
lcd_write(0, 1, "THROUGH W: ---Hz" );
for(i = 0; i < FRAME_SIZE; i++) {
Signal_out[i] = Signal_in[i]; // Copy Signal Buffer
}
}
// Wait till the codec is available for a new frame
while(WM8510IsWriteBusy(codecHandle));
// Write the frame to the output
WM8510Write (codecHandle,Signal_out,FRAME_SIZE);
// The CheckSwitch functions are defined in sask.c
if(Check_THROUGHSW() == 1){
if(filter_through == OFF) filter_through = ON;
else filter_through = OFF;
}
if(Check_FLT_SELSW_UP() == 1 && filter_through == OFF){
filter_select++;
if(filter_select > FILTER_NO_MAX) filter_select = 1;
}
if(Check_FLT_SELSW_DN() == 1 && filter_through == OFF){
filter_select--;
if(filter_select < 1) filter_select = FILTER_NO_MAX;
}
} //while
}
(JF1VRR)