STM32タイマー徹底活用:割り込み処理、PWM、エンコーダ制御
STM32マイコンのタイマーは、非常に強力で汎用性の高いペリフェラルです。 割り込み処理、PWM(パルス幅変調)、エンコーダ制御など、様々なアプリケーションで利用できます。 本稿では、STM32タイマーの基本的な概念から、具体的な応用例までを網羅的に解説し、STM32を使った開発をより効率的に進めるための知識を提供します。
1. STM32タイマーの基礎
STM32マイコンに搭載されているタイマーは、大きく分けて以下の種類があります。
- 汎用タイマー (General-purpose Timers): 最も基本的なタイマーで、割り込み生成、PWM出力、入力キャプチャなど、幅広い用途に使用できます。 16ビットまたは32ビットのカウンターを持ち、プリスケーラーを設定することで様々な周波数で動作させることができます。
- アドバンストタイマー (Advanced Timers): 汎用タイマーの機能に加え、デッドタイム挿入、ブレーク入力、相補PWM出力など、より高度な機能を搭載しています。 モーター制御や電源回路などのアプリケーションに適しています。
- ベーシックタイマー (Basic Timers): 汎用タイマーよりもシンプルな構成で、割り込み生成のみに特化したタイマーです。 リアルタイムクロック(RTC)のトリガーなど、特定の用途に使用されます。
- ローパワータイマー (Low-power Timers): 低消費電力モードでも動作可能なタイマーです。 電池駆動のアプリケーションに適しています。
- システムタイマー (SysTick Timer): ARM Cortex-Mコアに内蔵された24ビットのタイマーです。 OSのチックタイマーとして使用されることが多いですが、汎用的なタイマーとしても利用できます。
1.1 タイマーの基本構成
STM32タイマーの基本的な構成要素は以下のとおりです。
- カウンター (Counter): タイマーの心臓部であり、クロック信号をカウントします。 16ビットまたは32ビットのカウンターを持ち、カウントアップ、カウントダウン、中央アライメントモードなど、様々なカウントモードをサポートしています。
- プリスケーラー (Prescaler): カウンターへの入力クロックを分周します。 これにより、タイマーの分解能を向上させたり、非常に低い周波数の信号を生成したりすることができます。
- 自動リロードレジスタ (Auto-Reload Register – ARR): カウンターがARRの値に達すると、カウンターは0または指定された値にリセットされ、カウントを再開します。 これにより、周期的なイベントを生成することができます。
- コンペアキャプチャレジスタ (Capture/Compare Registers – CCR): カウンターの値とCCRの値を比較し、一致した場合に割り込みを発生させたり、PWM信号を生成したりするために使用されます。 複数のCCRを持つタイマーもあり、複数のチャンネルで独立した動作をさせることができます。
- クロックソース: タイマーの動作クロックの供給源を選択します。 通常は内部クロック(PCLK)を使用しますが、外部クロックを入力することも可能です。
- 割り込みフラグ (Interrupt Flags): タイマーイベントが発生したことを示すフラグです。 プログラムでこのフラグをチェックし、対応する割り込み処理を実行します。
- 制御レジスタ (Control Registers): タイマーの動作モード、カウント方向、プリスケーラー、割り込みイネーブルなどを設定します。
1.2 タイマーの動作モード
STM32タイマーは、以下の主要な動作モードをサポートしています。
- タイマーモード (Timer Mode): カウンターが指定された値までカウントアップまたはカウントダウンし、指定された周期で割り込みを発生させます。
- PWMモード (Pulse Width Modulation Mode): カウンターとCCRの値を比較して、指定されたデューティ比を持つPWM信号を生成します。
- 入力キャプチャモード (Input Capture Mode): 外部信号のエッジを検出し、その時のカウンターの値をキャプチャします。 これにより、外部信号の周波数やパルス幅を測定することができます。
- エンコーダーモード (Encoder Mode): ロータリーエンコーダーからの信号を処理し、エンコーダーの回転方向と回転量を検出します。
2. 割り込み処理
タイマー割り込みは、特定のタイミングで定期的にコードを実行する必要がある場合に非常に役立ちます。 STM32タイマーを使用すると、簡単に割り込みを生成し、割り込みハンドラーで必要な処理を実行できます。
2.1 割り込み設定
タイマー割り込みを使用するには、以下の手順が必要です。
- タイマーを有効にする: RCC (Reset and Clock Control) レジスタを使用して、使用するタイマーのクロックを有効にします。
- タイマーを設定する: プリスケーラー、自動リロード値 (ARR)、カウントモードなどを設定します。
- 割り込みを有効にする: タイマーの制御レジスタ (TIMx_DIER) を使用して、更新割り込み (Update Interrupt) などの必要な割り込みを有効にします。
- NVIC (Nested Vectored Interrupt Controller) を設定する: NVICを使用して、タイマーの割り込み優先度を設定し、グローバル割り込みを有効にします。
- 割り込みハンドラーを作成する: タイマーの割り込みが発生したときに実行される割り込みハンドラー関数を作成します。
2.2 具体的な手順 (HALライブラリを使用する場合)
以下に、STM32CubeIDEとHALライブラリを使用して、タイマー割り込みを設定する具体的な手順を示します。
- STM32CubeIDEで新しいプロジェクトを作成します。
- Clock Configurationタブで、必要なクロック設定を行います。
- Pinout & Configurationタブで、Timerを選択し、Modeを”Internal Clock”に設定します。
- Configuration -> Parameter Settingsタブで、PrescalerとCounter Periodを設定します。 Counter PeriodはARRに相当します。 プリスケーラーとARRを調整して、希望する割り込み周期を得ます。
- Configuration -> NVIC Settingsタブで、Timer global interruptを有効にします。
- Project -> Generate Codeをクリックして、コードを生成します。
生成されたコードには、タイマーの初期化関数と、割り込みハンドラーの空の関数が含まれています。
-
stm32xxxx_it.c
ファイルを開き、タイマーの割り込みハンドラー関数 (例:TIM2_IRQHandler
) を見つけます。 -
割り込みハンドラー関数に、実行したい処理を記述します。 例:
“`c
void TIM2_IRQHandler(void)
{
/ USER CODE BEGIN TIM2_IRQn 0 /
/ USER CODE END TIM2_IRQn 0 /
HAL_TIM_IRQHandler(&htim2);
/ USER CODE BEGIN TIM2_IRQn 1 /
// ここに割り込み時に実行したい処理を記述します。
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LEDをトグルする例
/ USER CODE END TIM2_IRQn 1 /
}
“`
main.c
ファイルで、タイマーを開始します。
c
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2); // TIM2を割り込み付きで開始
/* USER CODE END 2 */
2.3 割り込みハンドラーの注意点
割り込みハンドラーは、割り込み処理中に実行されるため、以下の点に注意する必要があります。
- 短い時間で処理を完了させる: 割り込みハンドラーの処理時間が長すぎると、他の割り込みやメインループの処理を妨げる可能性があります。
- グローバル変数のアクセスに注意する: 割り込みハンドラーとメインループで共有するグローバル変数にアクセスする場合は、クリティカルセクションで保護する必要があります。 クリティカルセクションは、割り込みを一時的に無効にすることで、データの競合を防ぎます。
- 遅延関数を使用しない: 割り込みハンドラー内で
HAL_Delay()
のような遅延関数を使用すると、システムの応答性が悪化する可能性があります。
3. PWM (パルス幅変調)
PWMは、モーター制御、LEDの明るさ調整、オーディオ信号の生成など、様々なアプリケーションで使用される一般的な技術です。 STM32タイマーを使用すると、簡単にPWM信号を生成できます。
3.1 PWMモードの設定
STM32タイマーでPWM信号を生成するには、タイマーをPWMモードに設定する必要があります。 PWMモードには、Mode 1とMode 2の2種類があります。
- PWM Mode 1: カウンターが0からARRまでカウントアップし、カウンターの値がCCRの値よりも小さい間は出力ピンがHighになり、カウンターの値がCCRの値よりも大きい間は出力ピンがLowになります。
- PWM Mode 2: カウンターが0からARRまでカウントアップし、カウンターの値がCCRの値よりも小さい間は出力ピンがLowになり、カウンターの値がCCRの値よりも大きい間は出力ピンがHighになります。
3.2 具体的な手順 (HALライブラリを使用する場合)
以下に、STM32CubeIDEとHALライブラリを使用して、PWM信号を生成する具体的な手順を示します。
- STM32CubeIDEで新しいプロジェクトを作成します。
- Clock Configurationタブで、必要なクロック設定を行います。
- Pinout & Configurationタブで、Timerを選択し、ChannelをPWM Generationに設定します。 使用するピンが自動的に設定されます。
- Configuration -> Parameter Settingsタブで、Prescaler、Counter Period、Pulse (CCR) を設定します。 Pulseは初期のデューティ比を設定します。
- PWM Generation Modeで、Mode 1またはMode 2を選択します。
- Project -> Generate Codeをクリックして、コードを生成します。
生成されたコードには、タイマーの初期化関数が含まれています。
main.c
ファイルで、PWM出力を開始します。
c
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // TIM3のチャンネル1をPWMモードで開始
/* USER CODE END 2 */
3.3 デューティ比の変更
PWM信号のデューティ比を変更するには、CCRの値を変更します。
c
TIM3->CCR1 = 500; // TIM3のチャンネル1のCCRを500に設定 (ARRが1000の場合、デューティ比は50%)
HALライブラリを使用する場合は、__HAL_TIM_SET_COMPARE()
マクロを使用することもできます。
c
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);
3.4 PWMの高度な機能
アドバンストタイマーには、デッドタイム挿入、ブレーク入力、相補PWM出力など、PWMの高度な機能が搭載されています。
- デッドタイム挿入: ハーフブリッジ回路などで、上下のMOSFETが同時にONになるのを防ぐために、短いデッドタイムを挿入することができます。
- ブレーク入力: 外部信号が入力された場合に、PWM出力を強制的に停止させることができます。
- 相補PWM出力: メインのPWM出力と位相が180度異なるPWM出力を生成することができます。 ハーフブリッジ回路などの制御に使用されます。
4. エンコーダー制御
エンコーダーは、モーターの回転量や位置を検出するために使用される一般的なセンサーです。 STM32タイマーには、エンコーダーインターフェースモードが搭載されており、エンコーダーからの信号を効率的に処理することができます。
4.1 エンコーダーインターフェースモードの設定
エンコーダーインターフェースモードを使用するには、タイマーをエンコーダーモードに設定し、エンコーダーの2つの出力信号(通常はA相とB相)をタイマーの入力キャプチャチャンネルに接続する必要があります。
STM32タイマーは、以下の3つのエンコーダーモードをサポートしています。
- Mode 1: 2つの入力チャンネル(TI1とTI2)で、A相とB相のエッジを検出します。
- Mode 2: TI1でA相のエッジを検出し、TI2でB相のレベルを検出します。
- Mode 3: TI2でB相のエッジを検出し、TI1でA相のレベルを検出します。
4.2 具体的な手順 (HALライブラリを使用する場合)
以下に、STM32CubeIDEとHALライブラリを使用して、エンコーダーインターフェースモードを設定する具体的な手順を示します。
- STM32CubeIDEで新しいプロジェクトを作成します。
- Clock Configurationタブで、必要なクロック設定を行います。
- Pinout & Configurationタブで、Timerを選択し、Modeを”Encoder Mode”に設定します。 使用するピンが自動的に設定されます。
- Configuration -> Parameter Settingsタブで、Encoder Modeを選択します (Mode 1, 2, or 3)。
- Encoder Parametersで、PolarityとIC1/IC2 Prescalerを設定します。 PolarityはA相とB相の極性を設定します。 IC1/IC2 Prescalerはノイズ対策として、入力信号を間引くことができます。
- Project -> Generate Codeをクリックして、コードを生成します。
生成されたコードには、タイマーの初期化関数が含まれています。
main.c
ファイルで、エンコーダーモードを開始します。
c
/* USER CODE BEGIN 2 */
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); // TIM4をエンコーダーモードで開始
/* USER CODE END 2 */
4.3 エンコーダーの回転方向と回転量の取得
エンコーダーの回転方向と回転量は、タイマーのカウンターの値から取得できます。 エンコーダーが時計回りに回転すると、カウンターの値が増加し、反時計回りに回転すると、カウンターの値が減少します。
c
int32_t encoder_value = TIM4->CNT; // TIM4のカウンターの値を取得
エンコーダーの回転量を正確に取得するには、カウンターの値がオーバーフローまたはアンダーフローした場合に、値を適切に処理する必要があります。
4.4 エンコーダー制御の応用
エンコーダー制御は、モーターの速度制御、位置制御、ロボットの移動制御など、様々なアプリケーションで使用されます。
5. コード例 (STM32CubeIDE & HAL Library)
以下に、STM32CubeIDEとHALライブラリを使用して、タイマー割り込み、PWM、エンコーダー制御を実装する簡単なコード例を示します。
“`c
// main.c
include “main.h”
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim4;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM4_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init(); // タイマー割り込み用TIM2の初期化
MX_TIM3_Init(); // PWM用TIM3の初期化
MX_TIM4_Init(); // エンコーダー用TIM4の初期化
HAL_TIM_Base_Start_IT(&htim2); // TIM2を割り込み付きで開始
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // TIM3のチャンネル1をPWMモードで開始
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); // TIM4をエンコーダーモードで開始
while (1)
{
// メインループの処理
// エンコーダーの値を取得
int32_t encoder_value = TIM4->CNT;
// PWMのデューティ比を変更 (エンコーダーの値に応じて)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, encoder_value % 1000); // 0-999の範囲でデューティ比を調整
HAL_Delay(10);
}
}
// タイマー割り込みハンドラー
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim2);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LEDをトグルする
}
// HALライブラリのコールバック関数 (タイマー割り込み)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim2)
{
// タイマー2の割り込み処理
// (上記TIM2_IRQHandlerでも同じ処理が可能)
}
}
// 初期化関数 (STM32CubeIDEで生成されたものを編集)
static void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7199; // 72MHz / 7200 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 9999; // 10kHz / 10000 = 1Hz (1秒周期)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz / 72 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 999; // 1MHz / 1000 = 1kHz
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初期デューティ比 = 0%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM4_Init(void)
{
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 65535;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 0;
if (HAL_TIM_Encoder_Init(&htim4, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
// GPIO初期化関数 (STM32CubeIDEで生成)
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/ GPIO Ports Clock Enable /
__HAL_RCC_GPIOA_CLK_ENABLE();
/Configure GPIO pin : PA5 /
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// エラーハンドラー
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
“`
このコード例では、TIM2を使用して1秒周期でLEDをトグルし、TIM3を使用してエンコーダーの値に応じてPWMのデューティ比を調整し、TIM4を使用してエンコーダーの回転量を読み取ります。
6. まとめ
STM32タイマーは、非常に多機能で柔軟性の高いペリフェラルであり、様々なアプリケーションで使用できます。 本稿では、タイマーの基本的な概念から、割り込み処理、PWM、エンコーダー制御などの応用例までを網羅的に解説しました。
STM32CubeIDEとHALライブラリを使用することで、タイマーの設定と使用が容易になります。 この記事が、STM32を使った開発をより効率的に進めるための一助となれば幸いです。
7. 付録
- STM32のリファレンスマニュアル: 各STM32デバイスのリファレンスマニュアルには、タイマーの詳細な情報が記載されています。
- STM32CubeIDE: STM32CubeIDEは、STM32の開発を支援する無料の統合開発環境です。
- HALライブラリ: HALライブラリは、STM32のペリフェラルへのアクセスを容易にするハードウェア抽象化レイヤーです。
- STM32コミュニティ: STM32に関する質問や情報交換を行うためのオンラインコミュニティが多数存在します。
この詳細な説明が、STM32タイマーの理解と活用に役立つことを願っています。