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