実際のプログラム

長い長い前振りでした。そろそろ飽きられる気もしますのでこれまで説明したことをもとに実際にプログラムを組んでみましょう。作るのは次のようなプログラムにします。

プログラム言語はCを使います。DMAの終了は割り込みを使ってもいいのですが今回は実験ですのでとくに終了検出はせず、手動終了させることにします。AC97コーデックの1サンプルは256ビットのフレームですので、これを16ビット×16ワード構成で受信します。この方法はAC97コーデックのデータ型に合致しませんが実験には十分です。AC97規格より、SPORTの設定は

SP0_RCRの設定

この条件でSPORT0の設定レジスタであるSP0_RCRがどうなるか見てみましょう。HRの9-14およびB-33ページも参照してください。

ICLKD( Internal Clock Disable )
のっけからよくわからないのですが、どうもSPORT0のRCLKピンをディセーブルにするビットです。イネーブルにするために0に設定します
CKRE( Clock Rising Edge )
ビット・クロックがデータをフォーリング・エッジでドライブしライジング・エッジでサンプルするならば1にします。ライジング・エッジでドライブし、フォーリング・エッジでサンプルするなら0にします。HRの9-15ページおよびB-33ページはおもいっきり間違えていますので注意してください。
LATFS( Eary/Late Frame Sync )
説明をはしょりますが、このビットはマルチチャンネル・モードでは0にします。マニュアルには書いてないようですが、Analog Devices社のSHARC時代からの伝統です。
LRFS( Hi/Low Frame Sync)
名前の由来がまったく理解できませんが、こういう英語のセンスの悪さがAnalog DevicesのDSPの特徴です。フレーム同期信号の極性を正極性にするときには0、負極性にするときには1にします。
RFSR( Frame Sync Required )
受信フレーム同期信号が必要なら1、不要なら0です。当然1だろうと思うところですが、マルチチャンネル・モードでは0にします。これもSHARCからの伝統です。罠だ。
IRFS( Internal Frame Sync )
受信フレーム同期信号をSPORT0内部で生成するときには1にします。
SLEN( SPORT Word Length )
受信ワード長-1を設定します。今回はワード長が16ビットですから15を設定します。
SENDN( SPORT Endian Format )
MSBから送信するなら0, LSBから送信するなら1を設定します。この名前もひどいですね。
DTYPE( Data type )
0,1,2,3がそれぞれ0フィル、符号拡張、μ則、A則にをあらわします。オーディオ信号の場合は普通は符号拡張を選びます。なお、μ、Aというのは通信用の圧縮方式のことです。
ICLK ( Input Clock )
RCLK( ビットクロック )が外部生成なら0、内部生成なら1です。ちなみにICLKのIは Internalじゃないかと思いますが。
RSPEN( Receive SPORT Enable )
0ならSPORTディセーブル、1ならばイネーブルです。

SP0_RFSDIVの設定

マルチチャンネル動作時のチャンネル数はどこで設定するのでしょうか。実はチャンネル数を明示的に設定するレジスタはありません。その代わりにフレーム同期信号の周期を設定することができます。1周期をワードサイズで割った商がチャンネル数です。

SP0_RFSDIV = ( チャンネル数 * 受信ワード長 ) - 1

今回は16ビット長で16チャンネルなのでSP0_RFSDIVには255を設定します。

SP0_MCMC0の設定

さて、今回ばかりは書いている私にとっても少々タフです。設定はこれに終わりません。さらにマルチチャンネル設定とDMAの設定が続くのです。がんばりましょう。MCMの設定はSP0_MCMC0とSP0_MCMC1で行います。HRの9-30ページとB-41ページも参照してください。まずはSP0_MCMC0から。

