見出し画像

Keil μVisionでマイコンのタイマリソースを使用してリアルタイムに時間を測定する方法

最終更新日:2023年5月11日

ARMマイコンにはタイマリソースが複数チャンネル搭載されています。タイマリソースをすべて使い切るような使い方はあまりないと思います。

未使用のタイマリソースを利用して、プログラムの処理時間を測定する方法を説明します。
未使用のタイマリソースはフリーランで起動しておくだけのため、ソフトウェアの負荷には影響がありません。

本記事ではSTマイクロエレクトロニクス社のSTM32シリーズのマイコンを使用しますが、考え方は他社製のマイコンでも同じです。

環境

  • NUCLEO-F401RE

  • USBケーブル (mini USB Type-B)

  • Windows 10パソコン

  • Keil μVision V5.28.0.0

事前準備

未使用のタイマリソースがあることを確認し、処理時間を測定するタイマリソースを決めます。
STM32F401RETはタイマリソースとして、TIM1~5TIM9~11の計8個あります。
本記事では、TIM11Nmsの生成で使用することにして、TIM11以外は未使用とします。
処理時間を測定するタイマリソースはTIM11以外であればどれを選んでも問題ありません。
本記事ではTIM3を選択することにします。

以上で事前準備の説明は終了です。

処理時間測定用タイマリソースの設定

処理時間測定用のタイマリソースとしてTIM3を使用することにしました。STM32CubeMXを使用してTIM3の設定をおこないます。処理時間を測定するタイマとして使用するので、1カウントが1μsecとなるように設定することにします。
1カウントをナノセカンド単位とすることも可能ですが、ナノ単位で処理時間を気にする人は、相応の測定器を使用して処理時間を測定します。一般的に処理時間の測定はマイクロセカンド単位で問題ありません。

TIM31カウントを1μsecとするために、TIM3に入力されるクロックを確認する必要があります。

STM32CubeMXのClock Configurationを表示します。マイコンのperipheralはAPB1とAPB2のどちらかに接続されています。
peripheralとは周辺リソースのことでタイマやUART等、マイコンの機能の全体を示します。本記事では、peripheralとしてTIM3TIM11を使用しています。
TIM3がAPB1とAPB2のどちらに接続されているか調べる必要がありそうです。

Clock Configuration

TIM3がAPB1とAPB2のどちらに接続されているかはデータシートを見ればわかります。データシートはググって見つけてください。
以下のスクリーンショットから、TIM3APB1に接続されていることがわかります。

DS10086 ARM® Cortex®-M4 32b MCU+FPU, 105 DMIPS, 512KB Flash/96KB RAM, 11 TIMs, 1 ADC, 11 comm. interfaces

STM32CubeMXのClock Configurationに戻り、TIM3APB1に接続されていることがわかったので、APB1 Timer clocks84MHzTIM3の入力クロックだとわかります。

Clock Configuration

TIM3の入力クロックは84MHzとわかったので、TIM3の設定をおこないます。

STM32CubeMXのPinout & Configrationを表示し、TIM3を選択します。
以下の設定をおこないます。

  • Clock  SourceInternal Clockに変更します。

  • Counter SettingsPrescaler84-1に設定します。

TIM3 Configuration

TIM3の入力クロックは84MHzですが、プリスケーラを84-1とすることで、84MHzを84分周して1MHzとします。

1MHzの周期は1μsecなので、TIM31カウントが1μsecとなります。

Prescalerの設定を84-1として、-1をしている理由は、ハードウェアマニュアルのTIM3_PSCレジスタの説明に「PSCレジスタの設定値に+1をした値を使用する」ことが記載されているからです。

TIM3_PSCレジスタ

以上で処理時間測定用タイマリソースの設定の説明は終了です。

処理時間測定用タイマリソースによる処理時間測定方法

処理時間を測定する対象がないと処理時間を測定することができません。また、測定した処理時間の妥当性も確認する必要があります。

タイマリソースのTIM11を使用し、任意の周期割り込みを発生させ、この周期割り込みの間隔を処理時間測定用タイマリソースで測定することにします。

分かりやすく言い換えると、TIM11をNms周期で割り込みが発生するように設定します。処理時間測定用タイマリソースでTIM11の周期を測定します。処理時間測定用タイマリソースで測定した結果がNmsとなれば期待値ということです。
NmsのNは任意の数値です。

実際のソースコードです。

#include "main.h"
#include "user.h"

extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim11;

