最も基本的なタイマー”System Timer”を使って、一定間隔でLEDが点滅するプログラムを作成してみた。
System Timer(SysTick)とは?
たとえば、自分の持っているFRDM-K64F(Coretex M4)というボードはFTM(Flex Timer Module)とかLPTMR(Low Power Timer)と色々な高機能なタイマーモジュールが実装されているが、そのなかでも、最も基本的なタイマーといえば、System Timer(SysTick)であり、これは、RTOSでタスクの制御などのシステムの根幹にかかわる動作で利用されている。らしい。
というのも、FTMやLPTMRは、STMやNXPなどのチップメーカが周辺機器としてボード上に実装しているのに対して、SysTickはCortex MシリーズのCPU内蔵の基本タイマーであり、マイコン開発ボードによらず、CPU自体に標準実装されている。つまり周辺機器の仕様の影響を受けずにシステムの開発が出来るという訳。
NXP社のReference Manual でNVICのリストを見ても、タイマーの割り込み処理も他のタイマがNon Core Vectorsというグループにリストされているのに対して、SysTickはARM Core System Handlerというグループにあり、他のタイマーと一線を画してる。
ただ、他のサイトの情報などを色々調査してみても、RTOSを利用しないBaremetalでのソフト開発では、Core側のタイマーを使用するか、Peripheral側のタイマを使用するかは、用途さえ合えば、あまり大差がないように感じた。
SystickはCore側で実装され、その他のタイマはPeripheral側で実装されているという理解だけ、とりあえずしとく。
ともかく、SysTickはボード側で実装されてる複雑で高機能なタイマーとは違い、比較的シンプルな仕組みになっていて、気軽に使えるので、使い方を調べてみる。
使用用途はどんな?
SysTickを使って、実現する機能は以下の二つ。
- 一定時間処理を遅らせるDelay関数をつくる
- 一定間隔で実行する関数をつくる
この記事では、1の例をトライ。
どんな仕組み?
SysTickは24bitのカウントダウンタイマーになる。つまり、最大で24bit = 16,777,215から0までカウントダウンしていくタイマー。常にこの最大値からカウントダウンする訳ではなく、SysTick Reload Value Registerというレジスタ内で設定されている値 (RELOAD)がカウントダウンの初期値に反映される。
下図に動作例として、Reload Value = 6の場合のSysTickの動作を簡単にまとめてみた。
カウントダウンはシステムのクロック周期毎にディクリメントされる。図の場合、6->5->4->3->2->1->0とシステムのクロック周期毎(実際には、クロックのリソースを選択できるが、ここでは、コアクロックに設定されている前提で話をすすめる)に減算され、最終的に、1から0になる瞬間、SysTick Control and Status Register 内にあるCOUNTFLAGが1になり、割り込み処理が発生する。割り込み発生時、CPUはSysTickの割り込み用のポインタ関数を読み込む、これがSysTick_Handlerという名前になる。
要するに、割り込みが発生すると、SysTick_Handlerという関数が呼ばれるので、同じ名前の関数をメインのCコード内で関数を定義して、割り込み時に実行させたい処理を書けばいい。
図内の周期Tは、割り込みをどのぐらいの間隔で発生させたいかで決まる。例えば1msec間隔で関数呼びたい!とかをまず決める、これが1secとか極端に大きい時間間隔とかにしすぎると、タイマーがオーバフローしてしまうので、注意する。
図中の計算式の -1 というのは、単純な算数の結果で、タイマーは、初期値のRELOADから0までカウントダウンするので、その1周期分はT=(RELOAD + 1) / (クロック周波数)になって、RELOADだけ左辺にもっていくと-1がでてくるだけの話。
冒頭で説明したとおり、SysTickタイマーはCPU内蔵のタイマーなので、例えばボード自体の取説をみても、設定のためにレジスタの情報を確認しようとしても、記載がないので、ARMのサイトでCortex-M系のユーザガイドを参照する必要がある。
下はCortex-M4 ユーザーガイドになる。
https://developer.arm.com/documentation/dui0553/b/
以下のプログラムは、SysTickを利用して、一定時間待機する関数Delayを定義して、LEDを点滅するプログラムになる。
/*
sample how to use systeme timer
*/
#include "board.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "fsl_debug_console.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define BOARD_LED_GPIO BOARD_LED_RED_GPIO
#define BOARD_LED_GPIO_PIN BOARD_LED_RED_PIN
#define SysTick_FREQ (1000U)
/*******************************************************************************
* Variables
******************************************************************************/
volatile int32_t tDelay;
volatile int32_t cnt;
//これ初期化用の関数ね
void SysTick_Init(uint32_t ticks)
{
SysTick->CTRL = 0U; // 処理の前にタイマーを止める
SysTick->LOAD = ticks -1; //ticks = クロック周波数 / 設定したい周波数
SysTick->VAL = 0;
SysTick->CTRL |=SysTick_CTRL_CLKSOURCE_Msk;
SysTick->CTRL |=SysTick_CTRL_TICKINT_Msk;
SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk;
}
void SysTick_Handler(void)
{
if (tDelay >0) tDelay--;
}
void Delay(uint32_t tSetTime)
{
tDelay=tSetTime ;
while (tDelay != 0)
{
};
}
/*!
* @brief Main function
*/
int main(void)
{
/* Board pin init */
BOARD_InitPins();
BOARD_InitBootClocks();
SysTick_Init(SystemCoreClock/SysTick_FREQ);
gpio_pin_config_t LED_RED_config = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 0U
};
/* Initialize GPIO functionality on pin PTB22 (pin 68) */
GPIO_PinInit(BOARD_LED_RED_GPIO, BOARD_LED_RED_PIN, &LED_RED_config);
while (1)
{
Delay(1000);
cnt++;
PRINTF("%d\r\n",cnt);
GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
}
}
ついでにコンソールで1sec間隔のタイマー結果も表示するようにした。動作結果は地味なので、載せないがちゃんと動いていた。
まとめ
- System Timer(SysTick)は他のタイマーと違いCore内蔵のタイマーであるため、周辺機器の影響を受けない標準で実装されてるタイマーになる
- タイマー自体は24bitのカウントダウンタイマーで割り込み関数はSysTick_Handler()で、割り込みの処理はここに記述する
- タイマーの初期値はReload value register内で設定する。値は (クロック周波数 ) / ( 割り込み関数をコールする周波数) -1で設定する。
感想
簡単。扱いやすい。