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

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

Я использую программное устранение дребезга следующим образом, чтобы обнаружить нормальное нажатие переключателя.

int buttonPress (void)
{
  static int downCount= 0; // static = value isn't lost between calls.

  int currentButton = ((IO0PIN & 0x00000200) == 0x00000200); 

  if (currentButton ) { // button is down

    if (downCount == 5) { // button has been down for 5 counts
      downCount++;      // increase the count so we don't trigger next time
      return 1;
    } else {              // some count other than 5
      if (downCount < 5)  // increase if it's less than 5
        downCount++;
    }
  } else   {    // button is up
    downCount = 0; 
  }
  return 0;
}   

Однако я хотел бы определить, была ли эта кнопка нажата в течение 5 секунд или дольше. Проблема, с которой я столкнулся, заключается в том, что я не могу сосчитать до 300 000, и если это не достигнуто, вернуться к оригиналу downCount5, чтобы отметить как обычное нажатие кнопки.

(Поскольку тактовая частота моего микроконтроллера составляет 60 МГц, то для достижения 300 000 потребуется 5 секунд.)

Я поиграл с некоторой логикой следующим образом, однако в случае, показанном ниже, из-за введения whileутверждения я считаю до 300 000 независимо от того, как долго кнопка была нажата.

if (currentButton ) {   // button is down

  if (downCount == 5) { // button has been down for 5 counts
    downCount++;      // increase the count so we don't trigger next time
    while (downCount > 5) { 
      if (debounce == 300000) {
        flag_5SEC = 1;
      } else if (debounce < 300000) {
         debounce++;
      }
    }
    return 1;
  } else {              // some count other than 5
    if (downCount < 5)  // increase if it's less than 5
      downCount++;
  }
}

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

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


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

Мне не нужно определять точную временную задержку, просто если кнопка была нажата дольше, чем, скажем, 2-3 секунды.

Комментарий @DiBosco уточняет далее:

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

Мне кажется, что как только downCount достигает пяти, вы просто застряли в этом цикле while. Я предполагаю, что вы вызываете это из основного бесконечного цикла? Вам нужен лучший способ входа в функцию из основного цикла, увеличения счетчика и выхода, если вы не достигли целевого количества. Однако, если вы не сделаете это из таймера (вы можете установить флаг в прерывании, даже если это общее прерывание) и не войдете сюда из основного цикла, когда этот флаг установлен, это будет крайне неточным.
PS Когда вы отвечаете кому-то, убедитесь, что вы используете их имя, чтобы они знали, что вы ответили. :)
@DiBosco, спасибо за ваш проницательный комментарий, да, я вызываю это из основного бесконечного цикла. Я согласен со всеми вашими утверждениями, упомянутыми, и я пытаюсь найти лучший способ сделать все упомянутые вещи. Будет ли полезен метод, который я пытаюсь использовать (без таймеров), если я просто хочу обнаружить нажатие кнопки дольше, скажем, 2 секунд, а не точное значение?
Извините, я неправильно прочитал код... не знаю, как вы это называете...
У меня есть if(buttonPress())утверждение, которое верно только наreturn 1
@ Rrz0 да, это работает, если предположить, что вызовы происходят регулярно и достаточно долго, но не слишком долго.
@DiBosco, в настоящее время я ищу решение в направлении текущих ответов (которые кажутся лучшим вариантом), однако я видел фрагмент ответа, который вы опубликовали, и мне было интересно, можете ли вы изменить / восстановить его?
Сделанный. Поскольку было два других хороших ответа, и я случайно нажал «Отправить», прежде чем он был готов, мой показался ненужным, но я надеюсь, что он поможет.
Добавил еще немного для большей ясности (надеюсь!)
Вы говорите, что не хотите использовать таймеры, но есть ли на используемой вами платформе функция, которая возвращает прошедшее время? (Похоже millis()на Ардуино).
@NickGammon, нет...
Как вы собираетесь обнаружить «5 секунд или больше», если вы понятия не имеете, сколько времени прошло? Все очень хорошо говорят, что вы будете считать инструкции, но если вы добавите что-то в цикл подсчета, он будет исключен.
«Поскольку тактовая частота моего микроконтроллера составляет 60 МГц, то для достижения 300 000 потребуется 5 секунд». Я что-то упустил, или вы (1) перепутали мега с килограммом и (2) предположили, что итерация цикла выполняется за один такт?
@ThomasPadron-McCarthy, спасибо, что указали на это. Да, это ошибка с моей стороны.

