Дополнительные внешние прерывания

step962
Offline
Зарегистрирован: 23.05.2011

 Не касаясь других вопросов, по поводу "а на какие ноги можно навесить прерывание" замечу, что схема распиновки Arduino содержит весь список функций, доступных на той или иной ноге:

Пользоваться схемой просто: стоит у ноги в скобочках функция PCINTx, значит можно, не стоит - низззя.

UPD: ATMega328 по-большому отличается от ATMega168 лишь размером памяти.

Monster
Offline
Зарегистрирован: 18.06.2012

Итого, более 5 (2+3) прерываний без анализа инициатора на Нано иметь невозможно?

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

1) ...или данное прерывание относится только к ногам этого порта и не более?

Именно так. PCINT0 - порт B, PCINT1 - порт С, PCINT2 - порт D.

2) В чем отличие PCINTx от INTx ?

Прерывание INTx можно настроить по: переднему фронту импульса, заднему фронту импульса, по переднему и заднему одновременно (любое изменение уровня) и на низкий уровень, это когда на ноге низкий уровень все время будет выполняться обработчик INTx. А прерывание PCINTx срабатывает только на любое изменение уровня и изменить это никак нельзя.

3) ...будет ли пропуск этого события?

Если во время выполнения обработчика какого-либо прерывания сгенерилось какоенить другое прерывание, то после выполнения текущего обработчика, это другое прерывание, будет выполнено, но только один раз, вне зависимости от того сколько раз оно сгенерилось. Никакого буфера для отслеживания нет. Есть флаг готовности этого прерывания (а он только один), который показывает что оно произошло и что надо надо срочно прыгнуть на обработчик этого прерывания. Более подробно как это работает тут, раздел прерывания easyelectronics.ru/avr-uchebnyj-kurs-podprogrammy-i-preryvaniya.html

4) ...Будет ли корректно так раскидать ноги..

Да так можно сделать, но, что-бы INT0/INT1 не накладывались на PCINT2, надо в регистре PCMSK2 назначить только одну ногу (PCINT23 - PD7 на МК) которая будет вызывать обработчик прерывания ISR(PCINT2_vect){}. Это можно сделать вот так PCMSK2 = 128.

>> Или можно вообще использовать все ноги порта D...

Нет, нельзя.

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

Monster пишет:

Итого, более 5 (2+3) прерываний без анализа инициатора на Нано иметь невозможно?

В яблочко!

Monster
Offline
Зарегистрирован: 18.06.2012

Настройка всех 4 прерываний на любое изменение логического уровня (чтобы условия для входов были одинаковы):

 

void setup() 
{        
  // разрешение прерываний INT0 и INT1
  EIMSK  =  (1<<INT0)  | (1<<INT1);
  // настройка срабатывания прерываний на любому изменению 
  EICRA  =  (0<<ISC11) | (1<<ISC10) | (0<<ISC01) | (1<<ISC00);
  // разрешение прерываний с портов B (PCINT[7:0]) и D (PCINT[23:16]), и запрет с порта C (PCINT[14:8])
  PCICR  |= (1<<PCIE2) | (0<<PCIE1) | (1<<PCIE0);
  // маскирование всех ног, кроме PB0 и PD7 - по одной на PCINT0 и PCINT2
  PCMSK0 |= (0<<PCINT7)  | (0<<PCINT6)  | (0<<PCINT5)  | (0<<PCINT4)  | (0<<PCINT3)  | (0<<PCINT2)  | (0<<PCINT1)  | (1<<PCINT0);
  PCMSK1 |=                (0<<PCINT14) | (0<<PCINT13) | (0<<PCINT12) | (0<<PCINT11) | (0<<PCINT10) | (0<<PCINT9)  | (0<<PCINT8);
  PCMSK2 |= (1<<PCINT23) | (0<<PCINT22) | (0<<PCINT21) | (0<<PCINT20) | (0<<PCINT19) | (0<<PCINT18) | (0<<PCINT17) | (0<<PCINT16);
}

Ну и сами обработчики:

// обработчик прерывания INT0
ISR(INT0_vect) 
{  
  PassiveLeftIntFlag = !PassiveLeftIntFlag;
}

// обработчик прерывания INT1
ISR(INT1_vect) 
{  
  PassiveRightIntFlag = !PassiveRightIntFlag;
}

// обработчик прерывания PCINT2
ISR(PCINT2_vect) 
{  
  ActiveLeftIntFlag = !ActiveLeftIntFlag;
}

// обработчик прерывания PCINT0
ISR(PCINT0_vect) 
{  
  ActiveRightIntFlag = !ActiveRightIntFlag; 
}

Теперь всё верно? :)

П.С. Избыточность в масках - для наглядности.

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

>> Теперь всё верно? :)