WOFF(Window Offset)
いきなりわけのわからんものが飛び出してきました。ADSP-2191のSPORTは通信インフラ用に最大128チャンネル構成をとることができます。そういう用途であっても実際に覗くのは数チャンネルですから全部のチャンネルの送受信は無駄です。そういうわけで、フレーム内に覗き窓(ウインドウ)を設けてその中だけ転送するようになっています。その窓の位置をWOFFフィールドで決めます。今回は大げさなことはしませんからこのフィールドには0を設定します。ちなみに、HR First Edition B-41のWOFFはこれまた間違いです。正しくはビット10ではなくビット9がWOFFの始まりです。
WSIZE( Window Size )
覗き窓の大きさを指定します。ここにNを設定すると窓の大きさは(N+1)*8になります。今回は窓のサイズは16なのでN=1です。なお、HR First Edition B-41ではWSIZEにビット9が含まれていますが、これは間違いでビット8までです。
MFD( Multi-Channnel Frame Delay )
フレーム同期信号を出す位置はプロトコルによって異なります。MFDにNを設定すると、フレームの最初の信号よりNクロック前にフレーム同期信号が現れると設定することになります。
MCM( Multi-Channnel Mode )
マルチチャンネル・モードで使用するときには1を設定します。

SP0_MCMC1の設定

続いてSP0_MCMC1の設定です。HRのB-43を参照してください。

FSDR( Frame Sync Data Relationship )
これはH.100通信プロトコル以外では無視します。0にしてください。
MCFF( TX FIFO Prefetch MAX DIstance )
今回は関係ないものの、これは大変重要な設定です。DMAエンジンは実際に送信しているワードの次のワードだけではなく、その先のワードまでプリフェッチします。これは遅い外つけメモリーを使用する場合や、他のデバイスがバスを占有した場合を考えてのことです。システムの安定性を考えればより先のチャンネルまでプリフェッチしたほうがよいといえます。しかし、うっかりするとプログラマがデータを送信バッファに置くより先にプリフェッチされるかもしれません。注意が必要です。今回は何でもいいので0にしてください。
MCOM( Channel Select Offset Mode )
WOFFを有効にするかどうか決めます。1なら有効です。0に設定します。
MCDRXPE ( Multi-Channel DMA Receive Packing Enabled )
EnableにするとSP0_MTCSxレジスタのビットマップで"1"になっているチャンネルのみ受信します。DisableにするとWindowで指定したチャンネルすべてが対象になります。ところが、実験してみるとDisableのときしか正常に動かないようです。今回はEnableにしました。
MCDTXPE ( Multi-Channel DMA Transmit Packing Enabled )
MCDRXPEと同じですが送信用です。
MCCRM( 2x Clock Recovery mode )
H.100通信プロトコル以外では無視します。

SP0_MRTCSxの設定

SP0_MRTCSxは、最大128チャンネル構成を許すSPORTに対して実際にはどのチャンネルを受信するかビットマップで指定します。SP0_MRTCS0は最初の16チャンネル、SP0_MRTCS1は次の16チャンネルといった具合にすべてのチャンネルに1ビットずつ割り当てられています。詳細はHR B-38を参照してください。

今回は16チャンネル全部を受信しますので、SP0_MRTCS0を0xFFFFとし、あとは0にします。

DMAとSP0_DRCFG

DMA転送を行うときに重要なことは、あらかじめバッファとデスクリプタが構成するリンクをよく理解しておくことです。今回の構成を下の図に示します。

デスクリプタの設定

今回のプログラムは1フレームだけ受信して終わりですので、デスクリプタのリンク・リストは簡単です。図にはrxTCBおよびterminationTCBという二つのデスクリプタからなるリンクリストが示されています。このうちterminationTCBはそのとおりDMAを停止させるためのTCBであり、目印としてconfigurationフィールドが0になっています。DMAはこのデスクリプタを読み込むと動作を停止します。実際にはconfigurationフィールドを読み込んだ時点で値が0なら動作を停止しますので、startPage以降は冗長です。

実際の転送はrxTCBデスクリプタに記述してあります。このデスクリプタは転送数16、転送バッファのページ番号0を決め打ちしており、startPageにデータを転送すべきバッファ(rxBuffer)のアドレスを格納しています。

configurationフィールドにはSP0_DRCFGレジスタにロードする設定値を書いておきます。なお、デスクリプタを使用する場合、以下のビットの相当数が読み出し専用になっています。そのため、レジスタには直接書き込めず、デスクリプタのCFGフィールドに値を書き込んでロードします。SPO_DRCFGの定義は次のとおりです。

