Прерывания - программа зависает

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Есть простенький код - 

int steps = 0;
void setup()
{
  attachInterrupt(4, ticker, CHANGE);
  pinMode(19, INPUT);
  Serial.begin(9600);
}

void ticker()
{
  steps++;
  Serial.println(steps);
}

void loop()
{
}

на прерыванческий пин повешена оптопара, внутри которой крутится колесико с прорезями. Код работает нормально, но в какой-то момент (кажется, произвольный) цифры в окошке мониторнига порта перестают появляться. То есть, если следующее число должно быть, к примеру, 124, печатается 12 и следующие числа не появляются. Что это может быть?

go6pbiu
Offline
Зарегистрирован: 30.10.2012

P.S. датчик при этом продолжает работать - на нем есть светодиод, дублирующий индикацию, он исправно мигает.

go6pbiu
Offline
Зарегистрирован: 30.10.2012

P.S. датчик при этом продолжает работать - на нем есть светодиод, дублирующий индикацию, он исправно мигает.

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

Читаем attachInterrupt() в особенности вот этот обзац


Замечание по использованию

Внутри функции обработки прерывания не работает delay(), значения возвращаемые millis() не изменяются. Возможна потеря данный передаваемых по последовательному соединению (Serial data) в момент выполнения функциии обработки прерывания. Переменные, изменяемые в функции, должным быть объявлены как volatile.

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

Там не написано об этом, но функция Serial.println на равне с delay() и millis() не будет работать в обработчике прерывания ее нужно вынести в основной цикл.

 

volatile int steps = 0, 
int steps_prev = 0;

void setup()
{
  attachInterrupt(4, ticker, CHANGE);
  Serial.begin(9600);
}

void ticker()
{
  steps++;  
}

void loop()
{
  if(steps != steps_prev) Serial.println(steps);
}

 

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Большое спасибо, я понял!

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

maksim пишет:

 

void loop()
{
  if(steps != steps_prev) Serial.println(steps);
}

Лучше все же переписать третью строку вот так:

if(steps != steps_prev) {
  Serial.println(steps);
  steps_prev = steps;
}

иначе смысла в проверке нет - условие будет выполняться всегда после первого (даже единственного) приращения steps.

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

Да, точно, просмотрел.

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Переписал программу в таком виде:

volatile int steps = 0;
volatile int steps_prev = 0;

void setup()
{
  attachInterrupt(4, ticker, RISING);
  pinMode(19, INPUT);
  Serial.begin(9600);
}

void ticker()
{
  steps++;
}

void loop()
{
  if (steps != steps_prev) {
  Serial.println(steps);
  steps_prev = steps;}
}

После этого вывод зависать перестал, но непонятки остались - почему-то steps ооочень быстро увеличивается, за один оборот колеса, по идее, переменная должна увеличиваться на 8, но приращение сразу в тысячах измеряется. Я чего-то еще не знаю о прерываниях?

 

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

А как подключаете энкодер? подтягивающий резистор стоит?

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Там датчик вот такой http://dvrobot.ru/shop/i148.datchik_skorosti.htm, думаю, обвязка вся есть.

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Кстати, пока я делал неправильно - Serial.print вызывал из процедуры прерывания, он считал точнее.

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

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

digitalWrite(19, 1);

но врядли это поможет

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

go6pbiu пишет:

Кстати, пока я делал неправильно - Serial.print вызывал из процедуры прерывания, он считал точнее.

Кстати, приведите кусочек вывода в терминал - интересно узнать, сколько инкрементов успевает произойти за один цикл loop'а.

Все описанное вами весьма похоже на шум датчика и в "неправильном" варианте два "минуса" (шум датчика и блокирование прерываний на время, необходимое для вывода в Serial), накладываясь друг на друга, давали - пусть и хилый - но плюс (фильтрацию шумов).

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Были мысли про шум - но я ведь пробовал и просто выводить значения с датчика в монитор порта, без использования прерываний - там четко - пока в прорезь светит диод, но мониторе 1, закрывается лепестком - 0, ничего никуда не прыгает не скачет. Вот вывод - это за один оборот колеса столько набегает.

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Видимо, всё-таки прав step962, и это шум - поставил колесо в граничное положение, где оно помаргивало и поймал сразу несколько тысяч приращения. Тогда есть ли способ более-менне снизить влияние шума на счетчик?

go6pbiu
Offline
Зарегистрирован: 30.10.2012

И еще вопрос - следуя логике программы, она должна выводить ВСЕ значения переменной step, правильно? И если да, то почему она этого не делает?

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

