2016年1月22日金曜日

PSoC 5LPでI2Sのテスト TDA1543編 DMAを使ってDDSで波形を出力

I2SコンポーネントにDMA転送しつつ波形テーブルを更新してみた。

GitHub
https://github.com/ryood/TDA1543/tree/master/PSoC/I2S_Test/PrototypingKit_DMA.cydsn

PSoC CreatorのTopDesign

DMAコンポーネントのデーターシートを読むと「Ping Pong」や「Round Robin」という技が載っていたが、(読むのがめんどくさいので)まずは簡単にDMA転送が終わったら割り込みを発生させてその隙に波形テーブルを更新できないかテストしてみた。

I2Sコンポーネントのデーターシートを読むとデータを保持するFIFOは各チャンネル4ByteあるらしいのでI2Sの転送スピードより速くメモリーを更新できれば間に合いそうだ。

また、今回はL/Rチャンネルをインターリーブではなく独立してDMAからデータを転送する設定にした。

↑Data interleaving:のところ

DMA転送完了の割込み(nrq)を使うにはDMA Wizardで「Enable nrq」をチェックする必要があるようだ。


以下の様なコードが生成される。

 /* DMA Configuration for DMA_0 */  
 DMA_0_Chan = DMA_0_DmaInitialize(DMA_0_BYTES_PER_BURST, DMA_0_REQUEST_PER_BURST,   
     HI16(DMA_0_SRC_BASE), HI16(DMA_0_DST_BASE));  
 DMA_0_TD[0] = CyDmaTdAllocate();  
 CyDmaTdSetConfiguration(DMA_0_TD[0], TABLE_LENGTH*2, DMA_0_TD[0], DMA_0__TD_TERMOUT_EN | TD_INC_SRC_ADR);  
 CyDmaTdSetAddress(DMA_0_TD[0], LO16((uint32)waveTable0), LO16((uint32)I2S_1_TX_CH0_F0_PTR));  
 CyDmaChSetInitialTd(DMA_0_Chan, DMA_0_TD[0]);  
 CyDmaChEnable(DMA_0_Chan, 1);  

nrqで割込みを発生させて以下の様なコードでサイン波とノコギリ波を交互に出力するようにした。

 int8 waveTable0[TABLE_LENGTH*2];  
 int8 waveTable1[TABLE_LENGTH*2];  
   
 const uint8 sineTable8[TABLE_LENGTH] =   
 {  
     128, 134, 140, 147, 153, 159, 165, 171,  
     177, 183, 188, 194, 199, 204, 209, 214,  
     218, 223, 227, 231, 234, 238, 241, 244,  
     246, 248, 250, 252, 253, 254, 255, 255,  
     255, 255, 255, 254, 253, 252, 250, 248,  
     246, 244, 241, 238, 234, 231, 227, 223,  
     218, 214, 209, 204, 199, 194, 188, 183,   
     177, 171, 165, 159, 153, 147, 140, 134,   
     128, 122, 115, 109, 103, 97, 91, 85,  
      79, 73, 68, 62, 57, 52, 47, 42,  
      37, 33, 29, 25, 22, 18, 15, 12,  
      10,  7,  6,  4,  2,  1,  1,  0,  
      0,  0,  1,  1,  2,  4,  6,  7,  
      10, 12, 15, 18, 22, 25, 29, 33,  
      37, 42, 47, 52, 57, 62, 68, 73,  
      79, 85, 91, 97, 103, 109, 115, 122  
 };       
   
 void genSineTable(void* buff)  
 {  
   int i;  
   
   // 符号付き16bit sineTableの生成  
   for (i = 0; i < TABLE_LENGTH; i++) {  
     ((int8 *)buff)[i*2]  = (int)sineTable8[i] - 128;  
     ((int8 *)buff)[i*2+1] = 0;  
   }  
 }  
   
 void genSawTable(void* buff)  
 {  
   int i;  
     
   // 符号付き16bit sawTableの生成  
   for (i = 0; i < TABLE_LENGTH; i++) {  
     ((int8 *)buff)[i*2]  = i * 2 - 128;  
     ((int8 *)buff)[i*2+1] = 0;  
   }  
 }  

TDA1543の出力

ch1はノコギリ波、ch2はサイン波とノコギリ波をテーブルを更新して交互に出力させている。想定通りの波形が出力された。

波形テーブル更新の前後でH/Lさせたもの(波形テーブルの書き換え時間)を捕捉


ch1:波形テーブルの書き換え時間 ch2:TDA1543の出力

拡大

ch1とch2の立ち上がりがずれているのは、I2SコンポーネントのFIFOにたまっている分だと思う。ch2の立ち上がりで実際にI2Sのデータが送信されはじめていると思うので、テーブルの更新の完了(ch2の立下り)は間に合っていないみたいだがDMA転送はCPUと独立しているのでなんとかなっている感じだ。

テーブル長が128なのでこれを32ぐらいに減らせば安心かな?

DDSで出力

Github
https://github.com/ryood/TDA1543/tree/master/PSoC/I2S_Test/PrototypingKit_DDS.cydsn

DDSで波形を生成するようにしてみた。

符号付16bitでテーブルを作成したので、これをI2Sのバイトストリームの順序に合うようにバイト順を入れ替えてバッファに転送した。
 void generateWave_0()  
 {  
   int i, index;  
   uint8* p8;  
    
   // 波形をバッファに転送  
   for (i = 0; i < BUFFER_SIZE; i+=2) {  
     phaseRegister_0 += tuningWord_0;  
     index = phaseRegister_0 >> 22;  
      
     p8 = (uint8 *)(sineTable + index);  
     waveBuffer_0[i]  = *(p8 + 1);  
     waveBuffer_0[i+1] = *p8;  
   }  
 }  

TDA1543はデーターシートを見るとビットレート9.2Mbits/sまで大丈夫そうなので、I2Sコンポーネントの入力クロックを12MHz(I2Sのビットレートは6MHzになる)に設定して、1kHzと10kHzのサイン波をL/Rに分けて出力してみた。


ch1:LOUT ch2:ROUT

だいたい10kHzと1kHzのサイン波が出力されている。LPFをいれていないので10kHzの方はガタガタでブレブレ。

WS(ワードセレクト)

ch1:LOUT ch2:WS

WSの周波数がサンプリング周波数になる。

I2Sコンポーネントに12MHzのクロックを入れているので
12MHz / 16(bit) / 2(ch) / 2 = 187.5kHz
ちょっとずれているが、まあまあ近い値だ。

PSoC 5LP Prototyping Kitの内蔵クロックの誤差かな?

メモ:

サンプリングレートが187.5kHz程度でもDDSだと10kHzのサイン波形はジッターがかなり出る。周波数設定時に浮動小数点演算で1周期分の波形を生成するという手もありそう。

波形テーブルにSRAMを64kBのうち32kB使うとして最大16kサンプル
192kHz / 16k = 10.125Hz
工夫無しで10Hzぐらいから出力可能。計算時間はかなりかかりそうだけど。

元の波形テーブルを1024でなくもっと増やす?

う~ん。