ガレスタさんのDIY日記

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

STM32でCMSIS DSPを使ってみるその2

stm32 Advent Calendar 2017 5日目の記事です。

ARM CMSIS DSPライブラリを使ってみようという記事です。

以前書いたCMSIS DSPの記事の追記です。
2017/12/4時点でのMacOS,SW4STM32,STM32F7環境でのCMSIS DSP導入説明です。
gsmcustomeffects.hatenablog.com

前回と異なる点は

  • リポジトリから最新版を利用*1
  • MacOS環境でSW4STM32を使っている。(インストール方法が若干変わったので注意*2

です。

早速やって行きましょう。

CMSIS DSPダウンロード

まずGithubのARM CMSISリポジトリからDSP関連のファイルをダウンロードします。
github.com

git cloneでもzipでもなんでもいいと思います。

プロジェクトの作成

CubeMXを用いてプロジェクトを作ります。今回はCortexM7で試していくのでF7系列のであればなんでもいいと思います。
僕の環境では以前書いた記事に合わせるためにSTM32F767 Nucleoを使用して行きます。

まずCubeMXでプロジェクトを作ります。

次にクロックの設定などを済ませてSW4STM32でプロジェクトを生成します。

その次にSW4STM32にインポートをします。

CMSIS DSPをプロジェクトに追加する

ダウンロードしてきたCMSIS DSPファイルをプロジェクトに配置します。
ファイルを解凍するとこのような配置になっているかと思います。
f:id:gsmcustomeffects:20171203231216p:plain

これをCMSISのフォルダにこのように配置する。
f:id:gsmcustomeffects:20171209145421p:plain

CubeMXの生成プロジェクトには元からこの3つのファイルが入っていますがこちらも最新版の方に差し替えます。
f:id:gsmcustomeffects:20171209145757p:plain

プロジェクトの設定

次にコンパイルオプションの指定をして行きます。
C/C++ Build/Setting/Tool Settings/MCU GCC Compiler/MiscellaneousのOther flag項目で

-c -fmessage-length=0 -fno-strict-aliasing -fno-builtin -ffunction-sections -fdata-sections

これを指定することで未参照コードを削減してバイナリザイズを削減できます。(最初このオプションしてなくて200KBとかになってた)

f:id:gsmcustomeffects:20171209150753p:plain

プリプロセッサ項目で

__FPU_PRESENT
ARM_MATH_CM7

を追加します。

f:id:gsmcustomeffects:20171209150953p:plain

コードを書いていく

次にコードを書いて行きます。
まずInclude文を書いて行きます。

#include "arm_math.h"

これでコンパイルが取ればあとはAPIを用いてDSPライブラリを使用することができます。

f:id:gsmcustomeffects:20171209151053p:plain

このようにコード内でブレークしたり逆アセンブルを見ることができます。
f:id:gsmcustomeffects:20171209182755p:plain

f:id:gsmcustomeffects:20171209182807p:plain

f:id:gsmcustomeffects:20171209182850p:plain

使用例など

以下記事リンクをはっています。

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

gsmcustomeffects.hatenablog.com

まとめ

STM32はCortexM7を複数リリースしており目玉機能のDSP系の導入記事が少なかったので書いて見た。
Cube自体の説明はあるがDSPに関してはドキュメントが結構あれでKEIL向けの導入が目立っている。
コンパイル済みライブラリはCubeにも含まれているがlinkerオプションでリンクしなければならないので多少手間がかかる。そんな中CMSIS5のリポジトリDSP自体のコードも公開されているのを知ったので今回の方法を試して見たという経緯である。
この方法ではlinker命令を手打ちする必要もないし最新版のコードを拾ってこれるのでCube自体のアップデートを待たなくてもいいという利点がある。そのほかにもディスアセンブルを自分で確認してきちんとDSP命令に置き換えられているかなど最適化のチェックに役立つなどデバッグ関連の利点もある。

実際に試してDSPライブラリの中身が気にならない人は.aファイルを素直にリンクした方が手っ取り早いです。
やり方はその1に書いてますのでそちらを参照してください。

gsmcustomeffects.hatenablog.com

以上がれすたさんでした。

*1:ちょくちょく更新されていてissueへの対応がされていてこちらの方が安心して使用できる

*2:.runファイルで配布されているのでshコマンドでインストールしないといけなくなった

i.MX RT1050の評価ボードがとどいた。

早速ですが届きました。

簡単にいえば600MHz動作できるCortexM7マイコンが乗ってるボードです。
f:id:gsmcustomeffects:20171125205106j:plain
以前キャッシュの話をしましたがこいつは32KBのキャッシュがあるので強いなぁと感じます。

キャッシュというかARTの記事は以下リンク(各社簡単な比較あり)
gsmcustomeffects.hatenablog.com

機能の概要はこちら
https://www.nxp.com/docs/en/brochure/I.MXRT1050-FEATURES.pdf

細かい機能が知りたい場合はUserManualみてくれ

その他気になったことなど

チップの機能含め書いていきます。

512 Mb Hyper Flashを搭載

12本の線でMCUと繋ぐSpansionの規格らしい

f:id:gsmcustomeffects:20171125204054p:plain

Slideshareはこちら

XIPに対応

eXecute In Place (XIP)の略でメモリからRAMにロードすることなく直接実行できる機能です。
同社のLPC4330もその類だった気がしますがQSPIでメモリマップが使えるならできなくもないかなと思っちゃいますよね。

というわけでこの機能に対応しています。

これ以外にもSDRAMとSD Cardもついてるのでメモリ系の勉強したいならこれ一台で色々いけると思います。

Flex RAM機能

f:id:gsmcustomeffects:20171125205600p:plain
ITCM(Instruction-Tightly Coupled Memory),DCTM(Data- Tightly Coupled Memory)),OCRAM(On Chip RAM)
を含むモジュールのことをこう呼んでるみたいですね。

