Опрос кнопки по внешнему прерыванию PCINT [Решено]

Тема в разделе "Микроконтроллеры AVR", создана пользователем Aleksander1997, 24 июн 2018.

  1. Aleksander1997

    Aleksander1997 Нерд

    Всем доброй ночи. Решил вернуться к таймерам avr после того как закончилась учеба. Конкретно - плата arduino pro mini(mega168). Сайты на которых нашел примеры -> https://arduinonsk.ru/blog/87-all-pins-interrupts, https://sites.google.com/site/qeewiki/books/avr-guide/external-interrupts-on-the-atmega328
    /**********************************/
    Задача пока простая - инвертировать состояние светодиода по нажатию кнопки.

    1 Прерывания INT0 и INT1 задействовать не буду, поскольку в планах 3 кнопки и размещены у меня на порте В (ножки PB0, PB1, PB2).
    2 Готовые библиотеки с антидребезгом для ардуино (Bounce) использовать тоже пока не собираюсь поскольку расход памяти критичен для кода и лучше писать на регистровом уровне
    3 Таймер 1 и 2 заняты под другие функции

    Смысл приведенного кода такой - отловить перепад сигнала с пина кнопки РВ0 с 1 на 0, при этом событии инкрементировать переменную счетчика на 1. Каждые 16мс происходит переполнение таймера Timer0 и вызывается прерывание. В обработчике инвертируем состояние диода если счетчик достиг значения 5. Здесь подразумевал что дребезг должен уйти после 5*16=80 мс
    /***************************/
    Код не работает, кнопка не опрашивается, светодиод хаотично мигает.
    Не пойму в чем ошибся
    Можно ли сделать антидребезг проще?

    Код (C++):
    #include <avr/io.h>
    #include <avr/interrupt.h>

    volatile uint8_t butcount = 0;

    void timer_init(void)
    {
       //Устанавливаем источник тактов (F_CPU/1024)
        TCCR0B = _BV(CS00)|_BV(CS02);
        //включаем прерывание
        TIMSK0 |= _BV(TOIE0);
        //разрешаем прерывания
        sei();

    }

    int main(void)
    {

       DDRB |= (1 << PB5);      // светодиод на выход
       DDRB &= ~(1 << PB0);     // кнопка на вход (PCINT0)

       PORTB &= ~(1 << PB0);    // гасим светодиод
       PORTB |= (1 << PB5);    // подтяжка кнопки к питанию

       PCICR |= (1 << PCIE0);  
       PCMSK0 |= (1 << PCINT0);

       timer_init();

        while(1) {};
    }

    ISR (TIMER0_OVF_vect)
    {
      /* 1/(16000000/1024/256) = 16,384мс */
      if(butcount > 5) { PORTB ^= (1 << PB5); butcount = 0; }
    }

    ISR (PCINT0_vect)
    {
       if( !(PINB & (1 << PINB0))) butcount++;
    }
     
    Последнее редактирование: 24 июн 2018
  2. AlexU

    AlexU Гуру

    Правильно ли понимаю, что с помощью таймера пытаетесь бороться с дребезгом?
    Если так, то делаю не много по другому: в обработчике таймера считаю количество переполнений, а в обработчике PCINT обрабатываю нажатие кнопок с устранением дребезга.
    Пример обработчика таймера:
    Код (C++):
    static volatile uint32_t ovfCount = 0;

    ISR(TIMER1_OVF_vect)
    {
        ovfCount++;
    // делаем ещё что-нибудь не долгое
    }

    uint32_t getOverflowCount(void)
    {
        return ovfCount;
    }
    Пример обработчика кнопок:
    Код (C++):
    #define BOUNCE_DELAY    60

    static uint32_t lastINTtime0 = 0;  // для первой кнопки
    static uint32_t lastINTtime1 = 0;  // для второй кнопки

    ISR(INT0_vect)
    {
        uint32_t currINTtime = getOverflowCount();
        if ((currINTtime - lastINTtime0) > BOUNCE_DELAY)
        {
            lastINTtime0 = currINTtime;

            // main operation
        }
    }
    Правда в коде два обработчика кнопок (INT0 и INT1) -- код выдернут из готового проекта. Что касается PCINT, то нужно сначала проверять маску изменения состояния пинов. Для этого нужно иметь переменную:
    Код (C++):
    static volatile uint8_t oldMask;
    И при изменении состояния того или иного пина с помощью приведённого кода устранять дребезг.
    Думаю сами сможете доработать код под свои нужды.
     
  3. Aleksander1997

    Aleksander1997 Нерд

    Да вы правы, спасибо, не могли бы вы код полностью приложить?

    В моем случае выходит в обработчике прерывания для таймера0 нужно считать количество переполнений в идеале каждую 1мс
    Затем аналогично вашей конструкции но в PCINT, а не в INT0 уже обрабатывать нажатие

    /***************************************************/
    Но свободной оперативной памяти на обработку кнопок у меня выходит копейки и поэтому если не затруднит переменные типа uint32_t объявленные тут глобально ключевым словом static занимают флэш память?

    Код (C++):
    #define BOUNCE_DELAY    60

    static uint32_t lastINTtime0 = 0;  // для первой кнопки
    static uint32_t lastINTtime1 = 0;  // для второй кнопки

    ISR(INT0_vect)
    {
        uint32_t currINTtime = getOverflowCount();
        if ((currINTtime - lastINTtime0) > BOUNCE_DELAY)
        {
            lastINTtime0 = currINTtime;

            // main operation
        }
    }
     
    Последнее редактирование: 24 июн 2018
  4. Aleksander1997

    Aleksander1997 Нерд

    Добавил строки, чтобы постараться плавно перейти к setup() и loop()
    Код (C++):

      cli(); //запрет прерываний
       TCCR0A = 0;// set entire TCCR0A register to 0
       TCCR0B = 0;// same for TCCR0B
    Но выдает ошибку
    Код (C++):
    C:\Users\Alex\AppData\Local\Temp\build4998539872587939610.tmp/core.a(wiring.c.o): In function `delayMicroseconds':
    E:\Arduino uno R3\Р?Рљ станция\arduino-1.6.0\hardware\arduino\avr\cores\arduino/wiring.c:49: multiple definition of `__vector_16'

    C:\Users\Alex\AppData\Local\Temp\build4998539872587939610.tmp\sketch_jun24a.cpp.o:/arduino-1.6.0/sketch_jun24a.ino:43: first defined here
    collect2: error: ld returned 1 exit status
    Ошибка компиляции.
    Измененный вариант
    Код (C++):
    #include <avr/io.h>
    #include <avr/interrupt.h>

    volatile boolean flag = 0;
    volatile uint8_t butcount = 0;

    void timer_init(void)
    {
       cli();//запрет прерываний
       TCCR0A = 0;// set entire TCCR0A register to 0
       TCCR0B = 0;// same for TCCR0B
       TCNT0  = 0;//initialize counter value to 0
       //Устанавливаем источник тактов (F_CPU/1024)
        TCCR0B = _BV(CS00)|_BV(CS02);
        TIMSK0 |= _BV(TOIE0);
        //разрешаем прерывания
        sei();

    }

    void setup(void)
    {
     
       DDRB |= (1 << PB5);      // set output led
       DDRB &= ~(1 << PB0);     // Clear the PB0 pin
       // PB0 (PCINT0 pin) is now an input

       PORTB &= ~(1 << PB0);    // turn Off led
       PORTB |= (1 << PB5);    // turn On the Pull-up
       // PB0 is now an input with pull-up enabled

       PCICR |= (1 << PCIE0);    // set PCIE0 to enable PCMSK0 scan
       PCMSK0 |= (1 << PCINT0);  // set PCINT0 to trigger an interrupt on state change

       timer_init();
        //sei();                    // turn on interrupts
    }

    void loop() {};


    ISR (TIMER0_OVF_vect)
    {
      /* 1/(16000000/1024/256) = 16,384мс */
      if (flag) butcount++;
      if(butcount > 5) { PORTB ^= (1 << PB5); butcount = 0; }
    }

    ISR (PCINT0_vect)
    {
        /* interrupt code here */
       if( !(PINB & (1 << PINB0))) flag=1;
    }
    Видимо конфликт с встроенной delayMicroseconds, сделайте одолжение подскажите как это решить
     
    Последнее редактирование: 24 июн 2018
  5. Aleksander1997

    Aleksander1997 Нерд

    Насчет маски, предположу, что вы имели ввиду тот факт что до 8 контактов порта имеют один и тот же вектор PCINTx. И когда сработает прерывание сложно будет определить на каком пине.

    Должно быть примененная конструкция в этом коде именно для этих целей и служит, если я правильно понял перевод

    Код (C++):
    #include <avr/io.h>
    #include <stdint.h>            // has to be added to use uint8_t

    #include <avr/interrupt.h>    // Needed to use interrupts  


    volatile uint8_t portbhistory = 0xFF;     // default is high because the pull-up


    int main(void)
    {
        DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); // Clear the PB0, PB1, PB2 pin
        // PB0,PB1,PB2 (PCINT0, PCINT1, PCINT2 pin) are now inputs

        PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); // turn On the Pull-up
        // PB0, PB1 and PB2 are now inputs with pull-up enabled
       
        PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan
        PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change

        sei();                     // turn on interrupts

        while(1)
        {
            /*main program loop here */
        }
    }



    ISR (PCINT0_vect)
    {
        uint8_t changedbits;


        changedbits = PINB ^ portbhistory;
        portbhistory = PINB;

       
        if(changedbits & (1 << PINB0))
        {
            /* PCINT0 changed */
        }
       
        if(changedbits & (1 << PINB1))
        {
            /* PCINT1 changed */
        }

        if(changedbits & (1 << PINB2))
        {
            /* PCINT2 changed */
        }

    }
     
  6. Aleksander1997

    Aleksander1997 Нерд

    Попробывал так, вроде работает...
    Но небольшой дребезг есть

    Код (C++):
    #include <avr/io.h>
    #include <avr/io.h>
    #include <avr/interrupt.h>

    static volatile uint32_t ovfCount = 0;
    static volatile boolean flag = 0;

    #define BOUNCE_DELAY    60

    static uint32_t lastINTtime0 = 0;  // для первой кнопки
    static uint32_t lastINTtime1 = 0;  // для второй кнопки

    void timer_init(void)
    {
       cli();//stop interrupts
       TCCR0A = 0;// set entire TCCR0A register to 0
       TCCR0B = 0;// same for TCCR0B
       TCNT0  = 0;//initialize counter value to 0
       //Устанавливаем источник тактов -> 1/(16000000/64)* 256 = 1 мс
       TCCR0B =(1<<CS00)|(1<<CS01); // Тактировать с делителем 64
       TIMSK0 |= _BV(TOIE0); // разрешаем прерывание по переполнению
        //разрешаем прерывания
        sei();
       
    }

    ISR (TIMER0_OVF_vect)
    {
      ovfCount++;
    }

    uint32_t getOverflowCount(void)
    {
        return ovfCount;
    }

    ISR (PCINT0_vect)
    {
        /* interrupt code here */  
       uint32_t currINTtime = getOverflowCount();
        if ((currINTtime - lastINTtime0) > BOUNCE_DELAY)
        {
            lastINTtime0 = currINTtime;
            if( !(PINB & (1 << PINB0))) PORTB ^= (1 << PB5);
            // main operation
        }
    }

    int main(void)
    {
       
       DDRB |= (1 << PB5);      // set output led
       DDRB &= ~(1 << PB0);     // Clear the PB0 pin
       // PB0 (PCINT0 pin) is now an input

       PORTB &= ~(1 << PB5);    // turn Off led
       PORTB |= (1 << PB0);    // turn On the Pull-up
       // PB0 is now an input with pull-up enabled

       PCICR |= (1 << PCIE0);    // set PCIE0 to enable PCMSK0 scan
       PCMSK0 |= (1 << PCINT0);  // set PCINT0 to trigger an interrupt on state change

       timer_init();
        //sei();                    // turn on interrupts

       while(1) {};
    }

     
     
    Последнее редактирование: 24 июн 2018
  7. AlexU

    AlexU Гуру

    Ну так настройте один из таймеров, что бы он генерировал прерывание с частотой 1 кГц. При тактовой в 16 МГц это можно сделать используя режим CTC. Но при этом с ШИМ-ом на двух пинах будут трудности (более точно на каких пинах надо смотреть какой таймер используется и схему).
    Именно так. И код Вы привели толковый -- только внутрь if-ов стоит добавит код с подавлением дребезга.
    Время подавления дребезга вычисляется опытным путём. Зависит от конструкции кнопки. Для частичного подавления дребезга можно параллельно контактам кнопки поставить конденсатор 100-200 пФ (керамика). Либо применить триггер шмитта в связке с RC-фильтром -- это, можно сказать, лучший вариант. Но, если нет желания аппаратно бороться с дребезгом, то боремся программно.

    В общем движение Ваших мыслей идёт в правильном направлении...
     
    Aleksander1997 нравится это.
  8. Aleksander1997

    Aleksander1997 Нерд

    Спасибо, очень вам благодарен за советы. Пришел в итоге к аппаратному способу подавления дребезга - поставил кондер на 100 нф
     
    Последнее редактирование: 24 июн 2018