C для приложения AVR - повторение ISR

Я пытаюсь заставить работать простую процедуру прерывания на ATMega328P. К PD6 подключен светодиод, а к PB7 - встроенная кнопка. Светодиод должен нормально мигать до тех пор, пока кнопка не будет нажата, а затем загорится ровным светом в течение 1,5 с, прежде чем снова начнет мигать. Вот код:

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void)
{
    // Enable pull-ups and set pin directions
    MCUCR |= (1<<PUD);
    PORTD &= ~(1<<PORTD6);
    DDRD |= (1<<DDD6);
    PORTB |= (1<<PORTB7);
    DDRB &= ~(1<<DDB7);

    // Enable pin change interrupt
    PCICR = 0x01;
    PCMSK0 = 0x80;
    sei();

    while (1) 
    {
        // Blink LED at standard rate
        _delay_ms(500);
        PORTD ^= (1<<PORTD6);
        _delay_ms(500);
        PORTD ^= (1<<PORTD6);
    }
}

ISR(PCINT0_vect,ISR_BLOCK)
{
    PORTD &= ~(1<<PORTD6);
    _delay_ms(500);
    PORTD |= (1<<PORTD6);
    _delay_ms(1500);
    PORTD &= ~(1<<PORTD6);
}

Прерывание срабатывает правильно, однако процедура ISR повторяется дважды. Я предполагаю, что это какая-то проблема с отскоком кнопки, но я не знаю, как с этим бороться. Я попытался ввести задержку 500 мс в начале, а также попытался очистить флаг прерывания смены вывода в ISR, чтобы он не запускался снова, но все еще работает. Заранее благодарю за любую помощь!


person Eric    schedule 02.11.2018    source источник
comment
Пожалуйста, не добавляйте задержку в обработчик прерывания, это должно быть как можно быстрее и может помешать обслуживанию других прерываний. Лучше записывать счетчик времени, когда была нажата кнопка. Если кнопка нажата слишком быстро после предыдущей, запишите время, но игнорируйте его. Управляйте светодиодом с нижнего уровня с информацией, установленной обработчиком прерывания.   -  person Weather Vane    schedule 02.11.2018
comment
Самым простым было бы, если бы ISR установил только флаг button_pressed и завершил работу. Затем поставьте if (button_pressed) {delay(1000); button_pressed=false;} после того, как включите свет в основном цикле. Однако в целом встроенный код должен полностью избегать циклов задержки и заменять их проверками часов и / или прерываниями по таймеру.   -  person AShelly    schedule 02.11.2018
comment
Я определенно слышал, что избегать delay () предпочтительнее, я поищу несколько примеров, чтобы заменить функции delay () элементами управления таймером.   -  person Eric    schedule 03.11.2018


Ответы (1)


Давайте работать, исходя из того, что вы можете игнорировать любые нажатия кнопок, пока светодиод горит 1,5 секунды. Вы можете написать свой обработчик прерывания следующим образом:

ISR(PCINT0_vect,ISR_BLOCK)
{
    button_pressed = 1;
}

и поместите это в начало вашего кода:

volatile int button_pressed = 0;

(См. эту страницу для получения информации о том, что такое volatile и зачем он нужен здесь.)

Тогда ваш основной цикл может выглядеть так:

while (1) 
{
    // Blink LED on and off

    PORTD |= (1<<PORTD6);   // Turn LED on.
    if (button_pressed) {
        _delay_ms(1500);    // Long delay if button was pressed.
        button_pressed = 0;
    } else {
        _delay_ms(500);     // Regular delay otherwise.
    }

    PORTD &= ~(1<<PORTD6);  // Turn LED off.
    _delay_ms(500);
}

Примечания для опытных читателей:

  1. volatile int button_pressed = 0; на самом деле может быть просто volatile int button_pressed;, поскольку статические int в области видимости файла инициализируются значением 0, но гораздо проще инициализировать явно.

  2. Программы на C часто используют for (;;) как идиому для "цикла навсегда" вместо while (1).

person Tim    schedule 03.11.2018
comment
Похоже, вы ошиблись при операциях XOR с OR; OP использует XOR в основном цикле, который включает и выключает светодиод; ваш код должен иметь OR вместо операции XOR в первой строке тела while (). - person Francesco Lavra; 03.11.2018
comment
@FrancescoLavra Вы совершенно правы. Ой. Спасибо, я исправлю. - person Tim; 03.11.2018
comment
button_pressedдолжно быть volatile - person tofro; 03.11.2018
comment
@tofro Спасибо. Да, эта страница дает некоторые подробности об этом. Я обновлю свой ответ. - person Tim; 03.11.2018
comment
Спасибо за этот пример. Интересно, что AtmelStudio 7 по умолчанию включает цикл while (1) {} в main (). - person Eric; 03.11.2018
comment
@Eric О, это интересно. Это просто вопрос стиля и истории, и нет ничего лучше другого. Удачи! - person Tim; 03.11.2018
comment
@Tim: Хотя может быть понятнее явно инициализировать глобальные переменные, это приведет к тому, что они будут занимать место в объектном файле и во флэш-ПЗУ целевого устройства. Так что этого обычно следует избегать на встроенных устройствах с ограниченными ресурсами флэш-памяти. Дополнение: последние версии GCC теперь достаточно умны, чтобы обнаружить эту ситуацию и вернуть переменные, которые явно инициализированы на 0, в раздел .bss. Тем не менее, другие компиляторы могут не выполнять эту оптимизацию, и, поскольку стандарт C гарантирует инициализацию, на него можно безопасно полагаться. - person Rev; 05.11.2018
comment
@ Rev1.0 Спасибо - это область, в которой у меня мало опыта, и наблюдение, которое я считаю действительно интересным. - person Tim; 06.11.2018