任意周波数の生成:DDS

任意周波数の正弦波を生成するにはどうすればいいでしょう。周波数が指定されるたびにウェーブテーブルを作り直す方法はすぐ思いつきますが、実際にはよいアイデアではありません。任意周波数の正弦波の生成にはいろいろ手法があるようですが、簡単で実用的なものは与えられた小規模のウェーブテーブルから任意の周波数の波形を生成するというものです。その代表的なアルゴリズムとしてDDS ( Direct Digital Synthesizer )があります。以下ではDDSを使った正弦波生成について説明します。

DDSの動作

DDSの動作は非常に簡単です(図1)。あらかじめ設定された周波数情報がレジスタに格納されており、単位時間ごとにアキュームレーターに加算されます。これによってアキュームレーターには瞬時の位相が格納されることになります。アキュームレーターの値が更新されるたびにウェーブテーブルを引いて位相を信号の値に変換します。これで設定した周波数の正弦波を生成できます。

図1 DDSの原理

DDSはアルゴリズムとしてよりも回路として有名です。この原理の回路は古くから知られ、現在ではワンチップ化されて数百MHzのサンプリングクロックで動作する製品が販売されています。

PLLと比較したDDSの特徴は次のようになります。

ソフトウェアによるDDSはハードウェアによるものよりぐっと周波数が落ちますが、数百KHzの信号ならば問題なく作ることができますし、注意深く最適化すればADSP-2191でも数MHzの信号を作ることができます。

プログラム

DDSの動作を確認するための簡易クラスを作りました。CxHzはその名のとおり周波数xHzの信号を生成します。このクラスはC1KHzの子のクラスで、C1KHzが内蔵している1KHz用ウェーブテーブルを使って任意周波数を生成します。周波数は1KHzより低いものを単位Hzでコンストラクタ呼び出し時に指定します。

//**************************************************************************
//
//  CxHz 型クラスの導出
//
//  CxHz型はC1KHzから導出する。C1KHzの正弦波テーブルを使って1KHz以下の
//  任意周波数を生成する。
//
//**************************************************************************
class CxHz: public C1KHz
{
private:
    int phase;
    int index;
    int freq;
public: 
    CxHz( int frequency );
    virtual void handleBuffer( struct mcm::sample * bufTx, 
                               struct mcm::sample * bufRx );
};

C1KHzでは1サンプル毎にウエーブテーブルの要素をコピーして1KHzを生成しました。CxHzでは代わりに図1のDDSの位相アキュームレーターを使ってウェーブテーブルにアクセスします。具体的には内部変数phaseとindexがアキュームレーターの役割を担います。phaseはアキュームレーターの下位でウエーブテーブルにアクセスするのは上位のindexです。phaseには毎回周波数freqが足され、1000を超えるとindexに桁上げがおきます。

//**************************************************************************
//
//  CxHz::CxHzの定義
//
//      内部位相変数とウェーブテーブルのインデックスを初期化する。
//  また、与えられた引数を周波数として保存する(単位Hz)
//
//**************************************************************************

CxHz::CxHz( int frequency )
{
    phase = 0;
    index = 0;
    freq = frequency;
}

//**************************************************************************
//
//  CxHz::CxHzの定義
//
//      内部位相に周波数を加える。1000を超えるとインデックスを進める。
//  これにより、1KHzのテーブルを元に任意周波数を生成することができる。
//
//**************************************************************************
void CxHz::handleBuffer( struct mcm::sample * bufTx, 
                         struct mcm::sample * bufRx )
{
    for ( int i=0; i< this->bufSize; i++ ){     // bufSizeはバッファの長さ
        bufTx[i].l = bufTx[i].r = wave[index];  // 正弦波をおく
        phase += freq;                          // 位相を進める
        if ( phase >= 1000 ) {                  // 桁上げはおきたか?
            phase -= 1000;                      // 桁上げがおきたら補正
            index ++;                           // 桁上げ分インクリメント
            if ( index >= 48 )                  // 必要ならば
                index -= 48;                    // ラップアラウンド
        }
    }
}

実験結果

CxHzはちゃんと所望の周波数の信号を生成しますが、音を聞いてみると1KHzのときと違って相当にごった音になります。この原因は次で解明します。

次は⇒DDSの位相誤差

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