Странности с прерыванием

ustas
Offline
Зарегистрирован: 12.03.2012

Хочу написать "сниффер" для захвата команд пульта ДУ и хочу это сделать без использования соответствующих библиотек.

Придумал такую схему - подключаю ИК-приемник ко 2 цифровому пину и использую прерывания:

#define IRrec  2

int pin = 5;
volatile int state = HIGH;

volatile int heartbit = 0;

volatile unsigned long seq[200];
 
void setup()
{
  Serial.begin(115200);
  Serial.println("IR rec");
  pinMode(pin, OUTPUT);
  digitalWrite(IRrec, HIGH);
  attachInterrupt(0, isr_pulse, CHANGE);
}
 
void loop()
{
  digitalWrite(pin, state);

  if(heartbit>4){
    detachInterrupt(0);
    Serial.println("----");    
  }
}
 
void isr_pulse()
{
  state = !state;
  heartbit++;
  seq[heartbit]=micros();
}

В функции обработки прерывания делаю три вещи:

1. для диагностики меняю значение state (чтобы осциллографом видеть, что происходит "на входе" и "выходе")

2. фиксирую текущее время в текущем элементе массива

3. увеличиваю значение счетчика.

Дальше планировал вывод этого массива для дальнейшей работы.

 

Но тут сразу есть две странности:

1. Почему-то в Serial ничего не выводится (хотя ограничение на использование Serial только внутри isr_pulse())

2. В выделенном условии я ожидал, что тут после 5 изменений уровня сигнала, отключаю обработку прерываний, но этого не происходит:

Голубой - первый канал - выход с ИК-приемника.

Пурпурный - третий канал - D5

 

Подскажите, почему так?

ustas
Offline
Зарегистрирован: 12.03.2012

и еще на осциллограмме видно, что "реверс" state происходит не всегда... вообще не дело :(

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

ustas пишет:

1. Почему-то в Serial ничего не выводится (хотя ограничение на использование Serial только внутри isr_pulse())

Что, даже "IR rec" не выводится?

ustas
Offline
Зарегистрирован: 12.03.2012

если закомментировать 34 строку, то все работает так, как ожидалось...

Тогда как же фиксировать время наступления событий (CHANGE)?

ustas
Offline
Зарегистрирован: 12.03.2012

step962 пишет:

ustas пишет:

1. Почему-то в Serial ничего не выводится (хотя ограничение на использование Serial только внутри isr_pulse())

Что, даже "IR rec" не выводится?

Не выводится :(

Кстати, этот скетч работает и без подключенного ИК-приемника (по крайней мере "IR rec" так же не выведется).

ustas
Offline
Зарегистрирован: 12.03.2012

ха. При закоментированной 34 строке в Serial все уходит превосходно...

 

Как быть?

leshak
Offline
Зарегистрирован: 29.09.2011

А доку на http://arduino.ru/Reference/AttachInterrupt почитать особенно раздел "Замечания по использованию"?

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

ustas пишет:

ха. При закоментированной 34 строке в Serial все уходит превосходно...

 

Как быть?

В приведенном вами коде 34-я строка - это закрывающая скобка функции loop().

Как вас понимать?

ustas
Offline
Зарегистрирован: 12.03.2012

leshak пишет:

А доку на http://arduino.ru/Reference/AttachInterrupt почитать особенно раздел "Замечания по использованию"?

Это я прочитал и еще раз хочу повторить тут:

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

Но я это учел:

1. внутри функции обработки нет delay 

2. внутри функции нет Serial

3. изменяемые переменные объявлены как volatile 

4. millis() тоже внутри функции нет.

Где моя ошибка?

ustas
Offline
Зарегистрирован: 12.03.2012

step962 пишет:

ustas пишет:

ха. При закоментированной 34 строке в Serial все уходит превосходно...

 

Как быть?

В приведенном вами коде 34-я строка - это закрывающая скобка функции loop().

Как вас понимать?

 

Извините, 33 строка, конечно. Там где как раз время событя CHANGE фиксируется и заполняется массив.

leshak
Offline
Зарегистрирован: 29.09.2011

ustas пишет:

2. внутри функции нет Serial

Ох... простите. Чего-то показалось что оно в обработчике. Бегло смотрел, а ошибка эта очень частая.

ustas пишет:

4. millis() тоже внутри функции нет.

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

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

 

 

ustas
Offline
Зарегистрирован: 12.03.2012

 

leshak пишет:

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

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

Дык и ее ровно один разик дергаю. Но вот если заполняю массив в обработчике - все, кранты - ничего не работает (правильно).

leshak
Offline
Зарегистрирован: 29.09.2011

Я, кажись, понял.

Одна причина - почти наверняка вызовет проблемы, вторая - возможна.

Превая: вы делаете  heartbit++;, но нигде не проверяете что он у вас не вашел за границы массива. Вообщем что у вас "массив не закончился". При heartbit>200 - вы своей строкой seq[heartbit]=micros() начинаете писать в случайную область памяти. Портить данные и сам код скетча. Поэтому - глюки будет любые, не обязательно потдающиеся логике.

Вторая (пока - маловероятная): нужно смотреть что-бы seq[200] влез в оперативку. когда памяти не хватает - тоже "глюки любые".

Вообщем пока вам нужано или

if(heartbit>=200)heartbit=0;

или

if(heartbit<200)heartbit++

ustas
Offline
Зарегистрирован: 12.03.2012

Первая причина вызывает проблемы, но не сразу :). Так что условие такое в обработчик, безусловно, добавляем.

Но общую картину это не меняет. 

По второй догадке - тоже есть смутные сомнения (использую Атмегу168), но пока еще не проверил.. попробую уменьшить размер массива. 

Завтра отчитаюсь (и выложу "актуальную" версию скетча).

leshak
Offline
Зарегистрирован: 29.09.2011

ustas пишет:

Первая причина вызывает проблемы, но не сразу :). Так что условие такое в обработчик, безусловно, добавляем.

