Алгоритм борьбы с дребезгом 🛎️ контактов (подключение кнопки) STM32

Настоящие механические коммутационные приборы - тактовые кнопки (в частности) имеют дребезг контактов (в отличии от виртуальных), что для цифровых устройств является неприятностью. Да и вообще, как правило, при нажатии кнопки действие нужно выполнить один раз, с чем также справится ниже описанный код для STM32 (легко редактируемый для других МК!).

Приобрести на Aliexpress

🏷️ DevBoard STM32F401/F411 MiniF4 ( 2.47-3.60💲): https://ali.ski/8TIiX
🏷️ STM32F401 STM32F411 (2.46💲): https://ali.ski/x6_x3y
🏷️ STM32F401CCU6 STM32F411CEU6 (2.46-3.58💲): https://ali.ski/0ZwNv9
🏷️ USB Logic Analyze 24M 8CH (4.69💲): https://ali.ski/nD1A0

🛒 Мой мультиметр T21D RM113D ( ~13.81💲): https://ali.ski/-04vOL

Как это выглядит?

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

Посмотреть что происходит при клацанье можно с помощью осциллографа, но я буду использовать  логический анализатор за 5$. В лучшем случае вот такая картина диких переходов в течении 110 мкс.

Ну а бывает и такое, херачит переходы в течении почти 26 мс  .

Цифровая система может и будет регистрировать эти переходы, таким образом если нужно что-то сделать при нажатии (например, изменить состояние свечения СИД), то конечное состояние хер предскажешь, нужно что-то делать.

Как лучше подключать кнопку?

При подключении кнопки к выводу МК или чего-то еще нужно установить четко логический уровень тогда, когда кнопка не нажата, для этого в схеме присутствует подтягивающий резистор (он может быть внутренним), советую подтягивать к плюсу, чтобы при нажатии кнопки устанавливался на выводе низкий уровень. На печатных платах общий полигон, как привило, минусовой (земля), именно поэтому в этом случае к одному выводу достаточно протянуть одну дорожку, а второй вывод легко подключается к большому полигону, охватывающему всю (или почти всю) поверхность слоя платы.

Программная борьба с влиянием дребезга контактов кнопок

Сразу отказываемся от подключения RC-цепей и триггеров  и решать проблему будет программно. Суть алгоритма в многократном подтверждении состояния, такого рода алгоритм для подавления влияния дребезга контактов кнопок вида для ПЛИС уже был описан, кроме этого цифровой фильтр использовался при подключении энкодера к STM32 (но он был аппаратным).

С частотой около (50-100) Гц, то есть с периодом (20-10 мс) будет происходить опрос в прерывании от таймер, что уже хорошо, т.к. не мешаем основной программе .

Итак, вот произошло прерывание, в его обработчике считывается текущий логический уровень на нужном выводе (к нему и подключена кнопка) и если она нажата (низкий уровень, т.к. используется схема слева) — то увеличиваем (инкрементируем) значение счетчика на единицу, теперь проверяем достиг ли счетчик нужного числа, например 4, что будет означать, что лог. уровень не изменялся на протяжении 4*10 мс = 40 мс (если прерывание с периодом 10 мс), то есть нужно выбирать такой промежуток, который будет явно больше ожидаемого дребезга (например, выше он составлял 26 мс), если же не нажата (высокий уровень) — обнуляем, таким образом если во время проверки из-за дребезга будет высокий уровень, счетчик

Программирование

Код реализующий подобный принцип может быть разным, вот один из возможных, он "прокачан" за счет сравнения текущего значения с предыдущим (так отслеживается нажатие и отпускание кнопки) [взят из:  Software debouncing of buttons snigelen February 5, 2015, и чутка переделан под STM32].

Используется целых три  глобальных переменных для каждой кнопки:

/********** PV **********/ /* PA0 */ uint8_t pres_pa0; uint8_t prev_pa0; uint8_t cnt_pa0; /* PA7 */ uint8_t pres_pa7; uint8_t prev_pa7; uint8_t cnt_pa7; /* Other */ uint8_t counter; /************************/

А вот и сама функция с кучей аргументов (перегружена, зато удобная):