Да, все так.

Одинадцатую строчку (в сетапе) можно было вообще не писать. Т.к. прерывание PCINT1 не разрешено не важно как настроен регистр PCMSK1, выполяться оно не будет. Но на правильность выполнения это, конечно, не повлияет.

Monster
Offline
Зарегистрирован: 18.06.2012

Вроде работает все как надо, НО при подаче сигнала на ноги ардуина д2/д3/д7/д8 статус флагов в переменных обработчиков меняется на 1 моментально, а вот при отключении сигнала флаг становится нулевым довольно долго. 

Каковы могут быть причины?  Возможно надо принудительно внутри обработчиков опускать флаги прерываний, но они и сами через время должны обнуляться... :(

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

>> а вот при отключении сигнала...

Это что значит? Переход с высокого уровня на низкий?

>> Возможно надо принудительно внутри обработчиков опускать флаги прерываний...

Флаг прерывания автоматически сбрасывантся в 0 как только начинает выполняться обработчик этого прерывания.

Monster
Offline
Зарегистрирован: 18.06.2012

Evgen пишет:
Это что значит? Переход с высокого уровня на низкий?
Да.
Evgen пишет:
Флаг прерывания автоматически сбрасывантся в 0 как только начинает выполняться обработчик этого прерывания
Тогда почему же тогда флаг обнуляется так долго?

Весь скетч:

// флаги прерываний
volatile int PassiveLeftIntFlag = LOW;
volatile int PassiveRightIntFlag = LOW;
volatile int ActiveLeftIntFlag = LOW;
volatile int ActiveRightIntFlag = LOW;

volatile boolean x=false;

// инициализация
void setup() 
{  
  // разрешение прерываний INT0 и INT1
  EIMSK  =  (1<<INT0)  | (1<<INT1);
  // настройка срабатывания прерываний на любому изменению 
  EICRA  =  (0<<ISC11) | (1<<ISC10) | (0<<ISC01) | (1<<ISC00);
  // разрешение прерываний с портов B (PCINT[7:0]) и D (PCINT[23:16]), и запрет с порта C (PCINT[14:8])
  PCICR  |= (1<<PCIE2) | (0<<PCIE1) | (1<<PCIE0);
  // маскирование всех ног, кроме PB0 и PD7 - по одной на PCINT0 и PCINT2
  PCMSK0 |= (0<<PCINT7)  | (0<<PCINT6)  | (0<<PCINT5)  | (0<<PCINT4)  | (0<<PCINT3)  | (0<<PCINT2)  | (0<<PCINT1)  | (1<<PCINT0);
  PCMSK1 |=                (0<<PCINT14) | (0<<PCINT13) | (0<<PCINT12) | (0<<PCINT11) | (0<<PCINT10) | (0<<PCINT9)  | (0<<PCINT8);
  PCMSK2 |= (1<<PCINT23) | (0<<PCINT22) | (0<<PCINT21) | (0<<PCINT20) | (0<<PCINT19) | (0<<PCINT18) | (0<<PCINT17) | (0<<PCINT16);
 
  // настройка таймера на период = 1с
  TIMSK1=0x01;   // enabled global and timer overflow interrupt
  TCCR1A = 0x00; // normal operation page 148 (mode0)
  TCNT1=0x0BDC;  // 16bit counter register
  TCCR1B = 0x04; // start timer / set clock
  
  pinMode(13, OUTPUT);
  digitalWrite(13,LOW);
}

// обработчик прерывания INT0
ISR(INT0_vect) 
{  
  PassiveLeftIntFlag = !PassiveLeftIntFlag;
}

// обработчик прерывания INT1
ISR(INT1_vect) 
{  
  PassiveRightIntFlag = !PassiveRightIntFlag;
}

// обработчик прерывания PCINT2
ISR(PCINT2_vect) 
{  
  ActiveLeftIntFlag = !ActiveLeftIntFlag;
}

// обработчик прерывания PCINT0
ISR(PCINT0_vect) 
{  
  ActiveRightIntFlag = !ActiveRightIntFlag; 
}

ISR (TIMER1_OVF_vect) 
{
  TCNT1=0x0BDC; // set initial value to remove time error (16bit counter register)
  x=true;  
}

void loop() 
{
  if (x==true)
  {
    digitalWrite(13,PassiveLeftIntFlag | PassiveRightIntFlag | ActiveLeftIntFlag | ActiveRightIntFlag);
    x=false;
  }
}

 

Monster
Offline
Зарегистрирован: 18.06.2012

Нужно ли еще в регистре SREG взвести I-бит чтобы разрешить прерывания глобально, или этого не надо и он по дефолту = 1?

step962
Offline
Зарегистрирован: 23.05.2011

 Эту (и ей подобные) инструкцию:

PCMSK2 |= (1<<PCINT23) | (0<<PCINT22) | (0<<PCINT21) | (0<<PCINT20) | (0<<PCINT19) | (0<<PCINT18) | (0<<PCINT17) | (0<<PCINT16);

без ущерба для конечного результата можно сократить до

PCMSK2 |= (1<<PCINT23);

ибо, во-первых, выражение вида (0<<PCINT16) это то же самое, что просто 0, и, во-вторых, в выражениях побитового ИЛИ (|) 0 никак на результат не влияет.

Цитата:

Нужно ли еще в регистре SREG взвести I-бит чтобы разрешить прерывания глобально, или этого не надо и он по дефолту = 1?

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

Ну а по дефолту он = 0. Если можно верить дейташитам от Атмеля.

Monster
Offline
Зарегистрирован: 18.06.2012

По дефолту он скорее = 1, т.к. в коде он нигде не включается, а прерывания работают.

Проблема оказалась несколько иной природы: судя по этой статье проблема связана с "дребезгом контактов".

И действительно, при замыкании входов на +5В даже пальцами (токи достаточно малы, но достаточны) диод на 13 ноге загорается, но гаснет весьма лениво.  А если же ногу закоротить на землю (или между собой) пальцем, то диод отрабатывает довольно адекватно в обе стороны.

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

Правда в этом случае придется уже анализировать внутри PCINTх-обработчиков состояние ноги, но все же задержки... :(

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

step962
Offline
Зарегистрирован: 23.05.2011

Monster пишет:

По дефолту он скорее = 1, т.к. в коде он нигде не включается, а прерывания работают.

"-Видишь суслика?
-Нет.
-И я не вижу. А он там есть...
"

По умолчанию таки 0:

Просто скрытая от глаз простого пользователя Arduino обертка функции setup устанавливает этот флаг, а также делает некоторые другие необходимые подготовительные операции.

Monster пишет:

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

В полном соответствии с афоризмом "Язык дан человеку для того, чтобы скрывать свои мысли" ;)

Monster
Offline
Зарегистрирован: 18.06.2012

Это все хорошо, но как решить задачу исключив дребезг?

Емкость на вход или есть еще варианты?

Li2n
Offline
Зарегистрирован: 03.02.2013

Прошу помощи по прерыванию.

Пытаюсь INT0 активировать при переходе с 0 на 1. Пин2.

Но при отсоединении пина от земли прерывание всеравно вызывается. Не понимаю.


#define LEDPIN 13        // Вывод светодиода
#define BTNPIN 2         // Вывод кнопки

volatile word count = 0, voda = 0;  
                       
//boolean clapan = 0;
//int count_vody=0, obor_in_litr=100;
//float litry=0;

ISR(INT0_vect)
{
    count = 1000;           // Инициализировать счётчик
}

void setup(){
  
    pinMode(LEDPIN, OUTPUT);                      // Вывод светодиода в режим вывода
    pinMode(2, INPUT);                       // Вывод кнопки в режим ввода
    EIMSK  =  (0<<INT0);
    EICRA  =  (0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (1<<ISC00);
    EIMSK  =  (1<<INT0);
   
    interrupts();                                 // Разрешить прерывания глобально
    Serial.begin (9600);
}

void loop(){
  
if (count>0) {
  digitalWrite(LEDPIN, HIGH);
  count=count-1;
  }
  else {
  digitalWrite(LEDPIN, LOW);
  }
Serial.println (count);
delay (5);
}



 

step962
Offline
Зарегистрирован: 23.05.2011

Li2n пишет:

Прошу помощи по прерыванию.

Пытаюсь INT0 активировать при переходе с 0 на 1. Пин2.

Как вы думаете - для чего служит строка 20 вашего скетча? Правильно - для выбора режима работы прерывания INT0 (кстати и INT1 тоже). После этого я открываю даташит на ATMega328 (у вас этот микроконтроллер?), нахожу в нем главу 12 (External Interrupts), нахожу там табличку 12-2 (Interrupt 0 Sense Control). Ну и выясняю, что комбинация ICS01=0 & ICS00=1 задает режим "Any logical change on INT0 generates an interrupt request.", то бишь - "прерывание при любом изменении логического уровня"

Цитата:

Но при отсоединении пина от земли прерывание всеравно вызывается. Не понимаю.

    EICRA  =  (0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (1<<ISC00);

Вам по-прежнему непонятно? ;)

 

PS: "переход с 0 на 1" это "The rising edge of INT0 generates an interrupt request.". Режим задаваемый комбинацией ICS01=1 & ICS00=1. Все та же таблица 12-2. Все того же даташита.

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

Во первых вы не правильно включаете прерывания. В 20 строке вы настроили прерывание INT0 на ЛЮБОЕ изменение уровня.  Вот так надо:

20 EICRA=(1<<ISC01); // настроили прерывание INT0 по переднему фронту

19 строку изменяем на:

19 digitalWrite(2, HIGH); // подтягиваем пин 2 к +5в на всякий случай

И во вторых, в обработчике прерывания, в 12 строке, желательно добавить паузу, чтобы исключить дребезг контактов.

12 count=1000; delay(50);

 

 

maksim
Offline
Зарегистрирован: 12.02.2012

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

Evgen
Evgen аватар
Offline
Зарегистрирован: 10.06.2011

maksim пишет:

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

Кстати да, не надо. с IAR-ом перепутал, там __delay_cycles в прерываниях нормально работает.

Li2n
Offline
Зарегистрирован: 03.02.2013

Спасибо. Буду пробовать.

Даташит пока не скачивал.

Второй день только ковыряюсь с ардуинкой.

Нужно контроллировать датчик холла+ сигналы с купюроприемника принимать+монетоприемник.

Еще нужно одно прерывание, блин. И как то все это дело считывать.

С помехами вообще засада.

maksim
Offline
Зарегистрирован: 12.02.2012

Тогда можно было прерывания 0 и 1 настроить дуиновскими функциями.

Zahar
Zahar аватар
Offline
Зарегистрирован: 16.11.2013

Evgen пишет:

Написал библиотеку которая добавляет Ардуине три дополнительных внешних прерывания, может кому пригодится.

Огромное спасибо за библиотеку.

7up
Offline
Зарегистрирован: 27.12.2016

Всем привет. Можно понтять темку? я пока только учусь :) потому не все из данной темы осилил.

прошу подсказать туда я или не туда :)