Но общую картину это не меняет. 

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

ustas пишет:

По второй догадке - тоже есть смутные сомнения (использую Атмегу168), но пока еще не проверил.. попробую уменьшить размер массива. 

У нее мозгов не так уж и много. Сделайте массив меньше и посмотрите что будет.

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

Скорее всего памяти не хватает, вы объявили массив в 200 элементов по 4 байта, в сумме получилось 800 байт, а у 168 всего 1 кбайт.
Попробуйте сохранять не текущее значение millis(), а разницу между текущим и предыдущим, тогда можно будет тип массива изменить на unsigned int.

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

Если работа программы будет менее 1 минуты, то, в принципе, можно поменять тип массива unsigned long на unsigned int и так.

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

Если в приведенной программе закоментить 33 строку, то, ввиду того, что обращений к массиву в программе нет, компиллятор (при оптимизации) этот массив вообще выбрасывает - и все начинает работать. Так что проблемма именно с нехваткой памяти.

ustas
Offline
Зарегистрирован: 12.03.2012

Текущий код

#define IRrec  2

#define MAX 50

int pin = 5;
volatile int state = HIGH;

volatile int heartbit = 0;

volatile unsigned long seq[MAX];
 
void setup()
{
  Serial.begin(115200);
  Serial.println("IR rec");
  pinMode(pin, OUTPUT);
  digitalWrite(IRrec, HIGH);
  attachInterrupt(0, isr_pulse, CHANGE);
}
 
void loop()
{
  digitalWrite(pin, state);

  if(heartbit>MAX){
    detachInterrupt(0);
    Serial.println("----");    
  }
}
 
void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {heartbit++;
  seq[heartbit]=micros();}
}

Это работает....

AlexFisher, в скетче ограничение на длинну исключительно для того, чтобы проверить работоспособность (дальше нужно захватывать полную последовательность). Вообще это условие предполагалось заменить на что-то типа "если после последнего CHANGE прошло более 1с", то "остановиться в записи и выдать зафиксированные времена).

Совместными усилиями разобрались, что "не взлетает" из-за того, что не хватает памяти.. 

Теперь модифицирую код следующим образом:

#define IRrec  2

#define MAX 200

int pin = 5;
volatile int state = HIGH;

volatile int heartbit = 0;
volatile unsigned long int pulse;
volatile unsigned long int prev;
volatile unsigned int seq[MAX];

 
void setup()
{
  Serial.begin(115200);
  Serial.println("IR rec");
  pinMode(pin, OUTPUT);
  digitalWrite(IRrec, HIGH);
  attachInterrupt(0, isr_pulse, CHANGE);
}
 