Так может это только когда вы его вручную крутите? Попродуйте как можно ближе подвинуть колесо к фототранзистору и соответственно как можно дальще от ИК-светодиода. Там где точка - это фототранзистор.

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Я так и собрал, колесо близко к тразистору. А вручную я кручу за второй конец оси, там обычное колесо, чтобы ездить. За него и кручу. Я вечером конструкцию сфотографирую, если надо.

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

А двигатель куда подключен?

go6pbiu
Offline
Зарегистрирован: 30.10.2012

Схема в двух словах такая - есть драйвер двигателей, к нему подключены двигатели, но не подключено питание (на время экспериментов с энкодером), драйвер двигателей подключен к ардуине мега 2560, и к ней же подключен вот этот самый датчик с оптопарой.

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

А светодиод на датчике нормально мигает?  Это пробовали?

 

maksim пишет:

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

digitalWrite(19, 1);

но врядли это поможет

go6pbiu
Offline
Зарегистрирован: 30.10.2012

За светодиод на датчие прям ручаюсь - отлично мигает, всё четко. По поводу digitalWrite(19, 1); - ещё не пробовал, и у меня вопрос - зачем писать в пин, настроенный на чтение? Что это даст? Не поломается ли чего-нибудь?

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

http://arduino.ru/Reference/DigitalWrite

Вход будет "подтянут" через резистор к +5В

Пробуйте.

volatile int steps = 0;
volatile int steps_prev = 0;

void setup()
{
  digitalWrite(19, 1);
  attachInterrupt(4, ticker, RISING);
  Serial.begin(9600);
}

void ticker()
{
  steps++;
}

void loop()
{
  if (steps != steps_prev) {
  Serial.println(steps);
  steps_prev = steps;}
}

это одно из самых первых что нужно было пробовать.

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

go6pbiu пишет:

И еще вопрос - следуя логике программы, она должна выводить ВСЕ значения переменной step, правильно? И если да, то почему она этого не делает?

Вы настроили свой UART/USART на скорость 9600. Это около 800 символов в секунду. Чтобы вывести очередное значение steps (предположим, "уже" трехзначное число), функции println потребуется передать 5 символов (в конце еще CR+LF), для этого потребуется около 6 миллисекунд (5/800=0,00625). Время, достаточное для завершения переходного процесса (в случае кнопки называемого обычно "дребезгом"). Сколько раз за это время ваш датчик включится/выключится - дело весьма непредсказуемое. Судя по приведенному выводу в терминал - от 5 до 60 раз.

Как с этим бороться? Снижать чувствительность системы. Для этого либо выключать обработчик прерывания на несколько миллисекунд после регистрации первого фронта, либо интерпретировать пачку фронтов как один переход с высокого уровня на низкий:

volatile int steps = 0;
volatile int steps_prev = 0;
int ticks = 0;

void setup()
{
  digitalWrite(19, 1);
  attachInterrupt(4, ticker, RISING);
  Serial.begin(9600);
}

void ticker()
{
  steps++;
}

void loop()
{
  if (steps != steps_prev) {
    steps_prev = steps;
    ticks++; // вот от этого значения и плясать дальше
    Serial.print(ticks);
    Serial.print(" (at ");
    Serial.print(millis());
    Serial.print(" msec) ");
    Serial.println(steps);
  }
}

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

PS: для приведенного варианта вывода превышение бует получено почти наверняка - слишком много букофф выводится. Увеличьте скорость UART до 57600, чтобы подстраховаться...

go6pbiu
Offline
Зарегистрирован: 30.10.2012

step962, вы крутой:) всё работает офигенски!

go6pbiu
Offline
Зарегистрирован: 30.10.2012

да вы тут все, в общем, крутые!

inspiritus
Offline
Зарегистрирован: 17.12.2012

хм... а еще можно было припаять на ногу 4 и gnd конденсатор порядка 0.1 мкф и было бы щастье.

или при входе в прерывание сделать cli() , потом некоторое ожидание и sei ()...

ожидание можно реализовать 

 

asm volatile ("nop");  // столько раз, сколько надо, например циклом for       
 
Disgust
Offline
Зарегистрирован: 09.08.2013

Насколько я понял эти правила относятся к обработчикам любых прерываний? Тоесть не важно - внешнее это прерывание или по таймеру?

Столкнулся с такойже проблемой зависания на Меге. Убрал все что связано с millis(), micros() и тп из обработчика. Про volatile и Serial.print() не знал - сегодня попробую с учетом этого. Заметил что зависание происходит в одно и то же время. Помимо прерываний в программе происходит отслеживание интервала через обьект Metro.  Не конфликтует ли Metro с прерываниями (по таймеру MsTimer2), ктонибудь в курсе?

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Пару советов ....

1. Если переменая инкрементируется от 0 и до.... я обычно использую unsigned int.

Те-же 4 байта, но зато какого  размера! ;)