сейчас использую ардуину нано и ее одно прерывание для фиксации срабатывания кнопки (например концевик двери). Считать внутри loop() не подходит, та как раз в 5мин я отправляю результаты счетчика на сервер, в это время отследить нажатие в loop() не реально, только использовать прерывание, сейчас использую его в режиме FALLING. Так вот задача инкрементить(считать) одной ардуинкой около 5-10 счетчиков(нажатий кнопок),частота нажатий каждой не чаще чем 1 в 5с.

Так как в одном корпусе находится и GSM модуль для GPRS соединения, а плодить в этом корпусе по ардуине на 2 счетчика(по 2 прерывания) не совсем красивый вариант. Использовать arduino zero или mega где больше прерываний казалось бы решит мою задачу, но они дороже и больше по размеру. Возможно есть более грамотное решение?

заранее спасибо.

7up
Offline
Зарегистрирован: 27.12.2016
ISR (PCINT0_vect) // handle pin change interrupt for D8 to D13 here
 {    
     digitalWrite(13,digitalRead(8) and digitalRead(9));
 }

 

верно ли я понимаю из данного мануала (http://playground.arduino.cc/Main/PinChangeInterrupt) что когда на одном из цифровых пинов от 8го до13го появиться логическая единица, то сработает прерывание и код остановиться? Далее мне нужно пройтись по этим пинам и проверить у кого еденица, в моем случае - это какой счетчик инкрементить.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Не совсем. Когда на одном из ваших пинов появится изменение состояния код не остановится, а произойдет передача управления на обработчик прерывания PCINT0. Далее, какая из ног "сработала" можно решить чтением всего порта сразу и операцией XOR с сохраненным предыдущим значением. В 1 остануться только те биты, которые изменились. Пробегая их в цикле (сдвигая например вправо делением на 2) и проверяя исходный бит можно получить ответ "куда сработало" в ноль или 1.

Примерно так сделано у меня в "общем обработчике" PCINTx:

/**
 * Определения и реализация с применением конечных автоматов для измерения длительностей сигналов и/или подсчета импульсов
 * по фронту/спаду на пинах прерывания PCINT 0..2.
 *
 * Подключаются, только если до вызова #include "pcint.h" определено количество пинов прерывания: MAX_PULSES
 * и определен используемый уровень прерываний 0,1,2 - реализация TEMPLATE на С, чтобы исключить принудительную
 * вставку в скетчи обработчиков прерываний.
 *
 * Допускается повторное включение файла для обработки нескольких уровней прерываний PCINT в одном скетче.
 *
 * Доступные прерывания в Ардуино Мега 2560:
 * PC_INT0[0..7] отключает SPI:SS(53),SPI:SCK(52),SPI:MOSI(51),SPI:MISO(50),pwm10(T2outA),pwm11(T1outA),pwm12(T1outB),
 *                         (pwm13 - нельзя использовать! Только на выход!)
 * PC_INT1[8..10] отключает USART0:RX(0), USART3:RX(15), USART3:TX(14) -- не реализовано для Мега2560 из-за разных регистров данных
 * PC_INT2[16..23] (Analog8..Analog15)
 * .. остальные ножки прерываний контроллера ATmega2560 в Ардуино - отсутствуют.
 *
 * Реализован "интерфейс" из ООП: методы обработки прерываний могут разрабатываться и расширяться без
 * существенных изменений кода. Пока есть 2 метода:
 *   pcint_micros()  -- измеряет длительность импульса или до таймаута
 *   pcint_encoder() -- подсчитывает прерывания по фронту/спаду/оба до таймаута или до запрета извне
 *
 * @example Примеры подключения:
 * 1. Подключение 1 узв. датчика для замеров расстояний (длительности импульса) на прерывание PCINT2:
 * #define PCINT       2
 * #define MAX_PULSES  1
 * include "pcint.h"
 *
 * 2. Подключение 4 энкодеров для подсчета импульсов на прерывание PCINT0:
 * #define PCINT       0
 * #define MAX_PULSES  4
 * #include "pcint.h"
 *
 * 3. Одновременное использование прерываний в скетче уровня 0 (8шт) и 1(2шт) (подключаем дважды! и имеем 2 обработчика в скетче):
 * #define MAX_PULSES  10 // 8+2 -- всего 10 структур обработчиков!
 * #define PCINT       0
 * #include "pcint.h"
 * #define PCINT       1
 * #include "pcint.h"
 *
 * Примечания:
 * 1. Номер уровня прерывания PCINT - только константа #define! изменяет результирующий код обработчиков и функций
 * 2. Уровень прерываний 1 для Ардуино Мега 2560 - не реализован: их всего 3 из 8 и они из разных регистров.
 * 3. При подключении файла #include "hcsr04.h" этот файл подключается автоматически, но все предопределения должны
 *    выполняться также в скетче ПЕРЕД включением.
 * 4. Файл arhat.h тут подключается автоматически, и если режим компиляции может быть указан точно также "до" включения этого файла.
 *
 * @author Arhat109-20151125. arhat109@mail.ru
 * @license:
 *   1. This is a free software for any using and distributing without any warranties.
 *   2. You should keep author tag with any changes. May be with adding.
 *   Это свободное ПО для любого использования без каких-либо гарантий и претензий.
 *   Требуется сохранять тег @author при любых изменениях. Можете дописать свой.
 *
 * This is free software, not any pay. But you may donate some money to phone +7-951-388-2793
 * Бесплатно. Но автор принимает пожертвования на тел. +7-951-388-2793.
 *
 */
#if defined(PCINT) && ( (PCINT==0 && !defined(PCINT_0))||(PCINT==1 && !defined(PCINT_1))||(PCINT==2 && !defined(PCINT_2)) )

#if !defined(MAX_PULSES)
#error *** pcint.h::ERROR! not defined how much pins will be used for pcints data
#endif

#if PCINT==0
#define PCINT_0 1

#elif PCINT==1
#define PCINT_1 1

#elif PCINT==2
#define PCINT_2 1
#endif

// Защелка для исключения повторных переопределений
#ifndef _PCINT_H_

#include "arhat.h"

Pulse            pulses[MAX_PULSES];    // One array for all measures! Один список для всех обработчиков измерений!

// template генерация названий функций для текущего уровня прерываний PCINT:
#define __pcint_init(p)         uint8_t pcint##p##_init ( \
uint8_t pulseID, uint8_t pin, uint8_t state,            \
PcintMethod method, uint16_t timeout                    \
)
#define _pcint_init(p)          __pcint_init(p)

