PSoC 4200 Prototyping Kit (15)

「入力モード」の FIFO、つまり CPU 側からデータを書き込み、UDB ハードウェア側からデータを読み出す設定の FIFO では、CPU「バス側」には FIFO の「空き容量」を示すステータス信号が、UDB ハードウェアの「ブロック側」には FIFO エンプティを示すステータス信号が出力されます。
オーバーフローやアンダーフローを防ぐためには、それらの FIFO ステータス信号を「ステータス・レジスタ」ハードウェアに立ち上げておき、CPU 側でケアしてやる必要がありますが、今回の応用では、それらの FIFO ステータス信号は全く利用していません。
TCPWM コンポーネントで作成した LRCLK 信号の立ち上がりの直後に UDB ハードウェア側は FIFO からデータを取り込み、CPU 側では数十クロックの割り込み応答時間が経過した後に割り込みハンドラに制御が移って FIFO への書き込みが発生します。
したがって、

UDB ハードウェアの FIFO 読み込み → CPU 側からの FIFO 書き込み

は、常にこの順番でペアになって発生します。
リセット後は FIFO は「空」ですから、最初の LRCLK の立ち上がりで FIFO から UDB 側にデータを読み出す時点で「アンダーフロー」となってしまいます。
「Architecture TRM」では、アンダーフロー状態の FIFO から読み出される値は「未定義」となっていますが、実際にはデータ自体が破壊されているわけではないので「周回遅れ」のデータとして読み出されてきます。
そういうわけで、FIFO ステータスについては全く考慮していません。
UDB の「補助コントロールレジスタ」(ACTL) の FIFO0_CLR ビットをセットしておくと、FIFO 動作ではなくシングル・レジスタとして動作し、常に同一のレジスタに対する読み書きするモードとなります。
ここで、以前にも掲載した、シグマデルタ変調器のテスト用の回路図を示します。
SdmDac16 コンポーネントを 2 インスタンス配置してステレオ出力としています。

SdmDac16 コンポーネントはデータパス・セルだけから構成されており、オーディオ・データのサンプリング・タイミングの発生機能や、インタラプト発生機能はありません。
上の図の PWM モードの TCPWM コンポーネント (インスタンス名: PWM1) でサンプリング・タイミング (LRCLK) 発生およびインタラプト発生を行っています。
インスタンス名 PWM1_ISR のインタラプト・コンポーネントの割り込みサービス・ルーチンの一部を下に示します。 (PWM1_ISR.c)

 . . . . . <中略> . . . . .

/*******************************************************************************
*  Place your includes, defines and code here 
********************************************************************************/
/* `#START PWM1_ISR_intc` */
    
#include <PWM1.h> 
#include <SdmDac16_L.h>    
#include <SdmDac16_R.h>  

// stereo DAC data and update flag
volatile int16_t dac16[2];
volatile int     dac_flag = 0;

/* `#END` */

 . . . . . <中略> . . . . .
********************************************************************************
*
* Summary:
*   The default Interrupt Service Routine for PWM1_ISR.
*
*   Add custom code between the coments to keep the next version of this file
*   from over writting your code.
*
* Parameters:  
*   None
*
* Return:
*   None
*
*******************************************************************************/
CY_ISR(PWM1_ISR_Interrupt)
{
    /*  Place your Interrupt code here. */
    /* `#START PWM1_ISR_Interrupt` */
    
    if (PWM1_INTR_MASK_TC & PWM1_GetInterruptSourceMasked()) {
// TC (Terminal Count) interrupt occurred,
// (rising edge of LRCLK, L ch out timing)
// clear interrupt source
      PWM1_ClearInterrupt(PWM1_INTR_MASK_TC);
// update Sigma-DeltaModulation DAC register    
#if (1)
      SdmDac16_L_Write(dac16[0]);
      SdmDac16_R_Write(dac16[1]);
#else    
      CY_SET_REG16(SdmDac16_L_sdmdac16_u0__16BIT_F0_REG,
                   dac16[0]);
      CY_SET_REG16(SdmDac16_R_sdmdac16_u0__16BIT_F0_REG,
                   dac16[1]);
#endif    
      dac_flag = 1;
     } // if 

    /* `#END` */
}

メイン側とのオーディオ・サンプル・データのやり取りはソフトウェア FIFO は設けず、ステレオ・オーディオ・サンプルひとつ分を格納する配列変数 dac16 を定義して使っています。
割り込みハンドラ内では、まず PWM1 インスタンスの TC (Terminal Count) 割り込みであるかどうかを判別してサンプリング・タイミングであるかどうかを決定しています。
サンプリング・タイミングであれば、メイン側からのオーディオ・データを変数 dac16
から引き取り、SdmDac16 の左右それぞれのインスタンスに書き込んでいます。
プリプロセッサの #if 〜 #else 〜 #endif ディレクティブで書いてある前半部分は API 関数を利用する場合です。
#else 以降の部分は、API 関数で「ラップ」しない場合の「素」の表現になっています。
「SdmDac16_L_sdmdac16_u0__16BIT_F0_REG」や「SdmDac16_R_sdmdac16_u0__16BIT_F0_REG」はビルド時に自動生成される「cyfitter.h」ファイルの中で定義されています。
長ったらしくて面倒ですが、API 関数定義を用意してラップすれば、「SdmDac16_L_Write()」や「SdmDac16_R_Write()」となって少し手数が省けます。
SdmDac16 の左右インスタンスへのオーディオ・データ書き込みが終了したら、「dac_flag」をセットして、メイン側に現サンプルのデータ出力が終了し、次サンプルのデータを発生させる必要があることを通知します。
「main.c」のソースを下に示します。

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include <project.h>

extern volatile int16_t dac16[2];
extern volatile int     dac_flag;

int main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
  int32_t sin_acc = 0;
  int32_t cos_acc = 0x70000000L;
  #define ACC_SHIFT (5)
 
  PWM1_Start();
  PWM1_ISR_Start();
  CyGlobalIntEnable;  /* Uncomment this line to enable global interrupts. */
  for(;;)
  {
      /* Place your application code here. */
    if (dac_flag) { // sample update timing
// shift upper halfword to lower halfword of sin/cos acc
// and convert to offset binary        
      dac16[0] = (0x8000UL ^ ((uint32_t)sin_acc) >> 16);
      dac16[1] = (0x8000UL ^ ((uint32_t)cos_acc) >> 16);
// DDA calculation    
      sin_acc += (cos_acc >> ACC_SHIFT);
      cos_acc -= (sin_acc >> ACC_SHIFT);
// wait for next sampling timing    
      dac_flag = 0;
    } // if (dac_flag) { ...
  }
}

/* [] END OF FILE */

SdmDac16 コンポーネントはソフトウェア的に起動してやる必要はないので、メイン・プログラムの無限ループに入る前の必要な初期化は、

だけです。
いつものように DDA (Digital Differetial Analyzer) によるサイン/コサイン波発生を行なっています。
L ch にサイン波、R ch にコサイン波を出力しています。
SdmDac16 コンポーネントではストレート・バイナリあるいはオフセット・バイナリの入力を仮定していますから、オーディオ・データの MSB (b15) を反転して、2 の補数表現からオフセット・バイナリに変換しています。