static uint16_t usStartCnt;
static uint16_t usEndCnt;
static uint16_t usUsTimer;

/// @brief 
/// @param  
void	UserInit ( void )
{
    // 処理時間測定用タイマ起動
    (void)HAL_TIM_Base_Start(&htim3);

    // 任意周期生成用タイマ
    (void)HAL_TIM_Base_Start_IT(&htim11);

    // 測定開始位置時間取得(ここは無くても良い)
    usStartCnt = __HAL_TIM_GET_COUNTER(&htim3);
}

/// @brief 
/// @param  
void	UserMain ( void )
{
    // NOP
}

/// @brief SysTick_Handler から1ms周期で呼ばれます。
/// @param  
void	UserTimer ( void )
{
}

/// @brief TIM11で指定した周期で呼ばれます。
/// @param  
void	UserTIM11Int ( void )
{
    // 測定終了位置時間取得
    usEndCnt   = __HAL_TIM_GET_COUNTER(&htim3);

    // 測定終了時間から測定開始時間の差を取得し、処理時間を取得する。
    usUsTimer  = usEndCnt - usStartCnt;

    // 測定開始位置時間取得
    usStartCnt = __HAL_TIM_GET_COUNTER(&htim3);
}
  • UserInit関数はデバッグ開始後に一回だけ実行されます。ここでTIM3とTIM11の起動をおこないます。

  • UserMain関数はメインルーチンから呼び出されますが、本記事では未使用です。

  • UserTimer関数はSysTick Timerから周期的に呼び出されますが、本記事では未使用です。

  • UserTIM11Int関数はTIM11で指定した周期で呼び出されます。本記事ではUserTIM11Int関数を777ms周期で呼び出されるようにしています。

処理時間測定開始時点のTIM3のカウント値を__HAL_TIM_GET_COUNTER(&htim3)を使用して取得します。

同様に処理時間測定終了時点のTIM3のカウント値を__HAL_TIM_GET_COUNTER(&htim3)を使用して取得します。

このTIM3のカウントの差が処理時間となります。

以下は実際に動作させたときのスクリーンショットです。処理時間測定開始時点のTIM3のカウント値usStartCntに保存し、処理時間測定終了時点のTIM3のカウント値usEndCntに格納します。ともに符号なしのunsigned short型にします。

usEndCntからusStartCntを減算した結果をusUsTimerに保存します。usUsTimerunsigned short型で、単位はμsecとなります。(TIM31カウント1μsecとしているため)

usUsTimerの値は777となり、TIM11の周期の777msと同一のため、正しく時間を測定できたことになります。

TIM11 777ms設定

処理時間はusEndCntからusStartCntを減算して求めています。TIM3のカウント値は0x0000~0xFFFFの範囲で、0xFFFFからカウントアップすると0x0000に戻ります。

このため、usEndCntが0x0001でusStartCntが0xFFFFのようにusStartCnt > usEndCntの関係になるときがあります。

この対策のために、変数はすべて符号なしのunsigned short型にしています。

usEndCntが0x0001でusStartCntが0xFFFFの例では、電卓アプリで実際に計算してみると、0x0001 - 0xFFFF = 0xFFFFFFFFFFFF0002のようになります。
usEndCnt - usStartCntの結果はusUsTimerに格納しますが、このusUsTimerを16bit型にすることで0x0002のみが格納され、差を2カウントと正しく測定できるようにしています。

注意点

処理時間測定用タイマで使用しているTIM3のカウント値は0x0000~0xFFFFの範囲です。本記事では1カウント1μsecの設定としているため、最大で65535μsecの測定しかできません。0xFFFFから1カウントアップすると、0x0000に戻ってしまうためです。

65535μsec以上の時間を測定したい場合、0xFFFFから0x0000になるタイミングでオーバーフロー割り込みを生成することができるので、オーバーフロー割り込みの回数をカウントすることで65535μsec以上の時間を測定することができます。
(オーバーフロー割り込み1回で65535μsecとなります)

別の方法としては、1カウント1μsecを1カウント10μsecにすればそれだけ長い時間の測定が可能になります。

最後に…

本記事の内容を応用して、例えば、過去10回の処理時間のMax時間とMin時間を調べたり、処理時間の平均を調べたりすることができます。また、GPIOを使用してオシロスコープで処理時間を測定する人がいると思いますが、GPIOを"H"にするだけでも数十μsecほど時間がかかるため、本記事の方法で測定するのが理想です。

この記事が気に入ったらサポートをしてみませんか?