#define __pcint_start(p)        void pcint##p##_start (uint8_t intNumber)
#define _pcint_start(pcint)     __pcint_start(pcint)

#define __pulsein(p)            uint16_t pulseIn##p (uint8_t pulseId, void (*action)(void))
#define _pulsein(p)             __pulsein(p)

#endif // _PCINT_H_
#if PCINT==0

uint8_t          pcint0old = 0;         // состояние пинов прерываний "предыдущее"
uint8_t          pcint0numbers[8];      // текущие номера КА из pulses[], измеряющие на пинах 62..69 = 8шт!

#elif PCINT==1

uint8_t          pcint1old = 0;         // состояние пинов прерываний "предыдущее"
uint8_t          pcint1numbers[8];      // текущие номера КА из pulses[], измеряющие на пинах 62..69 = 8шт!

#elif PCINT==2

uint8_t          pcint2old = 0;         // состояние пинов прерываний "предыдущее"
uint8_t          pcint2numbers[8];      // текущие номера КА из pulses[], измеряющие на пинах 62..69 = 8шт!

#endif // PCINT

/**
 * Замена типовой pulseIn(). В отличии от типовой - замеряет длительность сигнала по прерыванию.
 * Структура pulsesX[p] должна быть настроена на обработку ЗАРАНЕЕ.
 * Позволяет исполнять функцию без параметров, пока идет замер.
 *
 * template: uint16_t pulseIn{0,1,2}(uint8_t pulseId, void (*action)(void))
 * calls:
 *   pulseIn0(pulseId, callback);       // for PCINT0 level
 *   pulseIn1(pulseId, callback);
 *   pulseIn2(pulseId, callback);
 */
