私たちはプログラムを書いて、いろいろな処理をつくり上げていくわけですが、その処理時間を測定したいことがあります。
今回はクロック数をカウントする方法で処理時間を計測するお話です。
投稿時の開発環境を記しておきます。
PC:Windows10 OS
IDE: STM32CubeIDE Version1.5.0
Configurator: STM32CubeMX Version6.1.0
Board: STM32Nucleo-F401RE
概要
ARMプロセッサのデバッグ機能の中に DWT (Data Watchpoint and Trace Unit) があります。
その中にクロックをカウントする機能があるので、それを使ってみることにしました。
DWTのカウンターは32ビットです。
それからF401にはTIM2の汎用タイマがあり、クロックを分周することなくカウントできるようです。
こちらもカウンターは32ビットです。
これら2つの機能を使って処理時間を測定してみることにします。
プロジェクトを作成する
IDEを起動し、File- New – STM32 Project を選択し、Target Selection ウィンドウが出たら Board Selector タブを選択し Boards List から NUCLEO-F401RE を選択し Next ボタンを押します。
Project 名に F401DWT と入力し、Finishボタンを押します。
Initialize all peripherals with their default Mode ? と聞いてくるので Yesを押します。
This kind of project is associated with the STM32CubeMx perspective. Do you want to open this perspective now ? と聞いてくるので Yesを押します。
TIM2を有効にする
マニュアルには、TIM1は高機能タイマと書かれていますがカウンターは16ビットです。(チクリ)
汎用タイマのTIM2, TIM5は32ビットのカウンタを持っていますので、今回はTIM2を使うことにしました。
またTIM2はクロックを分周することなしに計測できるようなのでDWTと同じ条件で使うことができるようです。
Pinout & Configuration – Categories – Timers – TIM2 を選択し、Modeの Clock Source から Internal Clock を選択します。
ビルドする
ここまででひとまずビルドしてエラーがないことを確認しておきます。
コーディングする
main.c などIDE側で生成されるコードは USER CODE BEGIN と END の間に書くのがお決まりでしたね。。
(コンフィグレーターを使う場合に、せっかく書いたコードが消去されないようにするため)
/* USER CODE BEGIN xxx */
ここにコードを書く
…
…
/* USER CODE END xxx */
DWTに関してはマニュアルにあまり情報がなかったので、サイトを渡り歩いて私なりのコードを書きました。
DWT.h , DWT.c を追加して以下のようにコーディングします。
DWT.h
#ifndef INC_DWT_H_
#define INC_DWT_H_
#define __FPU_PRESENT 1U // エラーが出たので
#include "stm32f401xe.h" // IRQn_Typeが見つからなかったので
#include <core_cm4.h>
#define initCycleCounter() \
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
#define resetCycleCounter() \
DWT->CYCCNT = 0;
#define enableCycleCounter() \
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
#define disableCycleCounter() \
DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk;
#define getCycleCounter() \
DWT->CYCCNT;
float getTimeUs(uint32_t count);
float getTimeMs(uint32_t count);
#endif /* INC_DWT_H_ */
DWT.c
#include "DWT.h"
float getTimeUs(uint32_t count)
{
float us = 1000000 * (float)count / (float)SystemCoreClock;
return us;
}
float getTimeMs(uint32_t count)
{
float ms = 1000 * (float)count / (float)SystemCoreClock;
return ms;
}
main.c
/* USER CODE BEGIN Includes */
#include "DWT.h"
/* USER CODE END Includes */
main.c で TIM2 を使って usec を測定する
msec を測定する場合は for()ループの代わりに // HAL_Delay(35); を有効にする
35はマジックナンバーで全く意味のない数値です。。
__IO d = 0;
htim2.Instance->CNT = 0;
HAL_TIM_Base_Start_IT(&htim2);
for (__IO int i = 0; i < 100; i++)
{
d += i;
}
//HAL_Delay(35);
uint32_t freqCount = htim2.Instance->CNT;
float us = getTimeUs(freqCount);
float ms = getTimeMs(freqCount);
main.c で DWT を使って usec を測定する
msec を測定する場合は for()ループの代わりに // HAL_Delay(35); を有効にする
__IO d = 0;
initCycleCounter();
resetCycleCounter();
enableCycleCounter();
for (__IO int i = 0; i < 100; i++)
{
d += i;
}
// HAL_Delay(35);
uint32_t freqCount = getCycleCounter();
disableCycleCounter();
float us = getTimeUs(freqCount);
float ms = getTimeMs(freqCount);
解説と測定結果
DWTの方の ほにゃららCycleCounter()関数は、マクロになっています。
サブルーチンコールではないので、スタック操作の余計な時間まで計測することはありません。
STM32Nucleo-F401REボードのクロックは 84MHz です。
DWT, TIM2 ともに 32ビットのカウンターですから 2^32 / 84M = 51.13[sec] を超えるとカウンターがオーバーフローします。
試しにDWTの方だけ HAL_Delay(51200) を測ってみたところ、オーバーフローが確認できました^^
そこまで長い時間を計測することはないと思いますが、ご注意ください。
ご存じだとは思いますが HAL_Delay(35)は 約35msecの時間待ち関数です。(引数時間[msec]分の時間待ちを行う関数)
__IO d について
(意味のないコードに見えるので)念のため最適化されないように __IO 修飾子 (volatile) をつけておきました。
for()ループとHAL_Delay(35)それぞれについて計測した値を下表に載せておきます。
タイマー | DWT | TIM2 |
---|---|---|
forループ カウント数 | 1625 | 1619 |
forループ 処理時間[usec] | 19.3452377 | 19.2738094 |
HAL_Delay(35) カウント数 | 3019742 | 3019697 |
HAL_Delay(35) 処理時間[msec] | 35.9493103 | 35.9487724 |
STマイクロさま これらのカウント数の違いは、なぜ起きるのでしょうか。。
まあ、さほど大きな違いはないので私は気にしません^^
いかがでしたか、皆さんは うまく計測できましたか?
お疲れさまでした。