ガレスタさんのDIY日記

電子回路、Web、組み込み、自作エフェクターを語るblog

デジタルエフェクター:トレモロの実装例

ここ最近実装エフェクト数が増えてきたのでまとめておきます。
今回はトレモロです。
音量を周期的に揺らすエフェクトですね。

トレモロの仕組み

何かに追従して可変できる抵抗

f:id:gsmcustomeffects:20171007052538p:plain
Strymon様より引用


図に示すように周期的に音量を上下させるようにLDRなどを用いて構成します。

f:id:gsmcustomeffects:20171007053052p:plain
Codaエフェクト様より引用

このような実現方法もあります。

その他。帰還抵抗をLDRに変えたり。FETのような電圧で抵抗が変化するものを使ったりいろいろ実現方法があります。

LFO

もう一個工夫する点があります。
変調波であるLFOの作り方ですね。

これもアナログではいろいろな発振回路が用いられています。
f:id:gsmcustomeffects:20171007053757p:plain

デジタルでは?

というわけでアナログの場合

原音→可変抵抗素子で音量を上下に→出力

みたいな流れでエフェクトを実現していました。


デジタルの場合間接的な可変をするみたいな考えを捨てて脳死できます。
通常のトレモロ効果を得るならかんたんで
もらってきた原音に対してsin関数を書ければ終ります。
式で表すとこんな感じ

\displaystyle
out = input \times sin(wt)

オシロでみるとこんな感じ

演算が浮動小数点なので-1~+1の間でsin関数を用意してあげて乗算しているだけです。

これだけでは面白くないのでLFO波形を変えてみます。

二個の正弦波を足す

式で書くとこんな感じです。

\displaystyle
out = input \times (\frac{sin(wt)+sin(2wt)}{2})

処理後の波形はこんな感じです。


さらなる工夫

実際にはやってないですがアイデアとしてあげておきます。

  • ノコギリ波形
  • 三角波
  • 非周期的信号を使う

少しレベルアップすると

  • 入力をスプリットしてべつべつのフィルタを通過後別々の変調を行い加算

こういうこともできますね。

デジタルでやる利点

まずLFOの種類を増やすと回路規模が多くなります。
デジタルの場合LFOをいっぱい作れるし保存もできます。非周期的信号もメモリに持っておけば簡単に流すことができます。
こういった点で有利になりますね。

その他連続してトレモロをかけることもできますし並列にエフェクトをかけることができるのもいいですね。

C言語での実装例

ARM CortexM7で処理した時の例です。
サンプリング周波数は48kHz
作成LFO周波数 1Hz
固定少数(blackfinなど)の場合は数値の処理をきちんとしてください

#include "tremolo.h"
float tremolo_index = 0.0;
void effects_tremolo(float rx_float_buffer[],float tx_float_buffer[],ControlParamTypeDef param){
	//s = Asin(2*pi*f0*t)
	//t = n/fs
	//s = Asin(2*pi*(n/fs));
	//正規化するならAは1かな
	float	freq = 1.0;
	float	fs	 = 48000.0;
	tx_float_buffer[0] = rx_float_buffer[0] * (arm_sin_f32(2.0 * PI * freq * (tremolo_index++/fs)))
}

離散信号処理で正弦波を使うときの解説はこの方のが一番わかりやすいので読むのおすすめです。

aidiary.hatenablog.com

biquadBPFの処理負荷が高い・・・・・・

どうもがれすたさんです。

通常のLPFとHPFができてからいろいろ遊んでいたのですがBPFを作って計測してみると唖然・・・・・
f:id:gsmcustomeffects:20171007044533p:plain

22%も持ってくのお前・・・・・・

まあ理由もある程度分かってます。

alpha = sin(w0)*sinh( ln(2)/2 * BW * w0/sin(w0) ) (case: BW)

バンド幅を利用してalphaを算出するとlogとsinhがでてくる。
というかこの辺の関数がCMSIS DSPにないのでそこでだいぶ食ってる。

