Здравствуйте, уважаемые Датагорцы! Представляю вашему вниманию электронные часы с функцией измерения температуры, изготовленные мною в качестве подарка деду ко дню Советской Армии.
Особенностью проекта является использование радиомодуля nRF24L01 для беспроводного соединения с датчиком DS18B20. Это моя первая статья.
Содержание статьи / Table Of Contents
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.
Часы состоят из двух отдельных модулей — периферийного (далее — «ПМ»), измеряющего температуру и передающего по воздуху её значение центральному (далее — «ЦМ») для отражения совместно с текущими датой и временем на экране дисплея.
↑ Центральный модуль часов
Как видно из видео выше ЦМ — непосредственно часы с выведенными:а) экраном дисплея — на переднюю панель корпуса ,
б) ручкой энкодера — на верхнюю панель корпуса,
в) USB-кабелем питания — через заднюю панель корпуса.
↑ Детали и компоненты ЦМ
Для сборки ЦМ были использованы:• Arduino Nano на базе МК ATmega328P с частотой тактирования 16 МГц и предварительно удалённым бутлоадером
• TFT-дисплей ST7735
• Часы реального времени DS1307
• Радиомодуль/трансивер nRF24L01
• Энкодер
↑ Принципиальная электрическая схема ЦМ
Схема соединений элементов ЦМ представлена на Рисунке 1.
Рисунок 1. Схема соединений центрального модуля
Вывод SQ модуля DS1307 подключён к пину PD3 МК, поскольку сигнал с частотой 1 Гц на указанном выводе RTC использован в качестве источника внешнего прерывания INT1 ATmega328P для обновления значений времени и даты на экране дисплее.
Сигнал о поступлении от ПМ радио-пакета с вывода IRQ nRF24L01 передаётся на пин PD2 МК, инициируя внешнее прерывание INT0, что обеспечивает своевременное чтение из трансивера нового значения температуры.
Для обмена данными с RTC, дисплеем и трансивером использованы пины ATmega328P не аппаратных I2C (PC5/PC4 для SCL/SDA, соответственно) и SPI (PB3/PB4/PB5 для MOSI/MISO/SCK, соответственно), а произвольные, поскольку в проекте реализованы программные версии указанных протоколов, а выбор пинов определялся из соображений топологии платы.
Источником питания ЦМ послужило зарядное устройство смартфона на 5В.
↑ Алгоритм работы ЦМ
При включении ЦМ переходит в основной режим — отображения на экране дисплее текущих значений температуры, времени, даты и дня недели.
Рисунок 2. Значения, отражаемые на экране дисплея в основном режиме ЦМ
В основном режиме ЦМ реагирует на три события:
1. При поступлении сигнала с вывода SQ DS1307 микроконтроллер считывает из RTC текущие значения времени/даты, которые, в случае наличия изменений, обновляются на дисплее. Кроме того, символ двоеточия «:» в показаниях времени попеременно меняет цвет с белого на чёрный, т.е. блинкует с частотой 1 Гц.
2. В случае, когда nRF24L01 посредством прерывания INT0 сигнализирует о поступлении радио-пакета, МК считывает из трансивера текущее значение температуры и выводит на экран дисплея.
3. Длительное (более 4-х секунд) нажатие кнопки энкодера (далее - «Кнопка») переводит ЦМ в режим установки даты/времени. В указанном режиме, посредством вращения ротора энкодера (далее - «Ротор»), сопровождаемого перемещением рамки, и короткого нажатия кнопки можно выбирать между установкой времени, установкой даты, возвратом в основной режим.

Рисунок 3. Меню выбора установки времени/даты
При выборе пункта «time settings» на экран дисплея выводится следующее меню.

Рисунок 4. Меню установки времени
Здесь, вращая ротор и ориентируясь на текущее положение рамки, необходимо выбрать требуемый разряд (десятки или единицы часов/минут), после чего коротко нажать кнопку. Последующее вращение ротора приведёт к изменению значения выбранного разряда. Очередное нажатие кнопки возвращает вращению ротора функцию перемещения рамки для выбора того или иного параметра.
После окончательной установки значения времени достаточно выбрать пункт «save» меню, а затем коротким нажатием кнопки сохранить новые значения в EEPROM DS1307 и вернуться к меню из Рисунка 3.