_pulsein(PCINT)
{
#if PCINT==0
    pcint0_start(pulses[pulseId].pin & 0x3f);
#elif PCINT==1
    pcint1_start(pulses[pulseId].pin & 0x3f);
#elif PCINT==2
    pcint2_start(pulses[pulseId].pin & 0x3f);
#endif
    uint16_t startTime = (uint16_t)getOvfCount();
    while(
           (pulses[pulseId].state == PULSE_BUSY)
        || (pulses[pulseId].state == PULSE_SECOND)
    ){
        if( !((uint16_t)getOvfCount() - startTime > pulses[pulseId].timeout) ){
          action();
        }else{
          pulses[pulseId].state = PULSE_TIMER;
        }
    }
    return (pulses[pulseId].state == PULSE_OK? pulses[pulseId].res : 0);
}

/**
 * Настройка структуры замера на работу что и как измерять динамически. До вызова pcintX_start()!
 * Предпочтительнее статическая настройка в setup():
 *
 * @example Статическая настройка структуры pulses2[1]: для PCINT2, номер в структурах замера 1:
 *
 * pulses2[1] = {0, pcint_micros, 28, 0, PULSE_BUSY, ((2<<6)|8) };
 *
 * , что означает, данные КА:
 * {res=0, "замер длительности", ждать < 28*1024мксек, 0, "занят", "PCINT2, на 8 аналоговом входе"
 *
 * @return uint8_t pcint_number -- номер вектора прерываний в уровне [0..7]
 *
 * template: uint8_t pcint{0|1|2}_init(
 *     uint8_t pulseID, uint8_t pin, uint8_t state,
 *     PcintMethod method, uint16_t timeout
 * )
 * calls:
 *   pcint0_init(pulseID, pin, state, method, timeout);
 *   pcint1_init(pulseID, pin, state, method, timeout);
 *   pcint2_init(pulseID, pin, state, method, timeout);
 */
