TOTAL:703, TODAY:79

dsPIC33Fの10bit-ADコンバータで1Msps

今回はdsPIC33Fの10bit-ADコンバータを試します。dsPIC33FJ12GP202のデータシートによれば10bitの場合、1.1Mspsとなっていますが、以前、PIC24Fで試したところ、スペック通りのサンプリング周波数は実現できなかったので、自作オシロの目標スペックである1チャンネル1Msps、2チャンネル500Kspsが可能かを検証しました。前回同様、使用したのはデモボード(16-Bit 28-Pin Starter Board)です。プログラムは統合開発環境MPLAB IDE v8.0MPLAB C30 コンパイラ v3.02を使用しました。

実験ボードの回路図

きとんとサンプリングできているかを確認するために、SoftOscillo2で発生させた正弦波を見ることにしました。デモボードをトラ技付録のOPアンプ実験ボードと接続し、交流を1.65V(3.3Vの半分)シフトさせています。図中のVinにSoftOscillo2で生成した正弦波を入力するようにしました。
回路図に間違いがあったため、修正しました。OPアンプが負帰還ではなく正帰還になっていました。

上図には描かれていませんが、7.3728MHzの水晶発振子が接続されており、dsPIC33FのPLL回路により命令サイクル周波数を設定しています。dsPIC33Fの10bit-ADコンバータは4チャンネル同時にサンプリング可能で、スペック上は1チャンネルあたり1.1Msps(※spsはサンプリング/秒)と高速なサンプリングが可能です。

10bit-ADコンバータのプログラム

