STM32F4-Discovery 用FM音源プログラム -- TGSTM32F4 (2)
ボード上の外付けステレオ DAC (CS43L22) へ DMA を使用してオーディオ出力する簡単なテストプログラムが動くようになりました。 記事の最後にソースリストを示します。
単に、サンプリング周波数 48 kHz で周波数 440 Hz の音を、のこぎり波を左チャンネルから、三角波を右チャンネルから出力するだけです。
当初は、STMicro 提供のサンプル・プログラム「Audio_Playback_and_Record」(解説はアプリケーション・ノート AN3997) では DAC から出力されるのに、自作のテストプログラムでは、音どころか、MCLK や SCLK すら発生しない状態でした。
調べてみると、これは I2S 用の PLL の設定がされていないために、I2S モジュールへのクロック自体が供給されていないためと分かりました。
「Audio_Playback_and_Record」プログラムでは「main.c」から明示的に呼び出されるプログラム中には I2S PLL 設定のコードは含まれておらず、スタートアップ・プログラムから main() 関数の実行前に自動的に呼び出される「SystemInit()」関数からさらに「SetSysClock()」関数が呼び出され、その中で設定が行われていました。 (ファイルとしては「system_stm32f4xx.c」の中)
SetSysClock() 関数内では各種のクロックの設定を行っているので、その意味では I2S PLL の設定をするのにふさわしい場所ですが、FM 音源プログラムではサンプリング周波数の切り換えの必要があるので、後に示すソースリストのように、ユーザプログラム中に引っ張り出してきて、 I2S PLL クロックの設定を行うようにしました。
ただし、後に示すプログラムでは、I2S PLL を 86 MHz の固定値に設定するだけで、可変できるようにはなっていません。
プログラムのコンパイル方法は、まず、「Atollic TrueSTUDIO for STMicroelectronics STM32 Lite V2.3.0」で通常のように STM32F4-Discovery ボード用の新規 C プロジェクトを作成します。
そして、プロジェクト内の「Utilities / STM32_EVAL」フォルダ内の「stm32f4_discovery_audio_codec.h」ファイル中の各 #define に対しコメントの付け外しで、次のように設定します。
// #define I2S_INTERRUPT // #define AUDIO_MAL_MODE_NORMAL #define AUDIO_MAL_MODE_CIRCULAR #define AUDIO_MAL_DMA_IT_TC_EN #define AUDIO_MAL_DMA_IT_HT_EN #define USE_DEFAULT_TIMEOUT_CALLBACK
この設定で、
- I2S 送信を各サンプルごとの割り込みを使わずに DMA で行う
- DMA は通常モードではなくサーキュラー・モードで行う
- DMA の HalfTransfer 割り込みを使う
- I2C 通信のタイムアウト・コールバック関数はデフォルトのものを使う
という動作モードを選択することになります。
STM32F10x などの従来からある DMA コントローラの「サーキュラー・モード」と、「HalfTransfer」割り込み機能を利用して「ダブル・バッファ」を実現しており、機能アップした STM32F4 の DMA コントローラがハードウェアで本来持っている「ダブル・バッファ・モード」を利用したものではありません。
次に、メニュー・バーの「Project / Properties」を選んで開くダイアログで、
C/C+ Build / Settings / Tool Settings / C Compiler / Miscellaneous / Other Options
のテキスト・ボックスに
-DHSE_VALUE=8000000
を追加します。
これは、Std Periph Driver のシンボル「HSE_VALUE」 (外部高速クロック周波数) のデフォルト値が 25 MHz となっているので、ボードに実装されているクリスタルの実際の値 8 MHz でオーバーライドするためです。
STM32F10x の場合には、デフォルト値が 8 MHz だったので、特に設定が必要なかったのですが、STM32F4xx では、イーサネットの PHY 用のクロックとして使う 25 MHz がデフォルト値となったので、設定ファイルに変更を加えるか、あるいは上のように、コンパイル時のオプションとして追加する必要があります。
本来、シンボルの値の定義を追加する専用の「場所」は用意されているのですが、Lite 版の統合環境では、変更不可能なエントリとなっているので、上のように、コンパイル時の追加オプションとして与えます。
最後に、「src」フォルダ内の「main.c」を下のソースリストと置き換えてコンパイルします。
/****************************/ /* Wave generation sample */ /* using stereo DAC CS43L22 */ /* by DMA */ /* 2012/02/15 */ /****************************/ /* Includes */ #include "stm32f4xx.h" #include "stm32f4_discovery.h" #include "stm32f4_discovery_audio_codec.h" #include <stdlib.h> // for abs() function #include <string.h> // for bzero(), memcpy() function /* PLLI2S_VCO = (HSE_VALUE Or HSI_VALUE / PLL_M) * PLLI2S_N I2SCLK = PLLI2S_VCO / PLLI2S_R */ // I2SCLK = 258 [MHz] / 3 = 86 [MHz] #define PLLI2S_N 258 #define PLLI2S_R 3 #define WAVEQUESIZE (256) #define STEREO_MODE (1) #define AUDIO_SAMPLES_PER_DMA_BUF (8) // factor '2' for 'stm32f4_discovery_audio_codec.c/.h' V1.0.0 (19-Sep-2011) #define DMA_BUF_SIZE_ADJ (2) // factor '4' for 'stm32f4_discovery_audio_codec.c/.h' V1.1.0 (28-Oct-2011) //#define DMA_BUF_SIZE_ADJ (4) // DMA buffer for audio samples // factor 2 for STEREO sample // factor 2 for 16-bit (2-byte) data #define DMA_BUF_BYTES (2*2*AUDIO_SAMPLES_PER_DMA_BUF) typedef uint16_t dac_dma_buf_t[2*AUDIO_SAMPLES_PER_DMA_BUF]; dac_dma_buf_t dac_dma_buf[2]; // double buffer // wave queue handling defines / functions volatile int wq_wr_ix, wq_rd_ix; int16_t wave_que[WAVEQUESIZE][STEREO_MODE+1]; #define WAVE_QUE_HAS_ROOM (wq_wr_ix != wq_rd_ix) #define UPDATE_QUE_INDEX(ix, n) (ix = ((ix + (n)) & (WAVEQUESIZE - 1))) #define UPDATE_QUE_READ_IX(n) UPDATE_QUE_INDEX(wq_rd_ix, n) #define UPDATE_QUE_WRITE_IX(n) UPDATE_QUE_INDEX(wq_wr_ix, n) __inline__ void ENQUEUE(uint16_t Lch, uint16_t Rch) { wave_que[wq_wr_ix][0] = Lch; wave_que[wq_wr_ix][1] = Rch; UPDATE_QUE_WRITE_IX(1); } // void ENQUEUE() __inline__ void INIT_QUE_IX( void ) { wq_wr_ix = 0; wq_rd_ix = 0; } // void INIT_QUE_IX() // // I2S PLL setup for 86 MHz MCLK // void stm32_PLLI2S_MCLK_setup() { // disable PLLI2S for parameter setting RCC->CR &= ~((uint32_t)RCC_CR_PLLI2SON); /* PLLI2S clock used as I2S clock source */ RCC->CFGR &= ~RCC_CFGR_I2SSRC; /* Configure PLLI2S */ RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28); /* Enable PLLI2S */ RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON); /* Wait till PLLI2S is ready */ while((RCC->CR & RCC_CR_PLLI2SRDY) == 0) {} } // stm32_PLLI2S_MCLK_setup() int main(void) { int32_t tri_wave = 0; uint32_t Lch_data = 0; uint32_t Rch_data = 0; uint32_t ph_acc = 0; // phase accumulator // phase accum increment for 440 Hz @ fs = 48 kHz uint32_t ph_inc = ((440 * 0x800000UL) / 48000) << 9; STM_EVAL_LEDInit(LED6); STM_EVAL_LEDOn(LED6); // Blue LED ON // wave queue (FIFO) and DMA buffer setup INIT_QUE_IX(); // initialize queue indexes bzero(dac_dma_buf, sizeof(dac_dma_buf)); // clear DMA buffer // I2S DAC (CS43L22) setup stm32_PLLI2S_MCLK_setup(); // set I2S PLL MCLK = 86 MHz EVAL_AUDIO_SetAudioInterface(AUDIO_INTERFACE_I2S); // select I2S interface DAC EVAL_AUDIO_Init(OUTPUT_DEVICE_BOTH, 83, I2S_AudioFreq_48k); // vol = 83, fs = 48kHz // start CS43L22 stereo audio DAC playing by DMA EVAL_AUDIO_Play(dac_dma_buf[0], DMA_BUF_SIZE_ADJ*sizeof(dac_dma_buf)); /* Infinite loop */ while (1) { if (WAVE_QUE_HAS_ROOM) { ENQUEUE(Lch_data, Rch_data);// put stereo data in queue // saw and triangle wave generation tri_wave = abs((int32_t) ph_acc) - 0x40000000UL; Lch_data = (ph_acc >> 16); // sawtooth wave Rch_data = (tri_wave >> 15); // triangle wave ph_acc += ph_inc; // update phase accumulator } // if (WAVE_QUE_HAS_ROOM) } // while (1) } // int main() void fill_dma_buf(int n) { // copy audio sample data from wave_que[] to DMA buffer memcpy(dac_dma_buf[n], wave_que[wq_rd_ix], sizeof(dac_dma_buf[0])); UPDATE_QUE_READ_IX(AUDIO_SAMPLES_PER_DMA_BUF); } // void fill_dma_buf() // Callback used by stm32f4_discovery_audio_codec.c. // Refer to stm32f4_discovery_audio_codec.h for more info. void EVAL_AUDIO_TransferComplete_CallBack(uint32_t pBuffer, uint32_t Size){ // "1"-page of DMA buffer has transfered, fill it fill_dma_buf(1); } void EVAL_AUDIO_HalfTransfer_CallBack(uint32_t pBuffer, uint32_t Size){ // "0"-page of DMA buffer has transfered, fill it fill_dma_buf(0); } uint16_t EVAL_AUDIO_GetSampleCallBack(void){ /* TODO, implement your code here */ return 0; }
これを実行すると、青色の LED (LED6) だけが点灯し、のこぎり波が左チャンネルから、三角波が右チャンネルから出力されます。
TureSTUDIO で STM32F4-Discovery 用のプロジェクトを新規作成すると、プロジェクト内にコピーされてくる「stm32f4_discovery_audio_codec.c / .h」のバージョンは
V1.0.0 (19-Sep-2011)
と古く、一方、STMicro の web サイトからダウンロードした「stm32f4discovery_fw」に含まれるファイルのバージョンは
V1.1.0 (28-Oct-2011)
となっており、両者は異なっています。
EVAL_AUDIO_Play() 関数の第二引数として渡す、オーディオ・データ・バッファのサイズの扱いが両者で違っており、使うライブラリのバージョンによってサイズに掛けている定数値「DMA_BUF_SIZE_ADJ」の値の定義を変更する必要があります。