/** * @brief Debounce function for button * @note Perform it every 10-20ms (e.g. in timer's interrupt) * In main loop check first variable ((*pres_px), if (it > 0) nullify it and execute required code * @param Three global variables, IDR register, Bit for check (e.g. &pres_pa0, &cnt_pa0, &prev_pa0, GPIOA->IDR, GPIO_IDR_ID0) * @retval no */ void Debounce(uint8_t *pres_px, uint8_t *cnt_px, uint8_t *prev_px, uint32_t GPIOx_IDR, uint32_t GPIO_IDR_IDx) { /* Read current state */ uint8_t cur_px = (~GPIOx_IDR & GPIO_IDR_IDx) != 0; /* If level has changed */ if (cur_px != *prev_px) { /* Increase counter */ (*cnt_px)++; /* If consecutive 4*10 = 40ms approved */ if (*cnt_px >= 4) { /* The button have not bounced for four checks, change state */ *prev_px = cur_px; /* If the button was pressed (not released), tell main so */ if (cur_px != 0) { *pres_px = 1; } (*cnt_px) = 0; } } else { /* Reset counter */ *cnt_px = 0; } }

Взял крутую заряженную отладочную плату  MiniF4 (МК: STM32F411CEU6). Ну, а теперь нужно настроить вывод(ы) на вход (к нему подключена кнопка) и один на выход (к нему подключен светодиод):

/* GPIOC 13 OUT PP */ /* Clock GPIOC */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; /* 01: General-purpose output */ GPIOC->MODER |= GPIO_MODER_MODE13_0; GPIOC->MODER &= ~GPIO_MODER_MODE13_1; /* 0: Output push-pull */ GPIOC->OTYPER &= ~GPIO_OTYPER_OT13; /* 00: Low speed */ GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR13; /* 00: Np pull-pull up, pull-down */ GPIOC->PUPDR &= ~GPIO_PUPDR_PUPD13; /**********************/ /* GPIOA 0 IN PU */ /* Clock GPIOA */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* 00: Input (reset state) */ GPIOA->MODER &= ~GPIO_MODER_MODE0_0; GPIOA->MODER &= ~GPIO_MODER_MODE0_1; /* 01: Pull-up */ GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0; GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD0_1;<br>

Также прерывание от Таймера 11, частота 100 Гц (при APB2 = 16000000 Гц):

/* Clock Tim11 */ RCC->APB2ENR |= RCC_APB2ENR_TIM11EN; /* T=10ms */ TIM11->PSC = 160-1; TIM11->ARR = 1000-1; /* Update Interrupt Enable */ TIM11->DIER |= TIM_DIER_UIE; /* Counter enable */ TIM11->CR1 |= TIM_CR1_CEN; /* Tim11 Interrupt enable */ NVIC_EnableIRQ(TIM1_TRG_COM_TIM11_IRQn);

В прерывании выполняем функцию:

void TIM1_TRG_COM_TIM11_IRQHandler(void){ if(TIM11->SR & TIM_SR_UIF){ /* GPIOA-0 KEY */ Debounce(&pres_pa0, &cnt_pa0, &prev_pa0, GPIOA->IDR, GPIO_IDR_ID0); Debounce(&pres_pa7, &cnt_pa7, &prev_pa7, GPIOA->IDR, GPIO_IDR_ID7); TIM11->SR &= ~TIM_SR_UIF; } }

И в главном цикле при каждом нажатии будет изменятся состояние свечения СИД и инкрементироваться значение переменной (чисто для примера):

while(1){ if(pres_pa0){ pres_pa0 = 0; GPIOC->ODR ^= GPIO_ODR_OD13; counter++; } }

Полный код:

