Выходы МК переключаются одновременно. Это необходимо для того, чтобы оба канала имели равные временные задержки и работали одинаково. С этой целью в программу введён специальный буферный регистр rOutChannels. Изменение состояния выходов осуществляется только командой OUT pMainOut, rOutChannels. После старта МК буферный регистр содержит копию состояния порта pMainOut, большинство выводов которого, по аналогии с другими неиспользуемыми выводами, настроены как входы с внутренними подтягивающими резисторами. Поэтому никакие биты регистра rOutChannels (кроме двух старших) не должны модифицироваться программой. Также не должно изменятся состояние порта напрямую, т.к. любое изменение будет перезаписано содержимым буферного регистра.
АЦП, имеющий разрешение 10 бит, используется в режиме 8-битного преобразования, поскольку высокая точность измерений не требуется. Это позволяет ему работать на максимальной частоте (500 кГц), что обеспечивает высокое быстродействие. Согласно [12, раздел 2.8], допускается работа АЦП на частотах до 1 МГц. Уменьшенное до 8 бит разрешение служит также дополнительной защитой от помех. Для сокращения количества команд управления, АЦП работает в режиме автозапуска, сигналом которому является окончание предыдущего цикла преобразования. Первое преобразование запускается при инициализации МК.
Таймеры не используются. Они создают помехи работе АЦП. Для исключения этого явления существует специальный энергосберегающий режим АЦП ''ADC Noise Reduction''. Но поскольку режимы энергосбережения по причинам, указанным ранее, не используются, временные задержки пришлось реализовать на основе регистров или ячеек ОЗУ.
По окончании инициализации всем регистрам и ячейкам ОЗУ присваивается нулевое значение (блок SRAMInit.asm). В результате, не нужно помнить о том, чтобы все переменные были проинициализированы, т.е. не содержали бы случайных значений. Как следствие, значительно облегчается их поиск.
После инициализации регистров и ОЗУ продолжение программы приостанавливается на несколько секунд (осуществляется программная задержка). За это время напряжение питания МК и образцовое напряжение АЦП успевают достигнуть номинального уровня.
Для повышения быстродействия задействованные ячейки EEPROM копируются перед началом работы либо в регистры, либо в ОЗУ (блок Startup.asm). В большинстве случаев для этой цели используется специальный макрос EEPROMtoSRAM, находящийся в файле Macros.asm (на блок-схеме алгоритма не показан).
При таком подходе появляется возможность проверки считанных из EEPROM данных. Если введённый пользователем параметр настройки является недопустимым, он преобразуется к ближайшему допустимому значению (меньшему или большему). Это действие, условно называемое нормализация, выполняется не для всех ячеек, а только для тех, допустимые значения которых однозначны, а также для тех, недопустимые значения которых могут привести к явным ошибкам в работе устройства.
Допустимые значения ячеек EEPROM, а также значения по умолчанию указаны при описании ячеек в комментариях (блок Dimmer.asm). Значения некоторых ячеек умышленно ограничены диапазоном, удобным для восприятия и запоминания. Например, продолжительность включенного состояния функции ИПХ, задаваемая ячейкой eOwnersAtHomeOnPeriod, ограничена значением 240 минут, хотя функция могла бы исправно работать и при eOwnersAtHomeOnPeriod = 255. Однако значение 240 минут (4 часа) более привычно для запоминания и использования, нежели 255 минут (4,25 часа).
Одной из задач инициализации является однократное измерение сетевого напряжения. Поскольку заранее невозможно предсказать, в какой момент времени светильник будет подключен к сети, результатом этого измерения является случайное число, используемое далее в подпрограмме генератора псевдослучайных чисел как точка отсчёта seed (блок Procedures.EXT.asm).
Перед тем как передать управление основному блоку программа определяет, в каком состоянии находилось устройство перед отключением питания. В зависимости от результата устанавливаются те или иные флаги, сообщающие основной программе о том, какие действия необходимо предпринять. Например, если в момент пропадания сетевого напряжения был включен первый канал, то после восстановления электроснабжения он включится автоматически (если это было предварительно разрешено пользователем в настройках устройства).
Как уже отмечалось, код основного блока Main.asm выполняется в бесконечном цикле. Так как детектирование перехода сетевого напряжения через нуль осуществляется в каждом полупериоде, цикл Main.asm повторяется каждые 10 мс.
Первая команда основного блока – сброс сторожевого таймера. Следует отметить, что это единственное место во всей основной программе, где осуществляется сброс сторожевого таймера.
Первоначально для детектирования момента перехода сетевого напряжения через нуль использовался следующий алгоритм: АЦП циклически производил считывание входного напряжения и сравнивал результат преобразования с заданной константой. Как только происходило совпадение, подавалась команда на включение каналов, и цикл завершался. Из-за отсутствия кварцевой стабилизации тактовой частоты МК, для точного обнаружения момента перехода фазы сети через нуль требовался подбор константы под конкретный экземпляр МК. Был альтернативный вариант: вместо подбора константы осуществлять калибровку внутреннего RC генератора. И то и другое отрицательно сказывалось на повторяемости устройства. Но основная причина, побудившая изменить алгоритм, заключалась в невысокой помехоустойчивости. Действительно, если помеха (всплеск сетевого напряжения) возникала близко к моменту перехода фазы сети через нуль, МК продолжал измерять входное напряжение, ожидая его совпадения с константой. Поскольку после прекращения помехи входное напряжение оказывалось больше заданного константой, МК был вынужден оставаться в цикле измерений до следующего перехода фазы. Так как во время измерения напряжения каналы находятся в выключенном состоянии, визуально такой «простой» выглядел как моргание ламп(ы), т.к. в течение как минимум 10 мс напряжение на нагрузке отсутствовало.
Изменённый алгоритм основан на сравнении не с константой, а с предыдущим результатом измерения. Команда на включение каналов подаётся только тогда, когда текущий результат измерения становится больше предыдущего. В рассмотренном только что примере МК будет детектировать помеху как напряжение, превышающее предыдущее. Это приведёт к моментальному выходу из цикла, что визуально будет практически не заметно. В обычном режиме (без воздействия помех) МК осуществит выход из цикла, как только сетевое напряжение сменит направление с убывающего на возрастающее, т.е. сразу после перехода сетевого напряжения через нуль.
Таким образом, отпадает необходимость в кварцевом резонаторе, калибровке внутреннего генератора и в калибровке АЦП. Также не требуется дополнительная константа и настройка устройства под конкретный экземпляр МК.
Погрешность нового алгоритма нетрудно подсчитать. Для определения момента, когда сетевое напряжение начало возрастать, достаточно двух измерений. Время одного преобразования АЦП составляет 27 мкс (см. раздел Выбор_константы_cADCSamplesCount). Время на обработку полученного результата (первые 5 команд процедуры PhaseDetect) составит в худшем случае 8 мкс. Значит, максимальная погрешность равна 27 + 27 + 8 = 62 мкс. Согласно формуле U = A • sin(2 • p • f • t), при такой задержке мгновенное напряжение в сети будет находиться на уровне U = 310 • sin(2 • 3,14 • 100 • 62•10-6) = 12 В (относительно номинального сетевого напряжения).
Включение с интервалом 10 мс лампы мощностью 60 Вт с сопротивлением нити 882 Ом при напряжении 12 В создаст ток всего 12 / 882 = 14 мА. Такой малый ток не способен заметно повлиять на сокращение срока службы лампы, создать помехи, и т.п.
Ввиду разветвлённости алгоритма основной программы его наглядное изображение в графическом виде не приводится, т.к. будет затруднительно для восприятия. Отдельные процедуры основной программы описаны далее. Облегчить понимание логики работы призваны комментарии к программному коду.
Функция ИПХ (Имитация Присутствия Хозяев) использует в качестве счётчика времени во включенном и выключенном состоянии отдельную процедуру. Имеющуюся процедуру TurnOffTimeoutCh1 использовать нельзя. Тому есть три причины. Во-первых, она строго индивидуальна для каждого канала. Во-вторых, её модификация потребовала бы слишком больших усилий (понадобилось бы большое количество условных переходов). В-третьих, если продолжительность включенного или выключенного состояния составляет нечётное число, скажем, 3 минуты, то пришлось бы делить его поровну между двумя процедурами и иметь дело с дробными числами. Вариант, когда процедура выполняется только в одном из каналов, тоже не подходит, т.к. принято, что программный код обоих каналов одинаков (для удобства модификации и подсчёта времени выполнения программы).
В функции ИПХ практически везде опрашивается бит лишь одного (первого) канала. Это допустимо благодаря идентичности каналов, а также благодаря тому, что в данном режиме каналы управляются синхронно.
В момент включения функции ИПХ устанавливаются биты ebPowerState. Это сделано на случай пропадания сетевого напряжения в момент включения. Таким образом, при восстановлении напряжения работа функции будет продолжена.
В функции автоотключения процентное значение, на которое уменьшается яркость, умышленно ограничено интервалом от 10 до 90%. Такой диапазон хорошо подходит для визуального восприятия. Кроме того, уменьшение яркости менее чем на 10% незаметно.
Для определения значения регистра rSoftOffBrightChХ, хранящего величину уменьшенной яркости с учётом заданного пользователем процента, в целях ускорения программы используется табличный метод (таблица PercentageTable). Для каждого заданного пользователем значения процента Y, в таблице находится округлённое число Х, заранее вычисленное по формуле: X = ((100 – Y) / 100) • 256. Таблица состоит из (90 – 10) + 1 = 81 ячейки. Когда истекает время непрерывной работы канала, происходит обращение к таблице, затем текущее значение яркости умножается на полученное из таблицы число. Поскольку это число предварительно было умножено на 256, результат делится на 256 путём простого отбрасывания старшего байта, после чего результат округляется. Например, предположим, что текущая яркость, т.е. число в регистре rBrightnessChХ, равно 200, а заданный пользователем процент снижения яркости равен 25%. По истечении времени непрерывной работы канала программа обратится к таблице, в которой смещению 25 соответствует число 192. Программа произведёт умножение (200 • 192 = 38400), разделит результат на 256 (38400 / 256 = 150), округлит его (в данном примере в этом нет необходимости) и запишет итог в регистр rSoftOffBrightChХ. Нетрудно подсчитать, что исходная яркость 200, уменьшенная на 25%, действительно равна 150.