_pcint_init(PCINT)
{
  uint8_t intNumber = PCINT_pin2number(PCINT, pin);     // макрос от уровня PCINT! ищем номер прерывания по общему номеру пина

  PCINT_numbers(PCINT)[intNumber] = pulseID;            // сохраняем номер активной структуры
  pulses[pulseID].state = state;                        // закрываем доступ "идет замер"
  pulses[pulseID].pin = (PCINT<<6) | intNumber;         // пригодится для записи ошибки по таймауту
  pulses[pulseID].method = method;                      // метод обработки - подсчет длительности импульса/количества прерываний/..
  pulses[pulseID].timeout = timeout;                    // включаем таймаут для этого замера

  return intNumber;
}

/**
 * Запуск измерения длительности сигнала на ножке через прерывания PCINT2
 *
 * @param uint8_t  intNumber -- номер запускаемого прерывания этого уровня 0..7
 *
 * template: void pcint{0|1|2}_start(uint8_t intNumber)
 * calls:
 *   pcint0_start( intNumber );
 *   pcint1_start( intNumber );
 *   pcint2_start( intNumber );
 * or call:
 *   pcintX_start( pcintX_init(...));
 *
 * К моменту вызова структура замеров должна быть настроена на конкретную работу через pcintX_init()
 * @see pcintX_init();
 */
_pcint_start(PCINT)
{
  uint8_t       mask;

  mask = (1<<intNumber);                                // получаем маску для битовых операций {0}1{0}
  PCINT_DDR(PCINT)  |= mask;                            // ?надо Pullup? нога "на выход" {0}1{0}
  PCINT_PORT(PCINT) |= mask;                            // ?надо Pullup? сброс ножки прерывания в 1 ("echo")

  mask = ~mask;                                         // инвертируем маску для битовых операций {1}0{1}
  PCINT_old(PCINT) &= mask;                             // бит в "было раньше" = 0
  PCINT_DDR(PCINT) &= mask;                             // нога "на вход"
  PCINT_MSK(PCINT) |= ~mask;                            // и только теперь разрешаем прерывание с этой ноги

  pulses[pulseID].start = getOvfCount();                // фиксируем время старта

  PCICR  |= (1<<PCINT);                                 // и разрешаем вектор PCINT0..2
}

#ifndef _PCINT_H_
/**
 * One dispatcher for all pcint-workers for control pcint_timeout
 * Единый обработчик таймаутов для всех элементов структуры pulses
 * Если требуется проверка таймаут, проверяем и изменяем статус этой структуры
 *
 * for loop() as everyMillis()
 */