void loop()
{
  digitalWrite(pin, state);

  if(heartbit>0 && (micros()-prev)>5e6){
    detachInterrupt(0);
    Serial.println(heartbit);
    Serial.println("----"); 
    heartbit=0;
    
  }
}
 
void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {
    pulse = micros();
    seq[heartbit] = (int)(pulse-prev);
    heartbit++;
    prev = pulse;
  }
}

И тут уже снова "грабли" - условие останавливается в произвольном месте...

В сериал все выводится, но heartbit (несколько раз пробовал) каждый раз разное (и маленькое: 3, 16, 9 и т.п.), на осциллографе вижу соответствующую картину (в сериал вылетело 9):

В чем теперь может быть проблема?

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

Я бы на вашем месте убрал в 41 строке преобразование в int

seq[heartbit] = pulse-prev;

 

AlexFisher
AlexFisher аватар
Offline
Зарегистрирован: 20.12.2011

В строке 27 условие подразумевает что первое измерение должно начаться не познее 5 секунд от старта скетча.

Вообще это условие странное...

Я бы заменил на

if(millis()>10000L){

Вывод информации и окончание работы через 10 секунд после запуска скетча.

 

ustas
Offline
Зарегистрирован: 12.03.2012

maksim пишет:

Я бы на вашем месте убрал в 41 строке преобразование в int

seq[heartbit] = pulse-prev;

уберу, а почему так лучше?

ustas
Offline
Зарегистрирован: 12.03.2012

AlexFisher пишет:

В строке 27 условие подразумевает что первое измерение должно начаться не познее 5 секунд от старта скетча.

Вообще это условие странное...

Я бы заменил на

if(millis()>10000L){

Вывод информации и окончание работы через 10 секунд после запуска скетча.

 

в строке 27 я ожидал отработку ситуации "уже что-то считано (heartbit увеличивался в функции обработки прерываний) и от времени последнего вызова функции обработки прерываний прошло 5e6 микросекунд". 

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

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

ustas пишет:

maksim пишет:

Я бы на вашем месте убрал в 41 строке преобразование в int

seq[heartbit] = pulse-prev;

уберу, а почему так лучше?

В конечном итоге оно не на что не влияет, кроме как лишяя оперция, но вот посмотрите что происходит при преобразовании:

void setup() {
  Serial.begin(9600);
  
  unsigned long a = 40000;
  Serial.println(a, DEC); 
  unsigned int b = (int)a;
  Serial.println((int)a, DEC);  
  Serial.println(b, DEC);
}
void loop(){}

 

ustas
Offline
Зарегистрирован: 12.03.2012

Спасибо, понятно. Правда, компилятор это подкорректировал самостоятельно, похоже. Что с преобразованием, что без - размер скетча идентичный.

Теперь бы еще разобраться с условием...

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

С условием кажется достаточно просто, всё уже есть, можно, например, так:

volatile bool stop = false;

void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {
    pulse = micros();
    seq[heartbit] = pulse-prev;
    if( ( pulse - prev ) > 1e6 )
    {
      stop = true;
    }
    heartbit++;
    prev = pulse;
  }
}

А в loop проверять, если флаг установлен, значит переполнение есть, можно останавливать.

Код не сильно оптимизирован, для примера.

Я неспеша решаю ту же задачу, но через захват таймера 1, правда, когда закончу - неизвестно, хотя процентов на 85 уже готово, Ваш путь другой, тоже интересно, чем закончится. Вот только осуиллографа у меня нет, эхх.. :)

UPD: после 48 строки, нужно еще по переполнению массива закончить:

else
{
  stop = true;
}

leshak
Offline
Зарегистрирован: 29.09.2011

 

maksim пишет:
В конечном итоге оно не на что не влияет, кроме как лишяя операция

Нет тут лишней операции. Справа у нас long, слева int. Так что операция "преобразование" у нас должна быть выполнена в любом случае.

Только в одном случае мы сами явно сказали о ее необходимости, а вдругом компилятор "сам догадался". Так что " компилятор это подкорректировал" - верно. Только в обратную сторону - он не "убрал лишние преобразование", а "добавил упущенное".

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

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

 

