アプリケーションノート 3483

MAXQ2000を用いたオーディオフィルタリング

筆者: Kris Ardis

要約: 積和演算(MAC)ユニットとシングルサイクルのコアを組み合わせることによって、MAXQ2000を万能なマイクロコントローラ(µC)にすることができます。MAXQ2000は、アラームクロック、ハンドヘルド医療機器、ディジタル表示器など、低電力、高性能、および多数のI/Oを必要とする多くのアプリケーションに理想的な性能とI/O周辺回路を備えています。MACユニットを内蔵することによって、MAXQ2000は、DSP (µC)の領域に踏み込むことになります。

MAXQ2000は、内蔵したMACユニットからどの程度の性能を引き出すことができるのでしょうか?このアプリケーションノートでは、オーディオフィルタリングの例を用いてこの課題を検討し、MAXQ2000によってサポートされる性能についての定量的なガイドラインを示します。

ソフトウェアとハードウェアの要件

このアプリケーションノートは、オーディオフィルタについての簡単な実例を取り上げています。オーディオデータは、「The pipe began to rust while new.」という、あらかじめ録音した著者のメッセージです。このテキストは、無作為に選んだものではありません。各周波数成分が適度に組み合わされており、単純なフィルタの可聴効果を強調することのできるテキストです(http://www.cs.columbia.edu/~hgs/audio/harvard.html)。このオーディオ録音は、適当な長さの8kHzの録音で代用することができます(必須ではありません)。

このアプリケーションノートで必要なハードウェアは、MAXQ2000評価キットと、コンピュータのスピーカにインタフェース接続するための小さな回路です。

市販されているMAXQ2000評価キットは、MAXQ2000の性能を調べるのに優れたツールです。MAXQ2000評価キットには、LCDパネル、LEDバンク、およびMAXQ2000 µCのすべてのI/Oピンへのアクセス手段が含まれています。また、評価キットには、MAX1407 ADC/DACも搭載されています。これは、オーディオ出力用に使用することができます。

2つ目に必要なハードウェア部は、ブレッドボードで簡単に作成することができます。ここでのデモに使用した回路を図1に示します。この回路は、J7でMAXQ2000評価キットボードに接続するための1 × 8のメス型ヘッダと、いずれかのグランドに接続するための別の結合部が必要です(MAXQ2000評価キット上のTP1を選択することをお勧めします)。スピーカのコネクタは、どのようなタイプでもかまいません。ここに示した3.5mmのステレオジャックは、標準的なコンピュータスピーカに簡単に接続することができます。デモアプリケーションでは、1つのオーディオチャネル(モノラル音)だけを使用しているため、2つの入力チャネルを1つに結合していることに留意してください。

図1. オーディオ再生に必要な追加ハードウェア
図1. オーディオ再生に必要な追加ハードウェア

このデモを実行するのに必要なソウトウェアは、IAR Embedded Workbenchを使用して構築し、デバッグしました。このツールは、MAXQ2000のハードウェアサポートのデバッグ処理を利用して、優れたデバッグ環境を提供しています。ユーザは、ブレークポイントを設定したり、レジスタやメモリの設定や読み取りを行ったり、実際のハードウェア上で実行しながらコールスタックを確認したりすることができます。

デモアプリケーションの実行

MAXQ2000評価キットボード上の押しボタンを使用してフィルタを選択し、そのフィルタを介してオーディオサンプルを再生します。フィルタを選択するには、SW4ボタンを使用します。フィルタの名前がLCD画面上に表示されます(ハイパス:H、ローパス:LO、バンドパス:BP、オールパス:ALL)。選択したフィルタを介してオーディオの再生を開始するには、SW5ボタンを使用します。フィルタは、再生の最中でも変更することができます。

単純なFIRフィルタの設計

著者は、新しいフィルタを簡単に作成することのできるJava™アプレットを開発しました。フィルタのパラメータが与えられる標準のウィンドウ手法を使用するのではなく、図2に示すとおり、極-零点プロットにゼロを配置することによって、大まかに専用のフィルタを「設計する」ことにしました。このアプレットでは、座標面のどこにでもゼロを配置することが可能で、デモアプリケーションで必要となるFIRフィルタ係数が絶えず更新されます。ただし、デモはオールゼロのフィルタだけをサポートしています。IIRフィルタのサポートはそれ程難しいものではありません。詳細については、「IIRフィルタのサポート」の項で説明します。

図2. 極-零点プロットを使用した、単純なFIRフィルタの作成方法
図2. 極-零点プロットを使用した、単純なFIRフィルタの作成方法

一般的なフィルタは、以下に示すように一次方程式の形をとります:

y(n) + ΣbKy(k) = ΣaJx(j)

ここで、kはフィルタのフィードバック部分の次数を表し、jはフィルタのフィードフォワード部分の次数を表します。

例のIIRフィルタは、以下のように単純なものとなります:

y(n) = 0.5y(n - 1) + x(n) - 0.8x(n - 1)

フィルタの中には、FIRフィルタとして分類されているものがあります。FIRフィルタには、フィードバック部分が含まれていません。言い換えると、フィルタの特性方程式にyの部分が存在しないということです。

y(n) = ΣaJx(j)
y(n) = x(n) - 0.2x(n - 1) + 0.035x(n - 3)

どちらにせよ、フィルタとは、基本的に一定数の過去の入力値と出力値の加重平均である特性方程式ということになります。フィルタを設計するということは、これらAjとBkの値を生成することです。フィルタの出力を効率的に計算するには、符号付きの数値をすばやく乗算して加算することのできるハードウェアサポートが必要です。そこで、MAXQ2000の積和演算ユニットを利用することになります。

積和演算(MAC)ユニットを用いたフィルタの実装

前項で説明したアプレットは、グラフ内にゼロ座標を与えてフィルタ係数を計算することによって動作します。ただし、計算される係数は浮動小数点の数です。一方、マキシムのMACユニットは、純粋な16ビットの整数演算で動作します。この問題を解決するため、デモアプリケーションでは、固定小数点の数による方式を使用しています。この場合、係数の0~15ビットが小数点の右側になります(16番目のビットは、符号絶対値を表します)。演算が終わると、MACユニットのアキュムレータ内の48ビットの結果は、小数部が完全に取り除かれる位置までシフトされます。

この解決策は、速度のために精度を犠牲にしています。ほとんどの場合、この方法によって生じる誤差は、ごくわずかです。診断目的のため、アプレットは、計算したフィルタを3つのグラフで示します。1つ目のグラフは、64ビットの浮動小数点数を用いた理想的なフィルタの動作を示します。このグラフは、「Ideal Transform」という名前で、図2に示しています。

図3は、アプレットが生成する残りの2つのグラフを示しています。図3の1つ目のグラフは、16ビットの固定小数点数を用いた実際のフィルタを示しています。ほとんどの場合、誤差は目で見てわかりません。このため、最後のグラフで誤差を示しています。これは、実際の周波数応答によって分割された理想的な動作を示しています。理想的には、これはY = 1の直線となります。

図3. 16ビットで実装したフィルタの場合の実際の変換と四捨五入した誤差(誤差はほとんどなし)
図3. 16ビットで実装したフィルタの場合の実際の変換と四捨五入した誤差(誤差はほとんどなし)

簡単にするため、アプレットは、MAXQ®アプリケーションで必要となる浮動小数点の係数を生成しています。これによって、フィルタアプリケーションのソース(ファイルdata.asm)に新しいフィルタを簡単にカット&ペーストすることができるようになります。また、アプレットは、フィルタの次数(係数の数)とシフトカウントという2つの値も別途生成しています。これによって、アプリケーションは最終的な結果を適宜シフトすることができるようになります。このデータは、アプレットの底部のテキストボックスに、以下のように表示されます。

Zeroes:
    dc16
    dc16 12, 11, 0x1000, 0x26d3, 0x1e42, 0xf9a3, 0xecde, 0xff31, 0xa94, 
         0x2ae, 0xfd0c, 0xff42, 0xde 
Shift amount: 12

MAXQアセンブリ言語でフィルタを実装

最大限の性能を引き出して、正確な性能分析を実施するため、実際のフィルタはアセンブリ言語で実装することになります。これによって、1つの出力値を生成するのに必要なサイクル数を正確に数えることができるため、他のデータセットの性能を見積もることができます。

MAX1407は、12ビットのADCを備えていますが、入力データは16ビット幅であるため、ここで述べるフィルタは16ビットの結果として生成されます。したがって、このアプリケーションでは、これら4桁の最下位ビット(LSB)は無駄になるものの、性能については、16ビットの値(CD品質のオーディオは16ビット)を処理して生成しているかのように、問題なく分析することができます。

この例では、フィルタ係数はテーブル内のコードスペースに格納されます。フィルタが選択されると、アプリケーションは適切なフィルタを見つけて、シフト量とタップ数を読み取った後、データのフィルタリングを開始する準備を整えます。以下のコードは、このフィルタ係数を使用しています。

    move  MCNT, #22h           ; signed, mult-accum, clear regs first

zeroes_filterloop:
    move  A[0], DP[0]          ; let's see if we are out of data
    cmp   #W:rawaudiodata      ; compare to the start of the audio data
    lcall UROM_MOVEDP1INC      ; get next filter coefficient
    move  MA, GR               ; multiply filter coefficient...
    lcall UROM_MOVEDP0DEC      ; get next filter data
    move  MB, GR               ; multiply audio sample...
    jump  e, zeroes_outofdata  ; stop if at the start of the audio data
    djnz  LC[0], zeroes_filterloop  

zeroes_outofdata:
    move  A[2], MC2            ; get MAC result HIGH
    move  A[1], MC1            ; get MAC result MID
    move  A[0], MC0            ; get MAC result LOW
このコードを実行する前に、LC[0]にはフィルタ用のタップの数を設定し、DP[0]にはフィルタへの現在の入力バイトのアドレスを設定します。また、DP[1]はフィルタ係数の始まりを指し示しています。したがって、DP[1]ではインクリメント様式でフィルタ係数が処理され、DP[0]ではデクリメント様式で入力データが処理されます(最新の入力を最初に処理)。

MACユニットはシングルサイクルで機能するため、ここでは、それを処理するのに多くのコードは必要ありません。MCNTを22hに設定すると、符号付き整数を使用することを示します。メインループでは、その連続値がMAに書き込まれ、次に、MBが積和演算を起動し、結果は次のクロックサイクルで準備されます。アキュムレータは48ビット(乗算結果は32ビット)であるため、オーバーフローを心配する必要は一切ありません(ただし、フィルタに64,000のタップがあれば話は別ですが!)。

性能

サンプルアプリケーションは、8kHzで出力される16ビットのモノラルオーディオデータに対して動作します(これは、µCを酷使するには程遠いレベルです)。ここではアセンブリ言語でフィルタを記述しているため、使用されているサイクルを簡単に数えることが可能で、長さNのFIRフィルタが計算するのに要する時間を表す式を見つけ出すことができます。次に、この式を使用して、以前に一覧表示したアルゴリズムを使用して最大フィルタリングレートを見つけることができます。

オーディオサンプルを生成するために使用する機能は、初期化、フィルタ計算ループ、および結果の確定という3つの部分に分けることができます。ここに紹介した例では、初期化に38サイクルかかり、フィルタ計算ループにフィルタ係数当たり17サイクルかかり、結果の確定には9 + (6 × S)サイクルかかります(Sはシフトの量)。通常、シフトの量は約12であるため、結果の確定は81サイクルと推定することができます。したがって、1つのフィルタリング出力値を生成するのに、119 + (17 × N)サイクルかかります。20MHzにて、MAXQ2000は、約11kHzで100タップのフィルタを実行可能です。これは、音声データにとっては十分な品質です。

さて、ここでもう一度アプリケーションに戻って再分析し、強化可能な部分を調べてみましょう。ここではフィルタループに焦点を当てていますが、これは、使用しているサイクルの大部分が、あらゆる重要フィルタ上で行われているからです。

効率を改善するため、ループコードに対して実施可能ないくつかの重大な改良点があります。使用している録音済みのオーディオサンプルは、コードスペース内に保存されたものであることを思い出してください。MAXQはハーバードアーキテクチャであるため、コードスペースの検索は、データスペース内の検索よりもはるかに時間がかかります。UROM_MOVEDP1INCおよびUROM_MOVEDP0DECと呼ばれる関数は、それぞれ5サイクルを要します(LCALLに2サイクル、次に関数内で3サイクル)。フィルタをRAM内に格納していれば、また、RAMに格納されたリアルタイムの入力データを供給していれば、これらの各関数は2サイクルに置き換えることができます(ポインタの選択に1サイクル、そこから読み取るのに1サイクル)。RAMの256ワードをフィルタに提供できるのであれば、BP[Offs]を使用して循環バッファを実装し、入力データを保存することができます。このような変更を行うことによって、ループ時間は17サイクルから11サイクルに減少します。これによって、フィルタループは、次のようになります(サイクルカウントはコメント内の最初に表示):

zeroes_filterloop:
    move  A[0], DP[0]          ; 1, let's see if we are out of data
    cmp   #W:rawaudiodata      ; 2, compare to the start of the audio data
    move  DP[1], DP[1]         ; 1, select DP[1] as our active pointer
    move  GR, @DP[1]++         ; 1, get next filter coefficient
    move  MA, GR               ; 1, multiply filter coefficient...
    move  BP, BP               ; 1, select BP[Offs] as our active pointer
    move  GR, @BP[Offs--]      ; 1, get next filter data
    move  MB, GR               ; 1, multiply audio sample...
    jump  e, zeroes_outofdata  ; 1, stop if at the start of the audio data
    djnz  LC[0], zeroes_filterloop  ; 1
いったんフィルタと入力データをRAMに格納すれば、MAXQアーキテクチャのもう1つの裏技を利用することができます。MAXQの命令セットは、非常に直交的なものです。つまり、あらゆる演算において、ソースとして使用できるものにほとんど制限がないということです。したがって、フィルタデータと入力データをGRに読み込むのではなく、直接MACレジスタに書き込むことができます。これによって、ループは9サイクルにまで減少します。
zeroes_filterloop:
    move  A[0], DP[0]          ; 1, let's see if we are out of data
    cmp   #W:rawaudiodata      ; 2, compare to the start of the audio data
    move  DP[1], DP[1]         ; 1, select DP[1] as our active pointer
    move  MA, @DP[1]++         ; 1, multiply next filter coefficient
    move  BP, BP               ; 1, select BP[Offs] as our active pointer
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    jump  e, zeroes_outofdata  ; 1, stop if at the start of the audio data
    djnz  LC[0], zeroes_filterloop  ; 1
最後に紹介する改良点によって、このコードを極めてすばらしいコードにすることができます。ループを通過するたびに、現在のデータのポインタとオーディオ入力データの開始を比較し、範囲外に移行していないかどうかを確認します(MOVE A[0],DP[0]ステートメント、CMP比較ステートメント、およびJUMP Eステートメント)。初期のオーディオデータ(この時点では、BP[Offs]が指し示す循環バッファでこのデータを読み取っています)をすべてゼロに設定すると、これらのチェックを簡単になくすことができます。次の数千サンプルに対して4サイクルを節約することに比べれば、RAMを0に初期化するコストは取るに足らないものです。新しいループコードはスリムな5サイクルになります。
zeroes_filterloop:
    move  DP[1], DP[1]         ; 1, select DP[1] as our active pointer
    move  MA, @DP[1]++         ; 1, multiply next filter coefficient
    move  BP, BP               ; 1, select BP[Offs] as our active pointer
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    djnz  LC[0], zeroes_filterloop  ; 1
性能の式に戻る前に、計算結果を見てみましょう。48ビットの結果をシフトダウンする現在の方法は、無駄に思えます。
    move  A[2], MC2            ; get MAC result HIGH
    move  A[1], MC1            ; get MAC result MID
    move  A[0], MC0            ; get MAC result LOW
    move  APC, #0C2h           ; clear AP, roll modulo 4, auto-dec AP

shift_loop:    
    ; 
    ; Because we use fixed point precision, we need to shift to get a real 
    ; sample value.  This is not as efficient as it could be.  If we had a 
    ; dedicated filter, we might make use of the shift-by-2 and shift-by-4
    ; instructions available on MAXQ.
    ; 
    move  AP, #2               ; select HIGH MAC result
    move  c, #0                ; clear carry
    rrc                        ; shift HIGH MAC result
    rrc                        ; shift MID MAC result   
    rrc                        ; shift LOW MAC result   
    djnz  LC[1], shift_loop    ; shift to get result in A[0]
    move APC, #0               ; restore accumulator normalcy
    move AP, #0                ; use accumulator 0
実行可能な1つの解決策は、MACユニットをもう一度使用することです。12 (または0~16の任意の数)だけ右にシフトするのではなく、16からその数を減算した数だけ左にシフトすることができます(つまり、4だけ左にシフト)。こうすることで、MACユニットのレジスタ中央の16ビットワードに結果が収まります。左シフトは、実際には、2の累乗で実現されることに留意してください(元の右シフトが12と仮定した場合、24になります)。
    ;
    ; don't care about high word, since we shift left and take the 
    ; middle word.
    ;
    move  A[1], MC1            ; 1, get MAC result MID
    move  A[0], MC0            ; 1, get MAC result LOW
    move  MCNT, #20h           ; 1, clear the MAC, multiply mode only
    move  AP, #0               ; 1, use accumulator 0
    and   #0F000h              ; 2, only want the top 4 bits
    move  MA, A[0]             ; 1, lower word first
    move  MB, #10h             ; 1, multiply by 2^4
    move  A[0], MC1R           ; 1, get the high word, only lowest 4 bits significant
    move  MA, A[1]             ; 1, now the upper word, we want lowest 12 bits
    move  MB, #10h             ; 1, multiply by 2^4
    or    MC1R                 ; 1, combine the previous result and this one
    ;
    ; result is in A[0]
    ;
これによって、計算結果は、9 + (6 × S)サイクルではなく、12サイクルとなります。

ここで、先に使用した式を復活させましょう。新しい式は、控えめな見積もりである40サイクルのオーバーヘッドと、ループの反復ごとの5サイクルを使用します。先ほどの100タップのフィルタと同じ例を使用すると、MAXQ2000は、表1に見られるように、37kHzで16ビットのモノラルオーディオデータを処理することができます。

表1. FIRフィルタの最大サンプルレート(20MHzのMAXQ2000、ループ)
Filter Length (Taps) Max Rate (Hz)
50 68965.51724
100 37037.03704
150 25316.4557
200 19230.76923
250 15503.87597
300 12987.01299
350 11173.18436


より大きなサンプリングレートが必要で、かつコードスペースを犠牲にしてもかまわないようなアプリケーションの場合には、もう1つの性能改良を実行することができます。フィルタ係数を「インライン」に配置することによって、アクティブポインタを選択する必要性とループを実行する必要性の両方を排除することができるというものです(この手法はループ展開としても知られています)。この変更による犠牲は、コードスペースが増えるということです。先ほどの100ポイントのフィルタは、保存するのに100ワードが必要でした。ここでの変更によって、保存するのに300ワードが必要となります(各係数の移動に2ワード、各データ値の移動に1ワード)。16キロワードのデバイスで、性能上の利点を得るためには、これは微々たる犠牲と考えられます。新しいコードは、以下のようになります:
    move  BP, BP               ; select BP[Offs] as our active pointer
zeroes_filtertop:
    move  MA, #FILTERCOEFF_0   ; 2, multiply next filter coefficient
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    move  MA, #FILTERCOEFF_1   ; 2, multiply next filter coefficient
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    move  MA, #FILTERCOEFF_2   ; 2, multiply next filter coefficient
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    . . .
    move  MA, #FILTERCOEFF_N   ; 2, multiply next filter coefficient
    move  MB, @BP[Offs--]      ; 1, multiply next filter data
    ;
    ; filter calculation complete
    ;
この変更後の性能上の利点を計算するため、40サイクルのオーバーヘッドを再び仮定します。ただし今回は、実際にループを排除しているにもかかわらず、ループの繰り返しごとに3サイクルが存在します。100タップの性能の制限は、58kHzとなっています(表2を参照)。

表2. FIRフィルタの最大サンプルレート(20MHzのMAXQ2000、ループを展開)
Filter Length (Taps) Max Rate (Hz)
50 105263.1579
100 58823.52941
150 40816.32653
200 31250
250 25316.4557
300 31250
350 27027.02703


IIRフィルタのサポート

このアプリケーションノートでは、IIRフィルタの使用については実証していませんが、MAXQ2000がIIRフィルタをサポートすることができないというわけではありません。必要な変更を以下に示します:
  • 最新の出力サンプルを保存するためにRAMのセグメントを確保する(これは、前述の方法と同様、BP[Offs]レジスタを使用して、循環バッファとして実装するのが最も効率的です)
  • フィルタのフィードバック(「y」部分)用にフィルタの特性係数を含める
  • フィルタのフィードバック部分の結果として得られた値を累積し続ける別のループを追加する
別のループを加えることは、性能の低下を招くように思えますが、必ずしもそうとは限りません。フィルタの1つの出力を計算するにはより多くの時間が必要ですが、ほとんどの場合、IIRフィルタが出力値を計算するのに必要なタップは、より少なくてすみます(Nの値がより小さくなる)。

結論

MAXQ2000は、その性能と周辺機器により、優れた汎用µCにすることができます。MAXQ2000は、高速な汎用µCが必要なあらゆる環境、特にユーザとの対話が必要なアプリケーションで使用することができます。MACユニットを効果的に使用することで、特定のディジタルフィルタリング機能がMAXQ2000に備わり、最も汎用的なµCの1つとなります。

関連リンク
次のステップ
EE-Mail EE-Mail配信の登録申し込みをして、興味のある分野の最新ドキュメントに関する自動通知を受け取る。
© , Maxim Integrated Products, Inc.
このウェブサイトのコンテンツは米国および各国の著作権法によって保護されています。コンテンツの複製を希望される場合は お問い合わせください。.
APP 3483:
アプリケーションノート 3483,AN3483, AN 3483, APP3483, Appnote3483, Appnote 3483