STM8S-Discovery (3)
STmicro 社製の STM8S マイコン評価ボード「STM8S-Discovery」用のプログラム第二弾として、SD/MMC カード内の圧縮されていない .WAV ファイルを再生する、リニア PCM プレイヤープログラム「STM8SDP」を作りました。
暫定版の STM8SDP プログラムは (→こちら) に置いてあります。
前回の FMmelody プログラム用の DAC 回路 (BU9480) の接続はそのまま利用し、SD/MMC カードスロットを追加する形になります。
回路図を下に示します。
16 ビット・シリアル入力ディジタル・オーディオ用 DAC である BU9480 への出力には SPI を利用しており、SD/MMC カードのインターフェースにも SPI が必要なので、高速な SPI がふたつ必要になりますが、STM8S105C6 にはハードウェアの SPI モジュールはひとつしかありません。
UART モジュールには同期モードがあって、SPI としての応用も可能ですが、クロックレートの上限が CPU クロックの 1/16 なので、結局 1 MHz までしか使えません。
これではスピードが不足なので、DAC へはハードウェア SPI を使って出力し、SD/MMC カードとのインターフェースにはソフトウェア SPI を使うことにしました。
純粋なソフトウェア SPI では、1 ビットあたり 6 クロック程度までしか速度を上げられなかったのですが、Timer1 の機能を使って、SPI クロックは Timer1 ハードウェアで発生させるようにしたところ、1 ビットあたり 2 クロック、つまり SPI クロックとして 8 MHz を実現できました。
これはハードウェア SPI モジュールでのクロックの上限と同じです。
それでも、16 ビット・ステレオでは、惜しいところでサンプリング周波数 44.1 kHz には届かず、実用的なサンプリング周波数として 32 kHz を選択することにしました。 CPU クロック 16 MHz の分周で、32 kHz は誤差ゼロで実現できています。
開発環境としては、Raisonance 社の C コンパイラを使っています。 まだライセンス認証をしていないので、1 KB / 4 KB のサイズ制限が存在しています。
SD/MMC カードのアクセスのために、ChaN さんの「ぷち FatFS」および AVR 用のサンプル・プロジェクト「pfsample」を利用していますが、そのままでは制限に引っかかってコンパイルできないため、ファイルを分割するなどの「小細工」をしてあります。
プログラム・サイズの制限のため、非常に基本的な機能しか実現できていません。
機能としては次のようになります。
- プログラムを実行すると、
- SD/MMC カードのルート・ディレクトリにある、
- "32000.WAV" というファイル名の 16 ビット・ステレオ・32 kHz サンプリングの wave ファイルを、
- 演奏して終了
するだけのプログラムです。
プログラムの実行時に、何の操作もできませんし、ファイル名の変更にはソース・プログラムを書き換えて再コンパイルが必要です。
wave ファイルのパラメタは一切見ず、32 kHz/16 ビット/ステレオに決め打ちで、ファイル・ヘッダ部分もスキップせず、オーディオ・データとして再生してしまうために、最初の部分でノイズが発生します。
プログラムのロジックとしては、エラーのリトライや、活線挿抜には対応しておらず、正常終了の場合でも、エラーの場合でも、単に main() 関数の実行を終了するだけになっています。
しかし、C のランタイムの機能として、main() 関数を終了後、ループして再び main() 関数の実行に入るので、期せずして繰り返し演奏および活線挿抜の機能が実現されることになります。
エラーがあっても正しく抜けずに、どこかで引っかかってしまうと、ハングアップ状態になります。
また、カードを抜いたあと、ノイズが再生され続ける可能性もあります。
手を加えた「ぷち FatFS」由来のソースは、src フォルダの下の pff フォルダの中に入れてあります。
変更部分は「小細工」が主なもので、本質的な部分は変わりありません。
新たに作ったプログラムは、「main.c」と「soft_spi2.asm」だけです。
下にその主要な部分を示します。
/**************************************************/ /* STM8SDP: Linear PCM SD/MMC card player program */ /* for STM8S-Discovery + BU9480F (ROHM) */ /* */ /* 2010/02/28 Created by pcm1723 */ /* */ /**************************************************/ #include "stm8s_conf.h" #include "pff/pff2.h" #include "pff/diskio2.h" #define SEC_SIZE (512) #define BUF_BYTE_SIZE (2*SEC_SIZE) #define BUF_SAMP_SIZE (BUF_BYTE_SIZE/4) volatile bit sem = 1; // buffer semaphore volatile rbuf_ind = 0; // buffer index char WAVE_FILENAME[] = "32000.WAV"; FRESULT io_result; WORD xfr1, xfr2; FATFS f1; NEAR BYTE sbuf[BUF_BYTE_SIZE] = {0,}; // audio sample buffer ... (中略) ... // // Timer2 "Update" interrupt service routine // #ifdef _RAISONANCE_ void TIM2_UPD_OVF_BRK_IRQHandler( void ) interrupt ITC_IRQ_TIM2_OVF #else // COSMIC FAR @interrupt void TIM2_UPD_OVF_BRK_IRQHandler(void) #endif { TIM2->SR1 &= (~TIM2_SR1_UIF); // clear interrupt flag SPI->DR = sbuf[(rbuf_ind++) + 1]; // 16-bit DAC high byte SPI->DR = sbuf[(rbuf_ind++) - 1]; // 16-bit DAC low byte if (LRCK_MASK & LRCK_PORT->IDR) { // R-ch sample timing rbuf_ind &= (BUF_BYTE_SIZE-4); // update buffer index sem = ((BUF_BYTE_SIZE/2) & rbuf_ind); // set semaphore } // if (LRCK_MASK & ... } // void TIM2_UPD_OVF_BRK_IRQhandler() void main(void) { port_init(); enableInterrupts(); if (FR_OK == pf_mount(&f1)) { // mount OK if (FR_OK == pf_open(WAVE_FILENAME)) { // file open OK do { LED_ON; while(!sem) {wfi();} // wait until 1st buf free LED_OFF; io_result = pf_read((void*)&(sbuf[0]), SEC_SIZE, &xfr1); LED_ON; while( sem) {wfi();} // wait until 2nd buf free LED_OFF; io_result |= pf_read((void*)&(sbuf[SEC_SIZE]), SEC_SIZE, &xfr2); } while ( (FR_OK == io_result) & (2*SEC_SIZE <= (xfr1 + xfr2)) ); } // if (FR_OK == pf_open()) ... } // if (FR_OK == pf_mount())... pf_mount(0); // unmount disableInterrupts(); } // void main()
; ; Soft SPI module ; ; 2010/02/28 Created by pcm1723 ; $MODESTM8 ; ; prefix '?' to ASM function name as 'C' function ; public ?rcv_spi public ?xmit_spi ASM?CODE segment CODE START_SCK macro bset TIM1_CR1,#TIM1_CR1_CEN_BIT ; start TIM1 endm MOSI_HIGH macro bset mosi_port,#mosi_bit ; force MOSI to 'H' endm ; ; Start of CODE segment ; RSEG ASM?CODE ; ; Receive a byte from MISO ; (MOSI forced to 'H') ; ?rcv_spi: ldw x,#(miso_port) ; MISO port address MOSI_HIGH ; force MOSI to 'H' sim ; disable interrupt START_SCK ; start SCK generation clr a ; clear result at first nop nop nop ; timing adjustment rept 8 ; 2 clock / bit sll (x) ; [C] <-- MISO bit rlc a ; [Acc] <-- [C] endm rim ; enable interrupt ret ; ; Transmit a byte over MOSI ; (no input from MISO) ; ?xmit_spi: sll a ; [C] <-- Acc.b7 sim ; disable interrupt START_SCK ; start SCK generation rept 8 ; 2 clock / bit rlc a ; Acc.b0 <-- [C], [C] <-- Acc.b7 ld mosi_port,a ; output LSB (and others) endm rim ; enable interrupt ret ; END