#はじめに
STM32の開発を始めましたが、まだTimer機能を実装していないことに気付きました。
デバイスを制御する際には何かと時間的な手順が必要になります。
このままでは、機能が足りません。今こそTimerを使う時。
#開発ターゲット
STM32F303K8
#調査
使い方はReference Manualで確認します。
今回はシンプルそうなTIM6を使用することにします。
ブロック図は次のようになっています。
PrescalerやAuto-reload Registerなど私が見慣れていない言葉が書かれています。
PrescalerはTexas Instrumentsの記事によると
タイマでどれぐらい長い時間を作ることができるかは、カウンタの最大カウント数×クロック周期で計算できます。
大抵のマイコンでは、システム動作用の高速クロックをタイマ用のクロックにも使います。
例えば、10MHz クロックの 16 ビット・タイマだとクロック周期は 0.1μs なので、最長 6553.5μs(=6.5535ms)しか得られません。
これでは不便なので、大抵のマイコンはタイマ用のクロックを分周して低速にするための分周回路(プリスケーラ)をもっています。
Prescalerによって入ってくるクロックを1/2にしたり1/4にしたりすることで、システムに合わせたカウント周期に調節することができるようです。
Auto-reload Register(ARR)はEDN Japanの記事によると
カウンタの値が、0からスタートして、1ずつ増えていき、ARRの値までカウントすると、カウントを止めて、CPUに時間が経過したことを知らせる割り込み信号を送ります。
そのため「10回カウントした時に教えてほしい」場合は、このレジスタに10を入れておきます。
##レジスタ設定
TIM6の主なレジスタです。
- CR1 コントロールレジスタ
- SR ステータスレジスタ
- CNT カウンタ(16bit)
- PSC プリスケーラ(16bit)
- ARR 自動リロードレジスタ(16bit)
- DIER DMA/割り込みイネーブルレジスタ
###CR1(コントロールレジスタ)
CEN1にするとタイマーが動き始めます。
###SR(ステータスレジスタ)
1ビットのUIFだけあり、これがオーバーフローしたかどうかを示しています。
オーバーフローすると1になりますが、プログラムから0にしないかぎり1のままです。
###CNT(カウンタ)
このレジスタからカウント値を読み出します。16bit分カウント可能です。
###PSC(プリスケーラ)
このレジスタに設定したクロック数でTIMx_CNTをカウントアップさせます。
###ARR(自動リロードレジスタ)
ARRに設定した値までカウントします。16bitで指定可能です。
###DIER(DMA/割り込みイネーブルレジスタ)
DMAと割り込みの有効化を行うレジスタです。割り込みを使用するためUIEビットを設定します。
これはTIM6における割り込みの設定になります。
これが1になっているとオーバーフローが起きた時に割り込み要求がNVICの方へ行きます。
###値を決める
値を決めるためにクロックを確認します。
現状では8MHzのHSIクロックを使用しており、
AHB prescalerとAPB1 prescalerをともに1であるため、TIM6には8MHzのクロックが供給されるはずです。
8MHzなので1秒は8000000回のクロックですが、16bitでは足りません。そこでプリスケーラ使いカウントを遅くします。
プリスケーラで10000
ARRで800
という組み合わせにすることで1秒を知ることができます。
#プログラムを作る
プログラムは以下のようになります。
static void TimerStart_sec(int timeout_sec)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
TIM6->PSC = 9999;
TIM6->ARR = 800 * timeout_sec;
TIM6->CNT = 0;
TIM6->CR1 |= TIM_CR1_CEN;
TIM6->DIER = TIM_DIER_UIE;
NVIC_EnableIRQ(TIM6_DAC_IRQn);
}
これは秒数を指定してTimerを開始する関数です。
まず、クロックを供給します。TIM6はAPB1のため、APB1の設定レジスタからEnableしています。
つぎにプリスケーラとARRを設定しています。
プリスケーラで10000数えるためにPSCレジスタに9999を入れます(0も1回カウントされるので1つ減らします)。
そしてCNTを0にしてCR1でタイマを有効化しています。
最後にDIERのUIEビットを立てて割り込みを設定しています。
NVIC_EnableIRQ(TIM6_DAC_IRQn)
はNVICにおけるTIM6の割り込みを許可しています。
// add timer counting by seconds
int TimerAdd_sec(int timeout_sec, void (*function)(void))
{
if(timeout_sec > 80){
printf("Error : Over 80 seconds");
return -1;
}
callback = function;
TimerStart_sec(timeout_sec);
return 0;
}
void TIM6_DAC1_IRQHandler()
{
if(callback != NULL){
callback();
}
TIM6->SR = 0;
}
TimerAdd_sec()
関数で秒数指定とタイムアップ時のcallback関数の登録を行います。
ARRが16bitのため上限は80秒にしています。
TIM6_DAC1_IRQHandler()
が割り込みハンドラです。割り込みハンドラはスタートアップルーチンで登録しています。
スタートアップルーチンではweak
指定のためここでの定義が割り込みハンドラになります。
まず、事前に登録されているcallback関数を実行します。そしてSRレジスタを0にします。
割り込み要求はSRレジスタが1であるため発生します。この関数でSRレジスタを0にしておかないと割り込みが発生し続けます。
プログラム全体はgithubに置きました。
別途ミリ秒単位で計測できる関数も追加しています。
#テスト
適当にmain関数を作成します。
void timeup_function(void)
{
printf("timeup!!\n");
}
int main(void)
{
UsartInit();
ButtonInitIntr();
TimerInit();
init_printf(UsartPutc);
TimerAdd_sec(1, timeup_function);
while(1);
return 0;
}
1秒ごとにtimeup_function()
が呼ばれるように設定します。この関数は”timeup!!”と出力するだけです。
実行してみます。
テスト成功!!
※分かりにくいですが、1秒ごとに出力されています。
#おわりに
これでようやくタイミング制御もできそうです。
#参考サイト
yuki-sato.com