#include "main.h" /****** Prototypes ******/ void Cnf_GPIO(void); void Cnf_TIM11(void); void Debounce(uint8_t *pres_px, uint8_t *cnt_px, uint8_t *but_px, uint32_t GPIOx_IDR, uint32_t GPIO_IDR_IDx); /************************/ /********** PV **********/ /* PA0 */ uint8_t pres_pa0; uint8_t prev_pa0; uint8_t cnt_pa0; /* PA7 */ uint8_t pres_pa7; uint8_t prev_pa7; uint8_t cnt_pa7; /* Other */ uint32_t counter; /************************/ int main(void) { /* Configuration */ Cnf_GPIO(); Cnf_TIM11(); while(1){ if(pres_pa0){ pres_pa0 = 0; GPIOC->BSRR |= GPIO_BSRR_BR13; counter++; } if(pres_pa7){ pres_pa7 = 0; GPIOC->BSRR |= GPIO_BSRR_BS13; counter--; } } } /*****************************************************/ void Cnf_GPIO(void){ /* GPIOC 13 OUT PP */ /* Clock GPIOC */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; /* 01: General-purpose output */ GPIOC->MODER |= GPIO_MODER_MODE13_0; GPIOC->MODER &= ~GPIO_MODER_MODE13_1; /* 0: Output push-pull */ GPIOC->OTYPER &= ~GPIO_OTYPER_OT13; /* 00: Low speed */ GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR13; /* 00: Np pull-pull up, pull-down */ GPIOC->PUPDR &= ~GPIO_PUPDR_PUPD13; /**********************/ /* GPIOA 0 IN PU */ /* Clock GPIOA */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* 00: Input (reset state) */ GPIOA->MODER &= ~GPIO_MODER_MODE0_0; GPIOA->MODER &= ~GPIO_MODER_MODE0_1; /* 01: Pull-up */ GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0; GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD0_1; /* GPIOA 7 IN PU */ /* Clock GPIOA */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* 00: Input (reset state) */ GPIOA->MODER &= ~GPIO_MODER_MODE7_0; GPIOA->MODER &= ~GPIO_MODER_MODE7_1; /* 01: Pull-up */ GPIOA->PUPDR |= GPIO_PUPDR_PUPD7_0; GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD7_1; } void Cnf_TIM11(void){ /* Clock TIM11 */ RCC->APB2ENR |= RCC_APB2ENR_TIM11EN; /* T=10ms f=100Hz */ TIM11->PSC = 160-1; TIM11->ARR = 1000-1; /* Update Interrupt Enable */ TIM11->DIER |= TIM_DIER_UIE; /* Counter enable */ TIM11->CR1 |= TIM_CR1_CEN; /* TIM11 Interrupt enable */ NVIC_EnableIRQ(TIM1_TRG_COM_TIM11_IRQn); } void TIM1_TRG_COM_TIM11_IRQHandler(void){ if(TIM11->SR & TIM_SR_UIF){ /* GPIOA-0 KEY */ Debounce(&pres_pa0, &cnt_pa0, &prev_pa0, GPIOA->IDR, GPIO_IDR_ID0); Debounce(&pres_pa7, &cnt_pa7, &prev_pa7, GPIOA->IDR, GPIO_IDR_ID7); TIM11->SR &= ~TIM_SR_UIF; } } /** * @brief Debounce function for button * @note Perform it every 10-20ms (e.g. in timer's interrupt) * In main loop check first variable ((*pres_px), if (it > 0) nullify it and execute required code * @param Three global variables, IDR register, Bit for check (e.g. &pres_pa0, &cnt_pa0, &prev_pa0, GPIOA->IDR, GPIO_IDR_ID0) * @retval no */ void Debounce(uint8_t *pres_px, uint8_t *cnt_px, uint8_t *prev_px, uint32_t GPIOx_IDR, uint32_t GPIO_IDR_IDx) { /* Read current state */ uint8_t cur_px = (~GPIOx_IDR & GPIO_IDR_IDx) != 0; /* If level has changed */ if (cur_px != *prev_px) { /* Increase counter */ (*cnt_px)++; /* If consecutive 4*10 = 40ms approved */ if (*cnt_px >= 4) { /* The button have not bounced for four checks, change state */ *prev_px = cur_px; /* If the button was pressed (not released), tell main so */ if (cur_px != 0) { *pres_px = 1; } (*cnt_px) = 0; } } else { /* Reset counter */ *cnt_px = 0; } }

Проверка

Настроил на две кнопки. Работает замечательно, переключается состояние СИД и счетчик-переменная инкрементируется:

Подробнее в видосе

Итого

Вот так вот можно теперь подключать кнопочки и вообще использовать алгоритм последовательных подтверждений неизменяющегося состояния: https://github.com/Egoruch/Debounce-Button-STM32-CMSIS

Получается, что для каждой кнопки нужно целых три переменных, это конечно много, но тут уж куда деваться.

Есть также такая штука как вертикальные счетчики, ведь по сути счет идет от 0 до 4, а используется 8-ми разрядная переменная (0-255), суть в том, чтобы считать с помощью логических операций вертикально, взяв несколько uint8_t. Это актуально при подключении к одному порту и вообще при использовании 8-бит микроконтроллеров из-за ограниченности ресурсов, а у  STM32F411 памяти куча (ПЗУ: 512 кБ; ОЗУ: 128 кБ).

172
RSS
Нет комментариев. Ваш будет первым!
Загрузка...