Что не так с бесконечными циклами внутри loop()?

Я хотел запустить следующий код как что-то похожее на Tone или pwm. Однако по какой-то причине я не могу искать, это не работает.

#define WAIT 3200UL

void setup() {
  DDRB |= B00000001;  // установить вывод 8 в качестве выхода
}

void loop() {
  unsigned long t;
  while (1) {

    PORTB &= B11111110; // очищаем пин 8
    
    for (t = 0 ; t < WAIT; t++);

    PORTB |= B00000001; // устанавливаем пин 8
    
    for (t = 0; t < WAIT; t++);


    // ПОРТB &= B11111110;
    // задержкамикросекунд(WAIT);
    // ПОРТВ |= B00000001;
    // задержкамикросекунд(WAIT);

  }
}

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

Я удалил "в то время как(1)" оператор, поскольку он был ненужным на данный момент, он начал работать неправильно (изменение WAIT не дало никакого эффекта, и я не уверен, что получаю полный ход).

Вывод 8 подключен к светодиоду + R к земле и пьезоэлектрическому элементу параллельно им. светодиод горит постоянно, но зуммер издает низкий шум. Я измерил напряжение 3,8 В постоянного тока и около 9 В переменного тока на контакте, другие контакты, для которых я запускал код, были аналогичными.

В качестве еще одной попытки я поместил Serial.print в начало while(1), но он распечатался только один раз.

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

Благодарим вас за помощь.

, 👍1

Обсуждение

Отвечая на ваш заголовок - нет ничего плохого в бесконечном цикле внутри цикла, но в этом нет необходимости, поскольку сам цикл является бесконечным циклом. Переместить unsigned long t; в настройку, и у вас будет бесконечный цикл без необходимости явно кодировать while(1)., @Holmez


2 ответа


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

4

Во-первых, я дам вам несколько непрошеных советов по поводу вашего стиля написания кода. Вместо того, чтобы писать:

PORTB &= B11111110;
...
PORTB |= B00000001;

вы можете написать:

PORTB &= ~_BV(PB0);
...
PORTB |= _BV(PB0);

Это стандартные идиомы AVR, и они дают понять, что вы переключение контакта PB0.

Тогда, чтобы ответить на ваш фактический вопрос, ваш цикл задержки не работает. потому что он был оптимизирован компилятором. Это уже было сказано в ответе Мишеля Кейзерса, но позвольте мне уточнить.

Все современные компиляторы являются оптимизирующими компиляторами. Это означает, что они могут изменить свой код так, как они считают нужным, пока видимый эффекты остаются прежними. Эффекты, считающиеся видимыми, обычно Ввод/вывод и доступ к переменным, объявленным volatile. Весьма важно то, медленность программы не считается видимым эффектом для цель оптимизации. И если подумать, в этом есть смысл: если нужно было сохранить медлительность программы, то любая форма оптимизация была бы невозможна. Таким образом, петля, не влияющая на другие чем замедление вашей программы может быть законно оптимизировано.

Решение вашей проблемы довольно простое: если вам нужна задержка, то вызовите delayMicroseconds(). Или, может быть, даже delay(), если вы согласны с его низкое разрешение. С другой стороны, если вам нужна субмикросекундная разрешение, вы можете вызвать _delay_us() из файла avr-libc. Эта функция (точнее, макро) обеспечивает однотактное разрешение. Если вы пишете

#include <util/delay.h>

...
_delay_us(1.375);

вы получаете точно то, что просите, то есть задержку в 22 такта ЦП. (на Arduino Uno с тактовой частотой 16 МГц). Проблемы с этим состоит в том, что она специфична для AVR, и ее аргумент должен быть константа времени компиляции.

Вам было предложено написать цикл задержки с помощью volatile счетчик циклов. Я настоятельно рекомендую вам против делать это в течение трех причины:

  1. Таким образом, счетчик будет размещен в ОЗУ, поэтому потребление дефицитного и ценного ресурса без уважительной причины.
  2. Увеличение 32-разрядного счетчика в ОЗУ занимает 20 циклов ЦП. Добавлять к этому времени, необходимому для проверки условия конца цикла, и вы поймет, что ваше разрешение задержки намного хуже, чем у delayMicroseconds().
  3. Ваша фактическая задержка будет зависеть от тактовой частоты и, возможно, также в версии компилятора, что делает ваш код довольно хрупким.