2. Обычно вешаю на вход прерывания хотя-бы 1000 пик .

 

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

unsigned int - 2 байта

Disgust
Offline
Зарегистрирован: 09.08.2013

trembo пишет:

1. Если переменая инкрементируется от 0 и до.... я обычно использую unsigned int.

 

У меня обработчик прерывания сравнивает значение аналогового входа с эталонным и при превышении ставит флаг в true. В главном цикле этот флаг кушает другая функция.

а вообще тут дело не в типе данных (понятное дело если переменная никогда не будет или не должна быть отрицательной то мы добавим unsigned), а в области видимости.

Программа перестала виснуть когда я обьявил все переменные которые меняет обработчик прерывания как volatile.

nikolaki
nikolaki аватар
Offline
Зарегистрирован: 14.02.2013

Решил освоить прерывания-сделать счетчик об/мин по готовым примерам с отображением на ЛСД16х2.

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

Все запустилось замечательно.но потом заметил что вывод на экран начал зависать(и в сериал тоже).Прочитал про volatile и Serial.print().Переделал.

Если закоментировать все что касается lcd и запустить скетч-работает все ок  при частоте следования импульсов до 14кГц и в сериал выводит.

А при отображении на лсд -клинит . Почему?

И чем выше частота (200 Гц и выше)тем быстрее клинит.

#include <LiquidCrystal.h>
volatile unsigned long rpmcount = 0;// я их перевел в unsigned long

volatile unsigned long rpm = 0;// я их перевел в unsigned long

volatile unsigned long lastmillis = 0;
LiquidCrystal lcd(12, 11, 7, 6, 5, 4);

void setup(){

  Serial.begin(9600);
  lcd.begin(16, 2);  // инициализируем дисплей 
  attachInterrupt(0, rpm_fan, RISING);

}

void loop(){

  if (millis() - lastmillis == 1000){ //Uptade every one second, this will be equal to reading frecuency (Hz).

    detachInterrupt(0);//Disable interrupt when calculating

    rpm = rpmcount * 60; // Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.


       lcd.clear(); // 
       lcd.setCursor(0, 0);//
       lcd.print("RPM=");//
       lcd.print(rpm);//
       lcd.setCursor(0, 1);//
       lcd.print("Hz=");//
       lcd.print(rpmcount);//


    Serial.print("RPM =\t"); //print the word "RPM" and tab.
    Serial.print(rpm); // print the rpm value.
    Serial.print("\t Hz=\t"); //print the word "Hz".
    Serial.println(rpmcount); //print revolutions per second or Hz. And print new line or enter.

    rpmcount = 0; // Restart the RPM counter
    lastmillis = millis(); // Uptade lasmillis
    attachInterrupt(0, rpm_fan, RISING); //enable interrupt
  }

}

// this code will be executed every time the interrupt 0 (pin2) gets low.

void rpm_fan(){

  rpmcount++;

} 

 

Disgust
Offline
Зарегистрирован: 09.08.2013

попробуйте вместо отключения прерывания использовать функции noInterrupts() и interrupts()

подозреваю что detachInterrupt(...) выполняется значительно дольше чем хотелось бы тратить на это времени.

Disgust
Offline
Зарегистрирован: 09.08.2013

Насчет времени беру свои слова обратно. Проверил с пустым обработчиком прерывания. Разница всего 500 микросекунд.  Но правильнее всеравно было бы использовать noInterrupts() и interrupts()

Disgust
Offline
Зарегистрирован: 09.08.2013

nikolaki пишет:

А при отображении на лсд -клинит

Как именно клинит? Просто останавливается и не показывает?

Кстати, заметил кое что еще. НА выводе на ЛСД присутствуют статические данные?

Лучше убрать lcd.clear() , а остальное разбить на две части:

lcd.setCursor(0, 0);

lcd.print("RPM=");

lcd.setCursor(0, 1);

lcd.print("Hz=");

 - перенести в модуль setup(), а в loop() получится :

lcd.setCursor(4, 0);

lcd.print(rpm);

lcd.setCursor(3, 1);

lcd.print(rpmcount);

Вообще при работе с прерываниями их выключают только для критически важных операций. например для копирования переменной которую модифицирует обработчик в переменную с которой будет работать основная программа.

 

nikolaki
nikolaki аватар
Offline
Зарегистрирован: 14.02.2013

Да ,просто остается последняя информация и все - при изменении частоты не реагирует.Ресет ардуины и опять какое-то время нормально.

Подключил 7 сегментный с МАХ7219-то же самое . Поначалу еденички скачут.Через некоторое время замирает и отображает частоту но не реагирует на ее изменение.

#include <LedControl.h>
volatile unsigned long rpmcount = 0;// я их перевел в unsigned long