Рисунок 5. Меню установки даты
Аналогичные действия для настройки даты понадобятся после выбора пункта «date settings» меню на Рисунке 3.
↑ Периферийный модуль часов
Как следует из вышеприведённого видео, ПМ — выносной модуль измерения температуры с возможностью передачи её значения через радиоканал.↑ Детали и компоненты ПМ часов
Работу ПМ обеспечивают:• МК ATtiny85 с частотой тактирования 1 МГц
• Радиомодуль/трансивер nRF24L01
• Температурный датчик DS18B20
• Резистор номиналом 4.7 кОм, подтягивающий сигнальный вывод DS18B20 к питанию
• Батарейка CR2032 в качестве источника питания
↑ Электрическая схема ПМ
На рисунке 6 представлена схема соединений элементов ПМ.
Рисунок 6. Схема соединений периферийного модуля
Учитывая, что в ATtiny85 нет полноценной аппаратной поддержки требуемых протоколов обмена данными, в коде ПМ были использованы программные варианты 1-Wire (в случае с DS18B20) и SPI (в случае с nRF24L01), поэтому выбор соответствующих пинов МК — произвольный. Выводы IRQ и MISO трансивера не использовались, в связи с отсутствием необходимости.
↑ Алгоритм работы
Большую часть времени ПМ проводит в режиме сна. При этом, согласно даташитов, ATtiny85 потребляет 10 мкА, а nRF24L01 – 900 нА.Один раз в час МК:
• считывает текущее значение температуры из DS18B20,
• будит трансивер и отправляет данные центральному модулю,
• вводит трансивер в режим сна и засыпает сам.
Потребление ATtiny85 в активном режиме вырастает до 0.55 мА , а nRF24L01 — до 11.3 мА. Учитывая длительность операций в активном режиме (миллисекунды), можно предполагать, что емкости 240 мА/ч батарейки хватает на 15-18 месяцев бесперебойной работы ПМ.
↑ Программное обеспечение для часов-термометра
В проекте широко использовался код, изложенный в статьх камрада erbol, который, при необходимости, перекладывался мною с ассемблера или С на С++. Поэтому, далее буду делать ссылки на соответствующую публикацию указанного автора.Программное обеспечение для ЦМ и ПМ написано на языке С++ в Visual Studio Code, скомпилировано посредством GCC и выложено в архив статьи. Прежде, чем перейти к особенностям кода каждого из модулей, остановлюсь на общих для обоих моментах.
Содержимое файла с основной функцией main() я предпочитаю ограничивать вызовом двух функций — deviceInit() и deviceControl() — которые прописываю уже в файле device.cpp. Такой подход даёт возможность хранить main.cpp, наряду с Makefile, в папке с шаблонами и переносить его без изменений из проекта в проект.
main.cpp
Makefile и вспомогательный variables.mk оформлены в соответствии с Главой 6 следующей статьи.
Makefile
variables.mk
Для обмена данными с внешними устройствами были применены программные версии соответствующих протоколов. См. подробнее в датагорских статьях:
1-Wire - "Визуализация для микроконтроллера. Часть 4. Android"
I2С, SPI - "Ассемблер для микроконтроллера с нуля. Часть 6. Протоколы обмена данными"
Трансивер nRF24L01 - "Беспроводной канал связи 2,4 ГГц на базе трансивера nRF24L01+ от Nordic Semiconductor"
Во всех вышеуказанных статьях даны подробные объяснения и комментарии к коду, поэтому не вижу смысла дублировать их текст в данной работе.
↑ Код Центрального модуля
Для размещения кода ЦМ в МК потребуется 22.6 кБайт флэш-памяти, в т.ч. 3.1 кБайт — под шрифты. Кроме того, программа использует как минимум 982 байта ОЗУ, причём подавляющая часть этого объёма оперативной памяти выделяется под строковые переменные, применяемые при оформлении меню.Учитывая большое количество перекрёстных ссылок, а также для удобства были вынесены:
• макроопределения состояний флагов энкодера и определения структур — в файл globals.h,
• распиновка соединений МК с внешними устройствами — в файл pinout.h.
globals.h
Несколько пояснений.
1. Поле структуры device:
• encoderButtonFlag отражает факт нажатия кнопки и принимает значения ENCODER_BUTTON_PUSH_TIME_SHORT или ENCODER_BUTTON_PUSH_TIME_LONG при коротком или длительном нажатии, соответственно. Сбрасывается в состояние ENCODER_BUTTON_PUSH_TIME_NO_PUSH при инициализации энкодера и перед реакцией МК на нажатие.
• encoderRotorFlag отражает факт поворота ротора и принимает значения ENCODER_ROTOR_TURNED_CLOCK_WISE или ENCODER_ROTOR_TURNED_COUNTER_CLOCK_WISE при повороте по или против часовой стрелки, соответственно. Сбрасывается в состояние ENCODER_ROTOR_STOPPED при инициализации энкодера и перед реакцией МК на поворот.
• payloadReceivedFlag принимает значение 1 в обработчике прерывания от nRF24L01 при поступлении радио-пакета от ПМ. Сбрасывается в 0 при инициализации трансивера и перед реакцией МК на поступление пакета.
• colonBlinkFlag принимает значение 1 в обработчике прерывания при поступлении сигнала от DS1307. Сбрасывается в 0 при инициализации RTC и перед реакцией МК на поступление сигнала.
• colonState отражает текущий цвет символа двоеточия «:» в значении времени на экране дисплея (1 — белый, 0 — чёрный). Меняет состояние на противоположное при очередной прорисовке значения времени.
2. Поле структуры menu:
• state используется для выбора действия на экране дисплея при повороте ротора энкодера: 0 — движение рамки, 1 — изменение значения параметра (десятки/единицы времени и даты, день недели и т.д.).
• name определяет, какое именно меню будет прорисовано на экране дисплея в данный момент.
• Item идентифицирует элемент меню, на который в данный момент указывает рамка в тех меню, где предполагается её наличие и движение.
• TSsaveFlag и DSsaveFlag устанавливаются в 1 при выборе пункта «save» в меню «time settings» и «date settings», соответственно, что является сигналом для МК к сохранению установленных в указанных меню новых значений времени и даты в EEPROM DS1307.
Остальные поля структуры menu предназначены для хранения текущих и предыдущих параметров рамки, времени, даты и температуры.
pinout.h
Остановлюсь на деталях кода контроля за внешними устройствами, входящими в состав ЦМ:
• энкодер,
• RTC DS1307,
• трансивер nRF24L01,
• дисплей ST7735.
↑ Работа с энкодером
Контроль энкодера осуществляется посредством кода из одноимённых cpp- и хидер- файлов, содержимое которых представлено ниже.encoder.h
Пара пояснений:
1. Поле структуры encoder:
а) rotorTurnCounter обеспечивает подсчёт количества шагов ротора при его повороте в одном направлении.
б) counter предназначено для подсчёта длительности нажатия кнопки.
в) buttonState отражает текущее состояние кнопки и может принимать одно из четырёх значений, определённых в том же файле выше:
• ENCODER_BUTTON_IS_RELEASED (или 1, когда кнопка отпущена),
• ENCODER_BUTTON_IS_PUSHED_BEFORE_BOUNCE (или 2, когда кнопка нажата, но время дребезга не истекло),
• ENCODER_BUTTON_IS_PUSHED_AFTER_BOUNCE (или 3, когда кнопка нажата, и время дребезга истекло),
• ENCODER_BUTTON_IS_RELEASED_BEFORE_BOUNCE (или 4, когда кнопка отпущена, но время дребезга не истекло).
2. Параметры учёта текущего положения ротора необходимы для исключения дребезга при вращении последнего, о чём — ниже.
encoder.cpp
Несколько слов о том, как всё это работает.
Для полноценного функционирования ЦМ необходимо обеспечить корректную реакцию МК на четыре события, генерируемые энкодером:
1. Длительное (более 4-х секунд) нажатие кнопки.
2. Короткое (менее 4-х секунд) нажатие кнопки.
3. Поворот ротора по часовой стрелке.
4. Поворот ротора против часовой стрелке.
При этом, следует иметь в виду, что производителем модуля энкодера, использовавшегося в проекте, не предусмотрены цепи подавления дребезга.
Учитывая вышеизложенное, было принято решение использовать в коде обработки сигналов от кнопки:
а) прерывание с периодом 32.8 мс по переполнению счётчика таймера TIMER1, входящего в состав МК, с целью:
• организации задержки при нажатии кнопки для исключения дребезга, длительность которого, как правило, не превышает указанный период,
• подсчёта времени удержания кнопки в нажатом состоянии.
б) внешнее прерывание PCINT8 от пина PC0 МК, к которому, как видно из Рисунка 1, подключен выходной контакт SW кнопки, для обеспечения немедленной реакции на её нажатие/отпускание.
Алгоритм действий МК при нажатии/отпускании кнопки можно понять из Рисунка 9.