12bit-ADコンバータのページでも書いたように、dsPIC33FはPLL倍率がいろいろと設定可能です。水晶発振子の周波数をFinとすると、PLLありでの周波数Foscは次のようになります。
       Fosc = Fin * (M / (N1 * N2)
ここで、MはPLL倍率、N1はPLLプリスケーラー、N2はPLLポストスケーラです。デモボードの水晶発振子Finは7.3728MHzです。スペックではFoscは80MHz以下でなければなりません。しかし、結論から言うと、Foscが80Mhzでは、1Mspsは達成できませんでした。そのため、次のプログラムのように、幾つか周波数を変更して動かしてみました。

int main(void)
{
    // 発振器の設定:dsPIC33Fは、命令サイクル周波数Fcyは最大40MHz
    // Fosc= Fin * M / (N1 * N2), Fcy = Fosc / 2
#if OVERCLOCK == 0
    // Fosc= 7.3728 * 43 / (2 * 2) = 79.2576MHz, Fcy = Fosc/2 = 39.6288MHz
    PLLFBD = 41;        // M=43
#elif OVERCLOCK == 1
    // Fosc= 7.3728 * 46 / (2 * 2) = 84.7872MHz, Fcy = Fosc/2 = 42.3936MHz
    PLLFBD = 44;        // M=46
#elif OVERCLOCK == 2
    // Fosc= 7.3728 * 49 / (2 * 2) = 90.3168MHz, Fcy = Fosc/2 = 45.1584MHz
    PLLFBD = 47;        // M=49
#elif OVERCLOCK == -1
    // Fosc= 7.3728 * 33 / (2 * 2) = 60.8256MHz, Fcy = Fosc/2 = 30.4128MHz
    PLLFBD = 31;        // M=33
#endif
    CLKDIVbits.PLLPOST = 0;     // N1=2
    CLKDIVbits.PLLPRE = 0;      // N2=2

#defineマクロOVERCLOCKが0の場合Fcy=39.6288MHz、1の場合Fcy=42.3936MHz、2の場合Fcy=45.1584MHzとなります。また、-1の場合、Fcy=30.4128MHzです。一つだけ低い周波数を選んだのは、後述する「ADコンバータのクロック周期」Tadを最小値65nsecに設定できるちょうどいい周波数だからです。
ADコンバータの設定は次のようなプログラムになります。1チャンネルだけをサンプリングする場合、AN5をCH0に接続し、2チャンネルの場合はAN0もCH1に接続するようにしました。また、ADコンバータの割り込み(ADC割り込み)は4回のサンプリングで発生するようにしました。

#define OVERCLOCK               (2)    // 周波数選択
#define NUM_CHANNEL             (1)    // サンプリングチャンネル数
#define T3_INTERRUPT            (0)    // タイマー3割り込み処理の有無
#define NUM_ADC_INTERRUPT       (4)    // ADC割り込み発生のサンプリング数

/*
 * ADC_MODULE_ON        : ADC有効
 * ADC_IDLE_CONTINUE    : IDLE時動作継続
 * ADC_AD12B_10BIT      : 10bit高速サンプリング
 * ADC_FORMAT_INTG      : 符号なし整数に変換(0-1023)
 * ADC_CLK_TMR          : タイマー3割り込みで変換開始
 * ADC_SIMULTANEOUS     : 複数チャンネル同時サンプリング
 * ADC_AUTO_SAMPLING_ON : AD変換後、自動でサンプリング開始
 * ADC_SAMP_OFF         : サンプル有効化       
 */
unsigned int g_ADCON1 = 
    ADC_MODULE_ON & ADC_IDLE_CONTINUE & ADC_AD12B_10BIT & 
    ADC_FORMAT_INTG & ADC_CLK_TMR & ADC_SIMULTANEOUS &
    ADC_AUTO_SAMPLING_ON & ADC_SAMP_OFF;

/*
 * ADC_VREF_AVDD_AVSS   : VrefH = AVDD(28pin), VrefL = AVSS(27pin)
 * ADC_SCAN_OFF         : 自動スキャンオフ
 * ADC_SELECT_CHAN0     : 有効チャンネル
 * ADC_DMA_ADD_INC_4    : 4回のサンプリングで割り込み
 * ADC_ALT_BUF_OFF      : シングルバッファ(16ワード)
 * ADC_ALT_INPUT_OFF    : MUXAだけを使用
 */
unsigned int g_ADCON2 = 
    ADC_VREF_AVDD_AVSS & ADC_SCAN_OFF & 
#if NUM_CHANNEL == 1
    ADC_SELECT_CHAN_0 & 
#elif NUM_CHANNEL == 2
    ADC_SELECT_CHAN_01 & 
#endif
#if NUM_ADC_INTERRUPT == 1
    ADC_DMA_ADD_INC_1 & 
#else
    ADC_DMA_ADD_INC_4 & 
#endif
    ADC_ALT_BUF_OFF & ADC_ALT_INPUT_OFF;

/*
 * ADC_CONV_CLK_SYSTEM  : システムクロックを使用
 * ADC_SAMPLE_TIME_1    : サンプリング時間 1*Tad
 * ADC_CONV_CLK     : ADCS = Tad / Tcy - 1
 *
 * dsPIC33F 10bitモードの場合、Tad >= 65ns
 *
 * Fcy = 39.6288MHzの場合
 * Tcy = 1 / 39.6288MHz = 25.2ns, ADCS = 65 / 25.2 - 1 = 1.58 => 2
 * Tad = 25.2 * (2 + 1) = 75.6ns, 14*Tad = 1058.4ns
 *
 * Fcy = 42.3936MHzの場合
 * Tcy = 1 / 42.3936MHz = 23.6ns, ADCS = 65 / 23.6 - 1 = 1.75 => 2
 * Tad = 23.6 * (2 + 1) = 70.8ns, 14*Tad = 991.2ns
 *
 * Fcy = 45.1584MHzの場合
 * Tcy = 1 / 45.1584MHz = 22.1ns, ADCS = 65 / 22.1 - 1 = 1.94 => 2
 * Tad = 22.1 * (2 + 1) = 66.3ns, 14*Tad = 928.2ns
 *
 * Fcy = 30.4128MHzの場合
 * Tcy = 1 / 30.4128MHz = 32.9ns, ADCS = 65 / 32.9 - 1 = 0.97 => 1
 * Tad = 32.9 * (1 + 1) = 65.8ns, 14*Tad = 920.7ns
 */
unsigned int g_ADCON3 = 
    ADC_CONV_CLK_SYSTEM & ADC_SAMPLE_TIME_2 &
#if OVERCLOCK != -1
    (0xFFC2);
#else
    (0xFFC1);
#endif

/*
 * ADC_CH123_NEG_SAMPLEB_VREFN : サンプルBのCH1,CH2,CH3のリファレンスはVref
 * ADC_CH123_POS_SAMPLEB_0_1_2 : サンプルBのCH1,CH2,CH3入力はAN0,AN1,AN2
 * ADC_CH123_NEG_SAMPLEA_VREFN : サンプルAのCH1,CH2,CH3のリファレンスはVref
 * ADC_CH123_POS_SAMPLEA_0_1_2 : サンプルAのCH1,CH2,CH3入力はAN0,AN1,AN2
 */
unsigned int g_AD1CHS123 =
    ADC_CH123_NEG_SAMPLEB_VREFN & ADC_CH123_POS_SAMPLEB_0_1_2 &
    ADC_CH123_NEG_SAMPLEA_VREFN & ADC_CH123_POS_SAMPLEA_0_1_2;

/*
 * ADC_CH0_NEG_SAMPLEB_VREFN   : サンプルBのCH0のリファレンスはVref
 * ADC_CH0_POS_SAMPLEB_AN5     : サンプルBのCH0入力はAN5
 * ADC_CH0_NEG_SAMPLEA_VREFN   : サンプルAのCH0のリファレンスはVref
 * ADC_CH0_POS_SAMPLEA_0_1_2   : サンプルAのCH1入力はAN5
 */
unsigned int g_AD1CHS0 =
    ADC_CH0_NEG_SAMPLEB_VREFN & ADC_CH0_POS_SAMPLEB_AN5 &
    ADC_CH0_NEG_SAMPLEA_VREFN & ADC_CH0_POS_SAMPLEA_AN5;

ACコンバータのクロック周期Tadと命令サイクルTcyには次の関係があります。
    Tad = Tcy * (ADCS + 1)
ここで、ADCSはADxCON3レジスタにある変換クロック設定ビットです。dsPIC33FJ12GP202のデータシートによると、10bitモードの場合、Tadは65nsec以上必要です。Fcy=39.6288MHzの場合、Tcy=(1/39.6288MHz) =25.2nsecであり、ADCSは2以上を設定する必要があります。そして、ADCS=2の場合、Tad=75.6nsec(=25.2 * 3)となります。Fcy=42.3936MHz45.1584MHzの場合でも、ADCSは2以上となり、それぞれTadは70.8nsec, 66.3nsecとなります。一方、Fcy=30.4128MHzの場合、ADCSは1以上でとなるため、Tadは65.8nsecとなります。ADコンバータはタイマー3をトリガーにAD変換するようにしており、10bitモードの場合、12Tadで変換が完了します。その後、自動でサンプリングが行われ、またタイマー3割り込みによりAD変換が行われます。即ち、
    タイマー3の周期 = サンプル時間 + 12*Tad > 14*Tad
となります。10bitのモードの場合、このサンプル時間が2Tad以上必要です。結局、タイマー3の周期は14Tad以上必要となります。今回はタイマー3の周期を1チャンネルでは1μsec(1Msps)、2チャンネルでは2μsec(500Ksps)に設定しています。そのため、Fcy=39.6288MHzの場合、14Tadが1μsecを超えるため、不可となりますが、それ以外はなんとか実現可能なはずです。

プログラムでは、タイマー1の周期を250msec、タイマー3の周期を1μsec(2チャンネルの場合は2μsec)として、割り込みがかかるようにしています。タイマー1、タイマー3、ADコンバータの各割り込み処理でカウント用変数をインクリメントしているため、タイマー3とADコンバータのカウント変数はタイマー1の約250000(= 250msec / 1μsec)倍になっていれば、1Mspsが達成できています。2チャンネルの場合は、約125000(= 250msec / 2μsec)倍になっていれば、500Kspsが達成できています。Fcyを変えた場合のタイマー1やタイマー3の各設定は、下記プログラムを見ていただければ、分かると思います。

unsigned long countT1 = 0;      // タイマー1用のカウント変数
unsigned long countT3 = 0;      // タイマー3用のカウント変数
unsigned long countAD = 0;      // ADC用のカウント変数
unsigned long countT1s = 0;
unsigned long countT3s = 0;
unsigned long countADs = 0;

int main(void)
{
    :    : 途中略 :    :
    /*
     * タイマー1の設定
     * 
     * Fcy=39.6288MHzの場合
     * 250msecで割り込み    : (39.6288MHz / 256) * 250msec = 38700
     *
     * Fcy=42.3936MHzの場合
     * 250msecで割り込み    : (42.3936MHz / 256) * 250msec = 41400 
     *
     * Fcy=45.1584MHzの場合
     * 250msecで割り込み    : (45.1584MHz / 256) * 250msec = 44100 
     *
     * Fcy=30.4128MHzの場合
     * 250msecで割り込み    : (30.4128MHz / 256) * 250msec = 29700 
     */
    OpenTimer1(T1_ON & T1_IDLE_CON & T1_GATE_OFF & T1_PS_1_256 & 
        T1_SYNC_EXT_OFF & T1_SOURCE_INT, 
#if OVERCLOCK == 0
        38700-1);
#elif OVERCLOCK == 1
        41400-1);
#elif OVERCLOCK == 2
        44100-1);
#elif OVERCLOCK == -1
        29700-1);
