Приемопередатчик HC-12 и датчик температуры DS18b20 на AVR-ассемблере
Одним из таких моментов является беспроводная связь между устройствами, вторым — цифровые датчики (в книжке большей частью изложено обращение лишь с аналоговыми). По нижеизлагаемым причинам для связи я остановился на модуле HC-12, а в качестве цифрового датчика использовал одну из бессмертных разработок «всех времен и народов» — датчик DS18b20.
Для упрощения отладки проекта его составляющие сначала моделировались на Arduino, и затем воспроизводились на ассемблере на более адекватной элементной базе.
Уже столкнувшись пару-тройку раз лоб в лоб с «оптимизирующим компилятором», я полагаю, что необходимости углубленного изучения программирования вполне можно избежать, коли вы собрались простенький прибор построить, а не вникать в нюансы построения этих самых компиляторов. Оптимизировать код «по месту» вручную, согласно моему опыту, можно куда лучше и более предсказуемо, чем это сделает компилятор. Конечно, при условии, что вы привыкли изучать не «оптимизирующие компиляторы», а тщательно вникать в нюансы схемотехнических решений и функционирования программ. И научить этому, без сомнения, может только ассемблер.
Остальные соображения по этому поводу см. в книге.
Приемопередатчик HC-12
Сначала речь пойдет об организации беспроводной связи между двумя девайсами. Тема, можно сказать, топовая для наших дней, но при ближайшем рассмотрении приемлемое решение оказывается найти довольно сложно. Естественно, в простых устройствах целесообразно применение самых дешевых и компактных решений. Но дешевизна далеко не синоним простоты — малая цена и доступность описанного мной год назад RF-комплекта из приемника и передатчика ISM-диапазона 433 МГц вполне компенсируются сложностью библиотечного кода. Еще более усложнять задачу, переписывая его на ассемблере, просто глупо. И, кстати, за это время я выяснил, что качество модулей в продаже почему-то резко упало: даже из проверенных источников получить RF-комплект, не желающий работать в паре — совсем не исключение. Для проверки заказал десяток комплектов по дешевке у вроде бы солидного продавца на Ali, посмотрим на качество, когда доберутся.
Для ассемблерного подхода здесь бы идеально подошел какой-нибудь из приемопередатчиков, работающих через стандартные порты обмена (обычно это UART, иногда SPI). К сожалению, почти все они довольно дороги — в книге я ориентировал читателей на MBee-868, но самый дешевый из них находится поиском за цену порядка 1200 рублей, а для радиообмена ведь нужна их пара (и не ищите их на Ali — говорят, это чисто отечественная разработка). За такой порядок цен можно уже построить целую метеостанцию даже по ценам «Чипа-Дипа».
Остальные решения, такие, как LoRa, в основном еще дороже (если не рассматривать сомнительные и плохо документированные предложения с Ali). Потому я остановился на HC-12 на 433 МГц — пару таких модулей можно купить в более-менее надежном интернет-магазине рублей за 800 (или раза в полтора-два дешевле прямо на Ali). Смоделировав прием и передачу на Arduino, я убедился, что отзывы на форумах не врут: из коробки, без всяких настроек, модули работают просто отлично.
Обмен по умолчанию идет на скорости 9600. В качестве антенны использовался просто отрезок изолированной проволоки диаметром 0,5 мм и длиной 17 см (к модулям прилагается и спиральная антеннка, но, опять же по отзывам, она работает хуже). На дальность не проверял, но в пределах квартиры, включая передачу на лоджию через капитальную кирпичную стену под косым углом, никаких проблем не возникло.
Приятная особенность всех применяемых решений — отсутствие аналоговых компонентов, что позволяет не очень заботиться о качестве питания (напомню, что существенным фактором при работе RF-приемника является необходимость отдельного от цифровой части аналогового стабилизатора).
Энергосбережение при передаче
Все хорошо, и меня устраивает — процедуры обмена простейшие и в полпинка переводятся на ассемблер. Есть только одно довольно большое «НО»: потребление. Модуль в умолчательном режиме на холостом ходу потребляет 15 мА, которые возрастают до 100 и более мА в момент передачи. Исследования с помощью осциллографа показали, что передача длится дольше, чем это должно быть при формально выставленной скорости 9600. При такой скорости байт передается примерно за миллисекунду, измерения же показали, что при передаче четырехбайтового числа потребление возрастает до 100 мА примерно на 15 мс. С этим может справиться обычный 100-миллиамперный стабилизатор (LM2931, LP2950 и пр.) с хорошим танталовым конденсатором на выходе, но для батарейного питания все это совершенно не годится.
Конечно, я сначала рассчитывал на штатные способы энергосбережения, из которых выбрал периодическое погружение в сон и пробуждение с помощью AT-команд. Но первое же испытание управления HC-12 с помощью AT-команд (попытка получения стандартного отклика «AT»-«OK») показала полный облом. Вход в режим управления (заземлением вывода S) занимает длительное время — официально не менее 40 мс, в реальности может и все 200, отклик приходит через раз и может ожидаться вообще неопределенно долго. Плюс штатный выход из режима управления — формально не менее 80 мс, и черт его знает, сработало там все или зависло.
Почитав стенания по этому поводу на различных форумах, я решил, что такой хоккей нам не нужен! Пусть разбираются въедливые перфекционисты, а мы поступим проще: раз в умолчательном режиме модуль нормально работает, просто будем отключать питание, а при необходимости передачи — включать. Экспериментально удалось установить, что после включения модулю достаточно 30–35 мс на успокоение, для гарантии будем выжидать 50 мс. Включать питание модуля будем маломощным MOSFET-ключом (транзистор BS-170), замыканием на общий провод. Не забудем выждать до выключения необходимое время для гарантированного окончания передачи. Эксперименты показали, что при снижении паузы до выключения модуля ниже 20–30 мс процесс обмена нарушается, потому будем с запасом выжидать также 50 мс. Это проще в осуществлении, чем подача AT-команд, страхует от странностей работы модуля и занимает не больше времени, чем управлением штатным способом в идеальном случае. Причем в перерывах модуль совсем ничего потреблять не будет и не надо экспериментировать с выбором из различных предлагаемых режимов.
Моделирование передачи-приема на Arduino
Скетч, моделирующий простую передачу двухбайтового числа (HC-12_proba_peredatchik) вы найдете в архиве по ссылке в конце статьи. Раз в 4 секунды передается число 273. Соответственно, в простейшем скетче для проверки приема (HC-12_proba_priemnik) при приеме этого числа светодиод по выводу 13 меняет состояние на противоположное. В текстовых скетчах намеренно использован не слишком удобный способ побайтной передачи двухбайтового числа и формированием его заново на приемном конце, примерно так (
value
— двухбайтовая переменная типа word
или uint_16
): . . . . .
byte bb = Serialpr.read(); //мл. байт
value = Serialpr.read(); //ст. байт
value = value*256+bb; //присланное число
. . . . .
Возможности класса
Serial
позволяют и передачу и прием сделать более компактным способом, но в дальнейшем мы будем переводить это на ассемблер, где никаких классов Serial не имеется, потому сразу делаем программы передачи и приема совместимыми с ассемблерным вариантами.И для приемника и для передатчика можно использовать любой Ardiuno на основе ATmega328 (Uno, Nano, Mini или их аналоги, в том числе и с 3-вольтовым питанием). Схемы подключения и передатчика и приемника в тестовом варианте тривиальны: подключить оба вывода питания GND и Vcc модуля к питанию Arduino GND и +5V, и перекрестно соединить выводы RxD и TxD. В скетчах для связи через модули HC-12 использован программный UART: вывод 8 (RxD) и 9 (TxD) Arduino в передатчике, 2 (RxD) и 3 (TxD) в приемнике (см. исходный текст скетчей). Можно, конечно, использовать и штатный UART, но тогда необходимо каждый раз перед подключением USB отключать модуль и при проверке запитывать контроллер от автономного адаптера. Забывчивость мне уже стоила одного необратимо сожженного Uno и одного контроллера, лишившегося загрузчика, потому рисковать не советую (что интересно, в случае Xbee-модулей, которые также подключаются к UART, никаких таких катастроф не наблюдалось — просто Arduino IDE при подключении Xbee переставала получать отклик от порта). В ассемблерном варианте, который мы будем обсуждать далее, подключенный к порту UART адаптер, разумеется, не имеет значения, так как программирование через него не ведется. И мы там будем всегда использовать штатный порт, а на приемной стороне выводить полученные данные на дисплей.
Теперь соберем схему для испытаний модуля HC-12 на отключение и включение с помощью транзисторного ключа, согласно описанному ранее:
В соответствующем скетче (HC-12_proba_power_peredatchik) контроллер каждые 4 секунды отсылает в эфир возрастающее число. В перерывах питание модуля отключается. Управление включением и отключением питания модуля через ключ, соединяющий с модуль с «землей», осуществляется через вывод D5 Arduino (совпадает с выводом 5 порта D). Именно на такой схеме проводились измерения потребления модуля и проверялись необходимые величины задержек на установление питания.
Теперь отвлечемся от потребления и смоделируем расширенную передачу данных с выводом на дисплей. Усложненный скетч передатчика HC-12_peredatchik_array также имеется в архиве по ссылке в конце статьи. Контроллер передает в эфир целый массив, начинающийся с идентификатора передатчика (в данном случае трех символов «DAT»), после следуют два байта числа и затем символ »;» качестве конца данных. Опять же оформить подобную передачу можно средствами высокоуровневого языка более красиво и компактно, но мы остановимся на побайтной передаче массива, так как именно такой способ будем использовать в ассемблерном варианте. Текст скетча HC-12_peredatchik_array невелик и я привожу его здесь полностью:
#define Ledpin 13
#define RX 8 // * Определяем вывод RX (TX на модуле)
#define TX 9 // * Определяем вывод TX (RX на модуле)
#include // Библиотека программного последовательного порта
SoftwareSerial Serialpr(RX,TX); // Программный последовательный порт
word value=273; //условное 3-значное число (2-байтовое)
//передаваемые байты, "DAT"-идентификатор, ";" - разделитель (конец данных):
byte arr[6] = {'D','A','T',0,0,';'};
void setup() {
Serialpr.begin(9600);
pinMode(Ledpin,OUTPUT);
delay(100);
}
void loop() {
digitalWrite(Ledpin,HIGH);
//доформируем массив:
arr[3]=lowByte(value);
arr[4]=highByte(value);
for (byte i = 0; i < 6; i++)
Serialpr.write(arr[i]); //передаем массив через программный порт
delay(50);
digitalWrite(Ledpin,LOW);
delay(4000);
}
Может показаться, что накладные расходы такого способа слишком велики — четыре служебных байта на два байта передаваемого числа. Но во-первых, это тестовый макет (в реальности мы можем передавать сколько угодно чисел любой разрядности), во-вторых, таким способом мы можем повесить на один приемник любое количество датчиков, меняя их идентификатор (к примеру «DA1», «DA2» и так далее). А оконечный символ »;» служит здесь для проверки того факта, что массив передан и принят полностью (в Arduino без этого символа вполне можно обойтись, а в ассемблерном варианте он нам пригодится, чтобы не связываться со всякими таймаутами).
Для проверки можно просто принимать массив побайтно через программный порт, и потом отправлять принятое через обычный Serial на монитор порта для проверки. Для такого способа можно использовать слегка модифицированный скетч простейшего приемника (HC-12_proba_priemnik). Подробнее я останавливаться на этом не буду, так как мы тут сразу попробуем смоделировать отвязанный от USB приемник с дисплеем.
Приставим к Arduino обычный ЖК или OLED-дисплей 16×02, так как уже знаем (см. предыдущую статью), что потом перенести это в ассемблерный проект будет совсем несложно. Схема подключения дисплея и модуля HC-12 к Arduino показана на рисунке:
Соответствующий скетч носит название HC-12_priemnik_OLED16×02_Arduino, и также может быть найден в архиве по ссылке в конце статьи. Пятисекундная задержка в начале (в функции setup()
) с тестовым выводом на дисплей необходима для проверки правильности подключения и инициализации дисплея. После этого программа начинает отслеживать прием массива через программный UART (штатный Serial при этом используется для отладки). Прием устроен довольно нестандартным способом:
. . . . .
if (Serialpr.available()>0) {
i=0;
while (Serialpr.available()){
digitalWrite(Ledpin,HIGH); // Пришло! Считываем и анализируем
bb=Serialpr.read();
//обязательная задержка, иначе вылетаем из while:
delay(2);
// Serial.println(bb); //для отладки
// Serial.print(' '); //для отладки
{arr[i]=bb; i++;}
} //конец while
. . . . .
После того, как мы убедились, что пришел первый байт передаваемого массива (
Serialpr.available()>0
), производится отслеживание по условию while (Serialpr.available()
). Выход из этого цикла происходит автоматически по истечению таймаута на прием. По умолчанию в Arduino Serial.timeout
равен 1 мс, что примерно равно времени передачи байта со скоростью 9600. Поэтому через пару-тройку принятых байт мы из цикла вылетаем, не закончив прием массива. Чтобы этого избежать, после приема очередного байта ставится небольшой delay, тогда выход из цикла будет только после приема последнего байта массива. Задержка необязательна, если раскомментировать отладочные строки посылки через обычный Serial — время их выполнения как раз обеспечит необходимую задержку. Повторю, что средствами Arduino можно обеспечить куда более компактный прием (применив всякие там readString
и parseInt
), но мы моделируем будущий ассемблерный вариант. Вы увидите, что там проблема приема целостного массива будет решаться другим способом, где как раз посылка разделителя »;» будет играть решающую роль.Убедившись, что массив принят полностью (последний принятый байт содержит символ двоеточия »;»), а первые три байта полученного массива содержат необходимый идентификатор (строка «DAT»), мы, как и ранее, формируем двухбайтовое число из отдельных байт и посылаем результат на дисплей.
Передача и прием на ассемблере
Испытание ассемблерной версии передатчика проводилось по схеме, показанной на рисунке выше (сразу с возможностью отключения питания модуля HC-12). Контроллер ATtiny2313 может быть заменен в данном случае на почти любой другой AVR (о том, что именно надо менять в исходном коде при такой замене — см. упомянутую книгу). Кварц (в данном случае на 4 МГц) — обязательная для этого случая деталь, при работе от встроенного генератора UART на скорости 9600 будет сбоить. Потребление в сравнении с ардуиновскими 16 МГц падает примерно вдвое, а кварц шустрее 4 МГц нужен лишь в отдельных задачах, связанных с измерением времени.
Управление ключом питания модуля HC-12 производится через вывод 5 порта D (вывод 9 микросхемы). Стандартный для Arduino вывод сигнального светодиода D13 (это вывод 19 ATmega328) здесь заменен на вывод 6 порта D (выв. 11 ATtiny2313, крайний в корпусе). Заметьте, что на схеме указан стандартный светодиод, который при резисторе 620 Ом будет потреблять около 5 мА. Если хотите немного сэкономить дополнительно, поставьте суперяркий (1000 мкд и более) и к нему резистор порядка 20–33 кОм, в зависимости от яркости свечения выбранного типа.
Предварительно необходимо установить fuse-биты. Да-да, а что вы думали? Это в Arduino вам их менять не только нельзя, но и невозможно. А все контроллеры выпускаются с фабрики с установками на работу от встроенного генератора (причем с конкретной частотой 1 МГц, за редким исключением). Потому их нужно поменять, как минимум, на работу от внешнего кварца. Для облегчения программирования чипа 2313 привожу наглядную картинку настроек фьюзов для данного случая. Как там и написано, нажатая кнопка соответствует нулевому (programmed) значению фьюза:
Систему BOD мы здесь держим выключенной (все биты BODELEVEL в единичном состоянии), потому что это 20–30 мкА дополнительного потребления. При выключенной системе отслеживания питания контроллер при истощении батарейки может совершать непредсказуемые операции, но в общем случае это опасно только для содержимого EEPROM. В данной схеме долговременная память не употребляется и BOD можно выключить.
Соответствующая тестовая ассемблерная программа передатчика сразу с использованием энергосбережения (H-12_peredatchik.asm) приведена полностью далее:
.include "tn2313def.inc"
.def temp =r16
.def Razr0 = r17 ;разряды задержки
.def Razr1 = r18
.def value = r19 ;очередной байт числа
;============ прерывания ============
rjmp RESET ;Reset Handle
.org WDTaddr
rjmp WDT_over ;WDT Interrupt Vector Address
;========== программа ============
out_com: ;посылка байта из count с ожид. готовности
sbis UCSRA,UDRE ;ждем готовности буфера передатчика
rjmp out_com
out UDR,value ;value!!! а не temp
ret
;Число N для задержки T (с) при такт. частоте F (Гц) равно
;N = TF/4; F= 4 МГц
;Для T = 50 mc N= 50 000, $C3 50 = 195 80
.macro Delay65 ;процедура задержки
ldi Razr1,@0 ;старший байт N
ldi Razr0,@1;младший байт N
R_sub:
subi Razr0,1
sbci Razr1,0
brcc R_sub
.endm
WDT_over: ;пробуждение по Watchdog
sbi PortD,6 ;зажигаем светодиод
sbi PortD,5 ;включаем модуль
Delay65 195, 80 ;задержка 50 мс при 4 МГц
ldi value,Low(273) ;мл. байт числа 273
rcall out_com ;посылаем во внешний мир
ldi value,High(273) ;ст. байт числа 273
rcall out_com ;посылаем во внешний мир
Delay65 195, 80 ;задержка 50 мс при 4 МГц
cbi PortD,6 ;зажигаем светодиод
cbi PortD,5 ;включаем модуль
reti ;конец прерывания WDT
Reset:
ldi temp,low(RAMEND) ;устанавливаем указатель на стек
out SPL,temp ;для tiny2313 только SPL
ldi temp,1<
В главном цикле программы, как видите, совершается единственное действие — уход в энергосберегающее состояние. Все действия здесь реализованы в обработчике прерывания сторожевого таймера (WDT), настроенного на выход из сна каждые 4 секунды (максимальное значение 8 секунд). После выхода из сна через МОП-ключ BS-170 подключается передачик, дается пауза 50 мс для того, чтобы модуль «пришел в себя», и затем производится передача. Измерения показали, что в паузах вся схема потребляет не более 150 мкА. Активный период длится около 0,1 с, в это время потребление в среднем составляет около 50 мА. Напомним, что в ассемблерной программе подключение программатора ни на что не влияет, потому при отладке передатчика его можно не отключать (но не забывайте, что программатор питается от схемы и при измерении потребления отключать его все-таки необходимо!). Напомним, что в качестве светодиода Led1 можно поставить суперяркий с токоограничивающим резистором R2 величиной 20–30 кОм. Текст программы и скомпилированный hex-файл вы найдете в том же архиве в конце статьи.
Следует учесть, что WDT в контроллере ATtiny2313 расширенного типа, аналогичного встроенному в Arduino-контроллеры ATmega328. Сторожевой таймер в старых контроллерах, вроде ATmega8/16, не поддерживает режим выхода из сна в прерывание, только полный перезапуск. Это критично для программ, хранящих промежуточные результаты в памяти или регистрах, но в данном случае приведет лишь к необходимости в минимальной коррекции процедур инициализации сторожевого таймера и переноса всей функциональности в главный цикл (подробности см. книгу). Кроме того, старый WDT не поддерживает длинные выдержки в состоянии сна (более 2 с). Это следует помнить и проверять тип встроенного WDT, если вы захотите перенести программу на другой контроллер (кстати, популярный у ардуинщиков ATtiny13 также имеет расширенный WDT, но в нем отсутствует полноценный аппаратный UART, потому для наших целей его придется приспосабливать дополнительно).
Расширенную программу передатчика с посылкой массива, аналогично тому, как мы это делали на Arduino, вы также можете найти в архиве (H-12_peredatchik_2313_WDT.asm). Она отличается от приведенной выше только содержимым обработчика прерывания WDT:
. . . . .
WDT_over: ;пробуждение по Watchdog
sbi PortD,6 ;зажигаем светодиод
sbi PortD,5 ;включаем модуль
Delay65 195, 80 ;задержка 50 мс при 4 МГц
ldi value,'D' ;символ D
rcall out_com ;посылаем во внешний мир
ldi value,'A' ;символ A
rcall out_com ;посылаем
ldi value,'T' ;символ T
rcall out_com ;посылаем
ldi value,Low(273) ;мл. байт числа 273
rcall out_com ;посылаем
ldi value,High(273) ;ст. байт числа 273
rcall out_com ;посылаем
ldi value,';' ;символ ';', конец передачи
rcall out_com ;посылаем
Delay65 195, 80 ;задержка 50 мс при 4 МГц
cbi PortD,6 ;зажигаем светодиод
cbi PortD,5 ;включаем модуль
reti ;конец прерывания WDT
. . . . .
Программа каждые 4 секунды передает в эфир такой же массив, предваряемый идентификатором датчика, как и Arduino-скетч выше, потому, чтобы убедиться, что передатчик работает верно, проверить прием можно тем же скетчем приемника с дисплеем (HC-12_priemnik_OLED16×02_Arduino). Обратите внимание, что передаваемое число здесь 2-байтовое, но ограничено 3 десятичными знаками (0–999). Так в дальнейшем (при выводе на дисплей) удастся сократить процедуру BCD-преобразования — в задаче дистанционного термометра далее передаваемые величины температуры не будут выходить из этого диапазона.
Приемник на ассемблере
Для дальнейших действий нам потребуется приемник с дисплеем на основе ассемблера, потому давайте составим макет подобного устройства. За основу возьмем ATmega8, тогда схема может быть, например, такой:
Установка fuse-бит для Mega8 показана на рисунке (здесь питание от сетевого адаптера, экономия не требуется, потому систему BOD можно не отключать):
Программа, соответствующая этому рисунку (HC-12_priemnik_OLED16×02_proba.asm), получается довольно громоздкой, потому полностью здесь не приводится, и вы ее сможете посмотреть в архиве по адресу в конце статьи. Здесь мы остановимся на ключевых моментах. В начале программы, после всех необходимых установок, дисплей для проверки верности подключения и для образца заполняется тестовыми символами точно так же, как это делалось в программе OLED1602_proba.asm из предыдущей статьи. Затем программа переходит к замкнутому циклу, в котором принимает последовательно все элементы нашего массива, с проверкой трех символов идентификатора в начале и концевого символа »;» в конце:
Gcykle:
rcall in_com ;ждем прихода байта в temp
cpi temp,'D'
sbi PortD,Led ;зажигаем Led
brne Gcykle ;если не D, то в начало ожидания
rcall in_com ;ждем прихода байта в temp
cpi temp,'A'
brne Gcykle ;если не A, то в начало ожидания
rcall in_com ;ждем прихода байта в temp
cpi temp,'T'
brne Gcykle ;если не T, то в начало ожидания
;дальше два байта числа
rcall in_com ;ждем прихода байта в temp
mov valueL,temp ;получаем мл. разряд
rcall in_com ;ждем прихода байта в temp
mov valueH,temp ;получаем ст. разряд
rcall in_com ;ждем прихода байта в temp
cpi temp, ';'
brne Gcykle ;если не конец данных, то ничего не делаем
;иначе зажигаем Led
; переводим число valueH:valueL в BCD-форму и выводим на дисплей
rcall bin2BCD10
rcall unpack_bcd8 ;результат3 дес. разряда в ResH:valueH:valueL
Set_cursor 0,0 ;курсор строка 0 позиция 0
mov temp,ResH ;выводим старший
subi temp,-$30
rcall LCD_data
mov temp,valueH ;выводим средний
subi temp,-$30
rcall LCD_data
mov temp,valueL ;выводим младший
subi temp,-$30
rcall LCD_data
cbi PortD,Led ;гасим Led
rjmp Gcykle
Если эти служебные символы не совпадают с ожидаемыми, то происходит возврат в начало цикла ожидания. Если все совпало, то выполняем конвертацию полученного hex-числа в три отдельных десятичных цифры и выводим их дисплей в виде символов, для чего достаточно к каждой цифре прибавить число 0×30 = 48, соответствующее позиции нуля в таблице ASCII (во избежание недоумений: чтобы прибавить к регистру общего назначения константу, в AVR-ассемблере применяется команда вычитания отрицательного числа, команда сложения с константой имеется только для 16-битовых чисел).
in_com: ;прием байта в temp с ожид. готовности
sbis UCSRA,RXC ;ждем готовности буфера приемника
rjmp in_com
in temp,UDR ;принимаем
ret
То есть программа у нас львиную часть времени висит в ожидании прихода очередного байта. «Большим» программистам такой способ, несомненно, покажется диким и архаичным — они привыкли автоматически избегать ситуаций, потенциально могущих ничем не закончиться. На самом деле этот способ приема как раз удобнее и безопаснее других, так как будучи применен в главном цикле программы, совершенно не мешает вклинить сколько угодно параллельных процессов через прерывания. А если логика программы такая хитрая, что этот простой способ по какой-либо причине не «катит», то в книге у меня рассмотрены еще три способа, уже с использованием прерываний UART, причем этими тремя перечень возможностей не исчерпывается.
Датчик температуры DS18b20
В книге описана работа в основном с известным аналоговым полупроводниковым датчиком температуры TMP36 (под другими названиями его выпускает множество фирм). Вообще аналоговые датчики температуры не представляют никакой проблемы — у меня много лет отлично работал заоконный термометр с медным датчиком на основе 800-омной обмотки герметизированного реле РЭС-60. Сложнее с аналоговыми датчиками влажности, но и их приобрести отдельно не проблема, вопрос только в цене — дешевые наверняка будут вам показывать «погоду на Марсе», особенно вне комнатных условий. Впрочем, с цифровыми Arduino-датчиками ровно та же история, так что не будем отвлекаться.
Рассмотрим здесь один из самых приличных представителей цифровых датчиков температуры — DS18b20. Первые его аналоги появились на рынке более двух десятилетий назад, когда обо всем этом китайском ширпотребе, задающем сегодня тон на массовом рынке, еще речи не шло. Возможно, поэтому изделие по качеству получилось вполне на уровне: по моему опыту, точность 0,5 градуса «из коробки» датчик вполне выдерживает. Высокие метрологические качества подтверждаются фактом, что датчик DS18b20 внесен в Госреестр средств измерений (сертификат 44046–10 от 2010 года), и при соблюдении соответствующих условий может использоваться для официальных измерений. Цена на этот датчик зависит от диапазона, в котором гарантируется погрешность в 0,5 градуса, и совсем не кусается, причем оригинальный MAXIM даже в «Чип-дипе» можно приобрести менее, чем за сотню деревянных. Интерфейс OneWire позволяет длину линии в десятки метров, и потому китайские и отечественные магазины смогли предложить DS18b20 заделанным в герметичный кабель разной длины. Потребляет датчик копейки и вполне годится для энергосберегающих автономных устройств. В общем, удобная штуковина во всех отношениях, кроме, пожалуй, довольно навороченного протокола (что, очевидно, неизбежное следствие его высоких качеств).
А для нашей ассемблерной задачи DS18b20 удобен еще и потому, что, в отличие от всех этих Arduino-датчиков, выдает сразу калиброванные цифровые значения, соответствующие градусам Цельсия. То есть для вывода показаний не придется возиться с калибровкой и проводить громоздкие расчеты. Необходима только небольшая коррекция выходного кода, которую на ассемблере провести ненамного сложнее, чем на С.
Датчик подключим к выводу 0 порта С. На всякий случай приведем полную схему передатчика с датчиком DS18b20 на основе ATtiny2313:
Касательно экономичности: в сравнении с тестовой схемой изменения потребления после подключения датчика обнаружить не удалось, потому я не стал возиться с его отключением-подключением к питанию. Информационный вывод DQ датчика подключен в выводу PB0 порта B. Питание здесь отдельное, не «parasite mode», когда датчик питается от линии данных — возможно и такое, но сложнее в реализации. Датчик на линии единственный, потому вариант чтения наипростейший, без долгих запросов с извлечением длиннющих 64-битных индивидуальных номеров.
Можно составить Arduino-модель, конечно, но это мало что даст: использование библиотеки DallasTemperature чрезвычайно простое, достаточно сделать по образцу прилагаемого к ней примера. Это ничуть нас не продвинет в изучении протокола, а составлять его самостоятельно сразу на ассемблере даже проще, чем на С. Процедуры чтения DS18b20, в том числе и на AVR-ассемблере, обсуждаются в Сети уже не первое десятилетие, потому останавливаться на этом я не буду. Толковое описание алгоритма можно найти, например, в серии статей вот тут, советую с ним ознакомиться (правда, в некоторых нюансах реализации моя программа отличается от приведенных в этих статьях примеров).
Всемирную известность Dallas когда-то принесла идея встраивать литиевые батарейки прямо в чип обычной статической памяти SRAM. Таким образом вы получали некую имитацию в то время еще очень дорогой и сложной в обращении EEPROM, причем имитацию гораздо более скоростную, чем даже современные флешки. Сохранность данных гарантировалась в течение 10 лет, и такое решение широко применялось даже в ранних ПК и лэптопах для хранения установок BIOS. По мере удешевления EEPROM это относительно дорогое и ненадежное (через 10 лет микросхему неизбежно нужно менять на новую) решение отмерло естественным путем и к началу тысячелетия фирма Dallas разорилась. Но ее остроумным придумкам умереть не дала издавна с ней сотрудничающая фирма Maxim, в 2001 году поглотившая Dallas со всеми патентами и товарными знаками, чему мы и обязаны сохранением торговой марки DS.
Добавим, что совсем недавно, в июле 2020 года, Maxim сама была поглощена своим главным конкурентом Analog Devices. Sic transit gloria mundi!… Но стоит надеяться, что узнаваемая торговая марка DS все равно никуда не денется.
На простую шину 1-Wire можно сажать до десятков устройств на расстоянии до 30 м, или до 200–300 шт на расстояниях до 100–300 метров с применением специальных интерфейсных модулей и витой пары (подробности для интересующихся см. тут). Цифра 30 м относится к режиму «паразитного» питания, с отдельным питанием длина линии может быть больше даже без специальных мер. Но такие задачи возникают относительно редко — например, в публикациях упоминается распределенный контроль температуры в помещениях типа овощехранилищ. В любительской практике датчики, если их больше одного, обычно принципиально разнесены в пространстве (внешний и внутренний у метеостанции, два датчика для двух разных парников в огороде), и проще организовать беспроводную связь, чем тащить провода. Потому мы вместо 64-битного индивидуального номера DS18b20 и применяем самодельный идентификатор для беспроводных датчиков.
При ознакомлении с описанием DS18b20 поражаешься количеству наворотов, реализованных в таком простом и дешевом устройстве. Конечно, мы будем применять датчик в самом простом режиме однократного считывания температуры, но иметь в виду эти возможности следует.
Текст программы передатчика (HC-12_peredatchik_DS18b02.asm) можно найти все в том же архиве по адресу в конце статьи. Программа основана на приведенной выше программе посылки двухбайтового числа с идентификатором передатчика (H-12_peredatchik_2313_WDT.asm), но вместо произвольного числа один раз посылает считанный с датчика двухбайтовый код. В целях энергосбережения время между посылками увеличено до 8 секунд (его можно увеличить еще больше, если осуществлять вывод не в каждом прерывании WDT, а отсчитывать, например, каждое четвертое или даже восьмое). Обработку данных целесообразно производить на приемном конце, и на этом вопросе мы остановимся подробнее: — во-первых, это хорошая иллюстрация к ассемблерным методам обработки чисел на 8-разрядном контроллере,