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