leshak пишет:

maksim пишет:
В конечном итоге оно не на что не влияет, кроме как лишяя операция

Нет тут лишней операции. Справа у нас long, слева int. Так что операция "преобразование" у нас должна быть выполнена в любом случае.

Только в одном случае мы сами явно сказали о ее необходимости, а вдругом компилятор "сам догадался". Так что " компилятор это подкорректировал" - верно. Только в обратную сторону - он не "убрал лишние преобразование", а "добавил упущенное".

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

Так в том и дело что справа unsigned long, а слева unsigned int, а преобразовываем в int. Зачем?
Я понимаю что unsigned int и int места занимают одинаково и при преобразовании содержимое не меняется, но типы то разные.

ustas
Offline
Зарегистрирован: 12.03.2012

не выходит с условием... 

void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {
    pulse = micros();
    seq[heartbit] = pulse-prev;
    if( ( pulse - prev ) > 1e6 ) {
      stop = true;
    }
    heartbit++;
    prev = pulse;
  }
  else {
    stop = true;
  }
}

Вот такая функция. Вроде как по логике - все супер, но не работает. stop  сразу же принимает true (осциллограф подтверждает).

Может, есть какие-то ограничения?.. почему ардуина разницу во времени-то не отрабатывает?

Причем, условие с millis() - работает чудесно...

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

Так естественно сразу, предположим через секунду как запустили дуину происходит срабатывание прерывания, prev изначально равна 0, а micros() уже больше 1000000, вот и выполняется ваше условие сразу.

ustas
Offline
Зарегистрирован: 12.03.2012

так-так-так... kiksoft малость запутал :) 

maxim, чуток подправил условие (добавил проверку, что у нас там что-то уже "натикало" - мы же хотим получить "конец всей последовательности")

void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {
    pulse = micros();
    seq[heartbit] = (int) (pulse-prev);
    if( (heartbit >2) && (( pulse - prev ) > 1e5) ) {
      stop = true;
    }
    heartbit++;
    prev = pulse;
  }
  else {
    stop = true;
  }
}

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

leshak
Offline
Зарегистрирован: 29.09.2011

Особо не вникая, просто "общие соображения". Для декодирования, возможно, лучше было смотреть не CHANGE, а FALLING или RISING.

Вообщем сами "провалы" у вас почти всегда одинаковой длины. Зачем же тратить память на их замер (а потом при декодировании выяснить кто у нес где, где был HIGH, где был LOW). Вполне можно использовать "сумма плато и провала" что-бы отличать нули от едениц.

Вот смотрите, я объеденил, к примеру по FALLING какие интервалы вы намеряете

 

Красный прямоугольник явно "стартовая пульсация", белый - длинная,  зеленый - коротка (не помню сейчас что означает длинная короткая 1/0 или 0/1).

Не видно что-бы вы вообще где-то проверяли этот stop. 

Проверка. Зачем такая сложная? вы же строчкой выше уже посчитали интервал и положили его в  seq[heartbit], зачем еще раз это вычитание? Возмите уже готовое.

В третьих я бы вообще не пытался делать каких-то проверок (кроме выхода за границы массива) в isr(). Или, в крайнем случае пытался там "ловить" не "конец", а "начало". Что-то типа 


if(seq[heartbit]>START_PULSE){start=true;heartbit=0}// стартовый импульс тоже можем не запоминать
else if(start)heartbit++;

 

И только тогда начинал заполнять массив.

Проверку "остановки" - я бы выполнял  в loop(). Или если "массив закончился" или "мы получили ожидаемое количество пульсаций" либо "что-то долго вообще prev не менялся, пора попытатся декодить что наловили, очистить массив, сбросить heartbit и ждать следующего START_PULSE"

loop(){
if(heartbits>SOME_VALUE || millis()-prev>TIMEOUT){
  ... пытаемся декодить...
   heartbits=0;
  start=false;
}
}

 

leshak
Offline
Зарегистрирован: 29.09.2011

Кстати, не подскажете что за осцилограф?

ustas
Offline
Зарегистрирован: 12.03.2012

leshak, к сожалению, смотреть только FALLING не пойдет... скетч не одноразовый (пока по крайней мере, планируется). И пульты могут быть разные (это даже на осциллограммах успело проскочить: верхние две картинки это пульт от ресивера ямаха, а третья - пульт кондея) и видно, что даже "стартовые пульсации" разные и т.п.)