Рисунок 9. Реакция МК на нажатие/отпускание кнопки
Проследим за событиями от точки А до точки G на Рисунке 9, помня о том, что время между двумя соседними из них составляет 32.8 мс.
A. Происходит нажатие кнопки, в связи с чем в обработчике прерывания PCINT8:
• записывается соответствующее значение (2 — кнопка нажата, но время дребезга не истекло) в поле buttonState структуры device, отражающее текущее состояние кнопки,
• локально запрещается прерывание PCINT8 на период дребезга,
• включается тактирование таймера,
B. Генерируется первое, после нажатия кнопки, прерывание таймера, в обработчике которого:
• записывается соответствующее значение (3 — кнопка нажата и время дребезга истекло) в поле buttonState,
• сбрасывается записью в него 1 флаг PCIF1, поднятый дребезгом после нажатия кнопки,
• локально разрешается прерывание PCINT8 для фиксации отпускания кнопки,
• инкрементируется поле counter структуры device, призванное считать длительность нажатия кнопки.
C – E. Инкрементируется поле counter.
F. Кнопка отпускается, вследствие чего генерируется прерывание PCINT8, в обработчике которого:
• записывается соответствующее значение (4 — кнопка отпущена, но время дребезга не истекло) в поле buttonState,
• локально запрещается прерывание PCINT8 на период дребезга,
• в последний раз инкрементируется поле counter, после чего его значение анализируется (если оно меньше 125, значит длительность нажатия — меньше 32.8 мс х 125 = 4 секунд, в противном случае — больше 4 секунд) и обнуляется, а в поле encoderButtonGlag структуры device при этом записывается соответствующее значение (ENCODER_BUTTON_PUSH_TIME_SHORT или ENCODER_BUTTON_PUSH_TIME_LONG),
• обнуляется счётчик TCNT1 таймера, чтобы обеспечить корректное время задержки на период дребезга.
G. По завершению задержки на время дребезга генерируется прерывание таймера, в обработчике которого:
• записывается начальное (1 — кнопка отпущена) значение в поле buttonState,
• тактирование таймера отключается до следующего нажатия кнопки,
• сбрасывается записью в него 1 флаг PCIF1, поднятый дребезгом после отпускания кнопки,
• локально разрешается прерывание PCINT8 для фиксации следующего нажатия кнопки.
Точки A – G из Рисунка 9 указаны, для полноты понимания, в коде обработчиков прерываний таймера и PCINT8 в тексте приведённого выше файла encoder.c.
Алгоритм интерпретации сигналов от ротора основан на том факте, что комбинация логических уровней на его выходных контактах в любой момент времени составляет, как видно из Рисунка 10, одно из четырёх чисел — 0, 1, 2 или 3.

