Использование прерываний в качестве флагов/фиксаторов без какого-либо ISR

У меня есть Arduino Mega, периодически выполняющая операцию (~ 400 мс), которая чувствительна к повторяемости времени, поэтому я не хочу, чтобы она когда-либо прерывалась. Но я хотел бы иметь возможность использовать флаг прерывания от одного из встроенных периферийных устройств (PCINT, компаратор и т. д.), чтобы впоследствии проверить, произошло ли когда-либо внешнее событие (скажем, кратковременное повышение уровня вывода) во время операция.

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

р>

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

Просматривая документацию к каждому из периферийных устройств, было неясно, будут ли по-прежнему устанавливаться флаги при отключении самих прерываний. Где-то я видел упоминание о "EMPTY_INTERRUPT" макрос, но я предполагаю, что он все равно генерирует вектор прерывания с одним «возвратом». инструкции, так что я все равно буду терять циклы из-за преамбулы прерывания и т. д.

Можно ли это сделать вообще без вызова ISR? Или мне нужно добавить больше оборудования? Я не уверен, что перенос длительной задачи в noInterrupts()/interrupts() — это правильный выбор, если только это не мешает приему UART. Спасибо!

, 👍2

Обсуждение

Согласно [моей странице о прерываниях](http://gammon.com.au/interrupts), минимальное время выполнения ISR составляет 2,625 мкс плюс все, что делает код (например, устанавливает флаг). Это неприемлемо? Один раз? В вашей рутине на 400 мс? Это лишь малая часть того времени, которое занимает ваш код., @Nick Gammon

*Я не уверен, что перенос длительной задачи в noInterrupts()/interrupts() — это правильный выбор, если только это не мешает приему UART.* - так что некоторые прерывания на самом деле допустимы?, @Nick Gammon

Если вы получаете сигнал от UART, то время не имеет особого значения, не так ли? Какова скорость передачи данных?, @Nick Gammon

Каковы конкретные требования к этой периодической работе, джиттеру, частоте, допускам и т. д.?, @the busybee

Я стробирую светодиоды, чтобы обеспечить равномерное освещение примерно дюжины последовательных экспозиций промышленной камеры (используя ожидание занятости). Речь идет не столько о точном времени, сколько о повторяемости времени. Каждое воздействие длится всего пару сотен микросекунд, поэтому даже периодическое дрожание в 2,625 мкс увеличит время воздействия как минимум на 1%. Я мог бы использовать один из таймеров, но предпочел простоту прямого кода. Я не знал, что UART прерывает работу процессора; Мне придется просмотреть таблицу данных и, возможно, задать по этому поводу отдельный вопрос., @Nicholas

В соответствии с вашими требованиями я бы рассмотрел возможность добавления внешнего оборудования для обнаружения и буферизации состояния, предоставляя вам GPIO, который вы можете опросить, где высокий уровень означает «событие произошло с момента последнего его сброса». Я думаю, вы могли бы сделать это легко и дешево. Вы можете спросить на бирже стека электроники, как лучше всего подойти к этому вопросу. Тогда вам вообще не придется иметь дело с прерываниями., @Glenn Willen

... Извините, перечитав, я вижу, что вы рассматривали этот вариант и искали альтернативы., @Glenn Willen


2 ответа


Лучший ответ:

6

Ответ на ваш вопрос: да, вы можете использовать флаг прерывания даже если само прерывание отключено. Флаг поднимается всякий раз, когда периферийное устройство обнаруживает соответствующее событие. Вы можете проверить это у себя удобство.

Что касается сброса флага, способ сделать это варьируется от флага к флагу. Большинство из них сбрасываются записью в него 1 бита (да, это как бы наоборот, но это так работает). Остальные сбрасываются удалением условия это привело к их возникновению в первую очередь (например, прерывание по пустому регистру данных USART). Вам нужно будет проверить таблицу данных для конкретного интересующий вас флаг прерывания.

При этом я бы поставил под сомнение вашу идею о том, что вы не хотите код, который нужно прервать вообще. Как пишет в комментарии Ник Гаммон, минимальное время выполнения ISR составляет 2,625 мкс. Это около 0,0007% периода процесса. Если ваша операция настолько чувствительна ко времени, что не может терпеть такого рода вариации, то он не может работать на Mega at все. Керамический резонатор, который синхронизирует Mega, имеет типичную точность около 0,1%, что в 150 раз хуже эффекта минимального прерывать. Даже если вы откалиброваете этот генератор по атомному часов, всего через несколько часов его частота превысит 0,0007%.

Кроме того, если вы хотите, чтобы код работал без прерываний, вам придется отключить их явно с помощью noInterrupts(). Тогда ты будешь потеряйте функцию синхронизации Arduino millis(), delay() и т. д. И вы не сможет получать данные от UART, если вы не напишете свои собственные код, который опрашивает флаг прерывания UART «получение приема», а затем извлекает данные из регистра данных UART.

,

Вы уверены в этом? В таблице данных указано *Внешние прерывания 7–4 активируются внешними выводами INT7:4, если установлен I-флаг SREG и соответствующий маска прерывания в EIMSK установлена.* Насколько я понял, флаг устанавливается, если прерывание разрешено. Иначе как бы он узнал, растет он или падает?, @Nick Gammon

Я могу ошибаться в этом, признаю., @Nick Gammon

@NickGammon: В режиме обнаружения фронта флаг устанавливается, даже если прерывание отключено (я только что проверил). Однако это плохой пример, поскольку флаг прерывания не работает в режиме определения уровня: «_Эти флаги всегда сбрасываются, когда INT7:0 настроен как прерывание уровня._» Я изменил пример в своем ответе., @Edgar Bonet

Я тоже оставил комментарий выше, но фактический период процесса составляет ~ 200 мкс (повторяется много раз) при экспонировании изображения на датчик камеры. И он менее чувствителен к какой-либо конкретной тактовой частоте. Я просто хочу, чтобы каждое идентичное ожидание занятости занимало одинаковое время по настенным часам, чтобы не было никакой разницы в яркости между каждой экспозицией. Даже эти 2,6 мкс дают около 1,3% дополнительной освещенности. На 12-битном сенсоре это начинает быстро складываться. UART намеренно не отправляет и не принимает данные в течение этого периода, так что, надеюсь, можно просто оставить его включенным..., @Nicholas

Мне кажется, что вопрос о том, установлен ли флаг или нет, может зависеть от реализации. Даже если для чипов AVR это гарантировано на 100%, для других это может быть не так, поэтому полагаться на такое поведение не очень хорошая практика., @Mark Morgan Lloyd

@MarkMorganLloyd, весь код специфичен для одного конкретного чипа, так что это не имеет смысла., @hobbs

@hobbs: Ни моя Mega, ни [тот, который показан в магазине Arduino](https://store.arduino.cc/products/arduino-mega-2560-rev3). Если только вы не говорите о кварце, синхронизирующем 16U2, который выполняет преобразование USB в последовательный порт, что не имеет отношения к задаваемому вопросу., @Edgar Bonet

@hobbs на самом деле, ОП указывает «Arduino Mega», а не конкретный чип, даже если это будет несколько уточнено в последующем обсуждении. Однако использование флагов прерываний таким способом кажется (по крайней мере мне) фундаментальным проектным решением, и даже если ожидается, что это будет одноразовый хобби-проект, подобные вещи рискуют вызвать огромные проблемы в будущем, если кто-то else пытается использовать код, или если на рынке появится более подходящий чип., @Mark Morgan Lloyd


2

Я не уверен, что перенос длительной задачи в noInterrupts()/interrupts() — это правильный выбор...

Правильно. Отключение прерываний приведет к прекращению работы таймера/задержки, поэтому вы не сможете рассчитать время 200 мкс (если вы не используете задержкумикросекунды).

Каждое воздействие длится всего пару сотен микросекунд, поэтому даже периодическое дрожание в 2,625 мкс увеличит время воздействия как минимум на 1%.

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

Я не знал, что UART прерывает работу процессора...

Входящие (или даже исходящие) последовательные соединения вызывают прерывания. Таймеры вызывают прерывания. В частности, если вы не отключили его, Таймер 0 генерирует прерывания, которые учитываются, чтобы функции millis() и micros() могли работать. Вы можете отключить прерывания, но тогда определение точного времени интервалов станет проблематичным. Мое предложение использовать аппаратное обеспечение таймера обеспечит наиболее надежную и повторяемую работу, точно так же, как мое генерирование сигнала VGA было полностью без джиттера.

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


Другой подход — отключить прерывания только для воздействия (т. е. ваши 200 мкс, а не упомянутые вами 400 мс), а затем использовать задержкуМикросекунды() для определения времени воздействия. Затем, когда прерывания снова активируются, замыкание переключателя (внешнее прерывание) может быть обработано обычным ISR. Это может добавить небольшие различия во времени между воздействиями, но само время воздействия будет постоянным.

Даже при отключенных прерываниях внешнее прерывание «запоминается». и будет обработан, когда прерывания снова будут включены.


Это превращается в проблему XY. Было бы лучше упомянуть ваши точные требования (часть фотографии), а не задаваться вопросами об использовании прерываний в качестве задержек.


Пример кода

Чтобы продемонстрировать, как это можно сделать с помощью таймеров, я написал пример кода:

// Пример стробирования вспышки FRAMES_TO_TAKE раз для съемки
// Время между каждой вспышкой составляет TIME_BETWEEN_SEQUENCES
// Вспышка "включена" время через STROBE_ON_TIME
//
// Для Atmega328P (Arduino Uno и аналогичные)
// и Atmega2560 (Arduino Mega)

// Автор: Ник Гаммон
// Дата: 4 июля 2023 г.

const byte FRAMES_TO_TAKE = 12;
const byte CPU_CLOCK_SPEED = 16;  // МГц

const unsigned long TIME_BETWEEN_SEQUENCES = 20000;  // мкс
const unsigned long STROBE_ON_TIME = 200;  // мкс
const byte PRESCALER = 64;

// устанавливаем правильный выходной контакт
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
  const byte FLASH_PIN = 10;  // OC1B (Таймер сравнения выходов 1: сторона B) — Atmega328P
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  const byte FLASH_PIN = 12;  // OC1B (Таймер сравнения выходов 1: сторона B) — Atmeg12560
#else
  #error Processor not supported
#endif

volatile byte count;  // сколько раз он выстрелил

// Таймер 1. Сравнение "B" вектор ISR
// подсчитаем необходимое количество экспозиций, затем остановимся
ISR (TIMER1_COMPB_vect)
  {
  if (++count > FRAMES_TO_TAKE)
    {
    // остановка таймера 1
    TCCR1A = 0;
    TCCR1B = 0;
    } // заканчиваем, если достигнуто количество кадров
    
  }  // конец TIMER1_COMPB_vect

// делает одну последовательность кадров FRAMES_TO_TAKE, а затем останавливается
void startSequence ()
{
  // остановка таймера 1
  TCCR1A = 0;
  TCCR1B = 0;

  TCCR1A = bit (WGM10) | bit (WGM11);  // быстрый ШИМ — верх на OCR1A
  TCCR1B = bit (WGM12) | bit (WGM13);  // (режим 15)

  TCCR1A |= bit (COM1B1);   // Установка OC1B во время рабочего цикла
  
  OCR1A = ((TIME_BETWEEN_SEQUENCES * CPU_CLOCK_SPEED) / PRESCALER) - 1;  // общее время цикла
  OCR1B = ((STROBE_ON_TIME * CPU_CLOCK_SPEED) / PRESCALER) - 1;          // время рабочего цикла
  count = 0;

  TCNT1 = 0;    // сброс счетчика таймера
  
  TIFR1   = bit (OCF1B);    // очищаем "сравнить B" флаг
  TIMSK1  = bit (OCIE1B);   // прерывание при сравнении совпадения B
  TCCR1B |= bit (CS10) | bit (CS11);     // запускаем таймер с прескалером 64
} // конец startSequence

void setup ()
  {
  // остановка таймера 1
  TCCR1A = 0;
  TCCR1B = 0;

  // устанавливаем пин, чтобы мы могли его переключать
  pinMode (FLASH_PIN, OUTPUT);
  digitalWrite (FLASH_PIN, LOW);

  // для отладки и т. д.
  Serial.begin (115200);
  }  // конец настройки

void loop ()
  {
  startSequence ();
  delay (20);
  Serial.print ("But does it get goat's blood out?\n");
  delay (1000);
   }  // конец цикла;

Пояснение кода

Этот код использует Таймер 1 для определения времени необходимых вам интервалов. На основании того, что вы опубликовали ранее, я предположил:

  • 12 показов за «сеанс»; (FRAMES_TO_TAKE) – вы сказали «дюжина или около того»;
  • Воздействие должно быть с интервалом 20 мс (TIME_BETWEEN_SEQUENCES), поэтому длительность сеанса составляет 240 мс.
  • Время экспозиции должно составлять 200 мкс (STROBE_ON_TIME) — вы сказали «пару сотен микросекунд»;
  • Вы хотите заняться другими делами, например последовательной связью, проверкой переключателей и т. д.

Функция startSequence запускает таймер с периодом 20 мс и рабочим циклом 200 мкс. По завершении рабочего цикла вызывается ISR, который подсчитывает рабочие циклы. По окончании названного числа (12) таймер выключается.

Выходной контакт (контакт 12 на Mega, контакт 10 на Uno) включен во время рабочего цикла (т. е. на 200 мкс и выключен в остальное время). Поскольку это реализовано аппаратно, джиттера вообще нет. В своей демонстрации я использую последовательную связь, чтобы показать, что интервалы правильные, несмотря на прерывания, вызванные связью.


Вывод логического анализатора

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

Выход логического анализатора

На втором снимке экрана показано, что в этом интервале 12 импульсов.

Логический анализатор, показывающий количество импульсов

Я использовал прескалер 64, чтобы получить хороший длинный интервал, который можно рассчитать по времени (без переполнения 16-битного размера счетчика). Это дает степень детализации 64 тактовых цикла (4 мкс).


Шпаргалка по таймеру 1


Шпаргалка по таймеру 1

,

Это все хорошие предложения. Отключение прерываний для отдельных экспозиций кажется правильным решением. Я избегал таймеров только из-за удобства, поскольку мне не приходилось думать об обычных проблемах ISR: атомарное (16-битное) чтение/запись и т. д., но моя схема достаточно проста, и я, вероятно, мог бы просто установить таймер, занятое ожидание пока он не выстрелит, а затем продолжаю свой путь. Что касается проблемы XY, для меня это все еще интересный вопрос, и я рад, что знаю ответ для будущих проектов, даже если в конечном итоге я не буду использовать здесь подход с флагами без прерываний., @Nicholas

@Nicholas Если вы позволите таймеру самому изменить состояние контакта, вы устраните (небольшое) дрожание в зависимости от того, где в вашем жестком цикле проверка таймера на завершение, он фактически завершается. Чтение состояния таймера, сравнение и переход, вероятно, займут от 6 до 8 тактов., @Nick Gammon

Вы правы в том, что проверка флага таймера в программном обеспечении вызывает некоторое дрожание, но это не так уж плохо. Плотный цикл проверки флага на таймере 1, 3, 4 или 5 — это просто sbis + rjmp. Это занимает 3 цикла, таким образом, у вас есть 2-тактный джиттер. Таймеры 0 и 2 требуют lds, что делает цикл 5-тактным., @Edgar Bonet

@Nicholas См. измененный ответ с примером кода использования таймера., @Nick Gammon