An extension to the interrupt support for arduino - вопросы по коду

Тема в разделе "Arduino & Shields", создана пользователем Inversus, 4 май 2016.

  1. Inversus

    Inversus Нуб

    Всем привет ! По ссылке http://playground.arduino.cc/Main/PcInt лежит код для упрощения обработки внешних прерываний по всем IO пинам. Думаю с ним многие знакомы. Полный код в конце с указанием интересующих мест. Надеюсь кто-нибудь разъяснит следующие моменты:

    1. Каким образом может выполниться условие "port == NOT_A_PORT" если:
    массив возвращаемый макросом digitalPinToPort определяется так:
    Код (C++):
    const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
        PD, PD, PD, PD, PD, PD, PD, PD,
        PB, PB, PB, PB, PB, PB,
        PC, PC, PC, PC, PC, PC,
    };
    А константы NOT_A_PORT и портов так:
    Код (C++):
    #define NOT_A_PORT 0

    #ifdef ARDUINO_MAIN
    #define PA 1
    #define PB 2
    #define PC 3
    #define PD 4
    #define PE 5
    #define PF 6
    #define PG 7
    #define PH 8
    #define PJ 10
    #define PK 11
    #define PL 12
    #endif
     
    Т.е. нужно что-бы port == 0, но как Я понимаю даже при неправильном аргументе(>19) digitalPinToPort(pin) может возвратить что угодно, но не ноль ?

    2. Фрагмент
    Код (C++):
      // mask is pins that have changed. screen out non pcint pins.
    if ((mask &= *port_to_pcmask[port]) == 0) {
        return;
    }
    как прокомментировано отсеивает не-pcint пины. Однако каким еще образом может вызываться функция PCint, кроме как от инициализироваными в функции PCattachInterrupt прерываниями, тем более что на ней завязаны все вектора PCINTx_vect ?

    3. Что автор хотел сказать комментарием
    Чем именно этот путь "не хороший", и что тогда подразумевается под хорошим ? И как точно переводиться "back-map" ?


    Код (C++):
    #include "pins_arduino.h"
    /*
    * an extension to the interrupt support for arduino.
    * add pin change interrupts to the external interrupts, giving a way
    * for users to have interrupts drive off of any pin.
    * Refer to avr-gcc header files, arduino source and atmega datasheet.
    */


    /*
    * Theory: all IO pins on Atmega168 are covered by Pin Change Interrupts.
    * The PCINT corresponding to the pin must be enabled and masked, and
    * an ISR routine provided.  Since PCINTs are per port, not per pin, the ISR
    * must use some logic to actually implement a per-pin interrupt service.
    */


    /* Pin to interrupt map:
    * D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2
    * D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0
    * A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1
    */


    volatile uint8_t *port_to_pcmask[] = {
      &PCMSK0,
      &PCMSK1,
      &PCMSK2
    };

    static int PCintMode[24];

    typedef void (*voidFuncPtr)(void);

    volatile static voidFuncPtr PCintFunc[24] = {
      NULL };

    volatile static uint8_t PCintLast[3];

    /*
    * attach an interrupt to a specific pin using pin change interrupts.
    */

    void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) {
      uint8_t bit = digitalPinToBitMask(pin);
      uint8_t port = digitalPinToPort(pin);
      uint8_t slot;
      volatile uint8_t *pcmask;

      // map pin to PCIR register
      if (port == NOT_A_PORT) {    /* ВОПРОС 1 */
        return;
      }
      else {
        port -= 2;
        pcmask = port_to_pcmask[port];
      }

    // -- Fix by Baziki. In the original sources it was a little bug, which cause analog ports to work incorrectly.
      if (port == 1) {
         slot = port * 8 + (pin - 14);
      }
      else {
         slot = port * 8 + (pin % 8);
      }
    // --Fix end
      PCintMode[slot] = mode;
      PCintFunc[slot] = userFunc;
      // set the mask
      *pcmask |= bit;
      // enable the interrupt
      PCICR |= 0x01 << port;
    }

    void PCdetachInterrupt(uint8_t pin) {
      uint8_t bit = digitalPinToBitMask(pin);
      uint8_t port = digitalPinToPort(pin);
      volatile uint8_t *pcmask;

      // map pin to PCIR register
      if (port == NOT_A_PORT) {
        return;
      }
      else {
        port -= 2;
        pcmask = port_to_pcmask[port];
      }

      // disable the mask.
      *pcmask &= ~bit;
      // if that's the last one, disable the interrupt.
      if (*pcmask == 0) {
        PCICR &= ~(0x01 << port);
      }
    }
                                      /* ВОПРОС 3*/
    // common code for isr handler. "port" is the PCINT number.
    // there isn't really a good way to back-map ports and masks to pins.
    static void PCint(uint8_t port) {
      uint8_t bit;
      uint8_t curr;
      uint8_t mask;
      uint8_t pin;

      // get the pin states for the indicated port.
      curr = *portInputRegister(port+2);
      mask = curr ^ PCintLast[port];
      PCintLast[port] = curr;
      // mask is pins that have changed. screen out non pcint pins.
      if ((mask &= *port_to_pcmask[port]) == 0) {    /* ВОПРОС 2 */
        return;
      }
      // mask is pcint pins that have changed.
      for (uint8_t i=0; i < 8; i++) {
        bit = 0x01 << i;
        if (bit & mask) {
          pin = port * 8 + i;
          // Trigger interrupt if mode is CHANGE, or if mode is RISING and
          // the bit is currently high, or if mode is FALLING and bit is low.
          if ((PCintMode[pin] == CHANGE
              || ((PCintMode[pin] == RISING) && (curr & bit))
              || ((PCintMode[pin] == FALLING) && !(curr & bit)))
              && (PCintFunc[pin] != NULL)) {
            PCintFunc[pin]();
          }
        }
      }
    }


    SIGNAL(PCINT0_vect) {
      PCint(0);
    }
    SIGNAL(PCINT1_vect) {
      PCint(1);
    }
    SIGNAL(PCINT2_vect) {
      PCint(2);
    }

    volatile long ticktocks = 0;
    long i = 0;

    void tick(void) {
      ticktocks++;
    }

    void tock(void) {
      ticktocks--;
    }

    void setup()
    {
      Serial.begin(9600);
      pinMode(4, INPUT);
      pinMode(5, INPUT);
      delay(3000);
      PCattachInterrupt(4, tick, CHANGE);
      PCattachInterrupt(5, tock, CHANGE);
    }

    void loop() {
      i++;
      delay(1000);
      Serial.print(i, DEC);
      Serial.print(" ");
      Serial.println(ticktocks);
      if (i > 256) {
        PCdetachInterrupt(4);
        PCdetachInterrupt(5);
      }
    }
     
    Последнее редактирование: 4 май 2016
  2. AlexU

    AlexU Гуру

    1. Ни каким -- ошибка в логике.
    2. Функция 'PCInt()' является единым обработчиком для трех прерываний -- 'PCINT0', 'PCINT1' и 'PCINT2'. И эта проверка является этакой оптимизацией кода, что бы не крутить цикл впустую, если не зарегистрировано ни одного пользовательского суб-обработчика. Хотя логика у функции 'PCInt()' очень и очень не оптимальная.
    3. Возможно из-за того, что значение локальной переменной 'pin' не соответствует реальному номеру пина. Например для пина №8 значение переменной 'pin' будет 0, для пина №9 -- 1, а для пина №0 будет 16 и т.п. "back-map" -- "обратный меппинг" -- из номера порта и номера бита восстанавливается номер пина на плате.
    Вообще реализация старая (для более ранних версий IDE и компилятора avr-gcc) и логика странная, я бы переписал с нуля...
     
    Inversus нравится это.
  3. Inversus

    Inversus Нуб

    Рад видеть Вас снова.

    А Вы не подскажите элегантного способа сделать проверку корректности порта средствами IDE ?

    Т.е. это оптимизация на случай, если мы захотим добавить ещё один обработчик ? Например:
    Код (C++):
    SIGNAL(PCINT0_vect) {
      PCint(0);
      NewHandler(0);
    }
    Хотя смутно представляю целесообразность такой конструкции.

    Ну да в массиве векторов порядок портов получается В C D. Но тогда не вижу причины почему автор не сделал как в IDE - D B C, вместо того что-бы писать что так плохо ) Или это уже не автор комментировал ?

    Для этого как раз и копаюсь. Как будет время набросаю и выложу код. Хотя не уверен что мой код будет оптимальным, поэтому буду рад Вашим предложениям.

    Хотел сразу писать класс, но обработчик прерывания порта нельзя запихать в функцию класса, или отдельно вызвать для класса, а вызывать для конкретного объекта как-то не айс. Подумаю еще над этим.
     
    Последнее редактирование: 4 май 2016
  4. AlexU

    AlexU Гуру

    IDE -- Integrated Development Environment -- интегрированная среда разработки. Эта среда не проверяет порт и вообще ни как не участвует в работе AVR-контроллера (или любого другого). Она всего лишь предоставляет возможности по редактированию кода, его компиляции и возможно загрузке в микроконтроллер.
    Вы правильно разобрались в том, что разработчики библиотек для плат Arduino не корректно реализовали логику работы макроса 'digitalPinToPort'. Если Вас этот вопрос сильно волнует, можете сделать патч с исправлениями и отправить его разработчикам Arduino -- может они его применят.
    Нет. В обработчике, в конце, есть цикл и, чтобы его лишний раз не "крутить впустую", сделана эта проверка.
    На момент написания данного кода автору показалось, что это самый простой вариант. В современных библиотеках из комплекта Arduino IDE есть дополнительные макросы, которые могут решить некоторые из проблем, которые решали разработчики в момент написания этой библиотеки.
    Готов оказать помощь в написании кода. Тем более есть вероятность, что данная библиотека может оказаться востребованной, т.к. на этом форуме уже несколько раз сталковался с тем, что пользователи не знают о существовании внешних прерываний кроме INT0 и INT1.
     
  5. Alex19

    Alex19 Гуру

    Сама библиотека наверняка будет кому-то интересна.

    Если решите писать, рекомендую ознакомится с существующими библиотеками. К примеру PinChangeInterrupt - https://github.com/NicoHood/PinChangeInterrupt.

    Не работал с ней, но написана вроде неплохо.

    Хотя выглядят они все страшновато, ради 3 регистров работающих с Pin Change Interrupt, библиотеки. Впрочем всего лишь любитель и возможно чего-то не понимаю.
     
  6. Inversus

    Inversus Нуб

    Ок, за напоминание основ - отдельное спасибо )
    Да и Вы же сами написали
    Я имел ввиду именно функционал библиотек данной конкретной IDE, думал это и так понятно. Другими словами Я сократил вопрос, и полностью его сформулировать можно так "Можно ли с помощью функционала библиотек входящих в состав IDE Arduino красиво реализовать проверку корректности порта". Но это уже не вопрос.

    Абсолютно не волнует, Я предпочитаю написать свой код нежели латать чужой, так как в этом случае буду полностью понимать что он делает.

    Про цикл - итак очевидно. Я имею ввиду что вообще не вижу смысла этой конструкции, так как не вижу вариантов (кроме того что указал выше) когда она будет срабатывать - вызов PCInt возможен только при записи соответствующего бита в соответствующем регистре &PCMSKx, но вместе с этим битом прописывается и "суб-обработчик", соответственно если вызвана PCint, то для этого вызова будет и суб-обработчик - получается что проверка "mask &= *port_to_pcmask[port]" в этом случае избыточна, т.к. цикл прохода по битам в любом случае должен отработать хотя-бы одну итерацию . Другими словами
    то Я не вижу почему функция PCint вообще может быть вызвана (если только не сделать это просто так из кода, что бессмысленно). Я что-то упускаю ? Разъясните пожалуйста на пальцах как Вы это понимаете, если ход моих мыслей не правильный.
     
    Последнее редактирование: 5 май 2016
  7. AlexU

    AlexU Гуру

    Фразой "если не зарегистрировано ни одного пользовательского суб-обработчика" я ввёл Вас в заблуждение, прошу прощения.
    Похоже, что автор излишне перестраховался. Судя по логике функция 'PCInt()' должна вызываться только тогда, когда есть суб-обработчик.
    Это утверждение верно до тех пор, пока код целиком "помещается на экране монитора". А как только его объем перевалит за десятки тысяч строк, то проще разобраться в коде и исправить его, чем изобретать свой велосипед.
    Я, например, модифицирую Arduino'вские библиотеки, т.к. таким образом проще реализовать нужный функционал, с сохранением Arduino'вских "фишек".