STM32マイコンを使用する際、ボタン操作は頻繁に利用される機能です。しかし、機械式ボタンは接触不良によるチャタリングが発生しやすく、従来のHAL_Delay関数を使用したディレイ処理ではメインループの実行効率が大幅に低下します。この問題を解決するため、タイマー割り込みを利用した効率的なボタン検出方法を紹介します。
シングルプレス検出
まず、基本的なシングルプレス(単押し)検出の実装方法です。タイマー割り込みを使用してチャタリングを除去し、確実なボタン押下を検出します。
typedef struct {
uint8_t buttonState;
uint8_t detectionState;
uint8_t singlePressFlag;
} ButtonStatus;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
buttons[0].buttonState = HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin);
buttons[1].buttonState = HAL_GPIO_ReadPin(BUTTON2_GPIO_Port, BUTTON2_Pin);
buttons[2].buttonState = HAL_GPIO_ReadPin(BUTTON3_GPIO_Port, BUTTON3_Pin);
buttons[3].buttonState = HAL_GPIO_ReadPin(BUTTON4_GPIO_Port, BUTTON4_Pin);
for(int i = 0; i < 4; i++)
{
switch(buttons[i].detectionState)
{
case 0:
if(buttons[i].buttonState == GPIO_PIN_RESET)
buttons[i].detectionState = 1;
break;
case 1:
if(buttons[i].buttonState == GPIO_PIN_RESET)
buttons[i].singlePressFlag = 1;
buttons[i].detectionState = 0;
break;
}
}
}
}
タイマーを10ms間隔で割り込み設定し、各ボタンの状態を監視します。ボタンが押されると(GPIO_PIN_RESET)、まず状態1に移行し、次の割り込みでも押下状態が継続している場合にのみシングルプレスフラグをセットします。これにより、チャタリングによる誤検出を防ぎます。
長押し検出
次に、長押し機能の実装方法です。限られたボタン数で複数の機能を割り当てるために、押下時間に応じて異なる処理を行うことができます。
typedef struct {
uint8_t detectionPhase;
uint8_t buttonState;
uint8_t pressDetected;
uint8_t longPressFlag;
uint16_t pressDuration;
} ButtonStatus;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
buttons[0].buttonState = HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin);
buttons[1].buttonState = HAL_GPIO_ReadPin(BUTTON2_GPIO_Port, BUTTON2_Pin);
buttons[2].buttonState = HAL_GPIO_ReadPin(BUTTON3_GPIO_Port, BUTTON3_Pin);
buttons[3].buttonState = HAL_GPIO_ReadPin(BUTTON4_GPIO_Port, BUTTON4_Pin);
for(int i = 0; i < 4; i++)
{
switch(buttons[i].detectionPhase)
{
case 0:
if(buttons[i].buttonState == GPIO_PIN_RESET)
buttons[i].detectionPhase = 1;
break;
case 1:
if(buttons[i].buttonState == GPIO_PIN_RESET)
{
buttons[i].pressDetected = 1;
buttons[i].detectionPhase = 2;
}
break;
case 2:
if(buttons[i].buttonState == GPIO_PIN_SET)
{
if(buttons[i].pressDuration > 70)
{
buttons[i].longPressFlag = 1;
}
buttons[i].pressDuration = 0;
buttons[i].detectionPhase = 0;
}
else
{
buttons[i].pressDuration++;
}
break;
}
}
}
}
長押し検出では、pressDuration変数で押下時間をカウントします。ボタンが押されると状態2に移行し、ボタンが離されるまでpressDurationをインクリメントし続けます。ボタンが離された際にpressDurationが70(700ms)を超えている場合、長押しと判断してlongPressFlagをセットします。
ダブルクリック検出
最後に、ダブルクリック機能の実装方法です。ダブルクリックは2回の押下間の時間間隔を計測することで実現します。
typedef struct {
uint8_t detectionPhase;
uint8_t buttonState;
uint8_t doublePressFlag;
uint16_t timeSinceLastPress;
} ButtonStatus;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
buttons[0].buttonState = HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin);
buttons[1].buttonState = HAL_GPIO_ReadPin(BUTTON2_GPIO_Port, BUTTON2_Pin);
buttons[2].buttonState = HAL_GPIO_ReadPin(BUTTON3_GPIO_Port, BUTTON3_Pin);
buttons[3].buttonState = HAL_GPIO_ReadPin(BUTTON4_GPIO_Port, BUTTON4_Pin);
for(int i = 0; i < 4; i++)
{
switch(buttons[i].detectionPhase)
{
case 0:
if(buttons[i].buttonState == GPIO_PIN_RESET)
buttons[i].detectionPhase = 1;
break;
case 1:
if(buttons[i].buttonState == GPIO_PIN_RESET)
{
buttons[i].pressDetected = 1;
buttons[i].detectionPhase = 2;
if(buttons[i].timeSinceLastPress < 30)
{
buttons[i].doublePressFlag = 1;
}
buttons[i].timeSinceLastPress = 0;
}
else
{
buttons[i].detectionPhase = 0;
}
break;
case 2:
if(buttons[i].buttonState == GPIO_PIN_SET)
{
buttons[i].detectionPhase = 0;
}
break;
}
buttons[i].timeSinceLastPress++;
}
}
}
ダブルクリック検出では、timeSinceLastPress変数で前回の押下からの経過時間をカウントします。各タイマー割り込みでこの値をインクリメントし、ボタンが押された際にリセットします。新しい押下が検出されたときにtimeSinceLastPressが30(300ms)未満の場合、ダブルクリックと判断してdoublePressFlagをセットします。
以上の方法により、タイマー割り込みを使用して効率的にボタンのシングルプレス、長押し、ダブルクリックを検出することができます。これにより、メインループをブロックすることなく、安定したボタン操作を実現できます。