buiНадежное время в ISR
Я использую Nano. Приведенный ниже код представляет собой упрощенную проработку моего проблемного кода.
Суть этого заключается в асинхронном обнаружении нажатия кнопки с отключенным дребезгом. Фактический код устанавливает volatile bool, чтобы сообщить основному циклу, что он видел нажатие, но эта упрощенная версия переключает состояние встроенного светодиода. Другой светодиод следит за состоянием кнопки, чтобы можно было увидеть, правильно ли срабатывает ISR.
#define SHORT_PRESS_MS 50
#define PUSHBUTTON_PIN 2
#define BUILTIN_LED_PIN 13
#define OTHER_LED_PIN 12
volatile unsigned long fallTime = 0;
volatile int builtinLedState = LOW;
void isr_change()
{
unsigned long now = millis();
int pushbuttonState = digitalRead(PUSHBUTTON_PIN);
// показать состояние переключателя со светодиодом и 120 Ом на D12
digitalWrite(OTHER_LED_PIN, pushbuttonState);
if ((pushbuttonState == LOW) || (fallTime == 0))
{ // изменить на низкий => падающий край
fallTime = now;
}
if (pushbuttonState == HIGH)
{ // меняем на высокий => поднимающийся край
long pressMs = now - fallTime;
if (pressMs > SHORT_PRESS_MS)
{
builtinLedState = !builtinLedState;
digitalWrite(BUILTIN_LED_PIN, builtinLedState);
}
}
}
void setup()
{
pinMode(PUSHBUTTON_PIN, INPUT_PULLUP);
pinMode(BUILTIN_LED_PIN, OUTPUT);
pinMode(OTHER_LED_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(PUSHBUTTON_PIN), isr_change, CHANGE);
}
void loop() {}
Когда это выполняется, OTHER_LED рабски следует за кнопкой. Нет заметной задержки, и он никогда не рассинхронизируется, независимо от того, как автоматом расстреливают мои выходки с кнопкой.
Однако переключение встроенного светодиода ненадежно. То, что OTHER_LED остается синхронизированным с состоянием кнопки, означает, что событие всегда срабатывает, а состояние всегда такое, как я ожидаю, поэтому похоже, что есть проблема с моей попыткой измерить время.
Как правильно измерять время в ISR?
Применение предложенной диагностики подтвердило гипотезу принятого ответа. Определив характер проблемы, я переписал ISR следующим образом.
void isr_change()
{
static long fallTime = 0;
static int lastPushbuttonState;
int pushbuttonState = digitalRead(PUSHBUTTON_PIN);
if (lastPushbuttonState != pushbuttonState)
{
long now = millis();
if (pushbuttonState == LOW)
{ // изменить на низкий => падающий край
fallTime = now;
}
if (pushbuttonState == HIGH)
{ // меняем на высокий => поднимающийся край
long pressMs = now - fallTime;
if (pressMs > SHORT_PRESS_MS)
{
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
}
}
Тестирование показывает, что проблема решена и происходит ожидаемое поведение (надежное обнаружение нажатий минимальной продолжительности).
Также представляет интерес использование static
для переноса значений между вызовами ISR с локальной областью действия вместо использования глобальных переменных переменных.
@Peter Wone, 👍-1
Обсуждение2 ответа
Лучший ответ:
Я не могу понять это:
if ((pushbuttonState == LOW) || (fallTime == 0))
Почему || (fallTime == 0)
? Кроме этого, ваш ISR выглядит хорошо для меня.
И ваш способ измерения времени правильный. Единственный сценарий, который я могу
представьте, что это может сделать переключение ненадежным, если отсутствует ISR
переходы, что может произойти, если входной контакт дает очень короткие всплески.
Вот мой сценарий:
Кнопка нажата, fallTime
содержит правильное значение, вы отпускаете
кнопка. В этот момент кнопка подпрыгивания генерирует очень короткий
положительный импульс: НИЗКИЙ → ВЫСОКИЙ → НИЗКИЙ. Первый переход вызывает
запрос на прерывание. Однако существует некоторая задержка при обработке
запрос. К моменту запуска ISR пин уже переключился
обратно в LOW, а затем ISR неправильно обновляет fallTime
.
Чуть позже контакт снова переключается на ВЫСОКИЙ уровень навсегда. Однако,
now
очень близко к fallTime
, и нарастающий фронт игнорируется.
Вы можете проверить, пропустили ли вы переходы, переключив индикатор каждый раз, когда вы видите то же состояние, что и при предыдущем запуске ISR:
void isr_change()
{
static uint8_t previous_pin_state;
uint8_t pin_state = digitalRead(PUSHBUTTON_PIN);
if (pin_state == previous_pin_state)
digitalWrite(LED_BUILTIN, !digiatlRead(LED_BUILTIN));
previous_pin_state = pin_state;
}
|| (fallTime == 0)
на тот случай, если первое, что он увидит, это нарастающий фронт. Я собираюсь попробовать ваше диагностическое предложение., @Peter Wone
Я удалил его, потому что в более крупной программе, которая нуждается в этом, в любом случае есть отложенная инициализация, поэтому я могу отказаться от любого ожидающего нажатия на этом этапе., @Peter Wone
Подтверждаю вашу гипотезу. Думаю, мне следует пропустить обработку, когда нет смены направления., @Peter Wone
Вы переключаете состояние основного светодиода с CHANGE на HIGH. как он должен синхронизироваться с другим светодиодом? и могут быть более быстрые изменения (отскок), которые человеческий глаз не может увидеть на светодиоде
результат скетча:
Светодиоды **не предназначены** для синхронизации друг с другом. Я никогда не говорил, что они были. Один переключается, чтобы указать распознавание импульса желаемой минимальной продолжительности, другой следует за состоянием кнопки, чтобы обеспечить визуальную обратную связь, показывающую, что ISR реагирует на действие кнопки., @Peter Wone
@PeterWone, у светодиода есть переключатели отскока, которые вы не видите на другом светодиоде. так что вы никогда не знаете правильное состояние светодиода, @Juraj
- Влияет ли `millis()` на длинные ISR?
- Контейнерная программа Arduino Timer0
- Как справиться с rollover millis()?
- Использование millis() и micros() внутри процедуры прерывания
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- ардуино - миллисекунды ()
- Кнопка с таймером переключения и функцией сброса времени + светодиод обратной связи
- Использовать timer0, не влияя на millis() и micros().
но вы чередуете состояние светодиода. как он должен синхронизироваться с другим светодиодом? и могут быть более быстрые изменения (отскок), которые вы не видите на других светодиодах, @Juraj
Встроенный светодиод переключается при каждом нажатии. Другой светодиод горит, пока кнопка нажата, и не горит, когда она не нажата. Это синхронизировано с _button_. Чтобы ответить на ваше обоснованное беспокойство по поводу отскока, я буду считать как нарастающие, так и спадающие фронты и освещать только тогда, когда они равны., @Peter Wone
Это обязательно ИЗМЕНИТСЯ. Он срабатывает, когда он становится НИЗКИМ (нажатие кнопки), и снова срабатывает, когда он становится ВЫСОКИМ (кнопка отпускания). Я читаю состояние кнопки и записываю его в OTHER_LED., @Peter Wone