トークスルー

トークスルー( Talk Through )とはAnalog Devices社がよく例題で使う言葉で、入力から入った信号をそのまま出力に出すプログラムです。ループバックなどとも言います。これをMCMを使ってみましょう。実際のプログラムはアナログデバイセズが供給する例題をほぼそのまま使用してコメントを追加しています。

今回使用するのはMCMの中のfwEzKit2191というクラスです。このクラスはEZ-KIT Lite2191用のフレームワークで、SPORT0に接続されたAC97コーデックを初期化し、ダブルバッファ・アルゴリズムを使用してデータを入出力します。SPORTの設定、DMAの設定、バッファの管理、AC97コーデックの管理をすべて行ってくれるため、プログラマは目的とする信号処理に集中することができます。

実際にプログラマが行う仕事は次のようなものです。

  1. フレームワークに実際にしたい仕事を付加する
  2. インスタンス変数を用意する
  3. 割り込みをフレームワークに渡すハンドラを用意する
  4. 割り込みハンドラを登録する
  5. フレームワークのインスタンスを作る
  6. フレームワークがAC97コーデックの初期化を終えるのを待つ
  7. AC97コーデックのミュートをはずす
  8. 割り込みを待つ

フレームワークに仕事を付加するのはいいとして、それを実行しているところが見当たりません。実は実行は割り込みのなかでフレームワークが行います。そのために3.の割り込みハンドラを用意しているのですが、いずれにせよユーザーは実行タイミングやその詳細を考えなくていいことになっています。

では、実際にプログラムを見ながら各段階を追って見ましょう。

前処理

ソースコードの先頭ではヘッダーファイルを読み込み、関数プロトタイプを宣言します。mcm2191.hは今回使用するライブラリのヘッダです。これは必ず読み込んでください。

#include "mcm2191.h"
#include <signal.h>
#include <sysreg.h>

void sportHandler( int intr );

フレームワークに実際にしたい仕事を付加する

まずフレームワークに実際に行う仕事を付加します。フレームワークはクラスなので仕事の付加は派生型を定義することで行います。このとき、オーバーライドする関数は決まっており、必ずhandleBufferメンバー関数をオーバーライドします。この部分はいつも同じだと思ってかまいません。以下の例ではfwEzKit2191を元に新しい型としてCTT型を宣言しています。

//**************************************************************************
//
//  CTT 型クラスの導出
//
//  CTT型はEZ-KIT 2191用クラス fwEzKit2191 から派生させる。fwEzKit2191は
//  EZ-KIT 用のAD1815制御フレームワーク。handleBuffer内で送受信バッファ
//  操作を行う
//
//**************************************************************************
class CTT : public mcm::fwEzKit2191
{
public: 
        virtual void handleBuffer( struct mcm::sample * bufTx, 
                                   struct mcm::sample * bufRx );
};

次にクラスの宣言に従ってCTT::handleBufferメンバー関数を定義します。繰り返しますがこの関数が実際に仕事を行う場所です。今回行いたい仕事は入力から出力へのコピーです。そこでCTT::handleBufferのなかでどのようにコピーを行うか見てみましょう

handleBufferは引数としてbufTxとbufRxを受け取ります。bufRxは受信バッファであり、直前のDMA転送によって受信データを格納しています。handleBufferを呼ぶことになる割り込みは、この直前の受信DMA転送の終了が引き金になっています。bufTxは次のDMA転送で送信されるデータを格納するバッファです。いずれのバッファもsample型であり、長さはthis->bufSizeで知ることができます。sample型のうち必要なのは.lフィールドと.rフィールドです。この二つは名前のとおりに左と右のオーディオデータを格納しています。

以上のことからCTT::handleBufferのなかではthis-bufSize回のループを使ってbufRxからbufTxへデータをコピーしていることがわかります。また、mcm::とあるのはネームスペースです。MCMライブラリは名前空間のためにネームスペースを活用しています。これによって大域名が氾濫するのを防いでいます。

ちなみにfwEzKit2191ではbufSizeの値は48に固定されています。つまり、48サンプル分をまとめて処理します。これは48Kサンプル/秒のとき、ちょうど1m秒ごとに割り込みがかかることを意味します。MCM自身は他のサンプル数で動くこともできますが、その場合、より原始的なフレームワークから自分が使うものを派生させなければなりません。ほとんどの場合48サンプルで間に合うでしょう。

//**************************************************************************
//
//  CTT::handleBufferの定義
//
//  handleBufferメンバー関数はDMA転送が終わるたびに受信バッファと送信
//  バッファをパラメータに呼ばれる。受信バッファには直前のDMAで受信した
//  データが入っている。handleBufferから戻るときに送信バッファにデータを
//  おいておくと、次のDMA転送で送信される。
//
//**************************************************************************
void CTT::handleBuffer( struct mcm::sample * bufTx, struct mcm::sample * bufRx )
{
        for ( int i=0; i< this->bufSize; i++ ){  // bufSizeは送受信バッファの長さ
                bufTx[i].l = bufRx[i].l;         // Lch受信データを送信バッファにコピー
                bufTx[i].r = bufRx[i].r;         // Rch受信データを送信バッファにコピー
        }
}

