dsPICでデジタル信号処理でもやってみよう
最近ADCが内蔵されたdsPIC33FシリーズのdsPIC33FJ128GP804を使ってみようと言うことになり早速試してみました.まずは基板起こし4層.だいたい一日で完成.以前使っていた18F252→18F2525の基板の新バージョンとしてこれから使っていこうかと...今から思うとMCとGPの2種類があって何が違うか調べずに買ってしまったけど,MCの方が直行エンコーダなど,機能が若干多めです.



はじめはCCSを使用しようと思ったけど,クロック設定も十分にできずあきらめる・・・で,使用しているヒトが最も多い(だろう)C30を試す.一応スチュデントを登録してダウンロード,商用利用はしないのでまぁいいかな.さて,まずクロック,早速ググるとhttp://www.arakin.dyndns.org/にいいサンプルがありました.さすがに全部の無断転用はまずいので要項だけ抜粋.開発環境は現状MPLAB IDEはv8.15a,C30は3.11Bでした.MicrochipのHPからダウンロードでしてインストールしてくださいませ.

*宣言部
_FBS(BSS_NO_FLASH & BWRP_WRPROTECT_OFF);
_FGS(GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_PRIPLL & IESO_ON);
_FOSC(FCKSM_CSDCMD & IOL1WAY_ON & OSCIOFNC_ON & POSCMD_XT);
_FWDT(FWDTEN_OFF & WINDIS_OFF & WDTPRE_PR128 & WDTPOST_PS32768);
_FPOR(FPWRT_PWR128 & ALTI2C_ON);
_FICD(BKBUG_OFF & COE_OFF &JTAGEN_OFF & ICS_PGD1);

#define XTFREQ (7372800) // XT 周波数
#define FCY (39628800) // 命令サイクル周波数

*init関数の中で・・
  PLLFBD = 41;
  CLKDIVbits.PLLPOST = 0;
  CLKDIVbits.PLLPRE = 0;
  RCONbits.SWDTEN = 0;
  while(OSCCONbits.LOCK !=1 );



まぁ,外部クロックで7372800Hzを供給,最大80MHzで動くのでPLLで近い値にする.2クロックで1命令が実行されることから,結局39628800Hz≒25.2nsecで実行する.30Fよりも実質は高速なことになります.7372800Hzって中途半端に思えますが,UARTを考えると,実はぴったりのきりのいい数字であることが分かる(115200とか9600とかの倍数値).まずはディレイ関数がほしいところなので創ってみた.

#define uSEC_cnt 4

//クロックを80Mhzとして換算しています
void delay_us(int usec){
  int i;
  while(usec){
    for(i=0;i<uSEC_cnt;i++){
      asm("NOP");
    }
    usec--;
  }
}

void delay_ms(int msec){
  int i;
  for(i=0;i<msec;i++){
    delay_us(1000);
  }
}


まあ,だいたいこれでdelay_ms( ), delay_us( )が使えるようになりました.あまり正確ではありませんが・・・ 次に,標準入出力ですね.printfはないとデバッグ苦労しますから...

宣言部
#define BAUDRATE1    (115200)    // シリアルボーレート CH1
#define BRGVAL1    ((FCY/BAUDRATE1)/16)-1
#define BAUDRATE2    (19200)    // シリアルボーレート CH2
#define BRGVAL2    ((FCY/BAUDRATE2)/16)-1
unsigned int g_U1MODE =
  UART_EN & UART_IDLE_CON & UART_IrDA_DISABLE & UART_MODE_FLOW &
  UART_UEN_00 & UART_DIS_WAKE & UART_DIS_LOOPBACK & UART_DIS_ABAUD &
  UART_UXRX_IDLE_ONE & UART_BRGH_SIXTEEN &
  UART_NO_PAR_8BIT & UART_1STOPBIT;
unsigned int g_U2MODE =
  UART_EN & UART_IDLE_CON & UART_IrDA_DISABLE & UART_MODE_FLOW &
  UART_UEN_00 & UART_DIS_WAKE & UART_DIS_LOOPBACK & UART_DIS_ABAUD &
  UART_UXRX_IDLE_ONE & UART_BRGH_SIXTEEN &
  UART_NO_PAR_8BIT & UART_1STOPBIT;