#endif

    /* プライオリティレベル4、割り込み許可 */
    ConfigIntTimer1(T1_INT_PRIOR_4 & T1_INT_ON);

    /* UART1の設定 */
    OpenUART1(g_U1MODE, g_U1STA, BRGVAL);
#if OVERCLOCK == 0
    printf("Fcy=39.6288MHz, %d channels\r\n", NUM_CHANNEL);
#elif OVERCLOCK == 1
    printf("Fcy=42.3936MHz, %d channels\r\n", NUM_CHANNEL);
#elif OVERCLOCK == 2
    printf("Fcy=45.1584MHz, %d channels\r\n", NUM_CHANNEL);
#elif OVERCLOCK == -1
    printf("Fcy=30.4128MHz, %d channels\r\n", NUM_CHANNEL);
#endif

    /* 10bit ADコンバータ */
    SetChanADC1(g_AD1CHS123, g_AD1CHS0);
    OpenADC1(g_ADCON1, g_ADCON2, g_ADCON3, 0, 
        0x03C0, 0, 0, 0x0000);
    ConfigIntADC1(ADC_INT_PRI_6 & ADC_INT_ENABLE);

    /* タイマー3の設定 */
    /*
     * Fcy = 39.6288MHzの場合
     * 2usecで割り込み   : 39.6288MHz * 2us =  79.256 =>  79    1.99us
     * 1usecで割り込み   : 39.6288MHz * 1us =  39.629 =>  40    1.01us
     *
     * Fcy = 42.3936MHzの場合
     * 2usecで割り込み   : 42.3936MHz * 2us =  84.787 =>  85    2.01us
     * 1usecで割り込み   : 42.3936MHz * 1us =  42.394 =>  42    1.00us
     *
     * Fcy = 45.1584MHzの場合
     * 2usecで割り込み   : 45.1584MHz * 2us =  90.317 =>  90    1.99us
     * 1usecで割り込み   : 45.1584MHz * 1us =  45.158 =>  45    1.00us
     *
     * Fcy = 30.4128MHzの場合
     * 2usecで割り込み   : 30.4128MHz * 2us =  60.826 =>  61    2.01us
     * 1usecで割り込み   : 30.4128MHz * 1us =  30.413 =>  30    0.99us
    */
    OpenTimer3(g_TIMER3, 
#if OVERCLOCK == 0
  #if NUM_CHANNEL == 1
        40-1);
  #elif NUM_CHANNEL == 2
        79-1);
  #endif
