Счетчик импульсов, прерывания и millis()

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Приветствую участников форума!

Начал собирать простенький вроде бы проект, но столкнулся с непонятным результатом. Прошу помочь и объяснить мои ошибки. Суть проекта. Две ардуино нано. Одни из них генерит на 13 пине серии импульсов (250 мс HIGH и 250 мс LOW).  В сериал вводится число, ардуина его считывает и выдает соответсвующее количество импульсов. Тут все работает.

Вторая ардуино нано должна эти импульсы принимать на пин 2, вызывать прерывание, подсчитывать и выводить в сериал. Окончание серии импульсов пауза более 1 секунды после последнего импульса. Вроде бы простая штука. Алгоритм такой:
1. Получаем импульс, вызываем прерывание
2. В функции, вызываемой по прерыванию взводим флаг счетчика, увеличиваем счетчик импульсов, выводим их количество, получаем текущее значение millis().
3. В основном цикле проверяем флаг счетчика, если он взведен, значит был импульс. Проверяем сколько времени прошло с момента его получения. Сравниваем текущее значение millis() и значение millis(), полученное в прерывании. Если прошло более 1 секунды, то выводим сообщение об окончании серии, обнуляем счетчик, сбрасываем флаг счетчика.
На картинке показано как это работате на самом деле. Серия из 25 импульсов. Все импульсы посчитаны, но несколько раз до окончания серии появляется сообщение об окончании. Я никак не могу представить себе ситуацию, в которой это может происходить. По идее это сообщение выводится только в том случае, если текущее значение millis() на 1000 больше, чем то, которое получено в функции по прерыванию. А если импульс посчитан, значит прерывание было вызвано и значение millis() в нем было тоже обновлено. Но тем не менее эти сообщения каким-то образом появляются. Почему так происходит?

// Константы:
const int impPin = 2; // импульсы поступают на 2 пин, куда вешается прерывание
const int stopCounting = 1000; // пауза 1 секунда после последнего импульса

// Переменные:
volatile boolean counterFlag = 0;  // флаг счетчика
volatile unsigned long previousMillis;  // значение таймера
volatile int impCount = 0; // счетчик импульсов
unsigned long currentMillis;

void setup() {
  pinMode(impPin, INPUT);
  attachInterrupt(0, count, RISING); // 0-е прерывание вызывает функцию count() при появлении импульса

  //для отладки
  Serial.begin(9600);
  while (!Serial) {
    ;
  }
}

void loop() {

  if (counterFlag == 1) { // если счетчик запущен
    currentMillis = millis(); // получаем текущее значение millis()
    if (currentMillis - previousMillis >= stopCounting) { // и проверяем прошла ли с момента получения последнего импульса 1 секунда
      Serial.println("Окончание приема импульсов");//для отладки
      impCount = 0; // если прошла, то обнуляем счетчик импульсов
      counterFlag = 0; // сбрасываем флаг счетчика
    }
  }
}

// Подсчет импульсов:
void count() {
  impCount++; // увеличиваем счетчик
  previousMillis = millis(); // и запускаем таймер счетчика
  counterFlag = 1; // взвести флаг счетчика
  Serial.println(impCount);//для отладки
}

Соединение простейшее. Пин 13 ардуино-генератора соединен с пином 2 ардуино-счетчика. Земля обоих ардуин соединена. Пин 2 ардуино счетчика подтянут к земле. Думаю не надо рисовать?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Алексей Н пишет:
А если импульс посчитан, значит прерывание было вызвано и значение millis() в нем было тоже обновлено.

А потом ещё секнда после этого обновления прошла.

 

 

Алексей Н
Offline
Зарегистрирован: 02.01.2016

ЕвгенийП пишет:

Алексей Н пишет:
А если импульс посчитан, значит прерывание было вызвано и значение millis() в нем было тоже обновлено.

А потом ещё секнда после этого обновления прошла.

 

Между двумя импульсами в серии пауза 250 мс. То есть прерывание вызывается тоже раз в 250 мс. И обновляется значение , соответственно, каждые 250 мс. То есть пока серия не закончилась, паузы более 1 секунды быть не может. И значения в сериал 1,2,3 и т.д. выдаются через 250 мс (секундомером не измерял, конечно, но равномерно и без пауз). Но периодически откуда-то вываливается сообщение об окончании.

Или я все равно не уловил где появляется промежуток времени?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Но сигнал-то пришёл. Надо разбираться.