unsigned int g_U1STA =
  UART_INT_TX & UART_IrDA_POL_INV_ZERO & UART_SYNC_BREAK_DISABLED &
  UART_TX_ENABLE & UART_INT_RX_CHAR &
  UART_ADR_DETECT_DIS & UART_RX_OVERRUN_CLEAR;
unsigned int g_U2STA =
  UART_INT_TX & UART_IrDA_POL_INV_ZERO & UART_SYNC_BREAK_DISABLED &
  UART_TX_ENABLE & UART_INT_RX_CHAR &
  UART_ADR_DETECT_DIS & UART_RX_OVERRUN_CLEAR;

*init関数の中で・・
  //UART1の設定    PIC33FのI/Oピン割付 初期化 割込設定
  TRISB = TRISB & 0xFDFF;  //RB8-U1RX RB9-U1TX    
  RPINR18 = 8;      // RP8をU1RXに設定 入力マップ
  RPOR4bits.RP9R = 3;    // RP9をU1TXに設定 出力マップ
  OpenUART1(g_U1MODE, g_U1STA, BRGVAL1);
  ConfigIntUART1(UART_RX_INT_PR5 & UART_RX_INT_EN & UART_TX_INT_DIS);

  //UART2の設定    PIC33FのI/Oピン割付 初期化 割込設定
  TRISB = TRISB & 0xFF7F;  //RB6-U2RX RB7-U2TX    
  RPINR19 = 6;      // RP6をU2RXに設定 入力マップ
  RPOR3bits.RP7R = 5;    // RP7をU2TXに設定 出力マップ
  OpenUART2(g_U2MODE, g_U2STA, BRGVAL2);
  ConfigIntUART2(UART_RX_INT_PR6 & UART_RX_INT_EN & UART_TX_INT_DIS);


このシリーズ,ペリチュラルはピンが自由に設定できる.RP0-RP26位まで好きなピンに好きな機能を接続できるのはうれしいが,設定が増えるのはめんどい.ここで注意することは「入力ピンは機能にピン番号を割り振り」,「出力ピンはピン番号に機能を割振る」なんでこんな風になっているかよくよく考えてみると,1つの入力をいろいろな機能に割り振ることはできるけど,出力は衝突させられないからこういう構成になっているのだろう. とりあえず.苦労はしたけど,シリアル通信はできるようになりました.以前初期のdsPICでprintfを使ったときは,メモリの大半使ってしまったけど,今回はせいぜい数lですみました.コンパイラがよくなったのか,メモリが増えたからなのかは知りません.TRIS設定は,毎回ちゃんと設定する,また使うピンのみ変化させるようにする.結構こういうことが動かない原因を減らしてくれる.次にSPIを利用してみる.シリアルのROM(フラッシュROM)を使う必要があったので・・・

  //SPIの設定 PIC33FのI/Oピン割付 初期化 割込設定
  RPINR20 = 10;      // RP10をDIに設定 入力マップ
  RPOR5bits.RP11R = 7;  // RP11をDOに設定 出力マップ
  RPOR12bits.RP25R = 8;  // RP25をCLKに設定 出力マップ

  OpenSPI1(0x137,0x0000,0x8000);
  ConfigIntSPI1(SPI_INT_DIS);

データを書くときはWriteSPI1(データ)だけでよいが,読むのはReadSPI1( )だけではだめで,カラのWriteSPI1(0)を送って,少しdelayで待つか,while(DataRdySPI1() == 0)で,データがたまるまで待つ必要がある.一見whileの方がスマートだけど,通信にコケルと動かなくなってしまうので注意!.

  WriteSPI1(0x00);
  while(DataRdySPI1() == 0);
     //delay_us(10);
  dat[0]=ReadSPI1();