void pcint_timeout()
{
    Pulse   *ptr  = &pulses[MAX_PULSES-1];
    uint16_t time = (uint16_t)getOvfCount();

    do{
        if( ptr->timeout && (time - ptr->start > ptr->timeout) )
            pcint_end( ptr, PULSE_TIMER);
        ptr--;
    }while(ptr != pulses);
}
#endif // _PCINT_H_

/**
 * Обработчик прерывания для прерываний PCINT0..2
 *
 * При срабатывании - ищет сработавшие ножки и для каждой из них вызывает
 * свой метод замера, передавая ему предыдущее состояние пина.
 *
 * ISR( PCINT2_vect ) после подстановок формирует это:
 *
 * void __vector_ 11(void) __attribute__ ((signal, used, externally_visible));
 * void __vector_ 11(void)
 *
 * Время исполнения около 15+(15*N+8*M), где N-сработало ног, M-просмотрено бит (N+M<8!)
 * Оптимизация: шустрые ноги - размещать в начало уровня!
 */
ISR( PCINT_NAME(PCINT) )
{
  uint8_t       reg  = PCINT_PIN(PCINT);                // Первым делом читаем ноги регистра К (Analog8..15)
  uint8_t       temp = PCINT_old(PCINT);                // и предыдущее их состояние
  uint8_t     * ptrNumbers = PCINT_numbers(PCINT);

  PCINT_old(PCINT) = reg;                               // сразу сохраняем новое состояние
  reg = (temp ^ reg) & PCINT_MSK(PCINT);                // ищем разрешенные И сработавшие ножки (нормально 1шт)
  do{
    if( reg & 0x01 )                                    // Если прерывание от этого бита
    {
      Pulse * ptrPulse = pulses+(*ptrNumbers);          // получаем адрес структуры замера
      ptrPulse->method(ptrPulse, temp & 0x01);          // исполняем собственно обработчик
    }
    ptrNumbers++;                                       // следующий номер прерывания
    reg = reg >> 1;                                     // готовим следующую проверку "кто сработал"
    temp = temp >> 1;                                   // готовим его пред. состояние (raising|failing)
  }while(reg);
}

// And only this wa are defining blocking semaphore
// И только теперь определяем блокирующую защелку
#ifndef _PCINT_H_
#define _PCINT_H_     1
#endif

#endif // PCINT blocking for twice parsing

Смотрите строки 250..271. Генератор процедуры ISR() собственно обработчик прерывания. В нем выделяется какая нога сработала и вызывается та подпрограмма, которая привязана к этой ноге с передачей её текущего значения ноги прерывания. Она уже внутри себя может посмотреть какое изменение (0->1 или 1->0) и что-то сделать по этому поводу.

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

7up
Offline
Зарегистрирован: 27.12.2016

Понял Вас, спасибо за код. 

Но по факту лучше использовать 2 External + 3 PinChange прерывания, как описано выше в теме, что бы не тратить ресурс на определение ноги инициатора, верно? Или данный вариант необходим только при высоких частотах, что бы не пропустить срабатывания? Если в моем случае частота срабатывания по каждому прерыванию будет не чаще чем 1с то можно смело использовать вариант с определением ног?

А возможно же еще использовать и вот эту библиотеку http://playground.arduino.cc/Main/PinChangeInt верно?

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

Интересно ж не только реализовать, а и понять логику процесса.

Tohin
Offline
Зарегистрирован: 01.02.2016

Помогите разобратся с PCINT2


#define set_bit(reg,value) reg |= (_BV(value))
#define unset_bit(reg,value) reg &= ~(_BV(value))
volatile uint8_t ReadPortD;  //тут храним состояние порта D Пины с 0-7 на ардуино UNO


ISR(PCINT2_vect){      // сработало прерывание 
  unset_bit (PCICR,PCIE2);  //отключаем прерывание до отработки прерывания, во избежании повторных срабатываний 
  ReadPortD=PIND;   //записываем состояние порта D
}

void setup()  
{
  pinMode(5,INPUT_PULLUP); // Clock Подключаем к INT1, нельзя переназначать
  pinMode(6, INPUT_PULLUP); // второй вывод энкодера
  pinMode(7, INPUT_PULLUP); // кнопка энкодера

  Serial.begin(9600);
  while (!Serial);
  set_bit (PCICR,PCIE2);//Включаем прерывание PCINT16-23 (PortD) PCIE2, регистр PCICR принимает значение  0b00000100;
  PCMSK2 |= (_BV(PCINT23) | _BV(PCINT21)); //Указываем, что надо реагировать только на пины 5,7 
  // для порта D соответствуют pcint 16-23
  Serial.println(PCMSK2,BIN);
  sei();//включаем прерывания
  Serial.println("ready");
}