DOWN( Descriptor Owner )
このフィールドは次にデスクリプタを処理するのは誰かをあらわします。設定するにはメモリー上のデスクリプタのこのレジスタに対応するフィールド(CFG。今回のプログラムではconfigurationフィールド)のDOWNビットに値を書きます。1ならばDMAエンジン、0ならばプロセッサです。プロセッサはここに1をたててDMAを開始します。後でプロセッサがデスクリプタを確認したときにデスクリプタのこのビットが0になっていれば、このデスクリプタのDMAが完了したということです。
DS (DMA Completion Status )
レジスタを直接読んで確認するステータスです。0ならば正常終了、1ならば以上終了です。
FS ( DMA Buffer Status )
バッファの状態を表します。普通は参照しません。詳しくはB-44ページを参照してください。
DERE ( Interrupt on Error )
デスクリプタのこのビットを1にするとDMAの異常が起きたときに割り込みを起こします。詳細はHR 6-23ページを参照してください。
FLSH( DMA Buffer Clear )
DMAが異常終了した後、バッファを空にするためにこのビットに1を書き込みます。
DAUTO ( Autobuffer/Descriptor mode )
DMAには今回説明したデスクリプタを使う方法のほかにAutobufferモードがあります。1にするとAutobufferモードです。
DCOME ( Interrupt on DMA complete )
1にするとDMA完了時に割り込みを発生します。
TRAN (Transfer Direction )
DMAの転送方向を指示します。0ならばメモリーから読み出し、1ならばメモリーに書き込みです。このビットの名前もセンスないですね。
DEN (DMA Enable )
0ならばDMAディセーブル、1ならばイネーブルです。

DMA開始手続き

DMAはなかなか開始手続きが複雑です。詳細はHR 6-6を見てもらうとして、下に簡単に手続きを説明します。

  1. メモリー上にデスクリプタのリンク・リストを作ります。このときCFGフィールド(今回の構造体ではconfiguration)のDOWNビットを1にしてください。
  2. デスクリプタの先頭アドレスをSP0DR_CPレジスタに設定します。
  3. SP0DR_CPRに1を書き込みます。HR Second Edition 6-6では「DRビットに1を書け」とありますが、B-51にはDRビットの定義がありません(-_-)またかよ。ビット0がDRのようです。DRに1を書き込んだ時点でDMAエンジンはデスクリプタをロードします。
  4. SP0DR_CFGにのDENに1をセットします。これでDMA転送が開始されます。

一度DMA転送が始まると、DMAエンジンは自分自身でデスクリプタのリンク・リストを手繰っていきますのでソフトウェアの介入は不要です。転送量、転送場所、転送終了後の割り込みの有無はすべてデスクリプタに書いてあるため、ソフトウェアはこれらを自由に設定して手放しでデータ転送を行えます。

プログラム

お待たせしました。ようやくプログラムを披露できます。以下のプログラムはこれまで説明したことを全部盛り込んでいます。SPORTとデスクリプタの設定について改めて説明は不要でしょう。割り込みハンドラに関しては、アーキテクチャーの割り込みの構造ページやEZ-KIT Liteの割り込みを使おうページを参照してください。

#include <def2191.h>
#include "def2191bit.h"
#include <sysreg.h>
#include <signal.h>

#define BUFSIZE 16

struct TDMADescriptor 
{
        int configuration, startPage, startAddress, count;
        struct TDMADescriptor * nextDescriptor;
};

int rxBuffer[BUFSIZE];
struct TDMADescriptor  terminationTCB, rxTCB;

void sportRxHandler( int sig );
void setupSport( void );
void setupTCB( void );

int main(void)
{
        interrupt( SIG_INT5, sportRxHandler );  // default interupt for SPORT0 RX
        enable_interrupts();
        setupTCB();                             // TCB Configuratoin
        setupSport();                           // SPORT0 RX Configuration

        while ( 1 )
                ;
}