,

Проголосовал за (намного больше) объяснение, @Michel Keijzers

Re, «эффекты, считающиеся видимыми, обычно представляют собой ввод-вывод и ...» В языке программирования C++ нет ввода-вывода. Эффекты, которые считаются видимыми, — это вызовы функций с внешней связью и доступ к «изменчивым» функциям. Когда программа на C++ делает что-то вроде std::cout<<"hello world!\n";, это вызов внешней библиотечной функции., @Solomon Slow

static счетчик циклов должен работать так же хорошо в этом конкретном случае, сохраняя некоторые инструкции загрузки/сохранения., @wondra

@jameslarge: Последние версии Arduino IDE передают компилятору параметр -flto, который запускает полноценную оптимизацию времени компоновки программы. В этом случае внешняя компоновка не имеет значения, а вызовы внешних функций _могут_ быть оптимизированы (да, я пытался)., @Edgar Bonet

@wondra: я пробовал со «статическим» счетчиком, как глобальным статическим, так и локальным статическим. В обоих случаях цикл был оптимизирован, хотя в счетчик записывалось его значение в конце цикла., @Edgar Bonet

@EdgarBonet работает для меня с использованием VisualStudio, кажется, что такая конструкция может быть даже более опасной, чем ожидалось изначально. Еще одна причина использовать задержку от avr lib., @wondra

Ой! Я имел в виду... доступ к изменчивым _переменным_., @Solomon Slow


1

Первый

t = WAIT;
for (t = 0; t < WAIT; t++);

Первый оператор бесполезен, так как t будет присвоено значение 0 в цикле for.

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

Вы должны сделать t volatile. Это предотвращает его оптимизацию компилятором (out).

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

,

Даже если t отмечен как volatile, цикл будет выполняться так быстро, что вы даже не увидите его мерцания., @Kwasmich

может помочь увеличение значения WAIT ... не уверен, сколько тактовых циклов используется. Я бы сказал 16 МГц/количество тактовых импульсов для обработки одной итерации., @Michel Keijzers

И полезным устройством для анализа этого является логический анализатор, вы можете купить его за 5 евро в Китае, и его проще использовать для проверки слабого сигнала, чем (дешевый) осциллограф., @Michel Keijzers

Поправьте меня, если я ошибаюсь, но t используется (читается) в выражении t < WAIT, а также пишется и читается в выражении t++. Тот факт, что оператор цикла for пуст, может привести к оптимизации компилятора по другой причине, но мне не кажется, что t не используется., @wondra

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

Инкрементирование, сравнение и переход — это всего лишь одна цифра инструкций. При максимальном (65535) это около 600000 тиков, поэтому 600000/16000000 = 37 мс. Так что ДОЛЖНО быть заметное мерцание., @Kwasmich

@Kwasmich, почему 65535 совпадает с тиками 600000, а не с тиками 65535? Тогда это будет 3,7 мс, а поскольку в примере используется 32000, это будет ок. 2 мс или меньше, что, вероятно, незаметно., @Michel Keijzers

65535 итераций цикла, умноженное на однозначное количество тиков (10 или меньше), составляет примерно 600000 (округлено для удобства), да, 3200 — это довольно низкая задержка., @Kwasmich

Я закомментировал t=wait, что было глупо и осталось от while(t--) ; структура, но она все та же, и я уже пробовал много разных значений, чтобы попасть в разумный диапазон, но все равно не работает. Думаю, проблема где-то в другом., @user174174

Проверьте время до/после цикла и посмотрите, оптимизировано ли оно (вы даже можете добавить дополнительный цикл for для проверки более длительного времени)., @Michel Keijzers

@MichelKeijzers Хорошо, спасибо! определение t как volatile решило проблему. но я все еще сомневаюсь, как комментарий @wondra, поэтому я написал его как «for (t = 0; t < WAIT;) t++;», но компилятор все равно игнорирует его, если он не определен как volatile. что именно использует впоследствии, как упоминал @kwasmich?, @user174174

Вероятно, он имел в виду (и я указал в своем ответе), что при использовании volatile вы не позволяете компилятору игнорировать оператор for. Вот почему он работает с volatile и не работает без него., @Michel Keijzers