полный код скетча на сегодня:

/*
скетч для 
1. захвата сигнала с ИК-приемника
2. передачи сигнала через ИК-светодиод (позже)


D3 (PWM) - ИК-светодиод TSAL4400
D2 - ИК-приемник TSOP31238 (
D5 (PWM) - Светодиод 

*/

#define IRrec  2
#define LED 5
#define IRled 3

#define MAX 300

volatile boolean state = HIGH;

volatile int heartbit = 0;
volatile unsigned long int pulse;
volatile unsigned long int prev;
volatile unsigned int seq[MAX];

volatile bool stop = false;
 
void setup()
{
  Serial.begin(115200);
  Serial.println("IR rec");
  pinMode(LED, OUTPUT);
  pinMode(IRled, OUTPUT);
  digitalWrite(IRrec, HIGH);
  attachInterrupt(0, isr_pulse, CHANGE);
}
 
void loop()
{
  digitalWrite(LED, state);

    //if(heartbit>2 && (micros()-prev)>5e6){
    //if(heartbit>0 && stop){
    if((heartbit>0 && millis()>10000L) || stop){
    detachInterrupt(0);
    Serial.println(heartbit);
    Serial.println("----"); 
    for (int i=0; i<heartbit; i++){
      Serial.print(i);
      Serial.print(" ");
      Serial.println(seq[i]);
    }
    heartbit=0;   
  }
}
 
void isr_pulse()
{
  state = !state;
  if(heartbit<MAX) {
    pulse = micros();
    seq[heartbit] = (int)(pulse-prev);
    //if( (heartbit >2) && (( pulse - prev ) > 1e5) ) {
    //  stop = true;
    //}
    heartbit++;
    prev = pulse;
  }
  else {
    stop = true;
  }
}

а осциллограф DSO Quad. Очень удобная вещь оказалась. Правда, штатное ПО оказалось с глючком (иногда не давал "пролистать" записанный сигнал), но вчера поставил прошивку от энтузиастов - вроде работает, но реально еще потестить не успел. После обновления скриншоты станут чуть другими (но принцип - тот же).

leshak
Offline
Зарегистрирован: 29.09.2011

Ударюсь немного в "предания старины" и "сам себя не похвалишь - сидишь как оплеванный" :)

Я, пару лет назад, знакомство с электроникой как раз с построение IR-декодера и начинал. Поставил себе задачу - "сделать устройство от начала до конца". Купил ардуину, освоил фоторезист, сделал исвою плату, научился камни прошивать, нашел дешовую замену FT232, сделал корпус....

Так вот. Пульт который мне приглянулся "по дизайну" был Philips. И только после того как купил его я обнаружил что IRRemote - не умеет ее декодировать. Пришлось, таки, разбиратся со всеми этими кодировками, разгадывать ребусы (без осцилографа :) и дотачивать ее до поддержки филипсовского протокола. А потом добавлять в нее возможность отключать неиспользуемые протоколы (все прототипировалось на arduino меге, а когда начал заливать в конечное устройство обнаружил что "не влазит" в atmega8 ).

Да, к чему это я:

ustas пишет:

leshak, к сожалению, смотреть только FALLING не пойдет... скетч не одноразовый (пока по крайней мере, планируется). И пульты могут быть разные (это даже на осциллограммах успело проскочить: верхние две картинки это пульт от ресивера ямаха, а третья - пульт кондея) и видно, что даже "стартовые пульсации" разные и т.п.)

Не хочется спорить, но вы все-таки попробуйте мой совет. Хотя-бы мысленно. Нарисуйте на все карнитнках разбивку по FALLING, посмотрите что выходит. Не буду утверждать что "ну прямо все пульты/кодировки знаю", всегда есть шанс на "гиперэкзотику", но общий принцип у них общий. Сама длина импульса (падение в ноль) - не важна, кодирование происходит временем между импульсами.

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

Вообщем мое IMHO что FALLING тут подходит. По крайней мере ко всем примерам картинок что вы постили. Просто сделайе и выведите интервалы межуд FALLING. Это вообщем-то и будет то что в IRRemote называлось raw :)