#elif OVERCLOCK == 1
  #if NUM_CHANNEL == 1
        42-1);
  #elif NUM_CHANNEL == 2
        85-1);
  #endif
#elif OVERCLOCK == 2
  #if NUM_CHANNEL == 1
        45-1);
  #elif NUM_CHANNEL == 2
        90-1);
  #endif
#elif OVERCLOCK == -1
  #if NUM_CHANNEL == 1
        30-1);
  #elif NUM_CHANNEL == 2
        61-1);
  #endif
#endif

    /* 
     * 割り込みなしでもTIMER3の周期でAD変換トリガーとして設定可能だが、
     * 本アプリでは、TIMER3の周期も確認したいため、割り込みをONに設定
     * 但し、Fcy=30.4128MHzの場合、OFFにしないとタイマー1の割り込み処理
     * が動かない。恐らく、処理が間に合わないため。
     */
#if T3_INTERRUPT != 0
    ConfigIntTimer3(T3_INT_PRIOR_5 & T3_INT_ON);
#endif

    while(1)
    {
        if (adcFlag != 0)
        {
            unsigned long t3time, adtime;
            /* 標準入出力は128~512バイト程度ヒープ領域が必要 */
            /* UART1のみ使用可能 */
#if T3_INTERRUPT != 0
            t3time = (250 * 1000000) / (countT3s / countT1s);
            adtime = (250 * 1000000) / (countADs / countT1s);
            printf("T1=0x%04lX, T3=0x%08lX(%ld[ns]), "
                "AD=0x%08lX(%ld[ns]), %d\r\n", 
                countT1s, countT3s, t3time, countADs, adtime, adcData[0]);
#else
            adtime = (250 * 1000000) / (countADs / countT1s);
            printf("T1=0x%04lX, AD=0x%08lX(%ld[ns]), %d\r\n", 
                   countT1s, countADs, adtime, adcData[0]);
#endif
           adcFlag = 0;
        }
    }

    return 0;
}


