Top.Mail.Ru

Библиотека Button для обработки нажатий кнопок на STM32. KY-004.

Мы тут обсуждали вроде бы простейший вопрос, как обрабатывать нажатия кнопок наиболее универсально и удобно. В результате мне прислали библиотеку для этого, которой я и поделюсь в статье с разрешения автора (я правда изменил оформление библиотеки под привычный мне стиль). Можно это даже приурочить к моему «курсу» по датчикам, так как один из модулей этого набора состоит из тактовой кнопки. Фото я прикрепил сверху, это модуль KY-004, но это не важно, речь пойдет о любых тактовых кнопках на примере использования их с STM32.

Тактовая кнопка выглядит так:

Тактовая кнопка

Не путаем с переключателем:

Переключатель

Переключатель фиксирован либо в одном, либо в другом положении. Тактовая кнопка фиксирована в не нажатом положении, чтобы ее удерживать в нажатом положении надо прилагать усилия. Но это все и так знают )

От такой кнопки хочется чуть более расширенного функционала, который мне бывает нужен почти в каждом проекте:

  • отслеживание короткого нажатия кнопки
  • длительного
  • еще более длительного

Длительности могут быть разными для разных случаев. Этим целям и служит библиотека Button, которую я опишу. Только начну я с применения готовой библиотеки, а потом опишу, как она работает. Кому просто нужно готовое проверенное решение, вторую часть можно даже и не читать.

Схема подключения кнопок к STM32.

У меня есть:

  • Отладочная плата с STM32F401CCU6.
  • Кнопка «Up», то есть вверх, допустим для перехода по пунктам меню. Подключена к выводу PA2.
  • Кнопка «Down», то есть вниз. Подключена к PA3.

Схема подключения кнопок к STM32.

Обработка кнопок заключается в том, что нужно выполнять разные действия в зависимости от того, какое нажатие:

  • Короткое нажатие – меньше 500 мс.
  • Длинное нажатие – от 500 мс – до 3 секунд.
  • Продолжительное нажатие – удержание кнопки более 3-х секунд.

Создание проекта для обработки кнопок.

Я использую STM32CubeIDE, поэтому в STM32CubeMx настраиваю два вывода STM32 на вход и любой из таймеров (нужен для библиотеки Button). И еще один выход (PC13) для тестового светодиода. У меня кнопки подтянуты электрически к питанию, поэтому внутренняя подтяжка GPIO мне не нужна:

Обработка нажатий кнопок на STM32.

Таймер настроен на генерацию прерывания каждую миллисекунду:

Настройки Timer в STM32CubeMx.

Настройка прерываний в STM32CubeMx.

Тактирование у меня настроено так:

Тактирование STM32.

То есть на таймер приходит частота 84 МГц. При предделителе 8399 получаю частоту:

F = 84 МГц / (8399 + 1) = 10 КГц

То есть один отсчет таймера  – 100 мкс. Ставлю период (Counter Period), равный 10, получаю прерывание каждые - 100 мкс * 10 = 1 мс. Инициализация на этом закончена. Генерирую проект и перехожу к настройке библиотеки для обработки нажатий кнопок.

Конфигурация библиотеки Button для обработки кнопок.

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

  1. Добавляем файлы библиотеки Buttonв проект, эту операцию подробно думаю не надо описывать (если что спрашивайте в комментариях, я всем помогу):

Проект для обработки нажатий кнопок.

  1. В файле 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 мс:

#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)

Тоже ничего сложного.

  1. Последний шаг. В файле 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:

  1. Каждую миллисекунду будем вызывать функцию 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 мс, и значения такие, как я поставил (тоже в миллисекундах).

  1. Запускаем таймер в функции main(),  почему-то постоянно об этом забываю…
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim10);

/* USER CODE END 2 */
  1. Работу библиотеки и обработку кнопок обеспечивают три функции:
  • 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, здравая критика и дельные замечания!

Подписаться
Уведомить о
guest

28 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Kir
Kir
1 год назад

Ну как бы так себе, если кратко. Во-первых, как мне видится фрагмент кода в 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;
 }
}

KIR
KIR
Ответ на комментарий  TQFP
1 год назад

А нас в далеком 88 году так учили )) Но дело даже не в оформлении кода. Просто глупо сравнивать с нулем, который и есть False или 1, которая и есть True. Ну а в статье ошибка так и осталась... Надеюсь, вложение то хоть поправили? А то там ошибка с сбросом счетчика debonce тоже есть... И она приводит к тому что антидребезг только для нажатия срабатывает, а для отпускания нет

KIR
KIR
Ответ на комментарий  KIR
1 год назад

И проверка на равенство buttonPressCounter в конце вашей таймерной процедуры только лишние такты жрет... Сброс там гарантированный и он меньше тактов на асме займет чем проверка и сброс, ну и памяти меньше... Нас учили вписывать не вписуемое ибо память в 88-ом экономили )) Я бы понял на pc где, но у нас то микропроцессоры...

KIR
KIR
Ответ на комментарий  KIR
1 год назад

Что касается вопроса проверки на помехи, то он есть. Реализуется достаточно просто, может как-нибудь будет время создам темку

Aveal
Администратор
Ответ на комментарий  Kir
1 год назад

Четко, толково, дельные замечания, не так часто такое встречается.

Klin4
Klin4
1 год назад

Добрый день, а зажатие кнопки с помощью этой библиотеки не реализовать? (например, однократное нажатие увеличивает число в счетчике на 1, а зажатие кнопки позволяет ускорить инкрементирование)