普通のsin,cosはこういうふうにDSP_APIに置き換えてるので問題ないしLPFのようにsin,cos多用するのはものすごい早い
f:id:gsmcustomeffects:20171007045518p:plain

負荷は数%だった。

対策法としては

  • そこの部分をQを使った演算に直すか
  • あらかじめ計算した値を持っておいて配列で呼んでくるかどっちか。

こんな感じよね。

実際ARMのBiquadサンプルではピーキング系のフィルタで各dBを表引きして5バンドEQを達成している。
f:id:gsmcustomeffects:20171007045711p:plain
今後こういうEQブロックを書くエフェクトに差し込むことになるのでやって行くか

つか極論差分方程式を自分で書かないでCMSISDSPを使えって話ですよねw
f:id:gsmcustomeffects:20171007050014p:plain

こっちのほうが直接型Ⅱ転置構成なので遅延器を減らせるしバイクアッド関数は共通で係数計算ルーチンだけ別で用意すればいい。
まあいろいろやってみますわ。

余談

最近このソフトを気に入ってつかっている。

f:id:gsmcustomeffects:20171007045233p:plain

Eliさん作成のソフトだ。
Oxyplotで応答描画してくれるかなりわかりやすいソフトだ。

数式はMusicDSPの参考にしているらしい

ちなみにこの方を知ったのはこのソフトじゃないんですよね。
CircuitMakerというCADを使っていた時この方がKinetisマイコンでデジタルエフェクタを作っていたのでそのプロジェクトでだいぶ勉強させていただきました。

freescaleフォーラムで結構有名な方みたいですね。

ARMのData Watchpoint and Trace Unitを使って処理時間計測をしてみよう

毎度おなじみのARM共通の機能を使ってみようの記事です。

GPIOを使った処理時間の計測

まあオシロスコープがある方はこういう計測方法をよくやると思います。

MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,TRUE);
/*
計測したい処理を記述
*/
MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,FALSE);

処理時間の幅をはかればある程度の処理時間を計測できます。
f:id:gsmcustomeffects:20171006003331p:plain

ですがこれだと毎回つなぐのめんどいしGPIOの立ち上がり時間が入っちゃう(特に問題にならない)

Data Watchpoint and Trace Unit

DWTははオプションのユニットで、次のようなデバッグ機能を実行します。
DWTユニットには4つのコンパレータが組み込まれており、ハードウェアウォッチポイント、ETMトリガ、PCサンプライベント トリガ、またはデータアドレスサンプラ イベントトリガとして構成することができます。最初のコンパレータDWT_COMP0は、クロックサイクル カウンタCYCCNTに対して比較することもできます。2番目のコンパレータDWT_COMP1は、データコンパレータとしても使用できます。DWTにコンパレータが1つだけ含まれるように構成し、そのコンパレータをウォッチポイントまたはトリガとして使用可能にすることもできます。コンパレータが1つしか存在しない場合、データの一致はサポートされません。
DWTには、次のものに対して使用するカウンタが組み込まれています。

  • クロックサイクル(CYCCNT)
  • フォールドされた命令
  • ロード/ ストアユニット(LSU)操作
  • スリープサイクル
  • CPI(最初のサイクルを除くすべての命令サイクル)
  • 割り込みオーバヘッド

※ARM社より引用

ちなみにこの機能オプションなのでメーカーによってはない場合もある。
STはデータシートの一番下のDEBUGに搭載の記述があるので使える。
f:id:gsmcustomeffects:20171006010306p:plain

今回はこれのCYCCLE(クロックサイクルカウンタ)を利用して処理時間計測を行います。

DWTのレジスタをいじいじしていく

まずやらなければならないことはDWTのアクセスロックの解除です。
LARに対してアクセスキーを入力します。
f:id:gsmcustomeffects:20171006004602p:plain
まあサンプルが出ているのでそのまま使います。

#define DWT_LSR_SLK_Pos                1
#define DWT_LSR_SLK_Msk                (1UL << DWT_LSR_SLK_Pos)
// CoreSight Lock Status Register lock availability bit
#define DWT_LSR_SLI_Pos                0
#define DWT_LSR_SLI_Msk                (1UL << DWT_LSR_SLI_Pos)
// CoreSight Lock Access key, common for all
#define DWT_LAR_KEY                    0xC5ACCE55