volatile unsigned long rpm = 0;// я их перевел в unsigned long

volatile unsigned long lastmillis = 0;


LedControl lc=LedControl(12,11,10,1);





//int T1;         // переменная текущей температуры

int ones;
int tens;
int hundreds;
void setup() 

{
  attachInterrupt(0, rpm_fan, RISING);
lc.clearDisplay(0);// clear screen
lc.shutdown(0,false);// turn off power saving, enables display
lc.setIntensity(0,7);// sets brightness (0~15 possible values)
lc.setScanLimit(1, 3); 
 
}

void loop(void)
{ 
  if (millis() - lastmillis == 1000){ //Uptade every one second, this will be equal to reading frecuency (Hz).

    detachInterrupt(0);//Disable interrupt when calculating

   rpm= rpmcount * 60; // Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.

  





     
hundreds = rpmcount/100;
            
tens=(rpmcount-hundreds*100)/10;
    
ones=rpmcount-(hundreds*100+tens*10);
    

lc.setDigit(0,2,hundreds,false);  
lc.setDigit(0,1,tens,false);
lc.setDigit(0,0,ones,false);
rpmcount = 0; // Restart the RPM counter
    lastmillis = millis(); // Uptade lasmillis
    attachInterrupt(0, rpm_fan, RISING); //enable interrupt
     
}
}
void rpm_fan(){

  rpmcount++;

} 

звыняйте за говнокод...

Disgust
Offline
Зарегистрирован: 09.08.2013

Всетаки попробуйте заменить:

detachInterrupt(0) на noInterrupts() и второй attachInterrupt(0, ... RISING) на interrupts()

и напишите, может чтото изменится?

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

Disgust пишет:

Всетаки попробуйте заменить:

detachInterrupt(0) на noInterrupts() и второй attachInterrupt(0, ... RISING) на interrupts()

и напишите, может чтото изменится?

Ну что получится...

Кроме своего обработчика выключится также обработчик таймера/счетчика0 и перестанут обрабатываться переменные, используемые в millis() и micros(). Пусть и ненадолго, но все же весьма неприятный побочный эффект.

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

Какие там еще прерывания присутствуют в ядре Arduino?

В общем, вместо целенаправленного маскирования одного-единственного прерывания (см. исходники в WInterrupts.c) вы остановите весь механизм.

Не смертельно...

Но чревато...

 

Я бы обратил внимание на то, на какой скорости инициализируется последовательный порт.

Потом посчитал бы, сколька букафф выводится в каждой посылке.

Рассчитал бы, сколько времени требуется для вывода такого количества букафф и соспоставли это с частотой вывода.

М.б. вы банально засираете выходной поток порта и программа останавливается в ожидании, пока там рассосется?

М.б. просто на 19200 перейти? или на 57600?

 

Я бы также обратил внимание на первую строку функции loop:

if (millis() - lastmillis == 1000){ //Uptade every one second, this will be equal to reading frecuency (Hz).

и вместо проверки на равенство использовал проверку >=. В конкретной стадии разработки конкретной программы очень маловероятно, что вышеупомянутая проверка будет выполняться реже, чем один раз в миллисекунду, но в более сложных проектах пролететь момент точного равенства проще простого. А после такого пролета программа входит в вечный пустой цикл.

 

И таки да: послушал бы разъяснения, что же все-таки это такое - "клинит"?

Disgust
Offline
Зарегистрирован: 09.08.2013

step962 пишет:

и вместо проверки на равенство использовал проверку >=. В конкретной стадии разработки конкретной программы очень маловероятно, что вышеупомянутая проверка будет выполняться реже, чем один раз в миллисекунду, но в более сложных проектах пролететь момент точного равенства проще простого. А после такого пролета программа входит в вечный пустой цикл.

Да, действительно. Нужно использовать >=, ибо даже в небольшой программе есть вероятность пропустить такое равенство.

step962 пишет:

Я бы обратил внимание на то, на какой скорости инициализируется последовательный порт.

Ну так как в последовательный порт данные просто дублируются - его можно вообще отключить и посмотреть как это повлияет на ситуацию. Но отображение на LCD занимает куда больше времени чем вывод в последовательный порт.

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

nikolaki
nikolaki аватар
Offline
Зарегистрирован: 14.02.2013

последовательный порт (пост 36) и все лишнее я отключил . А до этого эксперементировал с скоростью.Добавлял в скетч и блинк без делея-показания на индикаторах замирали а лед продолжал мигать.

Попробую поменять == на >=.

Но это вечерком.

nikolaki
nikolaki аватар
Offline
Зарегистрирован: 14.02.2013

Поменял == на  >= и все заработало как надо.

Спасибо всем за помощь!