Ответы (4)

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

В этом случае вы все делаете неправильно. Если вам абсолютно не нужны высокое разрешение, низкое дрожание или другие аппаратные функции аппаратных таймеров, вы должны выполнять отсчет времени программно, используя один из аппаратных таймеров для генерации непрерывных периодических прерываний, которые вы можете подсчитать в ISR.

«Прерывание пульса» с периодом от 100 мкс до 100 мс (или более) является обычной чертой встроенных систем. Это позволяет создавать произвольное количество программных таймеров.

Даже если вы уже используете все аппаратные таймеры, пока хотя бы один из них выполняет что-то периодическое, например, генерирует сигнал ШИМ или создает часы для последовательного интерфейса, вы можете включить прерывания на нем и использовать их. для временной базы вашего программного обеспечения. Период может быть неудобным значением, но это простая проблема масштабирования, с которой может справиться программное обеспечение.

Как умы :) 341 идти..... барабанная дробь!
Итак, если у меня есть прерывание Timer с периодом 1 мс, я могу просто установить флаг при нажатии кнопки, а затем, пока флаг высокий, считать внутри прерывания. Это утверждение верно?
Я даже не из числа EE, и это был очень хорошо написанный и понятный ответ! Единственное, с чем я не знаком, так это с "ISR", но для этого и нужен гугл ;)
@ Rrz0: Да, но на самом деле вам нужно два счетчика: один для подсчета времени «устранения дребезга» в 10 мс или около того, чтобы определить, что коммутатор изменил состояние, а другой для подсчета времени «удержания» в 5000 мс для коммутатора.
Дэйв, на самом деле вам нужен только один счетчик и два уровня сравнения счетчиков. Если тактовая частота составляет 1 мс, счет 10 = отклонено, счет 5000 = удерживается, затем остановите счет.
@Trevor_G: Есть много способов реализовать несколько таймеров, когда у вас есть периодическое прерывание. Наличие отдельного счетчика для каждого, вероятно, проще всего понять. Один счетчик с несколькими компараторами, как правило, более эффективен, но его сложнее объяснить. Выбор сильно зависит от других аспектов системы.

Ваш код кажется немного неправильным, и его трудно понять, поскольку неясно, где находится этот цикл while по отношению к прерыванию или тому, что устанавливает функцию DownCount.

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

Ваше встроенное решение проблематично по многим причинам, не последней из которых является то, что ваше время будет варьироваться в зависимости от того, что еще происходит на микроконтроллере, если вы не остановитесь и не зациклитесь, пока считаете. Но останавливаться на пятисекундном счете — тоже плохая идея.

Этот тип функции обычно обрабатывается в микрос помощью счетчика тиков, также известного как сердцебиение. По сути, обычной практикой является резервирование одного таймера в качестве часов для вашего кода. Таймер будет генерировать регулярное прерывание, возможно, 10 раз в секунду.

Этот обработчик прерываний может позаботиться о многочисленных вспомогательных функциях, таких как устранение дребезга переключателей, мигание светодиодов, проверка тайм-аутов связи и т. д. Он также может включать подсчет времени нажатия кнопки. Когда он был нажат в течение соответствующего промежутка времени, указанный обработчик затем вызывает соответствующий обработчик для этого события.

Этот метод позволяет использовать один таймер для множества функций.

Спасибо, что указали на ошибку в моем коде. Это действительно неправильно.
Я начал понимать, что метод, упомянутый выше, действительно правильный. Вы предлагаете мне позаботиться о том, чтобы программное обеспечение полностью устраняло дребезг внутри прерывания?
@ Rrz0 да, вы также можете обработать устранение дребезга в прерывании тиковых часов на основе флагов прерывания кнопки, если оно есть, или путем опроса линии ввода-вывода, а также флагов и переменных, установленных из предыдущего состояния тиковых часов.
@ Rrz0 Rrz0 Я также должен упомянуть, поскольку из вашего кода неясно, вам нужно устранить дребезг как при нажатии кнопки, так и при ее отпускании. Удивительно, но переключатели тоже подпрыгивают, когда отпускаешь.

Поскольку кнопки управляются очень медленными людьми по отношению к скорости процессора, вы можете обойтись медленным опросом. Например, каждые 10 миллисекунд.
Вы можете легко создать одну функцию для управления многими кнопками со множеством шаблонов нажатия и удержания.

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

#define BUTTON_INTERVAL 10
#define BUTTON_COUNT    1

