Устранение "дребезга" кнопок




Здравствуйте.

В этой заметке описано несколько вариантов устранения «дребезга» кнопок при использовании микроконтроллеров stm32. Речь идёт именно о кнопках или о других низкоскоростных контактах — для частых импульсов, типа измерения оборотов на каком-нибудь моторе, не подойдёт.



Вариант с кратковременным нажатием и удержанием кнопки, то есть на одной кнопке два действия


Перед бесконечным циклом добавить переменные…

uint8_t short_state = 0;
uint8_t long_state = 0;
uint32_t time_key1 = 0;


В бесконечном цикле опрашиваем кнопку…

uint32_t ms = HAL_GetTick();
uint8_t key1_state = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); // подставить свой пин

if(key1_state == 0 && !short_state && (ms - time_key1) > 50) 
{
  short_state = 1;
  long_state = 0;
  time_key1 = ms;
}
else if(key1_state == 0 && !long_state && (ms - time_key1) > 2000) 
{
  long_state = 1;
  // действие на длинное нажатие
  HAL_UART_Transmit(&huart1, (uint8_t*)"LONG_PRESS\n", 11, 1000);
}
else if(key1_state == 1 && short_state && (ms - time_key1) > 50) 
{
  short_state = 0;
  time_key1 = ms;

  if(!long_state)
  {
    // действие на короткое нажатие
    HAL_UART_Transmit(&huart1, (uint8_t*)"SHORT_PRESS\n", 12, 1000);
  }
}

Время удержания более 2 сек, устанавливается здесь — 2000 (мс).

Кратковременное нажатие срабатывает при отпускании кнопки.




Просто кратковременное нажатие


Перед бесконечным циклом добавить переменные…

uint8_t flag_key1_press = 1;
uint32_t time_key1_press = 0;


В бесконечном цикле опрашиваем кнопку…

if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET && flag_key1_press) // подставить свой пин
{
  flag_key1_press = 0;
  // действие на нажатие
  HAL_UART_Transmit(&huart1, (uint8_t*)"KEY_PRESS\n", 10, 1000);
  time_key1_press = HAL_GetTick();
}

if(!flag_key1_press && (HAL_GetTick() - time_key1_press) > 300)
{
  flag_key1_press = 1;
}

При нажатиях чаще 300мс срабатывать не будет. Если нужно чаще, тогда уменьшайте значение 300. В принципе можно уменьшить до 100мс. Если сделать ещё меньше, то уже может появится «дребезг». Всё зависит от качества кнопки и того как быстро вы нажмёте и отпустите. Если есть дребезг при 300, тогда увеличивайте.




Кратковременное нажатие с защитой от случайного нажатия


uint8_t flag_key1_press = 1;
uint8_t flag_key1_wait = 1;
uint32_t time_key1_press = 0;



if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET && flag_key1_press) // подставить свой пин
{
	flag_key1_press = 0;
	flag_key1_wait = 0;
	time_key1_press = HAL_GetTick();
}

if(!flag_key1_wait && (HAL_GetTick() - time_key1_press) > 50)
{
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
	{
		// действие на нажатие
		HAL_UART_Transmit(&huart1, (uint8_t*)"KEY_PRESS\n", 10, 1000);
		flag_key1_wait = 1;
	}
	else
	{
		flag_key1_wait = 1;
		flag_key1_press = 1;
	}
}

if(!flag_key1_press && (HAL_GetTick() - time_key1_press) > 300)
{
	flag_key1_press = 1;
}


То же самое что и предыдущий вариант, но для того чтобы сработало, нужно удержать кнопку 50 мс. Это на тот случай если кнопка присобачена на каком-нибудь тракторе и время от времени замыкается сама по себе.


Во всех описанных вариантах цикл программы никак не тормозится.




Кнопка на прерывании


Настраиваем какой-нибудь таймер так чтоб он переполнялся и вызывал прерывание через 100-300мс, настраиваем внешнее прерывание на нужный фронт (или на оба) и добавляем колбеки.

Внешнее прерывание…

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == ВАШ_ПИН)
	{
		HAL_NVIC_DisableIRQ(EXTI15_10_IRQn); // сразу же отключаем прерывания на этом пине
		// либо выполняем какое-то действие прямо тут, либо поднимаем флажок	
                HAL_TIM_Base_Start_IT(&htim1); // запускаем таймер
	}
}


Переполнение таймера…

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1) 
	{
                HAL_TIM_Base_Stop_IT(&htim1); // останавливаем таймер
		__HAL_GPIO_EXTI_CLEAR_IT(ВАШ_ПИН);  // очищаем бит EXTI_PR (бит прерывания)
		NVIC_ClearPendingIRQ(EXTI15_10_IRQn); // очищаем бит NVIC_ICPRx (бит очереди)
		HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);   // включаем внешнее прерывание
	}
}

Как только вы нажмёте кнопку, прерывание сработает и отключится в колбеке, так что дребезжит кнопка или нет, нам уже не важно, прерывания отключены. Выполняем нужное действие и запускаем таймер. Таймер через 100-300мс вызовет своё прерывание, там мы его остановим, а далее будут очищены нужные биты и включиться внешнее прерывание.

Если настроить таймер в режиме One Pulse, тогда останавливать его не нужно, только запускать.

Обратите внимание, что команда HAL_NVIC_DisableIRQ(EXTI15_10_IRQn) отключит внешние прерывания на всей линии, то есть в данном случае на ножках с 15 по 10.

Так же в скобочках укажите свой EXTI...IRQn. Это относится ко всем функциям описанным выше и ниже.




Кнопка на прерывании, без таймера


В варианте с внешним прерыванием можно обойтись без таймера (чтоб не тратить его на такой пустяк), то есть всё будет так же, только включать прерывание будем по другому.


Создаём две глобальные переменные…

volatile uint8_t flag_irq = 0;
volatile uint32_t time_irq = 0;


Изменяем колбек внешнего прерывания…

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == ВАШ_ПИН)
	{
		HAL_NVIC_DisableIRQ(EXTI15_10_IRQn); // сразу же отключаем прерывания на этом пине
		// либо выполняем какое-то действие прямо тут, либо поднимаем флажок
		flag_irq = 1;
		time_irq = HAL_GetTick();
	}
}

Тут мы опять же отключили прерывание, установили флаг, и сохранили значение HAL_GetTick().

Всё что связано с таймером нам больше не нужно.


В бесконечном цикле проверяем флаг и ждём когда счётчик превысит 200мс…

if(flag_irq && (HAL_GetTick() - time_irq) > 200)
{
  __HAL_GPIO_EXTI_CLEAR_IT(ВАШ_ПИН);  // очищаем бит EXTI_PR
  NVIC_ClearPendingIRQ(EXTI15_10_IRQn); // очищаем бит NVIC_ICPRx
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);   // включаем внешнее прерывание
  
  flag_irq = 0;
}

Через 200 мс очищаем биты и включаем внешнее прерывание.


На этом пожалуй всё. Использование различных аппаратных средств в виде конденсаторов и прочего, я не планировал описывать, только программное решение.

Всем спасибо


Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 3577
Поддержать автора




Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.