Взаимодействие прерываний с delay и между собой

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Как-то не раз встречал информацию, что использование delay мешает аппаратным прерываниям (хотя, судя по документации, не должно http://arduino.ru/Reference/Delay ): то энкодер пропускает импульсы, то сигналы с IR-пульта не проходят и пр.

Понятно, что теоретически при правильном использовании прерыаний такие ситуации происходить не должны. Но раз происходят, значит, где-то прерывания используются неправильно, и, вероятно, такое использование достаточно распространено.

Опять же, кроме INT есть еще и PCINT. Как они взаимодействуют с delay и другими прерываниями?

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

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Может отказаться от делей? И тогда никто не будет мешать.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano,  в принципе delay() обычная программа, которая в while вызывает micros() и сверяет пройдённые тики с заданными. Но микрос()  сам использует прерывание (Timer0_overflow), но главное во время своей работы глобально запрещает любые прерывания. Таким образом если какому-то невезучему прерыванию  захочется выполнится, то сделать оно этого не сможет, и даже не встанет в очередь, как в случае если бы работало другое прерывание.  Решение -не использовать функции ардуино, писать свои, где будут учтены все нюансы :)

PS Вспомнилось, что  собственные функции компилятора avr-gcc  _delay_ms и _delay_us работают просто на подсчёте тактов процессора, так что не должны вызывать подобных проблем :) Только не забыть сделать #include <avr/delay.h>

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

dimax, правильно ли я понял, что функция delay для Ардуино написана вредителями? Ну или теми, кто собирался таким варварским образом отучить начинающих программистов от использования delay?

В связи с этим все-таки остается два вопроса:

1. Если delay сделана именно так, то почему она так сделана. Тут недавно обсуждали, почему в Ардуино нарушена нормальная логика конструкторов объектов. Я высказал предположение, что для того, чтобы дать разработчику вклиниться в код до инициализации железа. То есть люое даже странное решение обычно имеет какие-то плюсы, ради которых оно и было выбрано. Поэтому хотелось бы именно таких предположений. Мне казалось, что delay идеально подходить для выпаолнения альтернативной работы, начиная с прерываний и заканчивая фоновыми процессами. А, гляди ж ты, все сделано наоборот, почему?

2. Чего следует избегать при обновременном применении INT и PCINT, чтобы исключить возможность потери какого-либо прерывания?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

andriano, я ещё в первом сообщении немножко не прав был, прерывание всё равно должно выполнится, если в программе стоял запрет cli().  Как только запрет будет снят. У прерываний есть локальные флаги, в момент когда нужно сработать -ставится флаг, а по флагу уже выполняется прерывание. Соответственно можно инициировать запуск например того же прерывания INT записью флажка в соответсвующий регистр. Но вот отложенное прерывание как раз и может устроить  глюк. Например если по прерыванию считываются какие-то дополнительные порты, данные в которых могли уже изменится. А если выросла очередь из прерываний, то первым выполняется прерывание с меньшим номером (номера в даташите можно посмотреть).

на первый вопрос  компетентно вряд ли могу ответить. Из явного - delay() имеет не только недостатки, но и преимущество.  Вот недостаток  её конкурента _delay_ms в том, что эту функцию легко остановить прерыванием, а вернувшись из прерывания она начнёт досчитывать что ей осталось, не принимая в расчёт время, забранное прерыванием. А ардуиновский delay() такой проблемы не имеет.  А по второму вопросу уже частично описал процесс, тут просто нужно так организовать работу, что бы учитывалась возможность задержки выполнения команд.

Denis_1704
Offline
Зарегистрирован: 28.05.2014

dimax, как останавливать и потом запускать  работу прерываний???  полезная фича типо остановить библиотеку

интернет и 433 в какието ответственные моменты( в микрос()), а по их оканчанию запустить. 

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

Denis_1704 пишет:

как останавливать и потом запускать  работу прерываний???

cli();  // нехрен меня прерывать, я на Луну любуюсь
//
// код любования на Луну
//
sei();  // теперь можете прерывать.

Только это очень серьёзно. Надеюсь, Вы знаете, что делаете и зачем Вам это надо.

Denis_1704
Offline
Зарегистрирован: 28.05.2014

во время отключения прерывания счетчик micros(),millis() работает???

 

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

Denis_1704 пишет:

во время отключения прерывания счетчик micros(),millis() работает???

На все подобные вопросы мой внук получает ответ: "попробуй".

Попробуйте. Это лучший способ узнать.

bwn
Offline
Зарегистрирован: 25.08.2014

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

Denis_1704 пишет:

во время отключения прерывания счетчик micros(),millis() работает???

На все подобные вопросы мой внук получает ответ: "попробуй".

Попробуйте. Это лучший способ узнать.

Классика: "И жопыт, сын ошибок трудных"))))

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

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

Решил сделать например так:

void delaywd (unsigned int timeout, bool wd)
{  
  unsigned long previous;
  previous = millis(); 
  while((millis() - previous) < timeout); 
  if wd==1{wdt_reset();}
}
delaywd (2500,1)

тоесть в функции вставлен еще и перезапуск таймера вочдога.

Покритикуйте,  что может не так? Место заметно сэкономилось, а то уже 31кб флеша занято. Паузы у меня по 2-6 сек в основном. минимум 1кб уже сэкономил, буду и дальше оптимизировать сам скетч.

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Short Circuit пишет:

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

Решил сделать например так:

void delaywd (unsigned int timeout, bool wd)
{  
  unsigned long previous;
  previous = millis(); 
  while((millis() - previous) < timeout); 
  if wd==1{wdt_reset();}
}
delaywd (2500,1)

тоесть в функции вставлен еще и перезапуск таймера вочдога.

Покритикуйте,  что может не так? Место заметно сэкономилось, а то уже 31кб флеша занято. Паузы у меня по 2-6 сек в основном. минимум 1кб уже сэкономил, буду и дальше оптимизировать сам скетч.

 

Раз просил критики, смотри: что будет, если в timeout передать 80000? Во-первых - будет переполнение, если на платформе, под которую компилируешь - unsigned int занимает два байта (это так под 328-ю мегу и мегу 2560). Во-вторых - будешь висеть в while, НЕ сбрасывая собаку - и она сработает. Кошерный код должен быть таким, ПРИМЕРНО:

void delaywd (uint32_t timeout, bool wd)
{  
  uint32_t prev = millis();
  uint32_t now = prev;
  uint32_t elapsed = 0;
  uint16_t wdCounter = 0;

  while(1)
 {
   now = millis();
   uint16_t accum = now-prev;
   elapsed + = accum;
   wdCounter  += accum;
   prev = now;

  if(elapsed >= timeout) 
     break;

  if(wdCounter   > 1000 && wd == 1)
  {
     wdCounter  = 0;
      wdt_reset();
  }
   
 }
}
delaywd (2500,1)

По факту получаем - сброс собаки каждую секунду, пока крутится цикл. Это, ессно, наброски, сделанные на сонную голову.

Short Circuit
Short Circuit аватар
Offline
Зарегистрирован: 17.05.2015

прикольно:)  как много я еще не знаю...

Но, интересный момент, в моем первом варианте если unsigned int timeout то компилирует намного меньше чем если ставить uint32_t timeout ..

тут у меня пробелы, но стараюсь в разных вариантах проверять и делать выводы.

vlad072
Offline
Зарегистрирован: 01.08.2017

Есть классический приём чтения данных с serial, что то типа:

void loop() {
  char inp[128]; int len = 0;
  if (Serial.available())
    while (Serial.available()) inp[len++] = Serial.read();
  //.......
}

Логично, что время последовательного приёма байта в порт на порядок больше времени работы функции read(), которая за микросекунды забирает сразу целиковый байт из буфера, уменьшая счётчик, и если в буфере останется один единственный байт, а следующий будет в процессе приёма, то read() его быстро считает и последующий вызов available() уже вернёт 0. Соответственно строка (конанда и т.п.) будет считаться дочитанной и пойдёт дальше по коду в обработку (парсинг и т.п.), хотя на самом деле это не так. Есть основания подозревать что такое на самом деле происходит, особенно на малых скоростях Serial. Поправьте если я что то не правильно понимаю. И если так, то как с этим бороться?

зы. работаю на скорости 57600 с модемом SIM800, пробовал ставить _delay_us(200) после read(), вроде не легче.

Short Circuit пишет:

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

Если вдруг ещё актуально - намного эффектичней проверять от модема +CREC: 0

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

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

vlad072
Offline
Зарегистрирован: 01.08.2017

Чорт! Как я про таймаут не подумал то... действительно, спасибо за наводку. Поставил для начала 10 мс, будем наблюдать что из этого выйдет.

  byte _atlen = 0; 
  for (uint32_t _trecv = millis(); (millis() - _trecv) < 10ul;) {
    if (!modem.available()) continue;
    char _ch = modem.read(); _trecv = millis();
    if ((_atlen+1) < sizeof(at)) at[_atlen++] = _ch;
  } at[_atlen] = '\0';