Рисунок 10. Тайминг сигналов на выходных контактах ротора энкодера
Соответствующей код оформлен в виде функции getEncoderRotorState() в encoder.cpp и подробно прокомментирован, поэтому ограничусь лишь короткими пояснениями:
1. Выбор соседних пинов одного и того же порта МК (в нашем случае — PC2, PC1) для подключения контактов ротора позволяет заметно упростить код. При этом макроопределение ENCODER_ROTOR_PIN_LS в pinout.h обязательно должно быть дано пину с меньшим порядковым номером (в нашем случае — PC1).
2. Применение двойного сдвига влево предыдущего значения числа, формируемого комбинацией логических уровней на пинах PC2 и PC1, снижает влияние дребезга. Вследствие сдвига и последующей операции ИЛИ с текущим значением, окончательное число для определения направления поворота будет не 0, 1, 2, 3, а 1, 7, 8 14 при повороте по часовой стрелке или 2, 4, 11, 13 при повороте против часовой стрелки.
3. Накопление до 4 количества шагов поворота ротора в одном направлении так же, как и в предыдущем случае, помогает в борьбе с дребезгом.
↑ Код для часов реального времени
Содержимое файлов контроля за RTC DS1307 представлено ниже.DS1307.h
DS1307.cpp
Думаю, вы легко догадаетесь о назначении большинства полей и методов класса DS1307, исходя из их названий. Отмечу лишь, что:
• методы On() и Off() включают и выключают, соответственно, тактирование микросхемы часов реального времени,
• методы SQWEon() и SQWEoff() призваны разрешать и запрещать, соответственно, сигнал заданной частоты на выводе SQ RTC,
• методы decToBcd() и bcdToDec() необходимы для преобразования десятичного числа в двоично-десятичное и обратно, соответственно,
• в конструкторе класса осуществляется лишь включение тактирования.
↑ Управление трансивером nRF24L01
Работу трансивера обеспечивают, помимо упомянутого выше pinout.h, представленные ниже файлы.SPI.h
SPI.cpp
nRF24.h
nRF24.cpp
Поясню основные моменты.
1. При оформлении кода приняты во внимание требования спецификации nRF24L01, выложенной в архив статьи.
2. Метод begin():
• настраивает соответствующим образом пины МК, к которым подключены выводы CSN и CE трансивера,
• переводит nRF24L01 в режим передатчика (как в случае с ПМ) или приёмника (как в случае с ЦМ), в зависимости от значения входного аргумента mode.
• очищает регистры STATUS и FIFO трансивера.
3. Методы setPTX() и setPRX() обеспечивают перевод трансивера в режим передатчика и приёмника, соответственно, используя маски, определённые в хидер-файле.
4. Методы powerUp() и powerDown() позволяют реализовать режим сна nRF24L01.
5. Методы transmit() и receive() предназначены для передачи и чтения радио-пакета размером PAYLOAD_SIZE.
↑ Управление дисплеем на ST7735
Даташит ST7735 выложен в архив статьи. В проекте использовалась версия дисплея, поддерживающая SPI, в связи с чем для обмена данными с ним применялась та же библиотека протокола, что и в случае с nRF24L01.В основу кода легли материалы, представленные в статьях "Визуализация для микроконтроллера. Часть 2. TFT дисплей 1.8" (128х160) на ST7735" и "Визуализация для микроконтроллера. Часть 5. Графика".
Помимо упомянутых выше SPI.h и SPI.cpp работу дисплея обеспечивают нижеследующие файлы.
ST7735.h
ST7735.cpp
В вышеуказанной статье вы найдёте подробнейшие объяснения и комментарии. Остановлюсь лишь на двух наиболее важных моментах:
а) Метод begin() осуществляет инициализацию дисплея, включая:
• настройку пинов МК, связанных с выводами дисплея,
• включение дисплея,
• установку ориентации дисплея,
• окрашивание экрана дисплея в выбранный цвет (в нашем случае — чёрный).
б) Функция drawPixel() позволяет нарисовать точку размером 1х1 пиксель с учётом заданных параметров — цвета и координат. Именно на этой функции построены методы графической библиотеки GFX, файлы которой приведены ниже.
Кроме файлов ST7735, графическая библиотека GFX использует вспомогательные файлы iconsFonts, в которых содержатся:
• константные строковые переменные — «time settings», «datee settings», «exit» и «save»,
• шрифты для отражения значения времени (Arial36), а так же температуры, даты и других параметров (Arial16), сгенерированные посредством LCD Image Converter.
iconsFonts.h
iconsFonts.cpp
Сама графическая библиотека была модифицирована для работы с пропорциональным шрифтом.
GFX.h
GFX.cpp
В конечном итоге, с использованием всех описанных выше файлов был оформлен графический интерфейс проекта. Причём, было решено разбить его на две части:
• прорисовка значений времени, даты и температуры (файлы text.h и text.cpp).
• общая прорисовка меню и управляющий интерфейс (файлы display.h и display.cpp).
Такое разбиение обусловлено не только объёмом кода, но и соображениями о возможности использования файлов text в других проектах без внесения особых изменений.
text.h
text.cpp
Думаю, из комментариев и названий функций не сложно понять их назначение.
display.h
display.cpp
Рассмотрим детальнее функции из device.cpp.
1. diaplayInit():
• инициализирует ST7735,
• присваивает значение 0x0f полям структуры menu, хранящим предыдущие значения времени, даты и температуры для предотвращения непрорисовки соответствующего параметра в случае, если при первом считывании его значение равно нулю,
• прорисовывает меню из Рисунка 2.
2. drawMenu() — непосредственно функция прорисовки меню, результат исполнения которой зависит от текущего значения menu.name.
3. drawFrame() — функция прорисовки рамки в тех меню, где предполагается её наличие.
4. MAINcontrol() вызывается при каждом прерывании от DS1307 или nRF24L01 и ответственна за обновление в меню из Рисунка 2 значений времени, даты, температуры, а также цвета символа двоеточия.
5. TDEcontrol() вызывается для обеспечения:
• первоначальной прорисовки меню из Рисунка 3 — при длительном нажатии на кнопку в основном режиме ЦМ,
• для обеспечения движения рамки в указанном меню — при повороте ротора,
• перехода к меню из Рисунка 4 или 5, либо возврата к меню из Рисунка 2 — при коротком нажатии на кнопку.
6. TScontrol() через вложенные в неё TScontrolButtonPressedShort(), TScontrolRotorTurnedCW(), TScontrolRotorTurnedCCW() обеспечивает:
• изменение на экране дисплея значений того или иного параметра, а также движение рамки в меню из Рисунка 4 — при повороте ротора и коротком нажатии на кнопку.
• установку в 1 значения поля menu.TSsaveFlag и возврат к меню из Рисунка 3 при выборе пункта «save» меню из Рисунка 4.
7. DScontrol() и вложенные в неё DScontrolButtonPressedShort(), DScontrolRotorTurnedCW(), DScontrolRotorTurnedCCW() ответственны за те же действия, что и функции из п.6, но для меню из Рисунка 5.
↑ Общее управление Центральным модулем
Общее управление ЦМ осуществляется функциями из файлов device.device.h
device.cpp
Как видите:
1. В обработчиках прерываний от nRF24L01 и DS1307 лишь устанавливаются в 1 значения полей device.payloadReceivedFlag и device.colonBlinkFlag, соответственно.
2. В функции deviceInit() при подаче питания/сбросе:
• инициализируются все внешние устройства,
• локально и глобально разрешаются прерывания.
3. В функции deviceControl() прописана реакция МК на:
• прерывания от RTC и трансивера,
• нажатия кнопки и поворот ротора,
• выбор пункта «save» меню из Рисунков 4 и 5.
4. Функция getTime() считывает из DS1307 текущее значение времени, а затем помещает значения десятков и единиц часов и минут в соответствующие поля структуры menu.
5. Функция setTime() «склеивает» значения десятков и единиц часов и минут, выбранных в меню из Рисунка 4, а затем записывает их в RTC.
6. Функции getDate() и setDate() производят те же действия, что и две предыдущие, только по отношению к значениям даты.
7. Функция getTemperature() вычленяет из двух полученных от ПМ байтов значения знака, десятков, единиц и десятых долей текущей температуры и размещает их в соответствующих полях структуры menu.
↑ Код Периферийного модуля
Код управления ПМ занимает 4.8 кБайт флэш-памяти и 10 байт ОЗУ.↑ Контроль за температурным датчиком
За корректную работу DS18B20, даташит которого выложен в архив статьи, отвечает код следующих файлов.OneWire.h
OneWire.cpp
DS18B20.h
DS18B20.cpp
Поясню детали работы метода readTemperature():
1. Поскольку в ПМ используется лишь один датчик, для обращения к нему выбрана общая команда SKIP ROM.
2. Посредством команды CONVERT T запускается измерение датчиком температуры. При этом, требуется задержка в 750 мс, поскольку дефолтное разрешение DS18B20 – 12 бит (смотрите Таблицу 2 даташита).
3. Команда READ SCRATCHPAD позволяет считать данные из памяти датчика. В рамках проекта достаточно чтения первых двух байт, в которых и содержится значение температуры.
4. Результат измерения возвращается в формате float. Однако, при необходимости можно воспользоваться и непосредственно значениями считанных байтов, для хранения которых предусмотрено поле data класса. Именно этот вариант и реализован в проекте.
↑ Код для трансивера ПМ
Для контроля за трансивером ПМ применялась та же библиотека nRF24, что и в случае с ЦМ.Отличия в реализации следующие:
а) Входной аргумент метода begin() — PTX,
б) Большую часть времени трансивер посредством метода powerDown() погружён в сон, из которого выводится с помощью метода powerUp() лишь для передачи текущего значения температуры.
↑ Управление энергопотреблением МК
С целью максимально снизить энергопотребление ATtiny85 оформлены файлы sleep, представленные ниже.sleep.h
Следует пояснить, что переменная wdtCounter предназначена для подсчёта количества прерываний сторожевого таймера, а макроопределение WDT_COUNTER_MAX задаёт её максимальное значение.
sleep.cpp
Как видите:
а) В функции sleepInit():
• для МК выбирается режим сна Power Down,
• включается сторожевой таймер с периодом счёта 8 секунд, а так же разрешается его прерывание,
б) Функция sleepOn() погружает МК в сон посредством ассемблерной инструкции «sleep».
↑ Общее управление ПМ
Общий контроль за работой ПМ осуществляется посредством файлов device, код которых приведён ниже.device.h
device.cpp
Коротко о том, как работает код.При инициализации ПМ микроконтроллер переводится в режим сна Power Down, из которого выводится посредством прерывания сторожевого таймера. Поскольку максимальный период счёта последнего составляет 8 секунд, в обработчике прерывания происходит инкремент переменной wdtCounter и лишь по достижению ею значения 450 (т.е. один раз в час) микроконтроллер:
• выводится из состояния сна,
• измеряет посредством DS18B20 температуру,
• будит трансивер и отправляет ЦМ 2 байта со значением температуры,
• возвращает трансивер в режим сна,
• сам погружается в сон.
↑ Внешнее оформление часов
Все элементы ЦМ уместились в корпусе размером 66х66х50 мм, а ПМ — 53х53х25 мм. Детали корпусов обоих модулей и ручка энкодера были смоделированы в онлайн-среде Tinkercad и распечатаны на 3-D принтере ANYCUBIC I3 MEGA, а их объектные файлы выложены в архив статьи.
Рисунок 7. 3-D модели корпуса, крышки и ручки энкодера для ЦМ

Рисунок 8. 3-D модели корпуса и крышки для ПМ
↑ Файлы
🎁Файлы к статье. Исходный код; корпуса для 3D-печати 218.94 Kb ⇣ 23Благодарю за внимание!