Единственный пример, которому никто не учит

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

Как вы мигаете один?

Запустите скетч Blink.ino, и задача выполнена.

void loop() 
{
  digitalWrite( 13, HIGH );
  delay( 500 );
  digitalWrite( 13, LOW );
  delay( 500 );
}

Это простое и элегантное решение этой проблемы. Тот, который понятен даже ребенку. Но практично ли это?

Как поморгать двумя светодиодами?

Один из них один раз в секунду, а другой два раза быстрее? Давайте посмотрим, каким будет одно наивное решение.

void loop() {
  digitalWrite( 13, HIGH );
  digitalWrite( 7, HIGH );
  delay( 250 ); 
  digitalWrite( 7, LOW );
  delay( 250 );

  digitalWrite( 13, LOW );
  digitalWrite( 7, HIGH );
  delay( 250 ); 
  digitalWrite( 7, LOW );
  delay( 250 );
}

Подождите минутку — это не элегантно. Он был создан грубой силой. Добавление еще одного светодиода с другой частотой было бы непрактично и привело бы к беспорядку. Итак, давайте попробуем.

Как помигать тремя светодиодами?

Перейдите на следующий уровень и подумайте, как вы могли бы реализовать это с частотами 1, 2 и 0,67 Гц. Можно было бы использовать таймеры, но нужны ли они?

В предыдущем примере уже используется один таймер, и это будет таймер delay. Большинство встроенных архитектур имеют таймер, предназначенный для подсчета времени, например SysTick на ARM.

uint32_t led1_next = 0, led2_next = 0, led3_next = 0;

uint32_t led1_pulse = 1000 / ( 2 * 1 );    // 1Hz
uint32_t led2_pulse = 1000 / ( 2 * 2 );    // 2Hz 
uint32_t led3_pulse = 1000 / ( 2 * 0.67 ); // 0.67Hz 

void loop() {
  if( millis() > led1_next )
  {
    led1_next = millis() + led1_pulse;
    digitalWrite( 13, ! digitalRead( 13 ) );
  }

  if( millis() > led2_next )
  {
    led2_next = millis() + led2_pulse;
    digitalWrite( 7, ! digitalRead( 7 ) );
  }

  if( millis() > led3_next )
  {
    led3_next = millis() + led3_pulse;
    digitalWrite( 4, ! digitalRead( 4 ) );
  }

  // other tasks
  delay( 1 );
}

Чувствуется магия. Для этого нужен только один таймер и несколько контактов. Код аккуратный, проблемы разделены, и он масштабируется.

Это решение также обладает еще одним свойством: оно ведет себя подобно циклу событий.

Не менее важно учитывать недостатки решения. Что произойдет, если другие задачи будут иметь более длительное или непредсказуемое время выполнения?

Думай как инженер

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

Вы применяли эту технику раньше? Напишите в комментариях, как вы к этому пришли. Спасибо за прочтение!