/* Typedef for a button */
typedef struct button_s {
    void* port;         /* Use GPIO port type, whatever your platform uses, maybe even via bitbanding */
    uint32_t hi_time;   /* Time high */
    uint8_t pin;        /* Pin number */
    uint8_t flags;      /* Output flags */
} button_t;

/* The array of all buttons */
button_t buttons[BUTTON_COUNT];

/* Handle button polling */
void buttonHandler(void){
    uint8_t i;
    for(i=0;i<NR_OF_BUTTONS; i++){
        button_t *b = buttons[i];
        uint8_t state = *b->port & (1<<b->pin);
        if(state){
            // Saturating unsigned 32-bit addition 
            // your cpu might have an instruction for this eg: arm __uqadd(a,b))
            if(b->hi_time < 0xFFFFFFFF) b->hi_time += BUTTON_INTERVAL;
        }else{
            b->hi_time = 0;
        }
        // One of many possible flags comparisons:
        if( b->hi_time >= 5000 ){
            b->flags |= BUTTON_HI_5S;
        }
    }
}

/* Example of flag usage */
void waitForButton(){
    if( button[0]->flags & BUTTON_HI_5S ){
        button[0]->flags &= ~BUTTON_HI_5S;
        // ..stuff..
    }
}

Ключевые моменты, если вы собираетесь самостоятельно:
- Структура для всех данных, вы даже можете переназначить ключи на лету, если хотите.
- Бесконечные кнопки с использованием структур. Никогда больше не пишите этот код!
- Насыщение таймеров, они не переполняются.
- Флаги, легко заменяемые для вызовов операционной системы.

Если я правильно понимаю ваши потребности, вы хотите что-то вроде этого:

void SwitchCheck(void)
{   
    DebounceTimer = false; // If using timer flag
    if (currentButton) 
        {
        debounce++; 
        }
    else 
        {
        if (debounce > NORMAL_OPERATION)
              {
              // Do normal stuff here
              }
        debounce = 0;
        } 
    if (debounce == MAX_TIMEOUT) 
         {
         // Do your held down thing here
         }
}

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

Я много-много раз за эти годы делал дебаунс переключения без таймеров (хотя и не очень нравится), просто звонил из основного цикла и все работало. Однако, если вам нужна точность в пятисекундном хронометраже, этого будет недостаточно.

Дальнейшее редактирование:

В вашем таймере ISR:

TimerISR()
{
DebounceTimer = true;
// Other stuff    
}

В основном:

if (DebounceTimer)
    SwitchCheck();

Или просто вызовите его из main без проверки флага, если вы не используете таймер, и эмпирически поэкспериментируйте с MAX_TIMEOUT.

Я могу подтвердить, что я не ищу точности в этом конкретном случае.
@JohnSmith, я не понимаю, что ты имеешь в виду. (Без сарказма, действительно не совсем уверен, к чему вы клоните.) Он ничего не будет делать, пока не достигнет MAX_TIMEOUT, а затем выполнит то, что нужно выполнить, и nпросто выпадет. Если его удерживать ОЧЕНЬ долго, есть риск, что DebounceTimer пойдет по кругу и выполнится повторно. Вам может понадобиться проверка, чтобы убедиться, что этого не произойдет. Однако это всего лишь руководство, а не полное решение. Возможно, я что-то полностью упустил из-за того, что было после ОП.
@DiBosco, читая вышеизложенное, нет, вы не совсем упустили то, что мне нужно. Однако я хочу иметь возможность обнаруживать обычное нажатие переключателя, когда TIMEOUT будет намного меньше, чем MAX_TIMEOUT. Итак, поскольку ничего не происходит, пока не достигнет MAX_TIMEOUT, необходимо выполнить некоторую настройку.
@Rrz0 Rrz0 Итак, вы говорите, что если вы нажмете переключатель, скажем, на двадцать счетов, и он будет отпущен, это действует как обычное нажатие, которое выполняет одну функцию, и если он нажат и удерживается в течение двух секунд или около того, он выполняет другую функцию?
Точно, я в процессе реализации такого функционала. Несмотря на то, что другие ответы могут быть лучшей практикой, в этом конкретном случае я использую этот метод.
@ Rrz0 ОК, добавлено доп. Не тестировал его, но я думаю , что это сделает то, что вы хотите. Вам нужно попробовать разные значения MAX_TIMEOUT и NORMAL_OPERATION, чтобы получить то, что вы хотите.