Для начала просто посмотрите не приходят ли сигналы сами по себе. Пины прерываний очень нежные и иногда сигналы наводятся помехами. Чем Вы его там к земле притянули? Если валятся левые сигналы, то значит мало притянули.

Левое сообщение о завершении приходит сразу или чере "произвольный промежуток времени"?

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Никаких лишний сигналов не поступает. Пин притянут к земле резистором 10 кОм. Левые сообщения приходят как угодно. Могут и не появится. Сообщение после настоящего окончания серии появляется ровно через секунду, как и положено.

Попробовал для отладки выводить не только номер импульса, но и значение millis(). Каждый имульс получен на 500 мс позже предыдущего. Попробовал "ускорить" передачу. Теперь каждый импульс приходит ровно на 200 мс позже предыдущего. То есть значения millis() изменяются совершенно точно.

1
75107
2
75308
3
75507
4
75708
5
75908
6
76108
7
76308
и т.д.

 

Откуда вылезает сообщение непонятно.

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Добавил вывод обоих значений millis(). После последнего вывода совершенно точно видна разница в 1000 мс, что и является условием для вывода сообщения. А после 7-го импульса опять появилось левое сообщение, хотя значения millis() одинаковы. Как так то? Мозг кипит.

 

1
2789
0
2
2989
2989
3
3189
3189
4
3389
3389
5
3590
3590
6
3789
3789
7
3990
3990
Окончание приема импульсов
3990
3990
1
4190
3990
2
4390
4390
3
4590
4590
Окончание приема импульсов
4590
5590
 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, это скорее всего и есть левый импульс.

10К для пина прерывания слишком много. Поставьте 1К и посмотрите (заодно, даже при 1К старайтесь руки подальше от пинов держать).

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Заменил на 1 кОм. Пальцами и яйцами в плату не лазаю. Все равно сообщение выскакивает. Без какой-либо закономерности.

Опять же непонятно. Сообщение выводится в основном цикле по условию. И если посмотреть "отладочные" показания millis(), то условие не выполняется, а сообщение все равно появляется.

sadman41
Offline
Зарегистрирован: 19.10.2016

А я бы вот для для начала выкинул из обработчика прерываний Serial.println. Потом взял бы в ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ... } работу с volatile переменными. Ну, не целиком половину лупа, конечно, а места, где с ними непосредственно работа идет.

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

Измените тип у StopCounting на unsigned long и все встанет на место.

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Arhat109-2 пишет:

Измените тип у StopCounting на unsigned long и все встанет на место.

 

Ничего не изменилось. Сообщение так же выскакивает в непонятных местах.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, тогда надо смотреть внимательнее. Сегодня уже не могу, если до завтра не решится, посмотрю.

SergC4
Offline
Зарегистрирован: 18.10.2017

Попробуй убрать (закоментить) строчки 2 и 12, они как бы не нужны, может конфликтуют...

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

Вообще-то, и не должно было повлиять, хотя это и есть "причина". Там есть второй косяк - вывод на монитор из под обработчика прерывания, да ещё и на скорости 9600 .. это КАТЕГОРИЧЕСКИ запрещено для Ардуино библиотек. После такого возможно всё что угодно .. хотя бы скорость поднимите до 115200. А ещё лучше, читайте и выводите его из loop, он все одно глобален.

Пардон, попутал. Это из под прерывания времени нельзя делать.. в вашем случае, непонятно что происходит.. но все равно, стоит завести "предыдущее значение" этого счетчика и попробовать вывод в loop() если значение изменилось, убрав вывод из под прерывания.

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Arhat109-2 пишет:

стоит завести "предыдущее значение" этого счетчика и попробовать вывод в loop() если значение изменилось, убрав вывод из под прерывания.

Теперь только в понедельник смогу продолжить штудии и попробовать.

Не очень понятно, что даст такая перестановка кода. Судя по отладочным значениям millis() все происходит нормально. Импульсы не пропадают, лишних не появляется, времы выполнения соответствует тому что нужно. Но вот это левое сообщение... Тем более, как я писал уже выше, значения millis() в момент вывода этого сообщения не отличаются. И его, по идее, вообще не может быть. И еще, это же не просто сообщение, а счетчик тоже обнуляется, то есть выполняется весь код, который находится под условием.

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

 
 
 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Алексей Н пишет:

Не очень понятно, что даст такая перестановка кода. 

 

 