インスタンス変数を用意する

フレームワークの派生型を宣言したら、そのインスタンス変数を保持するためのポインタを用意します。宣言の段階ではまたインスタンスは作っていません。実際のインスタンス生成はmain関数のなかで行います。

//**************************************************************************
//
//  インスタンスの作成
//
//  フレームワークの派生クラスを作ったら、そのインスタンス変数を一つ
//  用意しておく
//
//**************************************************************************

CTT *tt;                        // トークスルー・アルゴリズムのオブジェクト

割り込みをフレームワークに渡すハンドラを用意する

フレームワークは割り込みで駆動されますので、そのためのハンドラを用意します。ハンドラの構造は簡単で、割り込みがおきたらhandleInterruptメンバー関数を呼ぶだけです。

//**************************************************************************
//
//  割り込みハンドラ
//  単にインスタンスに制御を渡すだけ
//
//**************************************************************************
void sportHandler( int intr )
{
        tt->handleInterrupt();  
}

実行プログラム

残りの処理はmain関数で行います。この部分の作業は決まりきった作業ですので、多くのプログラムで同じ処理を行うことになります。いくつかポイントを挙げておきましょう。

まず、割り込みハンドラの登録を最初に行ってください。あとで制御を開始するとすぐに割り込みがかかりますので、ハンドラの登録は重要です。次にインスタンスを作成して、runメンバー関数を呼びます。runはSPORTとDMAを初期化してSPORTをイネーブルにします。その結果として直ちにDMA転送が始まり、割り込みがかかり始めます。

次にコーデックの初期化終了を待ちます。フレームワークは割り込みがかかるたびにコーデックとやり取りを行い、内部状態を変更していきます。この内部状態がSTATE_RUNNINGになったら初期化終了です。

入出力のミュート解除は少し説明が必要でしょう。この処理はAC97コーデックにコマンドを投げることで実現します。コマンドを投げるにはtAC97Writeメンバー関数を使用します。この関数の第一引数はコマンドで、これはヘッダーファイルのなかで定義してあります。名前空間はac97です。第二引数はコマンドのパラメータです。これは構造が複雑ですので補助関数gainを使って組み立てます。gainの第一引数はミュート指定であり、今回は解除ですのでfalseを与えます。第二、第三引数は左チャンネル、右チャンネルのゲイン/減衰量設定です。この指定方法はコマンドに応じて異なりますので、詳しくはAD1885のマニュアルを読むなり、手探りで調べるなりしてください。

ミュートを解除するとすぐに信号が通り始めます。メインルーチンはあとは割り込みを待つだけで、割り込み処理の中で先のコピーが実行されます。すでに説明したようにDMAの制御やダブルバッファ処理はすべてフレームワークに閉じ込められていますので、プログラマはそういったわずらわしい処理に頭を悩ます必要はありません。

//**************************************************************************
//
//  メイン関数
//
//  メイン関数では次のことを行う
//
//  1. 割り込みハンドラ登録
//  2. フレームワークのインスタンス生成
//  3. フレームワークの実行
//  4. コーデックの初期化が終わるのを待つ
//  5. コーデックのミュート解除
//  6. 無限ループ
//
//  以上の処理はほとんどのプログラムで共通である
//
//**************************************************************************

int main (void)
{
        interrupt( SIG_INT5 ,&sportHandler); // 割り込みハンドラ登録
        enable_interrupts();                 // グローバル割り込みイネーブル
                                                
        tt = new CTT( );                     // インスタンス生成
        tt->run();                           // AC97制御開始

                                             // 入出力のミュートを解除
        tt->tAC97Write( ac97::HEAD_VOLUME, ac97::gain( false, 15, 15 )  ); 
        tt->tAC97Write( ac97::RECORD_GAIN, ac97::gain( false, 8, 8 ) );
        
                                             // メインルーチンではあとは割り込み待ち
        while( 1 )
                asm( "idle;" );
}

プロジェクト

さて、今回のプロジェクトはライブラリを使います。そのため、通常のプロジェクト構成に加えてライブラリ関連のファイルをプロジェクトに追加する必要があります。今回必要なファイルは次の三つです。

この三つのファイルはアナログデバイセズからダウンロードするサンプル・ファイルの中に入っています。これらのファイルを自分のプロジェクト・ディレクトリにコピーしてプロジェクトに追加してください。

プロジェクトに登録したファイル

プログラムをビルドし、ダウンロードして実行してください。EZ-KIT Liteのライン入力端子に信号を入力してライン出力をアンプ内蔵スピーカーやヘッドフォンに接続します。入力信号がそのまま出てくることがわかるでしょう。参考のためにプロジェクトをダウンロードできるように用意しておきました。

⇒次はカラオケ

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