PIC32MX220F032B (3) -- SPI/I2S クロックの検討 (3)
PIC32MX220F032B に接続した (ノンオーバーサンプリング) ディジタル・オーディオ DAC に約 440 Hz の正弦波を出力するプログラムを下に示します。
正弦波は DDA (Digital Differential Analyzer) アルゴリズムで発生させており、左右のチャンネルに 90° 位相の違う正弦波を出力しています。
MPLAB X で PIC32 用のプロジェクトを作成し、自動生成される「main.c」の内容を下のプログラム・リストの内容で置き換えてください。
/********************************************************/ /* Files to Include */ /********************************************************/ #include <plib.h> /* use PIC32 peripheral lib */ #include <stdint.h> /* For uint32_t definition */ #include <stdbool.h> /* For true/false definition */ //#include "system.h" //#include "user.h" // Work around #if !defined(SPI_CONFIG_MCLKSEL) #define SPI_CONFIG_MCLKSEL SPI_OPEN_MCLKSEL #endif int __attribute__((weak)) SpiChnTxBuffCount(SpiChannel chn) { return((1 == chn) ? SPI1STATbits.TXBUFELM : SPI2STATbits.TXBUFELM); } // end of work around // system clock frequency in Hz #define SYS_FREQ 42336000UL // 42.336 MHz // Configuration bits // SYSCLK = (PRICLK=12.096[MHz]) / 3 * 21 / 2 = 42.336[MHz] #pragma config FNOSC=PRIPLL, FPLLIDIV=DIV_3 #pragma config FPLLMUL=MUL_21, FPLLODIV=DIV_2 // PBCLK = SYSCLK / 1 = 42.336 [MHz] #pragma config FPBDIV=DIV_1 // HS xtal, Cock Sw En, FSCM En #pragma config POSCMOD=HS, FCKSM=CSECME // USBPLL = (PRICLK=12.096 [MHz]) / 3 * 24 = 96.768 [MHz] #pragma config UPLLEN=ON, UPLLIDIV=DIV_3 #pragma config FWDTEN=OFF // watchdog timer off #pragma config FSOSCEN=OFF // secondary osc off /********************************************************/ /* Global Variable Declaration */ /********************************************************/ const int fs_ind = 0; // 0 for 48 kHz, 1 for 44.1 kHz const uint32_t fs_tab[2] = {48000UL, 44100UL}; // sin_cos_freq = 48 [kHz] * 3775 / (2 * pi * 2^16) = 440.05 [Hz] // sin_cos_freq = 44.1 [kHz] * 4108 / (2 * pi * 2^16) = 439.96 [Hz] const uint16_t acc_mul[2] = {3775, 4108}; const uint32_t acc_shift = 16; // use SPI2 for DAC output const uint32_t DAC_SPI = SPI_CHANNEL2; const uint32_t SPI_CONFIG_params = ( SPI_CONFIG_MSTEN // master mode | SPI_CONFIG_FSP_HIGH // LRCK(SS) = 1 for L ch | SPI_CONFIG_ENHBUF // use FIFO | SPI_CONFIG_DISSDI // DISable SDI (and Rx) | SPI_CONFIG_CKP_HIGH // clock = _-_-_-_- ); const uint32_t SPI_CONFIG2_params = ( SPI_CONFIG2_AUDEN // AUDio mode ENable | SPI_CONFIG2_AUDMOD_RJ // right justified | SPI_CONFIG2_IGNTUR // IGnore Tx UnderRun ); void set_dac_fs(uint32_t fs) { SpiChnEnable(DAC_SPI, 0); // at first diable SPI if (48000UL == fs) { // fs = 48 kHz // use REFCLK as SPI clock (MCLKSEL = 1) // fs = 12.096 [MHz] / 3 * 24 / 31.5 / 2 / 32 = 48 [kHz] SpiChnConfigureEx( DAC_SPI, ( SPI_CONFIG_params | SPI_CONFIG_MCLKSEL ), SPI_CONFIG2_params); SpiChnSetBrg(DAC_SPI, 0); // Baud rate divi = 1/2 } else { // fs = 44.1 kHz // use PBCLK as SPI clock (MCLKSEL = 0) // fs = 12.096 [MHz] / 3 * 21 / 2 / 30 / 32 = 44.1 [kHz] SpiChnConfigureEx( DAC_SPI, SPI_CONFIG_params, SPI_CONFIG2_params); SpiChnSetBrg(DAC_SPI, 14); // Baud rate divr = 1/30 } // if (48000UL == fs) { ... SpiChnEnable(DAC_SPI, 1); // now, enable SPI } // void set_dac_fs() /********************************************************/ /* Main Program */ /********************************************************/ int32_t main(void) { int32_t sin_acc = 0; int32_t cos_acc = 0x7f80; // initial radius DDPCONbits.JTAGEN = 0; // diable JTAG pins SYSTEMConfigPerformance(SYS_FREQ); // REFCLK = (PRICLK=12.096 [MHz])/3*24/31.5 = 3.072 [MHz] OSCREFConfig( OSC_REFOCON_USBPLL, (OSC_REFOCON_OE | OSC_REFOCON_ON), 15); mOSCREFOTRIMSet(384); // trim = 384/512 = 0.75 // pin configurations // to monitor REFCLK on PA4 (pin 12) mSysUnlockOpLock( PPSOutput(3, RPA4, REFCLKO) ); // DAC_BCLK = SCK2 = RB15 (pin 26, not assignable) // DAC_LRCK = SS2 = RB9 (pin 18) // DAC_SDAT = SDO2 = RB8 (pin 17) mSysUnlockOpLock( (PPSOutput(4, RPB9, SS2), PPSOutput(2, RPB8, SDO2)) ); INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); ANSELA = 0; ANSELB = 0; // setup SPI for the fs (48 kHz / 44.1 kHz) set_dac_fs(fs_tab[fs_ind]); while(1) { if (2 > SpiChnTxBuffCount(DAC_SPI)) { // FIFO has room to store stereo sample SpiChnWriteC(DAC_SPI, cos_acc); SpiChnWriteC(DAC_SPI, sin_acc); // update sin/cos value using DDA calculation cos_acc -= ((sin_acc * acc_mul[fs_ind]) >> acc_shift); sin_acc += ((cos_acc * acc_mul[fs_ind]) >> acc_shift); } // if } // while } // int32_t main()
「Global Variable Declaration」というコメントの下の「fs_ind」を「0」に設定すると 48 kHz サンプリング、「1」に設定すると 44.1 kHz サンプリングの設定になります。
「PIC32UBL.exe」を使った書き込みでは、コンフィギュレーション・ワードを特別扱いせず、また、後から書き換えることもできないので、ソース・プログラム中に「#pragma config」ディレクティブを使って必要な分のコンフィギュレーションは全て記述しておく必要があります。
MPLAB X の PIC32 用プロジェクト作成では、コンフィグを記述する目的に「configuration_bits.c」というソース・ファイルが自動生成されますが、それは無視し、「main.c」の方にコンフィグを記述しています。
Microchip 提供の周辺ライブラリを使う形に書いてありますが、一部の記述が足りない部分があるようで、「Work around」と記してある部分で対応しています。
SPI/I2S モジュールの初期化には SpiChnOpenEx() 関数あるいは SpiChnConfigureEx() 関数を使うことができますが、SpiChnOpenEx() 関数では指定しなくても自動的に ON ビットを立てる動作が余計なので、指定された設定のみを行う SpiChnConfigureEx() 関数を使っています。
この設定のフラグのひとつに MCLKSEL ビットの設定を示す「SPI_CONFIG_MCLKSEL」があるのですが、これに対応する「SPI_OPEN_MCLKSEL」は定義されているのに、なぜか「SPI_CONFIG_MCLKSEL」の定義は抜けています。
シンボルに実際に定義されるビット・パターンは SPI_OPEN〜 でも SPI_CONFIG〜 でも同じなので、
#define SPI_CONFIG_MCLKSEL SPI_OPEN_MCLKSEL
として補っています。
また、SPI の送信 FIFO の中にあるデータの数を返す SpiChnTxBuffCount() という関数があるのですが、ヘッダ・ファイルでは定義されているのにオブジェクトのリンクの過程で、「そんな関数はない」と言ってきます。
ライブラリのソース (spi_chn_tx_buff_count_lib.c) を見てみると、関数本体の定義が
#ifdef _SPI_REV_5XX_ #endif
という条件コンパイルのディレクティブに囲まれていますが、実際にはエンハンスド・バッファ (FIFO バッファ) モードがないのは PIC32MX3XX/4XX シリーズなので、 #ifdef の部分は
#if defined(_SPI_REV_1XX_) | defined(_SPI_REV_5XX_)
または
#ifndef _SPI_REV_3XX_
となるべきだと思います。
また、「PIC32 Family Reference Manual」の「Section 23.Serial Peripheral Interface(SPI)」の「23.4.5.2 LEFT-JUSTIFIED MODE」および「23.4.5.3 RIGHT-JUSTIFIED MODE」の説明で、CKP = 0 として、ビット・クロック (SCK) の立ち上がりでシフト、立ち下りでサンプルするタイミング図が掲載されていますが、これは誤りだと思います。
これまでにマイコンに接続して使ったことのある
- ROHM BU9480F
- NEC μPD6376
- TI/BB PCM1716
- AKM AK4321
のいずれも、ビット・クロックの立ち下りでシフト、立ち上がりでサンプルというタイミングになっていました。
このタイミングで動作させるには、「23.4.5.1 I2S MODE」の項と同様に、CKP = 1 とする必要があります。
オーディオ・サンプルの L / R の同期を取るのに、送信アンダーランを無視するモードを使っています。
CONFIG2 のパラメタとして、SPI_CONFIG2_IGNTUR フラグを指定します。 (「IGNTUR」は「IGNore Transmit Under-Run」の意)
このモードでは、送信 FIFO が空になって送信すべきデータがない場合 (アンダーラン状態) には、代わりに「ゼロ」を送信します。
送信 FIFO にデータが書き込まれるとアンダーラン状態を抜けることになりますが、そのチェックは新しいオーディオ・フレームの開始のタイミング、つまり、次の L チャンネル・データを送信するタイミングまで引き伸ばされます。
アンダーラン状態でない場合には、L チャンネル送信タイミング、R チャンネル送信タイミングにそれぞれ 1 ワードずつ送信 FIFO からデータが読み込まれますが、いったんアンダーラン状態になると、以降のオーディオ・フレームの R チャンネル送信タイミングでの送信 FIFO 読み出しをしなくなります。
したがって、確実にアンダーランになっている状態で、すばやく L ch / R ch の順に送信データを送信 FIFO に書き込むと、そのチェックは次のオーディオ・フレームの頭の L ch 出力タイミングで行われるので、L ch データ / R ch データの順で送信されることが保証されます。
ここで「すばやく」というのは、L / R の書き込みタイミングの間隔がオーディオ・フレームの 1/2 以上にならないことという条件です。
書き込み間隔がそれ以上になると、L ch データが消費されてアンダーラン状態を抜けた後に R ch データが書かれないまま再びアンダーラン状態となる可能性があります。 そうすると L / R が逆転してしまいます。