Оптимизация кода: Прерывание при достижении значения 0
У меня есть эта рабочая программа, которую я хочу максимально повысить.
Он будет мигать медленно, затем все быстрее и быстрее, пока не перезагрузится.
Это работает, но моя главная проблема заключается в том, что в настоящее время я использую оператор if в цикле
void()
, и я уверен, что использование прерывания, когда конкретная переменная достигла 0, повысит эффективность.
byte amp = 0;
void setup() {pinMode(13, OUTPUT);
reset();
}
void loop() { //Следующий
digitalWrite(13,HIGH); //код для примера
delay(amp*100); // Упрощен, чтобы сосредоточиться на цикле
digitalWrite(13,LOW);
delay(amp*100);
amp--;
if(!amp){reset();} //<-Это прямо здесь
}
void reset() {amp = 10;}
Насколько я понимаю, оператор if вызывается каждый раз, когда цикл перезапускается.
- Можно ли настроить прерывание, когда определенная переменная достигает 0?
- Какой вариант потребует меньше вычислительной мощности, постоянно вызываемого цикла if или прерывания?
Спасибо!
@B7th, 👍1
Обсуждение2 ответа
Лучший ответ:
Вы не указали тип Arduino, который используете, но, основываясь на другом вашем недавнем вопросе, я предполагаю, что это AVR-версия Ардуино.
Вы не можете иметь прерывание, автоматически инициируемое переменной , достигающей нуля. Вместо этого вы можете вызвать прерывание программным обеспечением, переключив PIN, настроенный как выход, так и источник прерывания:
const uint8_t int_pin = 2; // вывод, используемый для прерывания
void setup() {
digitalWrite(int_pin, LOW);
pinMode(int_pin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(int_pin, reset, RISING));
}
static inline void trigger_interrupt() {
digitalWrite(int_pin, HIGH);
digitalWrite(int_pin, LOW);
}
// Внутри цикла():
if (!amp) trigger_interrupt();
Однако если вы сделаете это, то обнаружите, что прерывания происходят медленно.
Ужасно медленно по сравнению с простым тестом if:
digitalWrite()
сама по себе ужасно медленная. Это можно преодолеть, используя прямой доступ к порту, но это все равно два цикла для переключения контакта в каждом направлении.Вы потеряете дополнительный цикл в pin-синхронизаторе.
Как только IRQ повышается, процессору требуется четыре цикла, чтобы подготовиться к его обслуживанию.
Вектор прерывания
- это команда jmp, которая занимает 3 цикла.
Затем ISR должен сохранить каждый регистр, который он будет использовать, включая регистр состояния. Это занимает два цикла на регистр. И есть довольно много регистров, которые нужно сохранить ...
Этот ISR, который предоставляется ядром Arduino, затем будет искать обработчик прерываний, предоставленный вами с
помощью attachInterrupt()
. Это также включает в себя проверку того, что указатель на обработчик не равен нулю (тот тест, о котором вы, кажется, беспокоитесь). Затем он долженвызвать
(4 цикла) ваш обработчик, который должен будетret
urn (4 цикла) к ISR. Вы можете избежать этой косвенности, определив ISR самостоятельно, вместо того чтобы полагаться наattachInterrupt()
.Как только задание будет выполнено, все сохраненные регистры должны быть восстановлены (2 цикла умножаются на много регистров), а
инструкция reti (4 цикла) выдается для того, чтобы восстановить управление прерванной программой.
И последнее, но не менее важное: поскольку
amp
будет изменен в контексте прерывания, вам придется квалифицировать его какvolatile
. Это ключевое слово не позволяет компилятору выполнять оптимизацию, которая в данной ситуации небезопасна, и вы потеряете много времени на эти упущенные возможности оптимизации.
Напротив, эта строка:
if (--amp == 0) amp = 10;
будет переведено компилятором примерно так:
dec amp ; --amp, as amp is likely already in a register
brne 1f ; if (amp != 0) skip the following
ldi amp, 10 ; amp = 10
1:
Вся последовательность занимает 3 цикла, независимо от того, было ли
условие if истинным или ложным. Это, по крайней мере, на порядок
быстрее, чем решение на основе прерываний.
Очень полное объяснение того, как и почему. Спасибо, Эдгар, это помогает лучше понять, как работает сборка!, @B7th
Простые операторы if очень быстры и не сильно влияют на производительность даже при использовании в каждом цикле цикла.
Использование прерывания для реализации чего-то простого, например if (!amp) amp = 10;
, вероятно, замедлит работу:
- что-то должно будет отслеживать счетчик, чтобы генерировать прерывание, и это "что-то" занимает время MCU, если только это не периферийное устройство;
- прерывания имеют накладные расходы;
- в однопоточных микроконтроллерах прерывания останавливают регулярное выполнение кода до тех пор, пока они не будут выполнены;
amp = 10;
все равно придется выполнять в обычном коде или, если нужно, в прерывании.
Вы получите больше, избавившись от вызова функции reset()
, но компилятор, вероятно, сделает это за вас.
Кроме того, я бы не знал, как заменить обычное if
прерыванием, если только счетчик не хранится периферийным устройством (таймером).
Re “_amp = 10;
все равно придется выполнить в обычном коде_“: Зачем вам делать это в обычном коде? Это утверждение не займет больше времени, чем установка логического значения., @Edgar Bonet
@Edgar-Bonet: Да, в этом простом примере это всего лишь одно утверждение, так что здесь это не имеет значения.. Я не из той школы, которая говорит, что вы всегда должны устанавливать флаг только в прерывании и должны делать все остальное в цикле, на случай, если вы беспокоились, @ocrdu
- лучшая практика для производительности: пустой цикл () при использовании прерывания?
- Как объявить массив переменного размера (глобально)
- Использование millis() и micros() внутри процедуры прерывания
- Подсчет импульсов с прерыванием
- Как повторить кусок кода
- Устранение дребезга кнопки с помощью прерывания
- Программа arduino выдаёт ошибку expected //primary-expression before ')' token error: //expected ';' before '}' token E
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
Для чего вы пытаетесь сэкономить вычислительное время? Первым шагом было бы отказаться от всех вызовов
delay ()
(поскольку с ними у вас есть до 2 секунд, когда Arduino просто крутит большими пальцами) и использоватьmillis ()
, как в примереBlinkWithoutDelay
для неблокирующего стиля кодирования. Выполнение инструкции if занимает так мало времени, что в 99,9% случаев это не имеет значения. Что вы хотите сделать в своем коде, кроме мигания, что вам нужна эта оптимизация?, @chrislСпасибо за ваш ответ @chrisl, на самом деле это не мой обычный код и только для упрощенного примера., @B7th
на самом деле вы не можете использовать прерывание для замены оператора
if
... правильный способ обслуживания прерывания - это только установить флаг в ISR ... затем проверить флаг вloop ()
, обычно с помощью оператораif
, @jsotolaпосмотрите на ассемблерный код, созданный на основе скетча, если вы хотите понять сложные вещи., @Juraj
@B7th Я уже так думал, но вы все еще не написали о том, что делает ваш обычный код, кроме мигания. Я думаю, что вы могли бы искать совершенно ненужное направление для оптимизации вашего кода из-за недостающих знаний. Чтобы действительно знать это, нам нужно было бы знать, для чего вы пытаетесь сэкономить "вычислительную мощность". Только тогда мы сможем дать вам ответ, который действительно продвинет вас дальше., @chrisl