static inline void dwt_access_enable(int ena)
{
    uint32_t lsr = DWT->LSR;//ロックステータスのチェック

    if ((lsr & DWT_LSR_SLI_Msk) != 0)
    {
        if (ena)
        {
            if ((lsr & DWT_LSR_SLK_Msk) != 0)    //locked: access need unlock
                DWT->LAR = DWT_LAR_KEY;//ロック解除キーの入力
        }
        else
        {
            if ((lsr & DWT_LSR_SLK_Msk) == 0)    //unlocked
                DWT->LAR = 0;
        }
    }
}

プログラムの最初にこいつを呼んでやります。

あとは以下のようにしてDWTいじってあげます。

void init_cpu_cycle(){
	dwt_access_enable(1);
	CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
	DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
	(*((volatile uint32_t *) 0xE0001004)) = 0;//DWT->CYCCLE = 0;でも可
}

uint32_t get_cpu_cycle(){
	uint32_t cycle = DWT->CYCCNT;
	return cycle;
}

この二つでは主に何をやっているかというと

  • カウンタの有効
  • カウンタの初期化

です。

f:id:gsmcustomeffects:20171006005057p:plain

f:id:gsmcustomeffects:20171006005159p:plain

あとはこれらで取得したサイクルを減算してクロックで除算を行う関数を定義してやると楽です。
僕の場合オーディオ信号処理なので48kHzで割り込みが起きるので処理時間最大値のうちどのぐらいの時間を使っているかで負荷計測をしています。

float get_process_time(uint32_t start,uint32_t stop){
	float time = ((float)stop-(float)start)/(float)SystemCoreClock;
	return time*1000000;//マイクロで返したいから
}

float get_cpu_load(uint32_t start,uint32_t stop){
	float load = (((float)stop-(float)start)/(float)SystemCoreClock) / (float)(1.0/SAMPLE_RATE);
	return load*100;//%で返したいので
}

あとはこんな感じに埋め込む
f:id:gsmcustomeffects:20171006005429p:plain

biquadフィルタを計算させたときの動作はこんな感じ

f:id:gsmcustomeffects:20171006005758p:plain


5.6%とかなのでかなり余裕がある(ART+DSP関数を使っているので)

まとめ

ARMの共通機能のDWTについて理解を深めた
時間があればほかの機能についても試していきたい

STM32 ARTアクセラレータを試してみた。

最近ARM全般の話が多かったのですがST固有機能の話です。
ARTはF4にもついていますがCortexM7での話で書いていきます。

ART Accelerator(Adaptive real-time memory accelerator)

f:id:gsmcustomeffects:20171005182840p:plain

ART Acceleratorは図の赤の部分についてるTCMバス経由でのフラッシュの読み出しウェイトを事実上0にするキャッシュ群みたいなもの。

