前回の記事では、HAL_Delay()を使って、LEDを点滅しましたが、今回は、タイマー機能を使って、ある一定時間たつと割り込みが発生するようにしたいと思います。
NUCLEO-F302R8では、HAL_Delayで使われてるSystickタイマーよりも高機能なタイマーモジュールが実装されてます。ここでは、そのなかでも比較的扱いやすい基本タイマTIM6を使ってみたいと思います。
Basic timers (TIM6)
TIM6は他のタイマーモジュールにくらべて、入力信号の周期を計測したり、PWMなどの出力機能とかもありませんが、シンプルな分、基本機能を理解する上では、分かりやすいタイマーモジュールです。
システム概要をRM(reference manualの略)から抜粋してみました。まず、ベースとなるクロックが入ってきて、PSC prescalerの箇所で分周した後の信号をカウントアップ(ダウン)していき、Auto-reload registerで指定した値を超えたら割り込みを発生させるような仕組みです。
タイマーのクロックですが、使用するタイマーによって、クロック源(APB1とAPB2)がハード的に決まっています。下図のようにTIM6はAPB1を利用しているため、APB1のクロック周波数をみながら、もろもろの設定を検討する必要があります。
STM32CubeIDE上でClock Configurationを確認します。APB1は、64MHzで設定されてました。
CNTとPSCとARR
とりあえず、1秒間毎に周期的な割り込み処理が発生するように設定していきたいと思います。ここで、重要なのは、CNT(counter)とPSC(prescaler)とARR(auto-reloard register)の三つのパラメータです。まずPSCですが、これは、タイマーのクロック周波数の分周比です。どういうことかというと、下図のような感じに、APB1のクロックを間引いてくれます。
64MHzというと1クロックで、約0.0156usなので、これを1秒間分カウントするとなると、TIM6は、16bitのタイマーなので、余裕でオーバフローしてしまいます。なので、そんなことがないようにあらかじめPSCの設定により、間引いた信号で、クロックをカウントしていきます。
注意したいのが、PSC+1で分周されるという点です。つまり0で設定する場合は、分周比が1となりクロックは間引かれません。
PSCによって分周後のクロックをカウントアップした値がCNTです。緑線のようにTM6_CR1内のCENレジスタがセットされると、カウントアップを始めます。CNTがARRを超えた時点で割り込みが発生し、プログラム上では、HAL_TIM_PeriodElapsedCallbackというコールバック関数がよばれます。
割り込みの条件や割り込み許可はTIM6_CR1内のURS,UDISで設定します。図の右側のシチュエーションのようにARRを超えてなくても、ソフト側でTIM6_EGRのUGレジスタを1にセットすることで、CNTをリセットすることもできます。
ARRの計算
APB1のクロック周波数と目標とする割り込みの周期を決めれば、PSCを調整しながら、ARRの値を決めれます。このときARRが16bitにぎりぎり収まるようにします。その方が、時間的な解像度が向上するためです。
MHz
msec
ちなみにこの計算は、以下のSTマイクロエレクトロニクス社のプレゼン資料から引用したものになります。
設定方法
STM32CubeIDEでの設定方法です。まず左のCategoriesタブをクリックして、TimersからTIM6を選択します。PSCとARRを適当な値にセットします。
割り込みの有効化はNVIC Settingタブで設定できます。
Alt + Kでコード生成します。生成されたコードは、HAL_TIM_Base_Start_IT(&htim6)を追加して、タイマが起動するようにします。
何でもそうですが、プログラムを修正する場合は、/* USER CODE BEGIN x */ と/ * USER CODE END x/の間に入れるようにします。こうすると、次回のコード生成時に上書きされることがなくなります。
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
割り込み時のコールバック関数も別途追加しておきます。前回記事と同じ、LD2を点滅させるようにGPIOのToggle関数を用いました。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim6){
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
}
以上です。TIM6はシンプルなので、動作させるのも簡単でした。次はもっと複雑なタイマーを使用していきたいと思います。
それでは、基本タイマーばんざい。