Оптимизация кода: Прерывание при достижении значения 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 или прерывания?

Спасибо!

, 👍1

Обсуждение

Для чего вы пытаетесь сэкономить вычислительное время? Первым шагом было бы отказаться от всех вызовов delay () (поскольку с ними у вас есть до 2 секунд, когда Arduino просто крутит большими пальцами) и использовать millis (), как в примере BlinkWithoutDelay для неблокирующего стиля кодирования. Выполнение инструкции if занимает так мало времени, что в 99,9% случаев это не имеет значения. Что вы хотите сделать в своем коде, кроме мигания, что вам нужна эта оптимизация?, @chrisl

Спасибо за ваш ответ @chrisl, на самом деле это не мой обычный код и только для упрощенного примера., @B7th

на самом деле вы не можете использовать прерывание для замены оператора if... правильный способ обслуживания прерывания - это только установить флаг в ISR ... затем проверить флаг в loop (), обычно с помощью оператора if, @jsotola

посмотрите на ассемблерный код, созданный на основе скетча, если вы хотите понять сложные вещи., @Juraj

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


2 ответа


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

1

Вы не указали тип 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:

  1. digitalWrite() сама по себе ужасно медленная. Это можно преодолеть, используя прямой доступ к порту, но это все равно два цикла для переключения контакта в каждом направлении.

  2. Вы потеряете дополнительный цикл в pin-синхронизаторе.

  3. Как только IRQ повышается, процессору требуется четыре цикла, чтобы подготовиться к его обслуживанию.

  4. Вектор прерывания - это команда jmp, которая занимает 3 цикла.

  5. Затем ISR должен сохранить каждый регистр, который он будет использовать, включая регистр состояния. Это занимает два цикла на регистр. И есть довольно много регистров, которые нужно сохранить ...

  6. Этот ISR, который предоставляется ядром Arduino, затем будет искать обработчик прерываний, предоставленный вами с помощью attachInterrupt(). Это также включает в себя проверку того, что указатель на обработчик не равен нулю (тот тест, о котором вы, кажется, беспокоитесь). Затем он должен вызвать (4 цикла) ваш обработчик, который должен будет return (4 цикла) к ISR. Вы можете избежать этой косвенности, определив ISR самостоятельно, вместо того чтобы полагаться на attachInterrupt().

  7. Как только задание будет выполнено, все сохраненные регистры должны быть восстановлены (2 цикла умножаются на много регистров), а инструкция reti (4 цикла) выдается для того, чтобы восстановить управление прерванной программой.

  8. И последнее, но не менее важное: поскольку 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


1

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

Использование прерывания для реализации чего-то простого, например if (!amp) amp = 10;, вероятно, замедлит работу:

  • что-то должно будет отслеживать счетчик, чтобы генерировать прерывание, и это "что-то" занимает время MCU, если только это не периферийное устройство;
  • прерывания имеют накладные расходы;
  • в однопоточных микроконтроллерах прерывания останавливают регулярное выполнение кода до тех пор, пока они не будут выполнены;
  • amp = 10; все равно придется выполнять в обычном коде или, если нужно, в прерывании.

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

Кроме того, я бы не знал, как заменить обычное if прерыванием, если только счетчик не хранится периферийным устройством (таймером).

,

Re “_amp = 10; все равно придется выполнить в обычном коде_“: Зачем вам делать это в обычном коде? Это утверждение не займет больше времени, чем установка логического значения., @Edgar Bonet

@Edgar-Bonet: Да, в этом простом примере это всего лишь одно утверждение, так что здесь это не имеет значения.. Я не из той школы, которая говорит, что вы всегда должны устанавливать флаг только в прерывании и должны делать все остальное в цикле, на случай, если вы беспокоились, @ocrdu