実際flashの速度って30MHz程度が限界なので高速動作する場合はRAMにコードを置いたりする。(LPCなんかがそう

そこで下の図のようにflashから大量に先読みしてバッファするっていう脳筋ハードウエアペリフェラル
俗にいうインストラクションプリフェッチとか言われている奴をメモリで殴った感じ
f:id:gsmcustomeffects:20171005184126p:plain

インストラクションプリフェッチにもいろいろ問題があって例えばに実行時にキャッシュ上に必要なデータがあってうまくヒットした場合はいいが何かのデータを当てに分岐命令を書いている場合必ずしもヒットしない。
この場合再リロードを行う必要がある。この際flashからの応答にコアはwaitで待つことになる。

そういうことも見越してART内にはブランチキャッシュというものを持っておりある程度の緩和を行っているものだと思う。

実際の流れでは以下のようになる。

インストラクションプリフェッチなし

f:id:gsmcustomeffects:20171005185743p:plain

128bit読み出しを行っているので4命令までは連続フェッチ行けるがそのあとFlashをwaitしている。
この図はF4のなのでF7では256bit

インストラクションプリフェッチあり

f:id:gsmcustomeffects:20171005190302p:plain

一方こちらではインストラクションキャッシュがFIFO的に働くので送りながら次の命令をプリフェッチできている。
なのでCPU的には連続で実行できる。

AXIMのこと忘れてね?

おっとそうでした。
図に示す通りにFlashへのアクセスはTCMだけではなくAHBのバスマトリックスを経由したAXIMからでも行えます。
f:id:gsmcustomeffects:20171005191031p:plain

そもそもCortexM7にはI/Dキャッシュがあります。(0~64KBの間でベンダーが決定して搭載)
こっちを有効にすれば命令、データともにキャッシュが有効になります。

実質この機能だけでだいぶ高速になります。

このAXIMってバスはペリフェラルバスと同一なレーンにいるため渋滞を招くことも懸念されます。
そのためSTはTCIM経由でキャッシュできる構造を取ったのでしょう。

おまけ1:その他の高速化

とりあえずここまででARTが何なのか?がわかったと思うので僕が感心しているDTCMメモリでも話そうと思います。

f:id:gsmcustomeffects:20171005193349p:plain

要するにCPUから最速アクセス可能な密結合メモリのことです。

場所としてはここ

f:id:gsmcustomeffects:20171005193753p:plain

クリティカルなコードやデータをここに配置することで高速化を図れる。
おき方としてはリンカーでアドレスくくって定義てそこに対して起動時にコピーすれば行ける。

その他DTCMにはGPDMAでも書けるのでCPUとペリフェラルで書き換えが頻繁なものはこのアドレスに対して書き込ませれば処理時間が少しは早くなるかもしれない。
たこの領域はキャッシュの影響を受けないのでその点でも結構使いやすい。

各種配置による速度差はIAR様のこの資料がわかりやすいです。
https://www.iar.com/globalassets/pdf/st-kits-kk/8_work_with_effective_software_development_for_cortex-m7_201504.pdf

やりかた

やりかただけ書いておく。

CubeMXでCortexM7の欄を開く

f:id:gsmcustomeffects:20171005194738p:plain

このように設定する。

次にリンカスクリプトでROMの開始アドレスを0x00200000に変更する。

これで実行した時のプログラムカウンタがそれっぽい番地に来てればいいはず

f:id:gsmcustomeffects:20171005195433p:plain

ちなみにONとOFFでFIRフィルタを回した時はこんな感じだいぶ処理時間が変わってくる。

f:id:gsmcustomeffects:20171002073035p:plain

各社CortexM7のひかくてきなやつ

Kinetis® KV5x MCU

f:id:gsmcustomeffects:20171005195903p:plain

SAM V70

f:id:gsmcustomeffects:20171005200000p:plain

まとめ

CortexM7のキャッシュについて理解を深めた。
ARTが何なのかがわかった気がした。

参考文献

ネットの記事

APS様CortexM7解説
www.aps-web.jp

ねむいさんのブログ
ねむいさんのぶろぐ | STM32F7を使ってみる5 -AXIMとITCM-

CQ出版の解説ページ
www.kumikomi.net

マイナビニュース
news.mynavi.jp

書籍

  • ARMマイコンCortexM教科書
  • Interface2016年12月号付録 STM32便利帳2016

CMSIS DSPのFIRフィルタをやってみる

公開してるがかなり追記の予定

前回はIIRについてやったので今回はFIRについてやってみます。
今回もリアルテック氏(@realteck_KY )に協力してもらってます。


f:id:gsmcustomeffects:20171002044002p:plain

みればわかるとおりに移動平均の数を増やして言った形ですね。
タップ数を増やすとより特性が理想に近くなるというのも頷けますね。

窓関数法

今回は窓関数法で係数を出している

なんで窓関数を使うといいのかはFFTの項を調べれば載ってますので割愛


窓関数として用いているハミング窓は以下のような式です。

\displaystyle
w[m] = 0.54-0.46cos(\frac{2\pi m}{M})

ここはまだ書く余地あり_____________________________________________________________

CMSIS DSP

前回同様にCMSIS DSP APIを使っていく。

導入はここ
gsmcustomeffects.hatenablog.com

やる順序は前回と変わらず

  1. インスタンスの生成
  2. インスタンスの初期化
  3. APIコール

という感じ

インスタンスの生成

arm_fir_instance_f32 S3;

構造体のメンバはこんな感じ

  typedef struct
  {
    uint16_t numTaps;     /**< number of filter coefficients in the filter. */
    float32_t *pState;    /**< points to the state variable array. The array is of length numTaps+blockSize-1. */
    float32_t *pCoeffs;   /**< points to the coefficient array. The array is of length numTaps. */
  } arm_fir_instance_f32;

インスタンスの初期化

フィルタはMATLABのフィルタ設計ツール
カットオフは1kHz、タップ数は300

float32_t buffer3[300] = {0};
float32_t pCoeffs3[300]=
{0.000112,0.000096,0.000077,0.000057,0.000035,0.000012,-0.000012,-0.000037,-0.000063,-0.000089,-0.000114,-0.000140,-0.000165,-0.000188,-0.000211,
 -0.000230,-0.000248,-0.000262,-0.000272,-0.000278,-0.000280,-0.000276,-0.000267,-0.000251,-0.000230,-0.000202,-0.000168,-0.000127,-0.000081,-0.000028,
0.000029,0.000091,0.000157,0.000225,0.000295,0.000365,0.000434,0.000500,0.000562,0.000618,0.000666,0.000705,0.000733,0.000750,0.000753,0.000741,0.000714,
0.000670,0.000611,0.000534,0.000441,0.000333,0.000209,0.000073,-0.000075,-0.000233,-0.000398,-0.000568,-0.000738,-0.000907,-0.001070,-0.001225,-0.001367,						  -0.001492,-0.001598,-0.001681,-0.001737,-0.001764,-0.001759,-0.001721,-0.001648,-0.001538,-0.001393,-0.001211,-0.000995,-0.000746,-0.000467,-0.000162,0.000167,						  0.000513,0.000871,0.001236,0.001600,0.001958,0.002301,0.002623,0.002917,0.003174,0.003388,0.003553,0.003662,0.003710,0.003692,0.003604,0.003444,0.003210,0.002902,						  0.002521,0.002070,0.001551,0.000971,0.000336,-0.000346,-0.001066,-0.001814,-0.002577,-0.003342,-0.004097,-0.004828,-0.005519,-0.006156,-0.006724,-0.007208,-0.007593,						  -0.007867,-0.008015,-0.008026,-0.007890,-0.007598,-0.007142,-0.006516,-0.005717,-0.004745,-0.003598,-0.002282,-0.000800,0.000839,0.002625,0.004547,0.006589,0.008737,						  0.010973,0.013276,0.015626,0.018001,0.020379,0.022736,0.025049,0.027294,0.029448,0.031488,0.033394,0.035143,0.036719,0.038103,0.039280,0.040238,0.040966,0.041456,0.041702,						  0.041702,0.041456,0.040966,0.040238,0.039280,0.038103,0.036719,0.035143,0.033394,0.031488,0.029448,0.027294,0.025049,0.022736,0.020379,0.018001,0.015626,0.013276,0.010973,						  0.008737,0.006589,0.004547,0.002625,0.000839,-0.000800,-0.002282,-0.003598,-0.004745,-0.005717,-0.006516,-0.007142,-0.007598,-0.007890,-0.008026,-0.008015,-0.007867,-0.007593,						  -0.007208,-0.006724,-0.006156,-0.005519,-0.004828,-0.004097,-0.003342,-0.002577,-0.001814,-0.001066,-0.000346,0.000336,0.000971,0.001551,0.002070,0.002521,0.002902,0.003210,						  0.003444,0.003604,0.003692,0.003710,0.003662,0.003553,0.003388,0.003174,0.002917,0.002623,0.002301,0.001958,0.001600,0.001236,0.000871,0.000513,0.000167,-0.000162,-0.000467,						  -0.000746,-0.000995,-0.001211,-0.001393,-0.001538,-0.001648,-0.001721,-0.001759,-0.001764,-0.001737,-0.001681,-0.001598,-0.001492,-0.001367,-0.001225,-0.001070,-0.000907,-0.000738,						  -0.000568,-0.000398,-0.000233,-0.000075,0.000073,0.000209,0.000333,0.000441,0.000534,0.000611,0.000670,0.000714,0.000741,0.000753,0.000750,0.000733,0.000705,0.000666,0.000618,0.000562,						  0.000500,0.000434,0.000365,0.000295,0.000225,0.000157,0.000091,0.000029,-0.000028,-0.000081,-0.000127,-0.000168,-0.000202,-0.000230,-0.000251,-0.000267,-0.000276,-0.000280,-0.000278,						  -0.000272,-0.000262,-0.000248,-0.000230-0.000211,-0.000188,-0.000165,-0.000140,-0.000114,-0.000089,-0.000063,-0.000037,-0.000012,0.000012,0.000035,0.000057,0.000077,0.000096,0.000112};

バッファと係数を代入してInitを呼びます。
係数がおおい・・・・・・・・・・

arm_fir_init_f32(&S3, 300, pCoeffs3, buffer3, 1);

APIコール

前回同様にin,outとブロック数を入れてやります。

arm_fir_f32(&S3, &in, &out, 1);

実際に動かしてみる。

FIRなのでかなり特性は急になる。
2kHzでほぼゼロwww
f:id:gsmcustomeffects:20171002072808p:plain

f:id:gsmcustomeffects:20171002072712p:plain

余談

タップ数が300にもなるので普通にやると48kHzサンプリングで破たんする。
f:id:gsmcustomeffects:20171002073035p:plain

そのためCubeでARTとデータキャッシュを有効にしてやる必要がある。

f:id:gsmcustomeffects:20171002073126p:plain

これについては別の記事でまた書きます。

CMSIS DSPのbiquad-low-passをやってみる

今回はフィルタのお話です。
今まで作ってたSAIペリフェラルを使ったデジタルエフェクタのプラットフォームがある程度動作するようになったので部屋にこもってオシロスコープとお友達になりながら格闘していました。
んなわけでいくつかエフェクト使った後にフィルタないといろいろできないやん!ってことでCMSIS DSP APIを使って高速フィルタ計算をやらせてみたって記事です。

んなわけで使うのが比較的有名なbiquad filter(双二次IIR)ってやつ
フィルタ係数を変えるとハイパスだったりバンドパスだったりになったり便利な奴です。

ARM CMSIS DSPの図を引用するとこんな感じのフィルタ
タイプで言うと直接型Ⅱの転置構成
f:id:gsmcustomeffects:20170930205453p:plain

んなわけで係数は5つ

というか正確には6つ


\displaystyle
\frac{b_{0} + b_{1}z^{-1}+b_{2}z^{-2}}{a_{0} + a_{1}z^{-1} + a_{2}z^{-2}}

この式ではa0にあたる部分があるのだが基本形はここを1としてa0で割った形で表現する。(ここの数値がクオリティファクターにかかわるがARM CMSIS DSPのBiquadでは1で固定なのでカットオフ点で-3dBになる。
というか定数項は1にして正規化しろって習った気がする
次にこいつを差分方程式で表していく

\displaystyle y[n] = b_{0}  x[n] + b_{1}  x[n-1] + b_{2}  x[n-2] - a_{1}  y[n-1] - a_{2}  y[n-2]

ここでn-1,n-2ってのはディレイ信号でありn-1が一個前の信号n-2が二個前の信号ということになる。

ARMのDocumentではこう解説してる

y[n] = b0 * x[n] + d1;
d1 = b1 * x[n] - a1 * y[n] + d2;
d2 = b2 * x[n] - a2 * y[n];

まずd2の信号から考えると入力にb2をかけたものと出力に-a2をかけたものを足している。
示すとこの部分だ

f:id:gsmcustomeffects:20170930224020p:plain

次にd1の部分を考える。
先ほど求めたd2をまとめて書くとこういう感じ

f:id:gsmcustomeffects:20170930224206p:plain

最後にそいつらをまとめて

f:id:gsmcustomeffects:20170930224434p:plain

そんなわけでこういう式を書けば実現できるわけだがARMさんがCMSIS DSP Libraryと言うのを提供していてそれを使うと高速演算できるので今回はこれを使わせていただく。

CMSIS DSP Lib

てなわけでCMSIS DSPを使っていく
ライブラリの導入はこの記事が詳しいので読んでくれ。

gsmcustomeffects.hatenablog.com

ここにある通り導入が済んだらBiquadフィルタのAPIを使うための準備をしていく
順序はこの通り

  1. インスタンスの生成
  2. 初期化
  3. APIのコール

インスタンスの生成

arm_biquad_cascade_df2T_instance_f32 S;

このように宣言するちなみにこいつはこういう構造を持ってる

  typedef struct
  {
    uint8_t numStages;         /**< number of 2nd order stages in the filter.  Overall order is 2*numStages. */
    float32_t *pState;         /**< points to the array of state coefficients.  The array is of length 2*numStages. */
    float32_t *pCoeffs;        /**< points to the array of coefficients.  The array is of length 5*numStages. */
  } arm_biquad_cascade_df2T_instance_f32;

ステージ数とディレイバッファと係数である。
そのうち後者の二つはポインタ型なので適宜自分で定義してやる必要がある。

float32_t pCoeffs1[5];
float32_t buffer1[2];

定義したら係数を適当なところで代入してやる。
今回はカットオフ1kHzにしたのでこんな感じ。

  pCoeffs1[0] = 0.0010232;//b0
  pCoeffs1[1] = 0.0020464;//b1
  pCoeffs1[2] = 0.0010232;//b2
  pCoeffs1[3] = -1.0*-1.90750;//a1
  pCoeffs1[4] = -1.0*0.911594497;//a2

ここで注意が一つその辺のIIR設計ソフトウエアで設計すると係数が符号なしで出てくるのでブロック線図を見て符号をきちんと読み替える必要がある。
f:id:gsmcustomeffects:20170930225838p:plain

初期化

先ほど宣言した構造体メンバをセットしてくれるAPIがある。

arm_biquad_cascade_df2T_init_f32(&S, 1, pCoeffs1, buffer1);

第二引数の1はフィルタの段数なので2次系なら1、4次にしたかったら2(その数だけバッファ変数bufferの大きさを確保する)

APIコール

float in,out;
arm_biquad_cascade_df2T_f32(&S, &in, &out, 1);

ここで第四引数はブロック数
自分の場合はサンプルバイサンプル処理なので1にしている。
in outに値する部分は処理系に準じて適宜変更してくれていい。

自分ならこんな感じ

f:id:gsmcustomeffects:20170930230408p:plain

実際にやってみる。

ファンクションジェネレーターで1kHzの波形を入れてみる。

f:id:gsmcustomeffects:20170930230543p:plain

カットオフ点なので-3dBぐらいになっている。

信号処理時間

f:id:gsmcustomeffects:20170930231449p:plain

一応1us程度だった。
自分のリアルタイム信号処理環境では48kHzサンプリングなので21us(たぶん19uSecぐらいが限界)までは余裕がある。

キャッシュの有無による処理時間の差異

現在使っているSTM32F767ではIキャッシュとDキャッシュがある。
それに関して考察しているブログがあるので明確な違いはそこを見てほしい。

というわけで先ほどの波形はキャッシュをオンにした状態だがキャッシュをオフにしたときはこのぐらい遅くなる。
f:id:gsmcustomeffects:20170930232605p:plain

二倍近く遅くなっていることがわかる。

まとめ

CMSIS DSP APIでフィルターを動かすことができた。
今後は複数の処理を直列にしたデジタル信号処理をやって行きたい。