Умные люди говорят, что пока выполняется обработчик прерывания, остальные не выполняются и могут терять данные. Поэтому не надо в обработчике делать ничего, чтобы нельзя было бы сделать  в loop(). А то тут были случаи - забыл товарищ Serial.print в обработчике, потом искал трое суток почему у него I2C медленно работает.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Алексей Н, вы в коде не учитываете тот факт, что в процессе работы с previousMillis в Loop() переменная свободно  изменяется в прерывании, поэтому получаете адовы глюки при элементарных операциях. Вставьте перед 25 строчкой cli(); а перед 31 sei(); и всё заработает как надо.

sadman41
Offline
Зарегистрирован: 19.10.2016

Да я уже писал про atomic - видимо не хочет ТС локализовать ошибку, а хочет стать владычецей мор... чтобы было как он написал, но работало без сбоев.

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

Алексей Н пишет:

Не очень понятно, что даст такая перестановка кода. Судя по отладочным значениям millis() все происходит нормально. ...

По вашему отладочному выводу ни о чем судить как раз нельзя. Вы почему-то считаете что вывод из под прерывания, в т.ч. и миллис идет строго и непосредственно раньше некузявой надписи, а это может оказаться вовсе не так. Прерывания - сами по себе, а loop() - сам по себе. Чтобы убедиться что у Вас в реальности все нехорошо и надо перенести весь отладочный вывод в loop().

Да, и как писал Dimax - не забудьте про атомарное чтение меняющихся в прерывании значений.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

И не забуть, что Serial работает на прерываниях, а внутри прерывания прерывания запрещены, если мануально не разрешить.

Клапауций 112
Клапауций 112 аватар
Offline
Зарегистрирован: 01.03.2017

DetSimen пишет:

если мануально не разрешить.

я запретил мануальные разрешения. О_О

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Всем спасибо. Буду пробовать всё, что тут посоветовали.

sadman41 пишет:

А взял бы в ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ... } работу с volatile переменными.

Просто я не понял ни одного слова. Я же чайник. Начал читать в интернетах, пока ничего не понятно. Это имеет отношение к разркшению/запрету прерываний, о чем более понятно написал dimax?

 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Алексей Н пишет:

Просто я не понял ни одного слова. Я же чайник. Начал читать в интернетах, пока ничего не понятно.

Это выглядит примерно так:

#include <util/atomic.h>

volatile uint32_t previousMillis;
uint32_t safePreviousMillis;
...
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 
  safePreviousMillis  = previousMillis;
}

if (safePreviousMillis ...) { ... } 

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) - макрос, который при компиляции превратится в блок cli() ... sei(), о которых писал dimax, а так же сам сохранит и восстановит SREG. Это заставит МК приостановить обработку прерываний на время чтения переменной (previousMillis, в вашем случае), изменяемой в обработчике. Ибо прерывание может произойти на середине чтения многобайтовой переменной и изменить ее часть. Это, порой, вызывает удивительные, с точки зрения человека, эффекты. 

 

Алексей Н
Offline
Зарегистрирован: 02.01.2016

Запрет и разрешение прерываний с помощью cli(); и sei(); помогло. Все работает как надо. Это был мой первый опыт использования прерываний и я не ожидал подобных результатов. Большое спасибо всем за помощь. dimax и sadman41 самое большое спасибо! :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DetSimen пишет:

И не забуть, что Serial работает на прерываниях, а внутри прерывания прерывания запрещены, если мануально не разрешить.

А если вот так?

void setup() {
	Serial.begin(115200);
	Serial.print("Fun begins: ");
	delay(500);
	cli();
	for (int i=0; i < 16; i++) {
		Serial.print('*');
		delayMicroseconds(10000);
	}
	Serial.print('!');
}

void loop(void){}

/////////////////////////////
// РЕЗУЛЬТАТ
// 
// Fun begins: ****************!
//
// (Nano, IDE 1.8.1)

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

памойму, 

delayMicroseconds(10000)

разрешает прерывания унутре себя. 

Не могу спорить, но на грабли неработающего сериала натыкался при отладке в прерывании

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Нет, конечно, ничего она не разрешает. Просто с сериалом не всё так однозначно. Если "уметь готовить", то работать будет и при закрытых прерываниях. Хотя, конечно, для ньюби лучше считать что не работает и всё тут.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

да, прошу прощения, я посмотрел, явного разрешения там унутре нет (там 2 команды всего, не считая тех, что под #define).