Мы тут обсуждали вроде бы простейший вопрос, как обрабатывать нажатия кнопок наиболее универсально и удобно. В результате мне прислали библиотеку для этого, которой я и поделюсь в статье с разрешения автора (я правда изменил оформление библиотеки под привычный мне стиль). Можно это даже приурочить к моему «курсу» по датчикам, так как один из модулей этого набора состоит из тактовой кнопки. Фото я прикрепил сверху, это модуль KY-004, но это не важно, речь пойдет о любых тактовых кнопках на примере использования их с STM32.
Тактовая кнопка выглядит так:
Не путаем с переключателем:
Переключатель фиксирован либо в одном, либо в другом положении. Тактовая кнопка фиксирована в не нажатом положении, чтобы ее удерживать в нажатом положении надо прилагать усилия. Но это все и так знают )
От такой кнопки хочется чуть более расширенного функционала, который мне бывает нужен почти в каждом проекте:
- отслеживание короткого нажатия кнопки
- длительного
- еще более длительного
Длительности могут быть разными для разных случаев. Этим целям и служит библиотека Button
, которую я опишу. Только начну я с применения готовой библиотеки, а потом опишу, как она работает. Кому просто нужно готовое проверенное решение, вторую часть можно даже и не читать.
Схема подключения кнопок к STM32.
У меня есть:
- Отладочная плата с STM32F401CCU6.
- Кнопка «Up», то есть вверх, допустим для перехода по пунктам меню. Подключена к выводу PA2.
- Кнопка «Down», то есть вниз. Подключена к PA3.
Обработка кнопок заключается в том, что нужно выполнять разные действия в зависимости от того, какое нажатие:
- Короткое нажатие – меньше 500 мс.
- Длинное нажатие – от 500 мс – до 3 секунд.
- Продолжительное нажатие – удержание кнопки более 3-х секунд.
Создание проекта для обработки кнопок.
Я использую STM32CubeIDE, поэтому в STM32CubeMx настраиваю два вывода STM32 на вход и любой из таймеров (нужен для библиотеки Button
). И еще один выход (PC13) для тестового светодиода. У меня кнопки подтянуты электрически к питанию, поэтому внутренняя подтяжка GPIO мне не нужна:
Таймер настроен на генерацию прерывания каждую миллисекунду:
Тактирование у меня настроено так:
То есть на таймер приходит частота 84 МГц. При предделителе 8399 получаю частоту:
F = 84 МГц / (8399 + 1) = 10 КГц
То есть один отсчет таймера – 100 мкс. Ставлю период (Counter Period), равный 10, получаю прерывание каждые - 100 мкс * 10 = 1 мс. Инициализация на этом закончена. Генерирую проект и перехожу к настройке библиотеки для обработки нажатий кнопок.
Конфигурация библиотеки Button для обработки кнопок.
Настройка очень проста, я ее разделил на несколько этапов, чтобы запутаться было невозможно.
- Добавляем файлы библиотеки
Button
в проект, эту операцию подробно думаю не надо описывать (если что спрашивайте в комментариях, я всем помогу):
- В файле button.h задается перечисление
ButtonID
с «названиями» кнопок. У меня это кнопка «Up» и «Down»:
typedef enum { BUTTON_UP, BUTTON_DOWN, BUTTONS_NUM, } ButtonID;
Если бы было три кнопки, то мог бы быть вот такой вариант:
typedef enum { BUTTON_MY_1, BUTTON_MY_2, BUTTON_MY_3, BUTTONS_NUM, } ButtonID;
Думаю, суть вы поняли ) В файле button.h также задаем числовые значения для длительностей нажатия кнопок. У меня длинное нажатие начинается с 500 мс, продолжительное нажатие – с 3000 мс:
Примечание от Aveal - рекомендую переименовывать:
- DEBOUNCE_TIME_MS
- BUTTONS_LONG_PRESS_MS
- BUTTONS_VERY_LONG_PRESS_MS
Например, в:
- DEBOUNCE_TIME_COUNTS
- BUTTONS_LONG_PRESS_COUNTS
- BUTTONS_VERY_LONG_PRESS_COUNTS
Потому что счетчики, которые сравниваются с этими значениями инкрементируются в колбэке по прерыванию таймера. В данном случае таймер настроен на переполнение через 1 мс, поэтому и значения могут быть в мс. Но если таймер используется иначе, допустим, генерирует прерывание каждые 50 мс, то в BUTTONS_LONG_PRESS_MS нужно будет указать значение 10 и по факту логика наименований уже будет нарушена.
#define BUTTONS_LONG_PRESS_MS 500 #define BUTTONS_VERY_LONG_PRESS_MS 3000
Все настраивается максимально просто и понятно. Кнопка может быть подключена к STM32 по-разному – при нажатии замыкать на землю, либо на +3.3 В. У меня замыкает на землю. Это значит, что в не нажатом состоянии с кнопки приходит высокий уровень, поэтому я делаю так:
#define GPIO_BUTTON_NOT_PRESSED (GPIO_PIN_SET)
Если бы замыкала на питание, то есть в не нажатом состоянии – низкий уровень, то было бы:
#define GPIO_BUTTON_NOT_PRESSED (GPIO_PIN_RESET)
Тоже ничего сложного.
- Последний шаг. В файле button.c задаем, куда подключены кнопки:
static McuPin buttons[BUTTONS_NUM] = {{GPIOA, GPIO_PIN_2}, {GPIOA, GPIO_PIN_3}};
Размер массива равен BUTTONS_NUM
. То есть я добавил две кнопки:
typedef enum { BUTTON_UP, BUTTON_DOWN, BUTTONS_NUM, } ButtonID;
И задал два порта ввода-вывода. На этом конфигурация закончена. Подытожим, полная конфигурация заключается в настройке:
- button.h,
GPIO_BUTTON_NOT_PRESSED
- button.h,
enum ButtonID
- button.h,
BUTTONS_LONG_PRESS_MS
,BUTTONS_VERY_LONG_PRESS_MS
- button.c,
static McuPin buttons[BUTTONS_NUM]
Использование библиотеки Button на STM32.
Обработку нажатий кнопок тоже разбиваю на понятные шаги. Все в main.c:
- Каждую миллисекунду будем вызывать функцию
BUTTON_TimerProcess()
из библиотекиButton
:
/* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == htim10.Instance) { BUTTON_TimerProcess(); } } /* USER CODE END 4 */
Можно настроить прерывания и на больший период, тогда значения:
#define BUTTONS_LONG_PRESS_MS 500 #define BUTTONS_VERY_LONG_PRESS_MS 3000
Нужно будет задать другими. Если, например, период таймера – 100 мс, то все эти значения надо разделить на 100. У меня будет 1 мс, и значения такие, как я поставил (тоже в миллисекундах).
- Запускаем таймер в функции
main()
, почему-то постоянно об этом забываю…
/* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim10); /* USER CODE END 2 */
- Работу библиотеки и обработку кнопок обеспечивают три функции:
BUTTON_Process()
– здесь крутятся внутренние процессы.BUTTON_GetAction()
– эта функция возвращает состояние кнопки.BUTTON_ResetActions()
– эта функция сбрасывает состояния кнопок после их анализа.
В итоге получается такой код:
while (1) { BUTTON_Process(); // Work with buttons // Button "Up" if (BUTTON_GetAction(BUTTON_UP) == BUTTON_SHORT_PRESS) { // LED on HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); } // Button "Down" if (BUTTON_GetAction(BUTTON_DOWN) == BUTTON_SHORT_PRESS) { // LED off HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); } BUTTON_ResetActions(); }
Здесь у меня по короткому нажатию кнопки Up зажигается светодиод на PC13, по короткому нажатию кнопки Down – светодиод гаснет. Все очень просто, минимум действий – и полноценная работа кнопок в кармане. Если нужно отловить длительные нажатия кнопки Up, то:
if (BUTTON_GetAction(BUTTON_UP) == BUTTON_LONG_PRESS) { // Do something } if (BUTTON_GetAction(BUTTON_UP) == BUTTON_VERY_LONG_PRESS) { // Do something }
Все, больше никаких действий не требуется. Антидребезг, расчет длительности нажатия, переход между состояниями – все внутри библиотеки Button
. Я теперь ее пихаю абсолютно во все проекты, чаще всего для работы с меню на каком-нибудь дисплее. Подключаю 4 кнопки, настраиваю так:
typedef enum { BUTTON_UP, BUTTON_DOWN, BUTTON_LEFT, BUTTON_RIGHT, BUTTONS_NUM, } ButtonID;
И в main()
элементарно отлавливаю любые действия пользователя.
Готовый проект – KY004_Button.
Разбор кода библиотеки.
А теперь рассмотрим, что в библиотеке под капотом, для начала переменные:
debounceCounter
– счетчик для исключения дребезга.waitButtonRelease
- переменная-флаг, показывающая, что кнопка нажата и мы ждем ее отпускания.- В то время, пока кнопка нажата, счетчик
buttonPressCounter
отсчитывает время нажатия. buttonActions
– состояния каждой из кнопок, которые анализируем потом в нашем коде.buttonState
– а это низкоуровневые состояния кнопок.
Низкоуровневые состояния могут быть такие:
typedef enum { BUTTON_STARTING = 0, BUTTON_NOT_PRESSED = 1, BUTTON_WAIT_DEBOUNCE = 2, BUTTON_PRESSED = 3, } ButtonState;
Первое из них – состояние по умолчанию, оно позволяет отслеживать ошибки, если кнопка подключена неправильно, к примеру. BUTTON_NOT_PRESSED
– кнопка не нажата, BUTTON_WAIT_DEBOUNCE
– обработка антидребезга, BUTTON_PRESSED
– кнопка нажата.
Высокоуровневые состояния:
typedef enum { BUTTON_NONE = 0, BUTTON_SHORT_PRESS = 1, BUTTON_LONG_PRESS = 2, BUTTON_VERY_LONG_PRESS = 3, } ButtonAction;
BUTTON_SHORT_PRESS
– короткое нажатие, BUTTON_LONG_PRESS
– длинное нажатие, BUTTON_VERY_LONG_PRESS
– продолжительное (еще более долгое) нажатие. При желании можно добавить и еще таких состояний, для других временных интервалов, но это не так часто требуется.
Обработка кнопок заключается в вызове функции BUTTON_Process()
, внутри которой операции подразделяются на низкоуровневые и высокоуровневые: BUTTON_LowLevelManager()
и BUTTON_HighLevelManager():
void BUTTON_Process() { BUTTON_LowLevelManager(); BUTTON_HighLevelManager(); }
Посмотрим код первой из функций и проанализируем ее построчно:
void BUTTON_LowLevelManager() { uint8_t currentStates[BUTTONS_NUM]; for (uint8_t i = 0; i < BUTTONS_NUM; i++) { currentStates[i] = HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin); switch (buttonState[i]) { case BUTTON_STARTING: if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED) { buttonState[i] = BUTTON_NOT_PRESSED; } break; case BUTTON_NOT_PRESSED: if (currentStates[i] == GPIO_BUTTON_PRESSED) { buttonState[i] = BUTTON_WAIT_DEBOUNCE; debounceCounter[i] = 0; } break; case BUTTON_WAIT_DEBOUNCE: if (debounceCounter[i] == DEBOUNCE_TIME_MS) { if (currentStates[i] == GPIO_BUTTON_PRESSED) { buttonState[i] = BUTTON_PRESSED; } else { buttonState[i] = BUTTON_NOT_PRESSED; } } break; case BUTTON_PRESSED: if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED) { buttonState[i] = BUTTON_WAIT_DEBOUNCE; debounceCounter[i] = 0; } break; default: break; } } }
Процессы идут для каждой из кнопок в цикле for (uint8_t i = 0; i < BUTTONS_NUM; i++)
. Сначала считывается состояние:
currentStates[i] = HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin);
Далее в switch()
проверяем текущее низкоуровневое состояние. Если - BUTTON_STARTING
, то проверяем, что кнопка не нажата и переходим в другое состояние. Если кнопка подключена неправильно, то есть замыкает на +3.3 В вместо земли (например), то кнопка зависнет в этом состоянии.
Когда кнопка не нажата, проверяем ее новое состояние (if (currentStates[i] == GPIO_BUTTON_PRESSED)
). Если она стала нажатой, то переходим в состояние антидребезговой обработки:
if (currentStates[i] == GPIO_BUTTON_PRESSED) { buttonState[i] = BUTTON_WAIT_DEBOUNCE; debounceCounter[i] = 0; }
Антидребезг по стандартному алгоритму – ожидаем DEBOUNCE_TIME_MS
и, если кнопка осталась в нажатом состоянии, то переводим ее в BUTTON_PRESSED
. Иначе переходим в BUTTON_NOT_PRESSED
.
Если кнопка сейчас нажата (BUTTON_PRESSED
), то проверяем ее новое состояние ((currentStates[i] == GPIO_BUTTON_NOT_PRESSED)
). И если оно изменилось и стало GPIO_BUTTON_NOT_PRESSED
, то точно так же переходим к фильтрации дребезга контактов. Надеюсь, что я нормально описываю… Если что, пишите в комментарии, подправлю.
Осталась функция BUTTON_HighLevelManager()
, то есть высокоуровневые процессы:
void BUTTON_HighLevelManager() { for (uint8_t i = 0; i < BUTTONS_NUM; i++) { if (buttonActions[i] == BUTTON_NONE) { if (waitButtonRelease[i] == 0) { if (buttonState[i] == BUTTON_PRESSED) { waitButtonRelease[i] = 1; } } else { if (buttonState[i] == BUTTON_NOT_PRESSED) { waitButtonRelease[i] = 0; if (buttonPressCounter[i] >= BUTTONS_VERY_LONG_PRESS_MS) { buttonActions[i] = BUTTON_VERY_LONG_PRESS; } else { if (buttonPressCounter[i] >= BUTTONS_LONG_PRESS_MS) { buttonActions[i] = BUTTON_LONG_PRESS; } else { buttonActions[i] = BUTTON_SHORT_PRESS; } } } } } } }
Здесь проще, если кнопка не нажата:
if (buttonActions[i] == BUTTON_NONE)
Но при этом ее низкоуровневое состояние соответствует нажатой кнопке (if (buttonState[i] == BUTTON_PRESSED)
), то устанавливаем флаг waitButtonRelease[i]
в единицу. Здесь кстати так же вся работа в цикле по всем кнопкам. При следующем заходе в эту функцию, видим, что флаг в единице и ждем отпускания кнопки:
if (buttonState[i] == BUTTON_NOT_PRESSED) { waitButtonRelease[i] = 0; if (buttonPressCounter[i] >= BUTTONS_VERY_LONG_PRESS_MS) { buttonActions[i] = BUTTON_VERY_LONG_PRESS; } else { if (buttonPressCounter[i] >= BUTTONS_LONG_PRESS_MS) { buttonActions[i] = BUTTON_LONG_PRESS; } else { buttonActions[i] = BUTTON_SHORT_PRESS; } } }
При этом идет инкрементирование счетчика buttonPressCounter[i]
(об этом чуть ниже). Когда кнопка будет отпущена (if (buttonState[i] == BUTTON_NOT_PRESSED)
), по этому значению сделаем вывод о текущем нажатии.
Следующая функция библиотеки – BUTTON_TimerProcess()
– та, вызов которой мы добавили в callback по переполнению таймера:
void BUTTON_TimerProcess() { for (uint8_t i = 0; i < BUTTONS_NUM; i++) { if (debounceCounter[i] < DEBOUNCE_TIME_MS) { debounceCounter[i]++; } if (waitButtonRelease[i] == 1) { buttonPressCounter[i]++; } else { buttonPressCounter[i] = 0; } } }
Здесь просто инкрементируются использованные счетчики: счетчик для антидребезга и счетчик для нажатия. На этом я заканчиваю статью, мне эта библиотека очень подошла для обработки кнопок, может кому-то и не подойдет, как говорится «на вкус и цвет» )
Отдельная ссылка на библиотеку: Button.
Update 1.
Спасибо Kir за дельные замечания, обновил код и ссылки, а также добавил функцию инициализации void BUTTON_Init()
, которая задает начальные значения всех переменных. Как полностью справдливо замечено, слепо доверять компилятору мы не имеем никакого морального права! Вызываем эту функцию в main()
:
/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM10_Init(); /* USER CODE BEGIN 2 */ BUTTON_Init(); HAL_TIM_Base_Start_IT(&htim10); /* USER CODE END 2 */
Еще раз спасибо Kir, здравая критика и дельные замечания!
Ну как бы так себе, если кратко. Во-первых, как мне видится фрагмент кода в void BUTTON_LowLevelManager() :
case BUTTON_PRESSED:
if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED)
{
buttonState[i] = BUTTON_WAIT_DEBOUNCE;
currentStates[i] = 0;
}
должен выглядеть как:
case BUTTON_PRESSED:
if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED)
{
buttonState[i] = BUTTON_WAIT_DEBOUNCE;
debounceCounter[i] = 0;
}
Во-вторых, нет никакой защиты от помех и наводок. Да, в большинстве случаев оно будет работать. Но если при работе в ветке case BUTTON_WAIT_DEBOUNCE мы получим разовую помеху, то физическое состояние кнопки будет сброшено/установлено не верно. В каком-нибудь критически управляемом обьекте это может сыграть злую шутку... Ну и в-третьих, иногда от кнопки надо иметь гораздо больше состояний, например накликивание... Скажем по трем коротким кликам переходить в главное меню, по двум - на уровень выше и тп.
Опять же, нигде не делается инициализация переменных, используемых программой. Хорошо коли умный компилятор везде нули рассует, а если нет? Ну и код совсем не оптимизирован, например в моем исполнении функция void BUTTON_TimerProcess() выглядила бы так:
void BUTTON_TimerProcess()
{
uint8_t i;
for(i = 0; i < BUTTONS_NUM; i++)
{
if(debounceCounter[i] < DEBOUNCE_TIME_MS) debounceCounter[i]++;
if(waitButtonRelease[i]) buttonPressCounter[i]++;
else buttonPressCounter[i]=0;
}
}
Доброго дня)
А нас в далеком 88 году так учили )) Но дело даже не в оформлении кода. Просто глупо сравнивать с нулем, который и есть False или 1, которая и есть True. Ну а в статье ошибка так и осталась... Надеюсь, вложение то хоть поправили? А то там ошибка с сбросом счетчика debonce тоже есть... И она приводит к тому что антидребезг только для нажатия срабатывает, а для отпускания нет
И проверка на равенство buttonPressCounter в конце вашей таймерной процедуры только лишние такты жрет... Сброс там гарантированный и он меньше тактов на асме займет чем проверка и сброс, ну и памяти меньше... Нас учили вписывать не вписуемое ибо память в 88-ом экономили )) Я бы понял на pc где, но у нас то микропроцессоры...
Да, это бессмысленная проверка, я убрал, но естественно в статье забыл )) поправил )
Что касается вопроса проверки на помехи, то он есть. Реализуется достаточно просто, может как-нибудь будет время создам темку
Было бы интересно!
С 1 и 0 оставляю всегда, привычка видимо, нагляднее как-то) У меня такое ощущение, что браузер закэшировал, в Chrome скачиваю - старая версия, в Firefox скачиваю по той же ссылке - обновленная версия.
Обновил статью, еще раз большое спасибо!
Четко, толково, дельные замечания, не так часто такое встречается.
Добрый день, а зажатие кнопки с помощью этой библиотеки не реализовать? (например, однократное нажатие увеличивает число в счетчике на 1, а зажатие кнопки позволяет ускорить инкрементирование)
Отличная статья!
Привет! Да, без проблем можно организовать, я вот такой вариант накидал:
В итоге получаем, что при удержании кнопки каждые 50 мс (incrementPeriod = 50) происходит увеличение счетчика targetCounter. И каждые 250 мс (incrementSpeedPeriod = 250) увеличивается скорость инкрементирования.
Большое спасибо за участие и оперативность! Все заработало!
Отлично!
Aveal спасибо за реализацию )
Добрый день, спасибо за отзыв!
Здравствуйте! Подскажите, пожалуйста, я новичок в программировании микроконтроллеров да и в целом. Пытаюсь разобрать в вашем коде, практически до всего дошел, но вот одного не могу понять. Что означает BUTTONS_NUM в перечислении ButtonID:
typedef enum
{
BUTTON_UP,
BUTTON_DOWN,
BUTTONS_NUM,
} ButtonID;
Просто потом от этого значения присваиваются размеры массивов:
static McuPin buttons[BUTTONS_NUM] = {{GPIOA, GPIO_PIN_2}, {GPIOA, GPIO_PIN_3}};
uint8_t currentStates[BUTTONS_NUM] и т.д.
и какое значение в цикле for принимает переменная BUTTONS_NUM?
void BUTTON_LowLevelManager()
{
uint8_t currentStates[BUTTONS_NUM];
for (uint8_t i = 0; i < BUTTONS_NUM; i++)
{
currentStates[i] = HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin);
switch (buttonState[i])
{
case BUTTON_STARTING:
if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED)
{
buttonState[i] = BUTTON_NOT_PRESSED;
}
break;
case BUTTON_NOT_PRESSED:
if (currentStates[i] == GPIO_BUTTON_PRESSED)
{
buttonState[i] = BUTTON_WAIT_DEBOUNCE; debounceCounter[i] = 0;
}
break;
case BUTTON_WAIT_DEBOUNCE:
if (debounceCounter[i] == DEBOUNCE_TIME_MS)
{
if (currentStates[i] == GPIO_BUTTON_PRESSED)
{
buttonState[i] = BUTTON_PRESSED;
}
else
{
buttonState[i] = BUTTON_NOT_PRESSED;
}
}
break;
case BUTTON_PRESSED:
if (currentStates[i] == GPIO_BUTTON_NOT_PRESSED)
{
buttonState[i] = BUTTON_WAIT_DEBOUNCE;
debounceCounter[i] = 0;
}
break;
default:
break;
}
}
}
Заранее спасибо за ответ и помощь!
Доброго дня) Просто для удобства, в цикле, например, по всем кнопкам. Если 2 кнопки:
То BUTTONS_NUM = 2. Если добавляем еще одну кнопку
То BUTTONS_NUM = 3 за счет того, что по порядку перечисления в конце идет.
Я тоже споткнулся об BTTONS_NUM.
Зачем нужен понятно, а вот где он дефайнится непонятно.
Я так поимаю в main.h руками прописывается количество кнопок?
Не, он как член enum. Если у нас:
то значения будут такие:
Поэтому важно просто BUTTONS_NUM оставлять последним в этом перечислении, а его значение будет по сути автоматом соответствовать количеству кнопок.
А как оно автоматом вычисляется? оО
Это что-то в стандарте языка Си есть, что само суммирует количество вышестоящих элементов?
Не припомню такого...
Не суммирует. Если значения не заданы явно, то для первого элемента - 0, для последующих инкрементируется на 1.
Спасибо, вчера закрыл браузер и вспомнил тип ENUM, как они устроены 🙂
=)
Здравствуйте я в программирование контроллеров только начинаю разбираться, мне в целом код понятен, но как работает вот эта строчка кода
"currentStates[i] = HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin);" Как я понял тут вызывается функция HAL_GPIO_ReadPin, у нее в параметрах как я понял есть название порта через приведение типов (GPIO_TypeDef * GPIOx), вторым параметром идет номер пина (uint16_t GPIO_Pin),, а третьим его состояние GPIO_PinState PinState, причем как я понял последний параметр не обязателен для указания. Так вот, как buttons[i].port у вас в коде ссотносится с GPIO_TypeDef * GPIOx и buttons[i].pin с (uint16_t GPIO_Pin) я просто не очень понимаю как эти все приведения типов работают. Еще у вас в файле button.h есть структура
typedef struct
{
GPIO_TypeDef *port;
uint16_t pin;
}McuPin; я думаю, что все завязано на ней, но я не понимаю как она работает и откуда взялось buttons? Расскажите пожалуйста ою этом
Добрый день!
Постараюсь поэтапно описать. Сначала функция HAL_GPIO_ReadPin() - у нее два аргумента, все верно - типов GPIO_TypeDef* и uint16_t. И структура McuPin в себе объединяет именно такие два члена, с этими же типами данных.
Далее создаем массив buttons[] - это массив структур, то есть каждый элемент массива - это структура McuPin. Мы добавляем два элемента:
Получаем тогда, для первого элемента массива, например - buttons[0].port равно GPIOA, а buttons[0].pin равно GPIO_PIN_2, эти значения и передаем в функцию HAL_GPIO_ReadPin().
Добрый день!
Подскажите, пожалуйста, а как реализовать функцию выхода из STANDBYMODE при длительном нажатии на кнопку, игнорируя короткие нажатия.
Как я понял, при выходе из STANDBY код не видит нарастания фронта и ничего не происходит, т.к. контроллер уже проснулся при нарастании на WKUP пине
Добрый день! Мне кажется так не получится, потому что аппаратно пробуждение происходит по фронту.
Но есть такой вариант - контроллер просыпается по фронту, анализирует состояние на входе (там где кнопка), и если кнопка была слишком быстро отпущена, то засыпает опять. То есть просто через HAL_GPIO_ReadPin() читаем состояние входа и проверяем, сколько прошло времени.
Здравствуйте!
Спасибо за статью!
Скажите пожалуйста, можно ли скорректировать Ваш код так, чтобы действия по нажатию на кнопки выполнялись не по заднему фронту (отпусканию), а по переднему?
Спасибо!
Добрый день!
А здесь же все манипуляции по состоянию кнопки происходят, к фронту не привязано.
Необходимо отслеживание только короткого и длительного нажатия (отслеживание более длительного нажатия не нужно).
Как переделать код так, чтобы при длительном нажатии реакция на нажатие была не при отпускании кнопки, а в тот момент когда она еще удерживается?
Спасибо!
Можно в BUTTON_HighLevelManager() логику изменить, я по-быстрому накидал, не проверял:
Здравствуйте!
Большое спасибо что откликнулись. К сожалению пока код работает некорректно, буду пробовать разобраться в чем причина. Если у Вас будет возможность, просьба проверить этот код на корректность выполнения.
Спасибо!
В состояние BUTTON_SHORT_PRESS переходит стандартно, в состояние BUTTON_LONG_PRESS переходит при удержании. В состояние BUTTON_NONE переходит после отпускания.
Дальше надо смотреть по задаче - в этом варианте состояние BUTTON_LONG_PRESS будет сохраняться до финального отпускания кнопки.
Уважаемый Aveal, большое спасибо за оказываемую помощь!
Код заработал почти так как нужно. Действительно состояние BUTTON_LONG_PRESS сохраняется до отпускания кнопки и это приводит к тому, что пока кнопка не отпущена функция которая должна выполнятся только один раз при длительном нажатии кнопки, выполняется много раз пока не отпустить кнопку, что в моем случае недопустимо. Что нужно поменять чтобы при длительном нажатии действие выполнялось только один раз даже если кнопку продолжают удерживать? Где-то нужно установить состояние BUTTON_NONE?
Спасибо!
Рад, что продвигается ) Самый банальный и простой вариант, добавить флаги для кнопок, в button.c:
В функции инициализации добавить в цикле:
В BUTTON_HighLevelManager():
В одна BUTTON_TimerProcess() строка добавляется:
И в main.c:
Уважаемый Aveal, большое спасибо Вам за помощь!
Сделал все как Вы написали, вроде все работает, тестирую. Как я могу Вас отблагодарить?
Заходи на сайт, приглашай друзей/знакомых 🙂
Приветствую!
Только начинаю осваивать, и очень многое непонятно.
Решил на основе вашего обработчика кнопки, сделать управление светодиодами. (просто экспериментирую для набора опыта).
Бьюсь несколько дней - не получается.
Хочу сделать подсчет коротких нажатий, и чтобы при однократном нажатии постоянно мигал первый светодиод, при двукратном - второй, ну и т.д. Делаю счетчик нажатий.
int counter;
counter=0;
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
BUTTON_Process();
// Work with buttons
// Button "Up"
if (BUTTON_GetAction(BUTTON_UP) == BUTTON_SHORT_PRESS)
{counter=counter+1;}
if (counter==1)
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(330);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(330);}
if (counter==2)
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_Delay(330);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
HAL_Delay(330);}
.........
if (counter>=5)
{counter=0;}
При коротком нажатии кнопки, 0-й светодиод начинает моргать. Но следующее нажатие на кнопку, не переключает на моргание 1-й светодиод и т.д.
Что я делаю не так, не могу понять. Сразу прошу прощения за глупые вопросы. Только начинаю позновать.
Привет, а можешь полный проект скинуть, я посмотрю.
Приветствую. спасибо за ответ.
Сбросил на почту aveal@microtechnics.ru
Вроде нормально все, под отладчиком смотрел что происходит?
У меня, видимо, левая STM-ка, ни Кейл, ни CubeIde, не хотят проводить отладку. Ругаются, что STM не настоящая.
Это усложняет конечно. Остается отладка как в старые времена - светодиодом ) Оставляем минимум:
На каждое короткое нажатие должен мигать диод - если не будет, то значит проблема уже на этом этапе.
Приветствую.
Одиночные действия отрабатываются. Светик отмаргивает.
Но если вставляю счетчик, а потом происходит непрерывное действие, то все перестает работать. ((
А так работает?
При первом нажатии, не происходит ничего, как и прописано.
При втором нажатии, светик начинает непрерывно моргать. Третье нажатие, по идее, должно обнулять счетчик и светодиод должен погаснуть. Но он продолжает моргать. И на кнопку больше не реагирует.
Я, на всякий случай, увеличил счетчик до 4 и добавил условие, когда счетчик равен 4, чтобы начинал моргать следующий диод. Но этого не происходит. Все так же моргает первый диод. На кнопку нет реакции.
Я, на всякий случай, взял F411-ю STM. Но с ней, то же самое. И к сожалению она у меня тоже левая. Не позволяет проводить отладку. Вы не пробовали запустить эту конструкцию у себя. Может такое поведение из-за левых чипов?
А, блин, понятно все. Короче HAL_Delay(много_миллисекунд) нарушает работу драйвера кнопок, там же антидребезг итд по таймеру считается, а в BUTTON_Process() заходит раз в 1-2 секунды. Набросал вариант:
Заработало!. Спасибо большое. Я правильно понял, если я опять в цикле как-нибудь "угадаю " с задержками, опять может перестать работать?
Наверное стоит поискать, как реализовать кнопку по прерыванию.
Ну таких задержек как в HAL_Delay() лучше в целом избегать в большинстве случаев.
А в этом конкретном случае можно попробовать BUTTON_Process() вызывать из прерывания по какому-нибудь таймеру, либо если RTOS использовать, то вынести в отдельный таск.
Работает , но по двум нажатиям.И я не вижу в коде прерывания на нажатие и отжатие. Может че-то не понял.И убрал прерывания в void TIM3_IRQHandler(void) _ it.c файл
Выражаю благодарность админу данного сайта за помощь в настройке библиотеки для одной кнопки!
Нет проблем, обращайся )
Можно доработать библиотеку так, чтобы обрабатывать нажатие кнопки при удержании другой? Попробовал проверять короткое нажатие при длинном нажатии другой, работает, но кривовато.
По идее должно работать🤔 Там все разнесено отдельно для отдельных кнопок.
Сделал наоборот - работает.
Aveal, приветствую!
Скажите пожалуйста, как правильно адаптировать функции библиотеке для работы с FreeRTOS? Какие функции нужно вынести в отдельные задачи? В случае использования FreeRTOS можно ли обойтись без аппаратного таймера 1 мс для вызова функции ButtonTimerProcess()?
Спасибо!
Доброго дня!
Можно спокойно BUTTON_Process() вынести в отдельный таск. BUTTON_TimerProcess() в целом тоже, только счетчики buttonPressCounter / debounceCounter не инкрементировать, а добавлять к ним прошедшее между вызовами количество миллисекунд. Навскидку никаких препятствий не вижу.
День добрый, все адаптировал как в примере, файлы Button.c\h, находятся в папке Modules, которая является папкой источником, но при компиляции проекта выходит ошибка /Core/Src/main.c:30:10: fatal error: button.h: No such file or directory
30 | #include "button.h"
| ^~~~~~~~~~
Как бы проект не может найти библиотеку, где ошибка? СПС
Пропустил коммент... Может include paths в настройках проекта не заданы?
Мне вот интересно, а включить обработчик внешних прерываний на GPIO и обрабатывать все события (перепады по переднему и заднему фронту) в прерывании это не норма уже? Фильтр дребезга контактов уже ни кому не нужен? Потом новички вот это все читают и плодят говнокод.
Пока автор не ответил, сам отвечу по-быстрому.
Во-первых, даже при беглом просмотре очевидно, что тут есть антидребезг,.
Во-вторых, на условный десяток примитивных кнопок задействовать не особо нужный отлов всех событий по прерыванию напоминает популярную в некоторых местах и у некоторых авторов концепцию "использовать DMA, чтобы перекинуть 1-2 байта". То есть использовать что-либо не осмысленно, а просто потому что "оно же есть, надо задействовать из принципа".
Конкретно эту реализацию на момент выхода статьи сам использовал местами - четко, стабильно, без вопросов.
Если рассматривать данную статью, как некое, небольшое пособие для начинающих, как обрабатывать события от кнопки (например короткое нажатие, длинное нажатие), на мой взгляд было бы очень не плохо познакомить начинающих программистов, как раз таки с работой блока внешних прерываний, рассказать, что такое "дребезг контактов". Вместо того, что бы просто постоянно сидеть в регулярном коде и опрашивать состояние GPIO на входе. И не преподносить данный материал как библиотеку для работы с кнопкой. До нормальной полноценной подобной библотеки, тут как до Китая...
Я как раз эту статью рассматриваю как статью конкретно про обслуживание кнопок, а не про внешние прерывания, о которых собственно и нет никаких упоминаний в тексте. По итогу человек скачивает библиотеку, добавляет в свой код 2-3 вызова - имеет обработку произвольного количества кнопок с отслеживанием длительности и количества нажатий.
На мой вкус понятие "библиотека" именно и подразумевает некую абстракцию, которую с минимальными усилиями можно внедрить в свой код на любом контроллере, и которая стабильно и надежно выполняет свою конкретную задачу.
По поводу пресловутого дребезга - статья же не называется "что такое дребезг контактов", поиском прошел по тексту, про обработку этого самого дребезга объяснено доходчиво.
На Ваш вкус - какой должен быть функционал у некой базовой сущности под названием "библиотека для обработки нажатий кнопок"? По большому счету даже фиксация длительности нажатий в большинстве случаев не используется, только сам факт нажатия. По производительности - нагрузка минимальная, если уж высчитывать кол-во таков итд итп затраченное на проверку состояний.
Понятно, что можно рассуждать об экономии и оптимальности за счет использования аппаратных ресурсов по максимуму. Окей, сэкономим какие-то копейки, в данном конкретном случае за это придется заплатить удобством и прозрачностью архитектуры, стоит ли оно того - это уже из области извечных философских рассуждений из серии "а использовать ли HAL в целом или все делать руками".