Отличная статья!

Aveal
Администратор
Ответ на комментарий  Klin4
1 год назад

Привет! Да, без проблем можно организовать, я вот такой вариант накидал:

  • В button.c добавляем функцию:
/******************************************************************************/
ButtonState BUTTON_GetState(uint8_t index)
{
  return buttonState[index];
}



/******************************************************************************/
  • Ее же в button.h:
/* Functions -----------------------------------------------------------------*/

extern void BUTTON_Process();
extern void BUTTON_TimerProcess();
extern ButtonAction BUTTON_GetAction(uint8_t index);
extern void BUTTON_ResetActions();
extern void BUTTON_Init();

extern ButtonState BUTTON_GetState(uint8_t index);
  • Далее в main.c, переменные:
/* USER CODE BEGIN PV */
uint32_t targetCounter = 0;

uint32_t incrementSpeed = 1;
uint32_t incrementPeriod = 50;
uint32_t incrementSpeedPeriod = 250;

uint32_t previousMs = 0;
uint8_t speedUpdated = 0;

/* USER CODE END PV */
  • И в main():
while (1)
{
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    BUTTON_Process();

    uint32_t currentMs = HAL_GetTick();

    ButtonState currentState = BUTTON_GetState(BUTTON_UP);
    if (currentState == BUTTON_PRESSED)
    {
      if (((currentMs % incrementPeriod) == 0) &&
          (previousMs != currentMs))
      {
        targetCounter += incrementSpeed;
        previousMs = currentMs;
        speedUpdated = 0;
      }

      if (((currentMs % incrementSpeedPeriod) == 0) &&
          (speedUpdated == 0))
      {
        incrementSpeed++;
        speedUpdated = 1;
      }
    }
    else
    {
      targetCounter = 0;
      incrementSpeed = 1;
    }

    BUTTON_ResetActions();

}
/* USER CODE END 3 */

В итоге получаем, что при удержании кнопки каждые 50 мс (incrementPeriod = 50) происходит увеличение счетчика targetCounter. И каждые 250 мс (incrementSpeedPeriod = 250) увеличивается скорость инкрементирования.

Klin4
Klin4
Ответ на комментарий  Aveal
1 год назад

Большое спасибо за участие и оперативность! Все заработало!

Aveal
Администратор
Ответ на комментарий  Klin4
1 год назад

Отлично!

Алексей
Алексей
9 месяцев назад

Здравствуйте! Подскажите, пожалуйста, я новичок в программировании микроконтроллеров да и в целом. Пытаюсь разобрать в вашем коде, практически до всего дошел, но вот одного не могу понять. Что означает 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;
}
}
}

Заранее спасибо за ответ и помощь!

Aveal
Администратор
Ответ на комментарий  Алексей
9 месяцев назад

Доброго дня) Просто для удобства, в цикле, например, по всем кнопкам. Если 2 кнопки:

typedef enum
{
  BUTTON_UP,
  BUTTON_DOWN,
  BUTTONS_NUM,
} ButtonID;

То BUTTONS_NUM = 2. Если добавляем еще одну кнопку

typedef enum
{
  BUTTON_UP,
  BUTTON_DOWN,
  BUTTON_NEW_ADDED,
  BUTTONS_NUM,
} ButtonID;

То BUTTONS_NUM = 3 за счет того, что по порядку перечисления в конце идет.

Алекс
Алекс
Ответ на комментарий  Aveal
7 месяцев назад

Я тоже споткнулся об BTTONS_NUM.
Зачем нужен понятно, а вот где он дефайнится непонятно.
Я так поимаю в main.h руками прописывается количество кнопок?

Aveal
Администратор
Ответ на комментарий  Алекс
7 месяцев назад

Не, он как член enum. Если у нас:

typedef enum
{
  BUTTON_UP,
  BUTTON_DOWN,
  BUTTONS_NUM,
} ButtonID;

то значения будут такие:

BUTTON_UP = 0;
BUTTON_DOWN = 1;
BUTTONS_NUM = 2;

Поэтому важно просто BUTTONS_NUM оставлять последним в этом перечислении, а его значение будет по сути автоматом соответствовать количеству кнопок.

Алекс
Алекс
Ответ на комментарий  Aveal
7 месяцев назад

А как оно автоматом вычисляется? оО
Это что-то в стандарте языка Си есть, что само суммирует количество вышестоящих элементов?
Не припомню такого...

Aveal
Администратор
Ответ на комментарий  Алекс
7 месяцев назад

Не суммирует. Если значения не заданы явно, то для первого элемента - 0, для последующих инкрементируется на 1.

Алекс
Алекс
Ответ на комментарий  Aveal
7 месяцев назад

Спасибо, вчера закрыл браузер и вспомнил тип ENUM, как они устроены 🙂

Aveal
Администратор
Ответ на комментарий  Алекс
7 месяцев назад

=)

Сергей
Сергей
7 месяцев назад

Здравствуйте я в программирование контроллеров только начинаю разбираться, мне в целом код понятен, но как работает вот эта строчка кода
"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? Расскажите пожалуйста ою этом

Ant
Ant
2 месяцев назад

Добрый день!
Подскажите, пожалуйста, а как реализовать функцию выхода из STANDBYMODE при длительном нажатии на кнопку, игнорируя короткие нажатия.
Как я понял, при выходе из STANDBY код не видит нарастания фронта и ничего не происходит, т.к. контроллер уже проснулся при нарастании на WKUP пине

28
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x