Использование millis() для переключения светодиода каждые 500 миллисекунд

Я думал, что приведенный ниже код будет переключать светодиод каждые 500 миллисекунд. Но это не работает. Я не могу уловить ошибку, что, должно быть, очень глупо.

void setup() {
  pinMode(3, OUTPUT);
}

void loop() {
  unsigned long cnt=millis();
  if(!(cnt % 500)) {
    PORTD^= (1<<PD3);
  }
}

В любом случае код ниже работает. Но почему не предыдущий код?

void loop() {
  static unsigned long prev=0;
  // Примечание. Случай первого раза не обрабатывается. Как только мы захотим переключиться,
  // мы должны сохранить millis() в предыдущем. В этом случае это происходит после перезагрузки и может не быть проблемой.
  unsigned long current = millis();
  if((unsigned long)(current - prev) >= 500) {
    PORTD^= (1<<PD3);
    prev = current;
  }
}

, 👍4

Обсуждение

Еще одна проблема первого примера, еще не упомянутая, заключается в том, что логика if() перевернута. if( !(cnt % 500)) эквивалентен if( (cnt % 500) != 0 ), что верно для 499 значений и ложно только для одного. Знак "!" создает эффект, противоположный желаемому., @JRobert


4 ответа


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

16

если (!(millis() % 500)) ...

С этим есть две проблемы. Первая и самая очевидная заключается в том, что условие будет истинным в течение целой миллисекунды. В течение этой миллисекунды вы будете очень быстро включать и выключать светодиод. Независимо от того, в конечном итоге вы Можно только догадываться, будет ли выполнено четное или нечетное количество переключений.

Вторая и менее очевидная проблема заключается в том, что millis() пропускает некоторые значения. Примерно одно целое число каждые 42 пропускается. Это потому, что счетчик millis() увеличивается каждые 1024 мкс, что дает 24   ... целую миллисекунду, millis() догоняет, увеличивая счетчик на две миллисекунды вместо одной. Вот почему вы никогда не должны ставить условие что-то на millis() имеющее одно конкретное значение.

Почти правильное решение этой задачи показано в ответе Юрая:

если (millis() > следующий) ...

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

static unsigned long last;
if (millis() - last >= 500) {
    last += 500;
    ...
}

Поскольку вычитание следует правилам модульной арифметики, это гарантированно сработает при переносе событий.

,

перерасход происходит через 49 дней. Если у эскиза запланирован ежедневный перезапуск, то вычитание не нужно., @Juraj

@Juraj: Планирование перезапусков — это очень надуманный способ избежать «проблемы» переноса, которая изначально не является проблемой, если правильно выполнить арифметические расчеты., @Edgar Bonet

если он запланировал перезапуск по другим причинам, @Juraj

@Juraj А когда эти перезапуски прекращаются... внезапно возникает неожиданная, странная ситуация., @wizzwizz4

Вы в хорошей компании с запланированными перезагрузками! https://www.theguardian.com/business/2015/may/01/us-aviation-authority-boeing-787-dreamliner-bug-could-cause-loss-of-control И если вы забыли, это всего лишь тривиальное *полное отключение электропитания Boeing 787 и [потенциальная] «потеря управления» самолетом.*, @ta.speot.is

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

инициализируйте переменную последней, @Juraj

@Juraj: Он static, поэтому инициализируется правилами языка. Вы можете добавить = 0, если не доверяете языку, или = -500, если хотите, чтобы первая итерация началась немедленно, или даже = millis() - 500, если ваша setup() занимает безумно много времени., @Edgar Bonet


4

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

Делайте это следующим образом:

  static unsigned long next = millis();
  if (millis() - next > 500) {
    next += 500;
,

1
void loop() {
  unsigned long cnt=millis();
  if(!(cnt % 500)) {
    PORTD^= (1<<PD3);
  }
}

На каждой итерации cnt устанавливается с числом миллисекунд, прошедших с момента включения Arduino... Так что это не сработает. Вообще. Вам нужны две переменные. Одна, которая содержит последний счетчик millis(), когда вы мигнули светодиодом, и для текущего счетчика millis():

unsigned long lastCount;
void setup() {
  pinMode(3, OUTPUT);
  lastCount=millis();
}

void loop() {
  unsigned long cnt=millis();
  if((cnt-lastCount)>499) {
    PORTD^= (1<<PD3);
    lastCount=cnt;
  }
}
,

0

Я всегда этим пользуюсь

if((millis()/500)%2==0)) 
{
    PORTD |= (1<<PD3);
}
else
{
    PORTD &= ~(1<<PD3);
}

Сначала он делит миллис на 500 мс. Таким образом, вы получаете количество 500-мс кулачков (с момента запуска Arduino). Затем вы проверяете, четное ли это число. Если да, включите светодиодный вывод. В противном случае выключите светодиодный вывод.

,

Деление — это вычислительно затратная операция на большинстве встраиваемых микроконтроллеров. По возможности следует избегать ее., @Alnitak