Единственный пример, которому никто не учит
Между программистом и инженером-программистом есть существенная разница. При разработке прошивки вы должны думать как последний. Возьмем пример скромного моргания, который более полезен для проверки того, все ли работает, а не для обучения чему-то. Давайте посмотрим, как дальнейший шаг может улучшить технику.
Как вы мигаете один?
Запустите скетч 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.
Вы применяли эту технику раньше? Напишите в комментариях, как вы к этому пришли. Спасибо за прочтение!