void __attribute__((interrupt, no_auto_psv)) _T1Interrupt(void)
{
    IFS0bits.T1IF = 0;      /* 割り込みフラグをクリア */
    
    countT1++;
    if ((countT1 % 8) == 0)
    {
        adcFlag = 1;
        countT1s = countT1;
        countT3s = countT3;
        countADs = countAD;
    }
}

#if T3_INTERRUPT != 0
void __attribute__((interrupt, no_auto_psv)) _T3Interrupt(void)
{
    IFS0bits.T3IF = 0;      /* 割り込みフラグをクリア */

    countT3++;
}
#endif

void __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void)
{
    IFS0bits.AD1IF = 0;     /* 割り込みフラグをクリア */

#if NUM_ADC_INTERRUPT == 1
    adcData[0] = ReadADC1(0);
    countAD++;
#else
    adcData[0] = ReadADC1(0);
    adcData[1] = ReadADC1(1);
    adcData[2] = ReadADC1(2);
    adcData[3] = ReadADC1(3);
    countAD += 4;
#endif
}

上記プログラムを動かし、タイマー1用、タイマー3用、ADコンバータ用のカウント変数を見ると、次のような結果になりました。

Fcy[MHz] 39.6288 42.3936 45.1584 30.4128
Tad[nsec] 75.7 70.8 66.4 65.8
14Tad[nsec] 1059 991 930 921
ADC割り込み 4サンプル 4サンプル 4サンプル 4サンプル
1チャンネル 1Msps × ×
2チャンネル 500Ksps ×

結局、1チャンネル 1Msps、2チャンネル 500Kspsを実現するには、Tadが65nsecに近いFcy=45.1584MHz30.4128MHzで動かす必要がありそうです。Fcy=30.4128MHzの場合、タイマー3の割り込みをONにしたり、ADコンバータの割り込み(ADC割り込み)を毎サンプリングで起きるようにすると、タイマー1の割り込みが動かなくなります。そのため、Fcyを落とした分、かなりぎりぎりで動いているようです。また、Fcy=45.1584MHzで動かした場合でも、毎サンプルごとのADC割り込みではぎりぎり間に合っていないようです。Fcy=45.1584MHz、30.4128MHzで動かした場合の、シリアル端末での表示は次のようになります。どちらの周波数でも、1チャンネルの場合は約1000nsec周期で、2チャンネルの場合は約2000nsec周期でサンプリングできています。

1Mspsでサンプリングした波形

Fcy=45.1584MHzの設定で、周波数5KHzの正弦波を1Mspsでサンプリングした時の波形は次のようになりました。グラフの横軸目盛りは50μsecであり、4目盛り(200μsec)で一周期になっていることが分かります。

このグラフ表示アプリは、自作オシロ用に作成中のPCプログラムです。まだ1チャンネルしか対応しておらず、UIも未完成ですが、完成したら自作オシロページで紹介したいと思います。

最新の7件

OpenGL

電子工作

玄箱HG

ホームページ

日記

Copyright (C) 2007 Arakin , All rights reserved.