void loop() {
    if (ReadPortD) {  // если сюда что-то записано, значит было прерывание. Прерывания отключены, надо бы не забыть их включить.
      Serial.println("int detect");
      Serial.print("Pind: ");
      Serial.println(ReadPortD,BIN);
      ReadPortD=0; //мы все отработали, значит можно обнулить переменную
      set_bit (PCICR,PCIE2);  //включаем прерывание. Вроде не забыл
    }

}

Запускаю, сначала замыкаю 6-й пин на землю, потом 5й пин на землю. Получаю вывод:

10100000  //состояние PCMSK2. указание реагировать на изменения 5-го и 7-го 
ready
int detect
Pind: 10100111  //Опа! прерывание сработало на падении (PCINT22/OC0A/AIN0) PD6
int detect 
Pind: 11000111 //Снова прерывание, но падение уже на (PCINT21/OC0B/T1) PD5. Стоп! А где поднятие уровня на PD6?!
int detect
Pind: 11100111// третье прерывание, это уже восстановление уровня на PD5
 
 
 
Почему прерывание срабатывает на падение уровня на PD6?
strarbit
strarbit аватар
Offline
Зарегистрирован: 12.06.2016

стр# 30 условие не правильное

Image already added
 
 
 
Tohin
Offline
Зарегистрирован: 01.02.2016

А можно, для альтернативно одаренных, поподробнее? ReadPortD изначально равен 0. Условие не выполняется. Пока не сработает прерывание и не запишет в эту переменную значение порта D. Если при замыкании PD6 на землю срабатывает прерывание, то условие отрабатывается как истинное - и это правильно и логично.
Но почему срабатывает прерывание pcint 21, когда оно отключено маской PCMSK2?

strarbit
strarbit аватар
Offline
Зарегистрирован: 12.06.2016

можете видеть изменение  состояние выводов в прерывании

volatile uint8_t ReadPortD = 0;
volatile boolean StatePCINT2 = false;

ISR(PCINT2_vect) {
  PCICR &= ~_BV(PCIE2);
  ReadPortD = PIND;
  PCICR |= _BV(PCIE2);
  StatePCINT2 = true;
}

void setup() {
  Serial.begin(9600);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  PCICR |= _BV(PCIE2);
  PCMSK2 |= _BV(PCINT21) | _BV(PCINT23);
  Serial.print("PCMSK2 > ");
  Serial.println(PCMSK2, BIN);
  Serial.print("PIND > ");
  Serial.println(PIND, BIN);
}

void loop() {
  if (StatePCINT2) {
    Serial.println("PCINT2 > ");
    Serial.println(ReadPortD, BIN);
    StatePCINT2 = false;
  }
}

Если у вас включен PCINT только на одном выводе порта, вы можете предположить, что прерывание PCINT в PortX связано с единственным, которое вы включили. Но, если вы используете два или более на одном и том же порту, вам необходимо прочитать регистр, чтобы определить, какое из них вызвано.

 

Image already added
 
 
 
Tohin
Offline
Зарегистрирован: 01.02.2016

Опять непонятно. Я точно так же в прерывании записываю состояние всех пинов порта D и в основном цикле вывожу их. Оттуда и вижу, что идет срабатывание на PD6 (на выводе четко виден 6й бит Pind: 10100111), который отключен маской PCMSK2 (которую я тоже проверил выводом в конце setup 10100000).

Зачем лишняя переменная StatePCINT2 если значение ReadPortD может изменится только при вызове прерывания?

И какое отношение это имеет к вызову прерывания по падению PD6?

Как честный человек, я проверил предлагаемый код. И очень удивился - нет ложных срабатываний на PD6!

Для проверки убрал из кода лишнюю переменную:

volatile uint8_t ReadPortD = 0;
volatile boolean StatePCINT2 = false;

ISR(PCINT2_vect) {
  PCICR &= ~_BV(PCIE2);
  ReadPortD = PIND;
}

void setup() {
  Serial.begin(9600);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  PCICR |= _BV(PCIE2);
  PCMSK2 |= _BV(PCINT21) | _BV(PCINT23);
  Serial.print("PCMSK2 > ");
  Serial.println(PCMSK2, BIN);
  Serial.print("PIND > ");
  Serial.println(PIND, BIN);
}

void loop() {
  if (ReadPortD) {
    Serial.print("PCINT2 > ");
    Serial.println(ReadPortD, BIN);
    ReadPortD = false;
    PCICR |= _BV(PCIE2);
  }
}

Код все равно продолжает работать правильно. Скомпилировал свой код (там где "условие не правильное") - оно опять работает! 

Повторить проблему не удалось. Что за волшебство?