void setupSport( void )
{

        sysreg_write( sysreg_IOPG, SPORT0_Controller_Page );
        io_space_write( SP0DR_CFG, 0 );         // RX DMA Disable
        io_space_write( SP0_RCR, 0 );           // RX disable
        io_space_write( SP0_TCR, 0 );           // TX disable

        io_space_write( SP0_RFSDIV, 255 );      // 256 bit per frame
        io_space_write( SP0_MRCS0, 0xffff );
        io_space_write( SP0_MRCS1, 0 ); 
        io_space_write( SP0_MRCS2, 0 ); 
        io_space_write( SP0_MRCS3, 0 ); 
        io_space_write( SP0_MRCS4, 0 ); 
        io_space_write( SP0_MRCS5, 0 ); 
        io_space_write( SP0_MRCS6, 0 ); 
        io_space_write( SP0_MRCS7, 0 ); 

        io_space_write( SP0_MCMC1, 0 << WOFF_OFST 
                                 | 1 << WSIZE_OFST | 1 << MFD_OFST | MCM );
        io_space_write( SP0_MCMC2, MCDRXPE );

        io_space_write( SP0DR_CP, (int) &rxTCB );       // set chain pointer
        io_space_write( SP0DR_CPR, 1 );         // ready descriptor pointer
        io_space_write( SP0DR_CFG, DEN );
        io_space_write( SP0_RCR,  IRFS | SLEN_16 | RSPEN );
}

void setupTCB( void )
{
                        // setting up the DMA TCB
        rxTCB.configuration = DOWN | DCOME | TRAN | DEN;
        rxTCB.startPage = 0;
        rxTCB.startAddress = (int)rxBuffer;
        rxTCB.count = BUFSIZE;
        rxTCB.nextDescriptor = &terminationTCB;
                        // setting up the DMA TCB for termination
        terminationTCB.configuration = 0;
        terminationTCB.startPage = 0;
        terminationTCB.startAddress = 0;
        terminationTCB.count = 0;
        terminationTCB.nextDescriptor = ( struct TDMADescriptor * )0;
}

void sportRxHandler( int sig )
{
        sysreg_write( sysreg_IOPG, SPORT0_Controller_Page );
        io_space_write( SP0DR_IRQ, 1 );     // clear interrupt from SP0 RX
}

インクルードしているdef2191bit.hは、アナログデバイセズの日本語技術資料ページのANJ-017のサンプルに入っています。当該ページへはリンクページから飛んでください。

メインプログラムは、

  1. 割り込みハンドラを設定し、
  2. デスクリプタのリストを作り、
  3. SPORTを設定、実行させて、
  4. 無限ループに入ります

プログラムの終了はVisualDSP++から行ってください。終了の仕方は先の割り込みを使おうページの末尾に説明してあります。このプログラムを走らせると、SPORTのDMAはAC97コーデックAD1885から1サンプル文のデータを1フレーム16チャンネルに受け取ります。そのデータはrxBuffer中に展開されます。私が実行した結果を以下に示します。

演算結果

rxTCBを見ると、最後のフィールド(nextDescriptor)がterminationTCBのアドレスを指していることがわかります。また、3番目のフィールド(startAddress)はrxBufferを指しています。rxBufferには受信データが格納されています。

1ワード目はAC97のSLOTチャンネルです。このチャンネルは続くチャンネルのうち、どのチャンネルが有効かを示します。詳しくはAC97の規格を参照してもらうとして、9800とは、SLOTチャンネル、左チャンネル、右チャンネルが有効であるということです。

SLOTチャンネル以降はチャンネルあたり20ビットであることに気をつけてください。従って2ワード目以降は少しずつずらして考えなければなりません。上の絵を分解してみると、

となっています。

最後に上記プログラムをプロジェクトごと圧縮したファイルをダウンロードできるようにしておきました。興味のある方は動かしてみてください。

このプログラムはAC97コーデックがすでに動作状態にあることを前提にしています。AC97コーデックの状態は直前に実行したプログラムや、ブートしたファームウェアに強く依存します。そのため、必ずしも上のようにrxBufferにデータが格納されるとは限りません。まったくデータがない場合もあります。どのような場合も正しく動くプログラムは次回紹介します。

⇒次は連続受信

2191空挺団 | プログラム | EZ-KIT | こぼれ話 | アーキテクチャー | 命令 | レジスタ | DSP掲示板 | FAQ |