Счетчик импульсов, прерывания и millis()
- Войдите на сайт для отправки комментариев
Приветствую участников форума!
Начал собирать простенький вроде бы проект, но столкнулся с непонятным результатом. Прошу помочь и объяснить мои ошибки. Суть проекта. Две ардуино нано. Одни из них генерит на 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 ардуино счетчика подтянут к земле. Думаю не надо рисовать?
А потом ещё секнда после этого обновления прошла.
А потом ещё секнда после этого обновления прошла.
Между двумя импульсами в серии пауза 250 мс. То есть прерывание вызывается тоже раз в 250 мс. И обновляется значение , соответственно, каждые 250 мс. То есть пока серия не закончилась, паузы более 1 секунды быть не может. И значения в сериал 1,2,3 и т.д. выдаются через 250 мс (секундомером не измерял, конечно, но равномерно и без пауз). Но периодически откуда-то вываливается сообщение об окончании.
Или я все равно не уловил где появляется промежуток времени?
Но сигнал-то пришёл. Надо разбираться.
Для начала просто посмотрите не приходят ли сигналы сами по себе. Пины прерываний очень нежные и иногда сигналы наводятся помехами. Чем Вы его там к земле притянули? Если валятся левые сигналы, то значит мало притянули.
Левое сообщение о завершении приходит сразу или чере "произвольный промежуток времени"?
Никаких лишний сигналов не поступает. Пин притянут к земле резистором 10 кОм. Левые сообщения приходят как угодно. Могут и не появится. Сообщение после настоящего окончания серии появляется ровно через секунду, как и положено.
Попробовал для отладки выводить не только номер импульса, но и значение millis(). Каждый имульс получен на 500 мс позже предыдущего. Попробовал "ускорить" передачу. Теперь каждый импульс приходит ровно на 200 мс позже предыдущего. То есть значения millis() изменяются совершенно точно.
1
75107
2
75308
3
75507
4
75708
5
75908
6
76108
7
76308
и т.д.
Откуда вылезает сообщение непонятно.
Добавил вывод обоих значений 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
Ну, это скорее всего и есть левый импульс.
10К для пина прерывания слишком много. Поставьте 1К и посмотрите (заодно, даже при 1К старайтесь руки подальше от пинов держать).
Заменил на 1 кОм. Пальцами и яйцами в плату не лазаю. Все равно сообщение выскакивает. Без какой-либо закономерности.
Опять же непонятно. Сообщение выводится в основном цикле по условию. И если посмотреть "отладочные" показания millis(), то условие не выполняется, а сообщение все равно появляется.
А я бы вот для для начала выкинул из обработчика прерываний Serial.println. Потом взял бы в ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ... } работу с volatile переменными. Ну, не целиком половину лупа, конечно, а места, где с ними непосредственно работа идет.
Измените тип у StopCounting на unsigned long и все встанет на место.
Измените тип у StopCounting на unsigned long и все встанет на место.
Ничего не изменилось. Сообщение так же выскакивает в непонятных местах.
Ну, тогда надо смотреть внимательнее. Сегодня уже не могу, если до завтра не решится, посмотрю.
Попробуй убрать (закоментить) строчки 2 и 12, они как бы не нужны, может конфликтуют...
Вообще-то, и не должно было повлиять, хотя это и есть "причина". Там есть второй косяк - вывод на монитор из под обработчика прерывания, да ещё и на скорости 9600 .. это КАТЕГОРИЧЕСКИ запрещено для Ардуино библиотек. После такого возможно всё что угодно .. хотя бы скорость поднимите до 115200. А ещё лучше, читайте и выводите его из loop, он все одно глобален.
Пардон, попутал. Это из под прерывания времени нельзя делать.. в вашем случае, непонятно что происходит.. но все равно, стоит завести "предыдущее значение" этого счетчика и попробовать вывод в loop() если значение изменилось, убрав вывод из под прерывания.
стоит завести "предыдущее значение" этого счетчика и попробовать вывод в loop() если значение изменилось, убрав вывод из под прерывания.
Теперь только в понедельник смогу продолжить штудии и попробовать.
Не очень понятно, что даст такая перестановка кода. Судя по отладочным значениям millis() все происходит нормально. Импульсы не пропадают, лишних не появляется, времы выполнения соответствует тому что нужно. Но вот это левое сообщение... Тем более, как я писал уже выше, значения millis() в момент вывода этого сообщения не отличаются. И его, по идее, вообще не может быть. И еще, это же не просто сообщение, а счетчик тоже обнуляется, то есть выполняется весь код, который находится под условием.
В принципе, можно было бы забить на него. Так как это отладочная информация, и в конечном устройстве останется только счетчик импульсов. А он работает вроде нормально. Но получается, что программа работает не так как должна, и причину этого хотелось бы найти.
Не очень понятно, что даст такая перестановка кода.
Умные люди говорят, что пока выполняется обработчик прерывания, остальные не выполняются и могут терять данные. Поэтому не надо в обработчике делать ничего, чтобы нельзя было бы сделать в loop(). А то тут были случаи - забыл товарищ Serial.print в обработчике, потом искал трое суток почему у него I2C медленно работает.
Алексей Н, вы в коде не учитываете тот факт, что в процессе работы с previousMillis в Loop() переменная свободно изменяется в прерывании, поэтому получаете адовы глюки при элементарных операциях. Вставьте перед 25 строчкой cli(); а перед 31 sei(); и всё заработает как надо.
Да я уже писал про atomic - видимо не хочет ТС локализовать ошибку, а хочет стать владычецей мор... чтобы было как он написал, но работало без сбоев.
Не очень понятно, что даст такая перестановка кода. Судя по отладочным значениям millis() все происходит нормально. ...
По вашему отладочному выводу ни о чем судить как раз нельзя. Вы почему-то считаете что вывод из под прерывания, в т.ч. и миллис идет строго и непосредственно раньше некузявой надписи, а это может оказаться вовсе не так. Прерывания - сами по себе, а loop() - сам по себе. Чтобы убедиться что у Вас в реальности все нехорошо и надо перенести весь отладочный вывод в loop().
Да, и как писал Dimax - не забудьте про атомарное чтение меняющихся в прерывании значений.
И не забуть, что Serial работает на прерываниях, а внутри прерывания прерывания запрещены, если мануально не разрешить.
если мануально не разрешить.
я запретил мануальные разрешения. О_О
Всем спасибо. Буду пробовать всё, что тут посоветовали.
А взял бы в ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ... } работу с volatile переменными.
Просто я не понял ни одного слова. Я же чайник. Начал читать в интернетах, пока ничего не понятно. Это имеет отношение к разркшению/запрету прерываний, о чем более понятно написал dimax?
Просто я не понял ни одного слова. Я же чайник. Начал читать в интернетах, пока ничего не понятно.
Это выглядит примерно так:
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) - макрос, который при компиляции превратится в блок cli() ... sei(), о которых писал dimax, а так же сам сохранит и восстановит SREG. Это заставит МК приостановить обработку прерываний на время чтения переменной (previousMillis, в вашем случае), изменяемой в обработчике. Ибо прерывание может произойти на середине чтения многобайтовой переменной и изменить ее часть. Это, порой, вызывает удивительные, с точки зрения человека, эффекты.
Запрет и разрешение прерываний с помощью cli(); и sei(); помогло. Все работает как надо. Это был мой первый опыт использования прерываний и я не ожидал подобных результатов. Большое спасибо всем за помощь. dimax и sadman41 самое большое спасибо! :)
И не забуть, что Serial работает на прерываниях, а внутри прерывания прерывания запрещены, если мануально не разрешить.
А если вот так?
памойму,
delayMicroseconds(10000)
разрешает прерывания унутре себя.
Не могу спорить, но на грабли неработающего сериала натыкался при отладке в прерывании
Нет, конечно, ничего она не разрешает. Просто с сериалом не всё так однозначно. Если "уметь готовить", то работать будет и при закрытых прерываниях. Хотя, конечно, для ньюби лучше считать что не работает и всё тут.
да, прошу прощения, я посмотрел, явного разрешения там унутре нет (там 2 команды всего, не считая тех, что под #define).
Здраствуйте. Прошу помощи. Нужно отследить импульни на пине. Суть вот в чем, пока в автомобиле мигает лампа поворота, есль импульси с частотой 0.5-1 сек, то включить реле, если импульси отсутствуют (больше 1 сек), то реле виключить. Нужно реалиловать с помощью миллис. Буду очень благодарет. Прошу помощи
А если реле будет управляться от рычага переключения поворотника? И ничего программировать не надо.
Добрый вечер.
Если "сек" - это секунды, то частота в них не измеряется.
Какая именно помощь Вам нужна?
1) помочь найти ошибку в Вашем коде? - тогда Вы забыли его приложить.
2) пожелать Вам удачи? - тогда Желаю.
3) сделать вместо Вас? - тогда Вы выбрали не тот раздел форума, Вам сюда.
спасибо.
http://arduino.ru/forum/programmirovanie/otslezhivanie-impulsov-na-pine-...