お次はADC,もともと先述のHPから持ってきたんだけど,10bitのADの用にしか動いてくれない・・・SPIのときからそうなんですが,結局データシートとにらめっこしなければいけないので,Cライブラリの定数設定を利用しないで,直接レジスタを操作した方が,意外にバグが少なくなるのではと思うようになりました.使用するペリチュラルのデータシートのレジスタのページを印刷して,自分が利用したい設定にビットを決めていき,各レジスタの値をそのまま書き込んでいった方が,ハードウェアの理解はしやすいと思います.可読性は落ちるので,コメント文でカバーする必要はありますが...
*ADCの設定
// 12bit ADコンバータ
// ADC用アナログピン設定
  AD1PCFGL = 0x03C0; // AN00-AN05をアナログ
  TRISA = 0xFFFF; // とりあえず全部入力
  AD1CHS123 = 0x0000;
  AD1CHS0 = 0x0505;  //AN5を入力
  //AD1CON1 = 0x94e4;  //
  AD1CON1 = 0x97e4;  //DSP利用のため出力フォーマットをsignedに
  AD1CON2 = 0x0000;
  AD1CON3 = 0x0384;  //約2usで変換
  AD1CON4 = 0x0000;


とりあえず2usなら割り込みなくてもいいかな.

読むとき
Dat = ReadADC1(0)
これだけで読めました.



次にいよいよDACです.ほとんどWEBに情報無し. まずはデータシート片手に

  signed int DARDAT=0x8000;

// 12bit DAコンバータ
  ACLKCON = 0x0580;  //DAC用クロックの設定
  //DAC1CON = 0x8000;
  DAC1CON = 0x9000;  //signed intに変更
  DAC1STAT = 0x8484;
  IEC4bits.DAC1LIE = 1;
  IEC4bits.DAC1RIE = 1;
  IPC19 = 0x6500;

void __attribute__((interrupt, no_auto_psv)) _DAC1RInterrupt(void)
{
  //static int i;
  IFS4bits.DAC1RIF = 0; // 割り込みフラグをクリア
  DAC1RDAT = DARDAT;  
}

まずはじめにハマッタのは,こいつのクロック入力がどうなっているのか分からなかったこと.何も考えていないので,DAC用のクロックなんて積んでいない.そもそもオーディオ用だったっけ?これに気づくのにだいぶかかりました.OSCのブロック図を見て,専用のクロック線が必要なことを見つける.勿論,PLLのクロックにもつなげることができるので,79257600Hzを4で割った値がDACブロックに行くように設定する.ACLKCONですね.これちゃんとDACの項目に書いておいてくれてないのでホント困ったよ.DMAを使うこともできるけど,リアルタイムなデジタル信号処理を考えて,ふつうのDAのように使おうとします.でも,オーディオ用のせいか,自分の好きなタイミングでの変換ができない.FIFOレジスタにデータを入れると勝手にDA変換してレジスタがインクリメントされる構成である.そこでFIFOがカラになったら割り込みをかけるようにして,DA値の変数(DARDAT)をFIFOに送るようにしました.このDA値を自分の好きなタイミングで変えれば,すぐにDA変換されて出力される...と思っていましたが,なぜか,FIFOにコピーしてからDA出力されるまでに約200usもかかっている...クロック約20MHzで変換に256クロック必要と書いてあるので13us位のはずなんですが・・・原因は未だ分からず...200usも遅れてしまうと数kHzの信号でも位相がひどいことに....とりあえずしょうがないのでこのままいきます.

ここからはようやく後閑さんのdsPICの本が参考になります.数千円なのでdsPICfdliteを購入.これで好きなフィルタを作成できます.上記本のようにして*.sファイルを作成します.このIDEのバージョンv8.15aでは,以前のようにPICデバイスのライブラリやリンクファイルを指定する必要はありませんでしたが,libdsp-coff.a,libdsp-elf.a, lib-dsp-coff.a,lib-dsp-elf.a をライブラリに登録する必要がありました.あと創ったフィルタのファイル*.sももちろん登録します.

  while(1)
  {
    SigIn[0]=(ReadADC1(0));
    FIR(MaxSize, &SigOut[0], &SigIn[0], &BPF0Filter);
    DARDAT=SigOut[0]+ 0x8000;
    delay_us(50);
  }


そして,main( )の中のループで信号入力してFIRとかに放り込んで出てきたのを,信号出力するとあっという間にフィルタの完成.きわめて適当ですが,サンプリング周波数はdelay_us(50)で20kHzとなっています.これで,1kHz付近のバンドパスフィルタを創ってみましたが,ほぼ設計通りに動作しました.FIRで64タップなんですが,1クロックサイクルで実行できてしまうというのはすごい.PCでMATLABでも10秒くらいはかかります.それがリアルタイムとは...
一応,プログラム一式


とりあえず今日はここまで.そのうち写真追加します.