ITCM,DCTMはコアから最速アクセス可能なメモリのことです。これについては上に貼ったARTの記事にも記載があったと思いますのでよんでもらえれば良いと思います。

これの面白いところがメモリのサイズをユーザーが割り振れるということです。
STなんかは固定ですがAtmelはFlexと同じように割り振れるみたいですね。
f:id:gsmcustomeffects:20171125205801p:plain

図を見ればわかりますがPDRAMってのがメモリ実態で各種コントローラがアクセス可能な領域をユーザーが決めてアクセスすることで実現してるみたいですね。

アプリケーションノートにはうまい使い方とかが書いてあるのでそれを読むとアイデアが浮かぶと思います。


FlexRAMについてのアプリケーションノート
https://www.nxp.com/docs/en/application-note/AN12077.pdf

RT1020シリーズは500MHz,LQFPパッケージあり

下位モデルであるRT1020は500MhzですがMakerにも優しいLQFPパッケージで供給されるみたいです。

f:id:gsmcustomeffects:20171125210239p:plain

Embedded Artists社がコンピュートモジュールを出すみたいですね
f:id:gsmcustomeffects:20171125210416p:plain

まとめ

届いたのでまとめた。

現状Lちかしかできてない


なので今後信号処理向けにソフト組んでいきます。

EclipseのTODO機能が意外に便利やぞって話

タイトルの通りTODO機能がいいですって話です。



ものとしてはコメントにTODOを打つとTaskに追加されてやることが整理されるって機能。

f:id:gsmcustomeffects:20171024000847p:plain

使い道

  • 疑似コード書いといて整理したい時とかに使える。
  • とりあえず動くコードだけ書いてビルドアップする時にどうせ忘れるのでTODOで最低限しか書いてないよ〜とかコメント振っておくとわかりやすい

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

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


トレモロの仕組み

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

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について理解を深めた
時間があればほかの機能についても試していきたい