Буквально на днях мне на глаза попалась статья про прерывания и обработку оных в контексте ПеКа, но взгляд упал на часы. И тут я подумал, что вообще вместо линейного кода можно задействовать прерывания и таймеры, то есть бОльшую часть процедур выполнять не на каждой итерации void loop(), а раз в несколько итераций.
Я решил для начала изучить вопрос, наколько имеет смысл утаскивать логику под таймеры/триггеры.
Оказывается, опрос RTC - это ~20 мс за итерацию. То есть мы можем получить примерно 50 опросов таймера за секунду. На этот момент влияет шина I2C, которая, в случае общения с ds1307, работает на 100 КГц. У этой шины есть и "быстрый" режим работы, 400 КГц, но в данном случае кристалл по документации такой режим не поддерживает. Да и не нужно ему такой режим поддерживать, он не для этого. Предполагается, что RTC нужны для корректировки часов, как "эталонный" источник времени. За неимением более точного, естественно. И второй вариант - это "сохранение времени" на момент, когда основной прибор выключен.
Второй момент: опрос фоторезистора. Конкретно в данном случае - получение результатов analogRead(). Собственно, analogRead() по времени занимает несколько более чем 100 микросекунд. Не то чтобы для часов это критически большое значение. Тем не менее этот показатель вполне поддаётся некоторым улучшениям. Опираясь на статью http://yaab-arduino.blogspot.com/2015/02/fast-sampling-from-analog-input.html я решил немного поднять sampling rate за счёт "повышения разрешающей способности" ADC-таймера. По ходу дела идёт ссылка на документацию по ADC http://ww1.microchip.com/downloads/en/appnotes/atmel-2559-characterization-and-calibration-of-the-adc-on-an-avr_applicationnote_avr120.pdf (гуглить по DOC2559.PDF). Но это подробности. В дебри этих всех вещей я не лез, но, полагаю, внутри оно завязано на функцию tone(), которая завязана на прерывания от timer2. Для которого мы устанавливаем прескалер (делитель) на 16, против 128 по-умолчанию, повышая тем самым количество "тиков" этого таймера в единицу времени.
Акей, а что в наших часиках зависит от свободных вычислительных ресурсов? Внезапно, яркость подсветки нашего 7-сегментного LED индикатора. В нашей реализации яркость производится не аппаратно, за счёт проходящего тока, а программно, за счёт продолжительности подачи питания на сегменты.
Интересная особенность: в библиотеке SevSeg, есть два режима димминга - первый режим (по умолчанию) на каждой итерации работает только с одной цифрой. То есть если вызывать Display.refreshDisplay() раз в заметный промежуток (например, раз в 0.1 секунды), то можно будет заметить что реально горит только один разряд, одна цифра за итерацию. Второй режим работает со всем индикатором и мерцает всеми сегментами. При вызове с той же частотой, в 10 Гц, мы увидим, что мерцает весь индикатор, все цифры разом. То есть предполагается, что мы вызываем Display.refreshDisplay() как можно чаще, а библиотека сама решает, когда и как работать с индикатором.
Первый режим SevSeg у меня изначально не работал, то есть индикатор работал с максимальной яркостью. И теперь я понимаю, почему. Каждая итерация по loop() происходила слишком медленно. Убрав всё что только возможно под триггеры, которые устанавливаются таймерами, я получил корректный dimming в режиме updateWithDelays=false (в первом режиме), но с одной оговоркой. Периодически разные цифры вспыхивали чуть ярче других. Эффект занятный, я даже думал его оставить, но в результате всё-таки забил. Полагаю, что явление связано как раз с тем, что друг на друга накладываются затратные по времени операции и (абсолютное) время исполнения цикла значительно возрастает.
Второй режим SevSeg у меня работал всегда, но за счёт освободившихся ресурсов по идее он должен обеспечивать визуально более стабильный уровень подсветки.
В результате бОльшая часть итераций ограничивается Display.refreshDisplay(), но раз в полсекунды (обработчиком прерывания) ставится флажок и происходит считывание времени из rtc, а по другому таймеру раз в 40 мс происходит считывание показаний фоторезистора, и обновление строки, записываемой на дисплей.
Соответственно, в результате simple clock использует все доступные аппаратные таймеры платформы - это timer0 и функцию millis(), завязанную на него, timer1 и прерывание по его переполнению, генерируемое раз в полсекунды, и timer2 для регулирования яркости LED-индикатора. А казалось бы на первый взгляд ничего такого сложно во всём этом нет.
И по уровню подсветки резюмируя, можно сказать, что, конечно, аппаратная регулировка яркости подсветки - это хороший вариант для высвобождения ещё бОльшего количества ресурсов платформы, но программная подсветка позволяет менее затратно учитывать ттх LED индикатора, так как в моём случае они нелинейны. Поэтому приходится использовать софтовую регулировку яркости, чтобы получить картику, которая мне нравится.