Какой правильный способ запроса устройства I2C из процедуры обслуживания прерывания?

Например, для ADXL345 требуется, чтобы прерывания к, запросив регистр INT_SOURCE (источник прерывания). В примере кода для библиотеки SparkFun ADXL345 Arduino есть код, который выглядит так:

void ADXL_ISR() {
  // getInterruptSource очищает все запущенные действия после возврата значения
  // Не вызывайте снова, пока вам не понадобится перепроверить сработавшие действия
  byte interrupts = adxl.getInterruptSource();

  // Обнаружение свободного падения
  if(adxl.triggered(interrupts, ADXL345_FREE_FALL)){

Однако под прикрытием adxl.getInterruptSource() отправляет сообщение I2C и получает ответ. Это кажется плохой идеей в подпрограмме обслуживания прерываний (ISR).

Кажется, возможным вариантом является включение прерываний перед вызовом adxl.getInterruptSource(), однако есть много советов не делать этого (например, информативный пост Ника Гэммона). Мне удалось заставить его работать с чем-то вроде:

void setup() {
  attachInterrupt(digitalPinToInterrupt(INT_PIN), isr, LOW);
  ...
}
void isr() { 
  detachInterrupt(digitalPinToInterrupt(INT_PIN));
  interrupts();
  byte interrupts = adxl.getInterruptSource();
  ...
  attachInterrupt(digitalPinToInterrupt(INT_PIN), isr, LOW);
}

Кажется, это работает, но я боюсь, что что-то упускаю. Есть предложения?

РЕДАКТИРОВАТЬ: Спасибо за ответы, предлагающие опрос, однако я использую MKR1400 и хочу минимизировать энергопотребление, переводя процессор в спящий режим - просыпаясь только для обработки относительно редкой «активности» или «свободного падения». события. Как указал @Majenko, можно использовать комбинацию сна и опроса. Таким образом, остается только вопрос, почему это плохая идея.

, 👍3

Обсуждение

Я думаю, что ваше решение разрешено, но оно все еще совершенно безумно. Другие прерывания могут быть пропущены, потому что это занимает слишком много времени. Использование функции adxl.getInterruptSource в ISR, вероятно, связано с интерфейсом SPI. Можно ли использовать датчик с режимом SPI?, @Jot

@Jot спасибо за ответ. Я не совсем понимаю, почему пропускается еще одно прерывание (но очень рад быть образованным). Мы не можем перейти на SPI, и, по-видимому, он должен иметь возможность взаимодействовать с такими устройствами с помощью I2C., @James Brusey

Например, Timer0 работает с прерыванием 1 мс для миллис или при последовательном вводе со скоростью 115200 бод. Я думаю, что что-то пойдет не так со всем вместе., @Jot

@Jot, это было бы правдой, если бы я не снова включил прерывания во второй строке ISR. Как бы то ни было, прерывания отключаются только во время вызова detachInterrupt., @James Brusey


3 ответа


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

9

Тот факт, что контакт называется "прерыванием", не означает, что вы должны считывать его с помощью входа прерывания. Выводы INTx ADXL345 являются простыми «уровневыми» выходами. Все время ожидания "прерывания" (сигнал от ADXL345 о том, что что-то произошло) соответствующий вывод INT удерживается НИЗКИМ. Он остается НИЗКИМ до тех пор, пока прерывание не будет обработано вашим кодом.

Это не что-то очень краткое — это то, что сохраняется столько, сколько вам нужно.

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

,

Хотя это правда - это не помогает, когда у вас маломощное приложение. Опрос будет означать, что основной процессор постоянно включен, тогда как, если я управляю прерыванием, основной процессор может находиться в состоянии «сна» с низким энергопотреблением., @James Brusey

Вы все еще можете опросить и спать. Они не исключают друг друга. Опрос булавки прерывания., @Majenko

Хорошо - это немного громоздко, так как это означает, что вам нужно убедиться, что вы просыпаетесь *достаточно часто* для обслуживания прерываний. Можете ли вы помочь мне понять причину, по которой возникает проблема с отключением обработчика прерываний, включением прерываний и последующим продолжением?, @James Brusey

Что обременительно? Просыпайтесь по контакту прерывания, затем семплируйте в своем цикле. Или установите флаг в процедуре прерывания и используйте его, чтобы решить, следует ли выполнять выборку., @Majenko

ISR должны быть короткими. Во время работы ISR больше ничего не происходит. Вещи, которые используют прерывания, не могут использоваться внутри ISR. Лучше делать как можно меньше в ISR., @Majenko

Громоздкая часть заключается в том, что время опроса должно быть кратчайшим временем, которое обычно происходит между прерываниями, чтобы убедиться, что вы не пропустите ни одного. Это не может быть легко различимо. В моем случае это, возможно, около 10 мс, что означает, что у меня все еще будет довольно значительный рабочий цикл. Хотя, я согласен с вашими общими принципами - они именно таковы., @James Brusey

Время, затрачиваемое на опрос, ничтожно мало по сравнению со временем, затрачиваемым на выполнение операций I2C. Если вы спите и вас разбудил низкий уровень на выводе прерывания, вы можете сразу же перейти в цикл while, обрабатывающий любые ожидающие события, пока на выводе прерывания не появится высокий уровень. Где громоздкий компонент там? Тот факт, что вы проснулись, является вашим «прерыванием» для начала обработки любых доступных событий. Примечание. Я использую «события» для обозначения вещей, о которых ADXL хочет вам сообщить, и «прерывание» для запуска ISR, чтобы различать их., @Majenko

Если я вас правильно понял, вы говорите: есть ISR, который просто отключает прерывание; в основном цикле sleep_until_interrupt(); при пробуждении проверьте, низкий ли уровень на выводе, и если да, ответьте на I2C и повторно подключите прерывание. Спасибо! Вы хотите обновить свой ответ?, @James Brusey


2

"Как правильно запросить устройство I2C из процедуры обслуживания прерывания?"

Правильно НЕ запрашивать устройство I2C из ISR. Подпрограммы обслуживания прерываний должны очень выполняться быстро. (люди советуют избегать вызовов функций digitalWrite() и digitalRead() и напрямую использовать регистры портов, поскольку эти функции немного медленные. Все, что делается на последовательном линия по определению не быстрая. Чтение или запись нескольких символов из последовательного порта занимает несколько миллисекунд, а это целая вечность.)

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

ПРОСТО НЕ ДЕЛАЙТЕ ЭТОГО.

,

Я не уверен, что вы поняли мой код - я разрешаю прерывания сразу после вызова detachInterrupt. Период времени, когда прерывания отключены, не будет очень долгим., @James Brusey


5

Ответ Маженко полезен — вы можете просто отказаться от использования прерываний для этого и опросить вывод непосредственно в своем основном цикле. Особенно на AVR, где внешние выводы выставлены как регистры, которые могут считываться так же быстро, как основная память, это разумный подход. Но что, если вы действительно хотите, чтобы код был «быстрым к прерываниям» без написания кода, который мог бы заблокировать обработчик ISR? Учитывая случай с датчиком падения или удара, задержка может быть реальной проблемой. Что ж, вы можете кое-что сделать.

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

start = micros();
while (micros() - start < howLong) {
  // опрашиваем прерывание ADXL
}

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

Во-вторых, рассмотрите возможность использования прямого доступа к порту для опроса. Проверка наподобие if (PORTB & (1<<3)) компилируется в пару инструкций и стоит 2 цикла, если прерывание на самом деле не установлено (что будет обычным случай). Выполнение if (digitalRead(port)) вызывает функцию, которая занимает примерно в 25 раз больше времени. Это по-прежнему всего несколько микросекунд, но если вы воспользовались советом предыдущего шага опрашивать как можно чаще, чтобы свести к минимуму задержку, то вы не хотите, чтобы все эти микросекунды складывались и влияли на производительность другого кода.

Наконец, метод, который не имеет особого смысла для этого сценария, но может быть применим, если вы работаете в системе, отличной от Arduino на основе AVR, или если вы хотите чтобы реагировать на какое-то прерывание, отличное, чем прерывание от контакта, запускаемое по уровню: у вас есть ISR, который не выполняет никакой реальной работы, а вместо этого устанавливает глобальную переменную volatile в значение true а потом сразу возвращается. Затем в вашем основном коде вместо прямого опроса порта вы проверяете значение этой переменной. Если это правда, вы устанавливаете его в false, а затем запускаете свой код «обработчика прерываний». Обработчик запускается вскоре после срабатывания прерывания, но он запускается в контексте прерывания, когда безопасно выполнять более медленные операции или операции, для работы которых требуются прерывания по таймеру. Это эквивалентно старой концепции Linux о «нижних половинах».

,

Хороший исчерпывающий ответ. (Проголосовало) Я бы предложил добавить императив «Что бы вы ни делали, не пытайтесь читать последовательный порт из ISR»., @Duncan C

Спасибо за подробный ответ. Также может помочь, если вы сможете описать режим сбоя кода в вопросе (т. е. почему произойдет сбой?). Ваша вторая часть помогла мне больше всего., @James Brusey