Цифровой датчик температуры и влажности AM2302 (DHT22) достаточно популярен в сегменте DIY, так как при невысокой стоимости (если рассматривать реплики, сделанные в Китае) он обеспечивает неплохую точность измерений и весьма прост в подключении (три провода, включая питание). Однако, большинство примеров использования этого датчика рассчитаны на Arduino и написаны на языке программирования С/С++. Это прекрасно подойдет, если вы хотите ознакомиться с функционалом датчика или «по-быстрому» прикрутить термометр к уже существующему устройству. Но если же вы хотите собрать именно термометр/гигрометр и только его, использование целой платы Arduino (или просто большого МК с парой десятков выводов) вполне справедливо может показаться излишним.

В данной статье пойдет речь о простом термометре/гигрометре (далее – просто термометре), выполненном на одном из самых «маленьких» микроконтроллеров — ATtiny13 с весьма скромными характеристиками – 1Кб программной памяти, 64 байтами ОЗУ и 5-ю (6-ю, если отключить вывод сброса) интерфейсными выводами. В статье предполагается, что читатель уже немного знаком с микроконтроллерами AVR и их программированием, но статья, в основном, ориентирована на новичков в этой области. Кстати, о языке программирования – программа термометра полностью написана на ассемблере.

Итак, начнем. Для отображения информации о температуре и влажности был выбран 8-разрядный 7-сегментный светодиодный индикатор, позволяющий отображать оба параметра сразу без необходимости переключения между ними. Такой индикатор имеет 16 выводов (8 сегментов + 8 разрядов), что явно «не под силу» небольшому контроллеру ATtiny13. К счастью, фирма Maxim выпускает микросхему MAX7219, специально предназначенную для таких случаев – внутри микросхемы содержится весь функционал динамической индикации на 8 разрядов плюс последовательный интерфейс, совместимый с SPI. Таким образом, с этой микросхемой весь наш индикатор можно подключить к МК с помощью всего трех проводов (не считая землю и питание). Вот это уже вполне подходит для контроллера с 5-ю интерфейсными выводами. К слову, стоимость одного комплекта из индикатора, микросхемы и печатной платы в сборе составила всего $1.3 на aliexpress.

В качестве датчика температуры и влажности используется, как было сказано выше, AM2302. Он подключается к МК с помощью только одного провода. Таким образом, из имеющихся в наличии 5-ти интерфейсных выводов МК используются только 4, и на оставшийся 5-й можно «повесить» какую-либо дополнительную функцию. Также, если у вас в наличии есть HVSP-программатор, можно отключить вывод сброса и использовать его как 6-й интерфейсный вывод, но это несколько затруднит обновление прошивки МК.

Итак, вся схема термометра представлена на рисунке ниже:
Принципиальная схема

Поскольку все интерфейсы для работы с внешними устройствами МК реализованы программно, то выбор выводов (пинов), к которым подключается тот или иной сигнал – чисто произвольный и сделан, скорее всего, по принципу «куда было удобнее вставить этот проводок на макетной плате». Так что смело можно выбирать и другие выводы, надо будет только в коде поправить их номер. Единственное ограничение – не стоит подключать датчик температуры к одному из выводов, используемых для программирования МК через SPI – это может создать конфликт, т.к. выходы двух устройств окажутся соединенными вместе, что недопустимо с электрической точки зрения.

Теперь, когда с подключением датчика и индикатора все ясно, приступаем к написанию непосредственно кода. И тут нас ожидает новый «вызов» — ATtiny13 не имеет на борту никаких последовательных интерфейсов, т.е. всю их логику придется реализовывать программно. К счастью, реализация SPI для MAX7219 не составляет особого труда, т.к. протокол синхронный, микросхема работает на частоте до 10Мгц, да и интерфейс в нашей схеме работает только на вывод. А вот общение с АМ2302 будет более сложной задачей, потому что он подключается только одним проводом, данные по которому передаются в обе стороны и скорость передачи полностью определяется самим датчиком. Тут следует сказать, что большинство библиотек для работы с АМ2302 идут по «простому пути» — запрещают прерывания и считывают всю информацию с датчика одним вызовом функции. Это простое и надежное решение, но оно вряд ли подойдет, если на МК возложены какие-либо другие функции реального времени (например, динамическая индикация или непрерывный анализ данных из других источников), потому как весь цикл чтения информации о температуре и влажности занимает от 4-х до 6-ти миллисекунд (в зависимости от передаваемых данных). Не смотря на то, что в данном термометре никаких других функций реального времени нет, было принято решение написать универсальный код, который бы считывал информацию с датчика «в фоновом режиме», т.е. на прерываниях.

Для максимального упрощения схемы ATtiny13 тактируется от встроенного RC-генератора, выдающего около 9.6Мгц. Это позволяет, вызывая прерывание каждые 128 тактов процессора, получить частоту опроса АМ2302 75КГц или 13.33 микросекунды между соседними опросами. По спецификации АМ2302 минимальная длительность импульса на его выходе составляет 26 микросекунд, что практически в два раза превышает интервал опроса и гарантирует стабильное чтение данных. Конечно, 128 тактов между двумя прерываниями не очень-то много для реализации алгоритма опроса, но AVR выполняет большинство команд за 1 такт, поэтому написать работающую программу при таких условиях вполне возможно, еще и останется время для выполнения основной программы.

АМ2302 по спецификации можно опрашивать не чаще, чем один раз в две секунды. Однако практика показывает, что он вполне способен отдавать результат и чаще – до нескольких раз в секунду, при условии, что после включения питания ему дадут 1-2 секунды (по спецификации – 2) на инициализацию. В данном термометре датчик опрашивается один раз в секунду, однако интервал опроса легко изменить на любое другое значение.

К сожалению, АМ2302 (возможно, тут сказывается его китайское происхождение) имеет достаточно большую погрешность результата – два последовательных запроса температуры могут вернуть разницу в 0.5 или даже более градусов, поэтому было решено программно усреднять данные последних 8-ми измерений, чтобы показания термометра не прыгали.

Теперь перейдем непосредственно к коду. Исходный asm и результирующий hex-файл размещен в приложении в конце статьи, здесь же я поясню основные моменты. Будет удобно открыть исходный код программы в другом окне и смотреть туда в процессе чтения статьи.

В начале программы идет два важных определения:

#define SKIPNEXT1W (PC + 2)
#define DS(var) Y + var - _dataStart

Первое позволяет осуществлять условный переход через следующую команду размером 16бит (1 слово, большинство команд AVR), т.е. пропускать ее без введения дополнительной метки, например:

	inc	R16
	cpi	R16, 5
	brne	SKIPNEXT1W
	dec	R16
	...

Второе позволяет обращаться к первым 64-м байтам оперативной памяти МК с помощью 16-битных команд. Здесь расскажу подробнее – обычно для чтения или записи в ОЗУ МК применяются команды lds/sts, которые занимают 2 слова (32 бита) и выполняются за 2 такта. Они позволяют адресовать до 64Кб (без расширений) ОЗУ. К сожалению, размер в 32 бита (4 байта) – это уже весьма много для МК с объемом программной памяти всего 1Кб. Поэтому, для экономии программной памяти в регистр Y МК при старте помещается адрес начала ОЗУ (0x60 для ATtiny13), больше в процессе работы программы этот регистр никто не меняет, а доступ к первым 64 байтам ОЗУ выполняется с помощью косвенной адресации со смещением по регистру Y, например:

	ldd	R16, Y + 6

Команды ldd/std также выполняются за 2 такта, но занимают только 16 бит (2 байта), т.е. по сравнению с командами lds/sts такой вид адресации позволяет экономить половину объема программной памяти. Для того, чтобы не высчитывать в каждой команде смещение какой-либо переменной вручную, в самом начале сегмента данных ставится метка _dataStart:

.dseg
_dataStart:
...
testVar:		.byte	1

А в команде используется макрос DS (сокращение от Data Segment):

	ldd	R16, DS (testVar)

Компилятор преобразует это в строку:

	ldd	R16, Y + testVar - _dataStart

Автоматически высчитывая нужное смещение. Следует отметить, что такой вид адресации ограничен возможностями самой команды ldd, а это первые 64 байта относительно базового регистра. Но, в случае с ATtiny13, которая имеет как раз 64 байта ОЗУ на борту, он позволяет адресовать всю память. Тем не менее, в других МК, имеющих больший объем ОЗУ, также возможно применять данный способ, размещая наиболее часто адресуемые переменные в первых 64-х байтах сегмента данных. Расплата за такой способ адресации – регистр Y (два 8-битных регистра R28 и R29), значение которого нельзя менять ни в какой точке программы.

Далее в программе определяются номера битов порта В (именно битов внутри байта, не физических выводов микросхемы), к которым подключены внешние устройства. Поскольку все протоколы взаимодействия с устройствами программные, номера битов можно менять без каких-либо ограничений.

Особенностью МК AVR является то, что первые 16 регистров R0 – R15 являются «неполноценными», с ними не работают команды, содержащие внутри себя операнд – например, ldi или subi. Т.е. чтобы даже загрузить значение, отличное от 0 в один из этих регистров, надо использовать дополнительный регистр:

	ldi	R16, 32
	mov	R0, R16

Поэтому, часто такие регистры используются как «переменные с быстрым доступом». Для этого компилятор имеет директиву .def, позволяющую присвоить регистру дополнительное символьное имя, например:

.def	R_TS = R0

В программе термометра регистр R0 всегда хранит состояние приемника данных АМ2302, регистр R1 используется для подсчета времени приема сигнала, R2 содержит принимаемые данные, R3 используется как счетчик таймера, увеличивающегося с частотой 100Гц, а R4 и R5 – как обратный счетчик таймера 75КГц, считающего от 749 до 0.

Сегмент данных МК поделен на 4 части – блок принятых с АМ2302 данных (5 байт), буфер для десятичной печати числа (4 байта), буфер для усреднения показаний термометра и гигрометра на 8 значений (8*2*2 = 32 байта) и стек МК (ему отделена вся оставшаяся память, т.е. 23 байта). В действительности, конечно, стек занимает меньше, и в памяти можно еще найти несколько байт для дополнительных функций, но увлекаться уже не стоит.

Теперь перейдем непосредственно к сегменту кода. Он традиционно начинается с таблицы прерываний, для ATtiny13 это 10 векторов, включая вектор сброса. Неиспользуемые прерывания сразу же содержат команду reti, используемые (а их два) – команду перехода на обработчик. Термометр использует два прерывания, обслуживаемые одним обработчиком – это прерывание по переполнению таймера и прерывание по равенству таймера значению OCRA. Можно было бы обойтись одним, однако такой метод на 2 команды короче (не надо изменять режим работы таймера с обычного на СТС).

Сразу после векторов прерываний идет таблица перевода цифр в коды для зажигания 7-сегментных индикаторов. Можно было бы воспользоваться встроенной в MAX7219 функцией декодирования, однако тогда было бы сложнее выводить на индикатор строковые сообщения.
За таблицей начинается программа инициализации термометра, выполняемая сразу после сброса МК. Она выполняет начальную установку указателя стека МК, сторожевого таймера watchdog (устанавливается на 4 секунды), занесение начальных значений в регистры МК, а также инициализацию портов ввода-вывода, MAX7219 и основного таймера МК. После этого программа ждет 2 секунды, пока инициализируется АМ2302 (демонстрируя простую анимацию из гаснущих знаков «минус» на дисплее) и переходит в свой основной цикл.

Основной цикл начинается с инициации запроса к АМ2302 посредством изменения состояния приемника данных в регистре R_TS (R0). Ближайшее прерывание таймера определит изменение состояния и начнет цикл опроса датчика. По его завершению в биты состояния регистра R_TS будет помещено значение TMS_NONE, а до этого момента основная программа может выполнять любые действия. В данном случае выполнять нечего, поэтому программа просто переводит МК в режим сна (sleep) и ждет окончания цикла опроса.

После завершения опроса бит 3 регистра состояния определяет, были ли данные получены успешно (значение 1) или же произошла ошибка (значение 0). В случае успешного получения данных программа проверяет их контрольную сумму и, по необходимости, передает управление обработчику ошибки. Обработчик ошибки считает количество ошибок, идущих подряд, и как только это значение станет равным трем, выводит на дисплей сообщение «Sn Error», сигнализирующее о неисправности сенсора или соединительной линии. Как только данные о температуре и влажности будут получены успешно, счетчик ошибок сбрасывается. Такой механизм позволяет игнорировать одиночные ошибки сенсора, которые время от времени имеют место в реальной жизни.

В случае успешного получения данных, предыдущие измерения, находящиеся в буфере усреднения данных, сдвигаются вверх, и новые данные добавляются в его начало. Параллельно вычисляются средние значения, которые будут показаны на дисплее. Тут следует отметить, что АМ2302 выдает отрицательную температуру не в дополнительном коде, привычном для обработки процессорами, а в виде абсолютного значения температуры и отдельного бита её знака. Для того чтобы складывать такие числа и вычислять их средние значения, используя обычные команды МК, данные надо перевести в дополнительный код.

Поскольку изначально буфер усреднения не инициализируется, средние значения температуры и влажности отображаются только после проведения восьми успешных измерений. До этого момента на дисплей выводятся текущие значения. На практике это означает, что в первые 8 секунд после включения термометра значения температуры и влажности могут прыгать в пределах градуса, после чего показания стабилизируются. Следует сказать, что усреднение из 8-ми последних значений очень благотворно влияет на показания термометра – теперь они в основном изменяются не более чем на 0.1 градуса в секунду.

Температура выводится на дисплей в формате « х.х», « хх.х», «ххх.х», «- х.х» или «-хх.х» в зависимости от ее значения. Влажность выводится в формате « х.х» или « хх.х». Для преобразования двоичного числа, находящегося в регистре Х в десятичную форму (в соответствии с кодами для 7-сегментного индикатора), применяется функция printDecX. Поскольку МК не имеет команды деления, функция основана на последовательном вычитании из исходного числа значений 1000, 100 и 10. Максимальное число, которое может вывести функция – 9999, если при её вызове в регистре Х окажется число больше, функция вернет ошибку переполнения, установив флаг переноса.

Для работы с MAX7219 применяется функция maxWriteWord, которая записывает значение из регистра XL МК в регистр MAX, номер которого задан в регистре XH. После вывода значений текущей температуры и влажности на дисплей, программа делает задержку в 1 секунду и повторяет основной цикл заново. Для реализации задержки используется функция wait100Hz, которая выполняет задержку на время R16*0.01c с использованием счетчика R_TICK100, увеличение которого происходит по прерыванию таймера.

Получение данных с датчика температуры выполняется с помощью функции am2302proc, которая вызывается из обработчика прерывания таймера. Функция представляет собой конечный автомат, состояние которого хранится в регистре R_TS (R0) МК. В зависимости от состояния функция ждет определенного уровня сигнала от датчика, инициируя передачу и последовательно получая все 40 бит передаваемой информации. Синхронизация происходит на каждом изменении уровня входного сигнала, поэтому особой точности от частоты прерываний таймера не требуется (что позволяет МК работать от встроенного генератора). Функция состоит из быстрого обработчика состояния простоя (TMS_NONE), позволяющего минимизировать нагрузку на процессор МК в то время, когда обмена данными с датчиком не происходит, обработчика таймаута, предназначенного для сброса автомата в исходное состояние, если ожидаемый сигнал не приходит длительное время (около 3 мс), и обработчиков каждого отдельного состояния автомата. Следует отметить, что данная функция не обладает помехозащищенностью – если даже импульсная помеха изменит уровень линии данных на короткий промежуток времени, но именно он попадет на операцию чтения из порта, функция прочитает неверные данные. Для компенсации этого в основной программе происходит проверка контрольной суммы прочитанных данных, поэтому отображение неверной информации практически исключено. Однако такая реализация может оказаться не самой лучшей, если вы захотите вынести датчик за пределы термометра и подключить его к МК соединительной линией большой длины.

На данный момент термометр собран на макетной плате и выглядит следующим образом:

Внешний вид термометра

В будущем планируется поместить термометр внутрь корпуса существующих электронных часов, организовав его питание от БП часов.

Текущая программа занимает около 75% программной памяти МК. Что можно добавить в программу? Возможно, кому-то пригодится изменение яркости свечения дисплея (это реализовано непосредственно в драйвере MAX7219) по внешней кнопке или датчику освещенности (используя встроенный в МК АЦП и свободный интерфейсный вывод), кому-то может пригодиться запоминание и отображение минимальной и максимальной температуры. Для небольших доработок место еще есть. Более крупные доработки могут потребовать смену МК на другой, имеющий на борту больше программной и оперативной памяти. Что касается интерфейсных выводов – на данный момент у МК есть один полностью незадействованный вывод и еще один можно получить, отключив RESET. Также два вывода из интерфейса SPI (DATA и CLK) можно использовать для других функций, т.к. пока на выводе CS не будет низкого уровня (конкретно для МАХ7219 важен переход с низкого уровня на высокий) сигналы на этих выводах значения не имеют. Т.е., в принципе, заменив МК на более мощный, например, ATtiny85, можно подключить к термометру Real Time Clock (RTC) и до четырех кнопок.

Моей же целью было именно создание простого термометра/гигрометра, поэтому, скорее всего, я оставлю его себе в таком виде.

Текст программы

// *********************************************
// *** Simple digital thermometer/hygrometer ***
// *********************************************
// ***         (c) SD, 14.03.2016            ***
// *********************************************

// Based on ATtiny13, AM2303 and MAX7219

// **************
// *** Clocks ***
// **************

// MCU clock frequency is 9.6MHz (internal oscillator)
// Timer frequency is 75KHz = 9.6MHz/128
// (13.3 us between interrupts)

#define SKIPNEXT1W (PC + 2)
#define DS(var) Y + var - _dataStart

// ************
// *** Pins ***
// ************

// MAX7219 output pins
.equ	MAX_DIN = 0
.equ	MAX_CS = 1
.equ	MAX_CLK = 4

// AM2302 input pin
.equ	AM2302_PIN = 3

// MAX7219 registers
.equ	MAX_DECODE = 0x09
.equ	MAX_INTENSITY = 0x0A
.equ	MAX_SCANLIMIT = 0x0B
.equ	MAX_SHUTDOWN = 0x0C
.equ	MAX_DISPTEST = 0x0F

// Temperature measurement state register
// Bits 0 - 2 define the byte number being received
// Bit 3 is set when there are valid data received
// Bits 4 - 7 define the current receiver state
.def	R_TS = R0

// Temperature measurement tick
.def	R_TT = R1

// Temperature data register
.def	R_TD = R2

// Temperature measurement states
.equ	TMS_NONE =			0x00	// TMS_NONE - do nothing an wait until
									// somebody changes the state
.equ	TMS_START =			0x10	// Start of the measurement cycle
.equ	TMS_ST_LOW =		0x20	// Initial low signal is being sent
									// (1 ms = 75 timer ticks)
.equ	TMS_WRSP_LOW =		0x30	// Initial low signal has been sent,
									// waiting for the response low signal
.equ	TMS_WRSP_HIGH =		0x40	// Response low signal has been received,
									// waiting for the response high signal
.equ	TMS_W1ST_BIT_LOW =	0x50	// Waiting for the first bit low signal
.equ	TMS_WBIT_HIGH =		0x60	// Waiting for the bit high signal
.equ	TMS_WBIT_LOW =		0x70	// Waiting for the bit low signal
.equ	TMS_WHIGH =			0x80	// Waiting for the final high signal

// Timer 100Hz tick counter
// (counts upwards from 0 to 255)
.def	R_TICK100 = R3

// Timer 16bit 75KHz tick counter
// (counts downwords from 749 to 0)
.def	R_TICKL = R4
.def	R_TICKH = R5

// ************
// *** Data ***
// ************

.dseg
_dataStart:							// Data start label

tempData:			.byte	5		// Data, received from the AM2302 sensor
displayData:		.byte	4		// Decimal printing result

.equ	DATA_BUF_SIZE =		8		// AM2302 data buffer size in samples
									// (each sample is 4 bytes)

dataBuffer:			.byte	DATA_BUF_SIZE*4

.cseg
.org	0

	// *** Interrupts ***

	// Reset Handler
	rjmp	start

	// IRQ0 Handler
	reti
	
	// PCINT0 Handler
	reti

	// Timer0 Overflow Handler
	rjmp	timerOvfl

	// EEPROM Ready Handler
	reti
	
	// Analog Comparator Handler
	reti

	// Timer0 CompareA Handler
	rjmp	timerCompA

	// Timer0 CompareB Handler
	reti

	// Watchdog Interrupt Handler
	reti

	// ADC Conversion Handler
	reti

// Table to convert decimal digit into 7-segment code
hexTable:
	.db		0b01111110, 0b00110000, 0b01101101, 0b01111001
	.db		0b00110011, 0b01011011, 0b01011111, 0b01110010
	.db		0b01111111, 0b01111011

start:
	cli
	ldi		R16, RAMEND
	out		(SPL), R16

	// Init watchdog (4s interval)
	wdr
	ldi		R16, (1 << WDCE) | (1 << WDE)
	out		(WDTCR), R16
	ldi		R16, (1 << WDE) | (1 << WDP3)
	out		(WDTCR), R16

	// Init registers
	ldi		YL, low (_dataStart)
	ldi		YH, high (_dataStart)
	clr		R_TS
	clr		R_TT
	clr		R_TICKL
	clr		R_TICKH
	clr		R_TICK100

	// Init ports
	out		(PORTB), R_TS
	ldi		R16, (1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK)
	out		(DDRB), R16

	// Init LED driver
	// Set all digits to "-"
	ldi		XL, 0b00000001
	ldi		XH, 1
init1:
	rcall	maxWriteWord
	cpi		XH, 9
	brne	init1

	// Set control registers
	ldi		XL, 0					// Decode
	rcall	maxWriteWord
	ldi		XL, 4					// Intensity
	rcall	maxWriteWord
	ldi		XL, 7					// Scan limit
	rcall	maxWriteWord
	ldi		XL, 1					// Shutdown
	rcall	maxWriteWord
	ldi		XH, 0x0F
	ldi		XL, 0					// Display test
	rcall	maxWriteWord

	// Init timer for 1 interrupt each 128 CPU cycles
	ldi		R16, 127
	out		(OCR0A), R16
	ldi		R16, 0b00000110
	out		(TIMSK0), R16
	ldi		R16, 0b00000001
	out		(TCCR0B), R16

	// First part of the initialization is done.
	// Enable interrupts
	sei

	// Wait 2 sec (while AM2302 initialize itself)
	// with little animation
	ldi		XH, 1
	ldi		XL, 0
init2:
	ldi		R16, 25
	rcall	wait100Hz
	rcall	maxWriteWord
	cpi		XH, 9
	brne	init2

	// R6 will contain the number of
	// measurement values received
	clr		R6

	// R7 will contain the number of
	// continious errors
	clr		R7

loop:
	// Reset watchdog timer
	wdr

	// Initiate measurement
	ldi		R16, TMS_START
	mov		R_TS, R16

loop1:
	// Wait for the TMS_NONE state
	// which indicates that the measurement
	// is done
	sleep

	mov		R16, R_TS
	andi	R16, 0xF0
	brne	loop1

	// Do we have the valid data?
	sbrs	R_TS, 3
loop_error1:
	rjmp	loop_error

	// Check control sum of the received data
	ldd		R16, DS (tempData)
	ldd		ZL, DS (tempData + 1)
	add		R16, ZL
	ldd		ZL, DS (tempData + 2)
	add		R16, ZL
	ldd		ZL, DS (tempData + 3)
	add		R16, ZL
	ldd		ZL, DS (tempData + 4)
	cp		R16, ZL
	brne	loop_error1

	// We have valid new measurement data,
	// reset error count
	clr		R7

	// Move up data in the buffer
	// and count the sum at the same time.
	// R12:R13 will contain the humidity value and
	// R14:R15 the temperature value
	clr		R12
	clr		R13
	clr		R14
	clr		R15
	ldi		ZL, low (dataBuffer + (DATA_BUF_SIZE - 2)*4)
	ldi		ZH, 0
buf1:
	ldd		R16, Z + 0
	ldd		R17, Z + 1
	std		Z + 4, R16
	std		Z + 5, R17
	add		R12, R16
	adc		R13, R17

	ldd		R16, Z + 2
	ldd		R17, Z + 3
	std		Z + 6, R16
	std		Z + 7, R17
	add		R14, R16
	adc		R15, R17

	subi	ZL, 4
	cpi		ZL, low (dataBuffer - 4)
	brne	buf1

	// Add new humidity value to the buffer
	// and to the sum
	ldd		R16, DS (tempData + 1)
	ldd		R17, DS (tempData)
	std		DS (dataBuffer + 0), R16
	std		DS (dataBuffer + 1), R17
	add		R12, R16
	adc		R13, R17

	// Add new temperature value to the buffer
	// and to the sum
	ldd		R16, DS (tempData + 3)
	ldd		R17, DS (tempData + 2)
	
	// Check for a negative value
	and		R17, R17
	brpl	buf2

	// Convert negative temperature to the 2's
	// complement form
	clr		ZL
	andi	R17, 0x7F
	neg		R16
	sbc		ZL, R17
	mov		R17, ZL

buf2:
	std		DS (dataBuffer + 2), R16
	std		DS (dataBuffer + 3), R17
	add		R14, R16
	adc		R15, R17

	// Divide the humidity and temperature
	// sum values by 8 (by shifting them right
	// three times)
	ldi		R16, 3
buf3:
	asr		R15
	ror		R14
	asr		R13
	ror		R12
	dec		R16
	brne	buf3

	// Do we have 8 full measurements?
	mov		R16, R6
	cpi		R16, 7
	
	// If so, use the average values from
	// the buffer
	breq	buf4

	// Otherwise use the latest measurement
	ldd		R12, DS (dataBuffer + 0)
	ldd		R13, DS (dataBuffer + 1)
	ldd		R14, DS (dataBuffer + 2)
	ldd		R15, DS (dataBuffer + 3)
	inc		R6

buf4:
	// Print out values

	// *** Humidity ***
	movw	X, R12
	rcall	printDecX

	ldi		XH, 1
	ldd		XL, DS (displayData + 3)
	rcall	maxWriteWord

	ldd		XL, DS (displayData + 2)
	ori		XL, 0x80
	rcall	maxWriteWord

	ldd		XL, DS (displayData + 1)
	rcall	maxWriteWord

	ldd		XL, DS (displayData)
	rcall	maxWriteWord

	// *** Temperature ***
	movw	X, R14

	// Check for a negative value
	and		XH, XH
	brpl	buf5

	// Calculate the absolute value
	clr		ZL
	neg		XL
	sbc		ZL, XH
	mov		XH, ZL

buf5:
	rcall	printDecX

	ldi		XH, 5
	ldd		XL, DS (displayData + 3)
	rcall	maxWriteWord

	ldd		XL, DS (displayData + 2)
	ori		XL, 0x80
	rcall	maxWriteWord

	ldd		XL, DS (displayData + 1)
	rcall	maxWriteWord

	// If temperature is negative
	// write the minus sign to the first digit
	// (temperatures of -100.0 and below
	// are not supported anyway)
	ldd		XL, DS (displayData)
	and		R15, R15
	brpl	SKIPNEXT1W
	ldi		XL, 1
	rcall	maxWriteWord

loop2:
	// Wait for 1 sec
	ldi		R16, 100
	rcall	wait100Hz

	// And repeat
	rjmp	loop

loop_error:
	// An error had occured.
	// Increment error count
	inc		R7

	// Do we have 3 or more errors in a row?
	mov		R16, R7
	cpi		R16, 3

	// No? Just do nothing
	brne	loop2

	// Prevent error count from growing
	dec		R7

	// Display error
	ldi		ZL, low (errText*2)
	ldi		ZH, high (errText*2)
	rcall	maxWrite8Bytes
	rjmp	loop2

errText:
	// "Sn Error"
	.db		0b00000101, 0b00011101, 0b00000101, 0b00000101
	.db		0b01001111, 0b00000000, 0b00010101, 0b01011011

// **********
// Waits given number (R16) of 100Hz ticks
// Uses: Z
wait100Hz:
	// Enable sleep
	ldi		ZL, 0b00100000
	out		(MCUCR), ZL
	
	mov		ZL, R_TICK100
w100:
	sleep
	mov		ZH, R_TICK100
	sub		ZH, ZL
	cp		ZH, R16
	brcs	w100
	ret

// Timer interrupt

timerOvfl:
timerCompA:
	push	R16
	in		R16, (SREG)
	push	R16
	push	ZL
	push	ZH

	// Receive AM2303 data
	rcall	am2302proc

	// Decrement current 75KHz tick
	ldi		R16, 1
	sub		R_TICKL, R16
	brcc	timerRet
	sub		R_TICKH, R16
	brcc	timerRet

	// Initialize 75KHz tick value
	ldi		ZL, low (750 - 1)
	ldi		ZH, high (750 - 1)
	movw	R_TICKL, Z

	// Increment current 100Hz tick
	inc		R_TICK100

timerRet:
	pop		ZH
	pop		ZL
	pop		R16
	out		(SREG), R16
	pop		R16
	reti

// **************
// *** AM2302 ***
// **************

amStart:
	// Send the start low signal.
	// Switch corresponding PORTB pin to output
	// (there is already 0 in the PORTB register)
	sbi		(DDRB), AM2302_PIN
	ldi		R16, TMS_ST_LOW
	rjmp	amSetState

amStartLow:
	// Initial start low signal is being sent.
	// Wait for 75 ticks
	cpi		R16, 75
	brne	amNone

	// Switch PORTB pin back to input
	cbi		(DDRB), AM2302_PIN
	ldi		R16, TMS_WRSP_LOW

	// Do not check AM2303 input pin at this tick
	// since it's possible that it has not recovered
	// from the low state yet.
	rjmp	amSetState

amWRespLow:
	// Waiting for the response low signal
	sbrc	ZH, AM2302_PIN
	ret

	ldi		R16, TMS_WRSP_HIGH
	rjmp	amSetState

amWRespHigh:
	// Waiting for the response high signal
	sbrs	ZH, AM2302_PIN
	ret

	ldi		R16, TMS_W1ST_BIT_LOW
	rjmp	amSetState

amW1StBitLow:
	// Waiting for the first bit low signal
	sbrc	ZH, AM2302_PIN
	ret

	// Get ready to receive the first bit
	ldi		R16, 1
	mov		R_TD, R16

	// Set new state and reset the byte counter
	ldi		ZL, TMS_WBIT_HIGH
	rjmp	amSetState2

amBitHigh:
	sbrs	ZH, AM2302_PIN
	ret

	// If the bit low signal was there too long
	// (longer than 5 ticks (5*13.3 = 66.5us)
	// something went wrong)
	cpi		R16, 6
	brcc	amResetState

	ldi		R16, TMS_WBIT_LOW
	rjmp	amSetState

am2302proc:
	// First, check for the TMS_NONE state.
	// In this case just do nothing to
	// not waste MCU cycles.
	mov		ZL, R_TS
	andi	ZL, 0xF0

	cpi		ZL, TMS_NONE
	breq	amNone

	// Increment receiver tick
	inc		R_TT

	// If we are waiting for too long,
	// something went wrong, reset the state
	breq	amResetState

	// Save the current tick into a more
	// convenient register
	mov		R16, R_TT

	// Get input signal
	in		ZH, (PINB)

	// Branch depending on the current state.
	// Check for TMS_WBIT_LOW first since it
	// has the longest service routine
	cpi		ZL, TMS_WBIT_LOW
	breq	amBitLow

	cpi		ZL, TMS_START
	breq	amStart

	cpi		ZL, TMS_ST_LOW
	breq	amStartLow

	cpi		ZL, TMS_WRSP_LOW
	breq	amWRespLow

	cpi		ZL, TMS_WRSP_HIGH
	breq	amWRespHigh

	cpi		ZL, TMS_W1ST_BIT_LOW
	breq	amW1StBitLow

	cpi		ZL, TMS_WBIT_HIGH
	breq	amBitHigh

	cpi		ZL, TMS_WHIGH
	breq	amWHigh

amResetState:
	// In case of an error, reset state to
	// the default TMS_NONE
	ldi		R16, TMS_NONE

amSetState:
	// Preserve the current byte number
	mov		ZL, R_TS
	andi	ZL, 0x07
	or		ZL, R16

amSetState2:
	mov		R_TS, ZL
	
	// Clear receiver tick counter
	clr		R_TT

amNone:
	ret	

amBitLow:
	sbrc	ZH, AM2302_PIN
	ret

	// The high bit signal was too long?
	cpi		R16, 8
	brcc	amResetState

	// Store input bit (inverted, since cpi produces
	// inverted result in the carry flag)
	cpi		R16, 4
	rol		R_TD

	// Initally we set R_TD to 1, so when all 8
	// bits are received, the carry flag will be set
	// indicating that a full byte has been received.
	// Otherwise, receive the next bit
	ldi		R16, TMS_WBIT_HIGH
	brcc	amSetState

	// We have the full byte. Invert it
	com		R_TD

	// Save it
	mov		ZL, R_TS
	andi	ZL, 0x07
	subi	ZL, low (-tempData)
	ldi		ZH, high (tempData)
	st		Z+, R_TD

	// Did we receive all 5 bytes?
	cpi		ZL, low (tempData + 5)
	ldi		R16, TMS_WHIGH
	breq	amSetState

	// OK, receive the next byte.
	// Increment the byte counter
	inc		R_TS

	// Initialize R_TD
	ldi		R16, 1
	mov		R_TD, R16

	ldi		R16, TMS_WBIT_HIGH
	rjmp	amSetState

amWHigh:
	sbrs	ZH, AM2302_PIN
	ret

	cpi		R16, 6
	brcc	amResetState

	// We received everything. Set
	// the state to TMS_NONE and set
	// the data validity bit
	ldi		R16, 0x08
	mov		R_TS, R16
	ret

// *********

/*
// Write data from Z
// Uses R16 - R19, X, Z
maxWriteData:
	lpm		XH, Z+
	tst		XH
	brne	SKIPNEXT1W
	ret
	lpm		XL, Z+
	rcall	maxWriteWord
	rjmp	maxWriteData

maxInit:
	.db		MAX_DECODE, 0
	.db		MAX_INTENSITY, 4
	.db		MAX_SCANLIMIT, 7
	.db		MAX_SHUTDOWN, 1
	.db		MAX_DISPTEST, 0
	.db		0, 0

maxTest:
	.db		0, 0b00011101, 0b00010101, 0b00010000, 0b00011100, 0b00111101, 0b00000101, 0b01110111
*/

// Writes 8 bytes from (Z) (program memory)
// to MAX7219
// Uses R16 - R19, X, Z
maxWrite8Bytes:
	ldi		XH, 0x01

mw8b1:
	lpm		XL, Z+
	rcall	maxWriteWord
	cpi		XH, 9
	brne	mw8b1
	ret

// Write word X (XL = data, XH = address) to MAX2719
// Uses R16 - R19, X
maxWriteWord:
	// Set all pins to zero
	in		R17, (PORTB)
	andi	R17, ~((1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK))
	out		(PORTB), R17

	ldi		R19, (1 << MAX_CLK)

	mov		R16, XH
	rcall	mww1

	mov		R16, XL
	rcall	mww1

	// Set LOAD(CS) to high thus writing all 16 bits into
	// MAX register
	sbi		(PORTB), MAX_CS
	
	// Increment MAX register number
	inc		XH
	ret

mww1:
	ldi		R18, 8

mww2:
	bst		R16, 7
	bld		R17, MAX_DIN
	out		(PORTB), R17

	lsl		R16
	dec		R18

	// Create clock impulse by toggling clock output twice
	out		(PINB), R19
	out		(PINB), R19

	brne	mww2
	ret

// *********

printDecX:
	ldi		ZH, low (1000)
	ldi		R16, high (1000)
	rcall	pdx

	// Change zero digit to empty space
	cpi		ZL, 0b01111110
	brne	SKIPNEXT1W
	ldi		ZL, 0
	std		DS (displayData), ZL

	ldi		ZH, 100
	ldi		R16, 0
	rcall	pdx

	// If this digit is zero and the first
	// digit is empty (i.e. it was zero too)
	// change this digit to empty space
	ldi		R16, 0b01111110
	eor		R16, ZL
	ldd		ZH, DS (displayData)
	or		R16, ZH
	brne	SKIPNEXT1W
	ldi		ZL, 0
	std		DS (displayData + 1), ZL

	ldi		ZH, 10
	ldi		R16, 0
	rcall	pdx
	std		DS (displayData + 2), ZL

	mov		ZL, XL
	rcall	pdx3
	std		DS (displayData + 3), ZL
	
	// Clear carry flag to indicate that
	// no error occurred
	clc
	ret

pdx:
	ldi		ZL, 0
pdx1:
	sub		XL, ZH
	sbc		XH, R16
	brcs	pdx2

	cpi		ZL, 9
	breq	pdxOverflow
	inc		ZL
	rjmp	pdx1

pdx2:
	add		XL, ZH
	adc		XH, R16

pdx3:
	subi	ZL, -low (hexTable << 1)
	ldi		ZH, high (hexTable << 1)
	lpm		ZL, Z
	ret

pdxOverflow:
	// Set carry flag to indicate error
	sec

	// Pop return address out of the stack
	// so we can return to the caller of printDecX
	pop		R16
	pop		R16
	ret


HEX-файл (fuses: H:FF, L:7A)
:020000020000FC
:100000000EC018951895C2C018951895BFC01895C0
:10001000189518957E306D79335B5F727F7BF8940D
:100020000FE90DBFA89508E101BD08E201BDC0E6DA
:10003000D0E00024112444245524332408BA03E1D9
:1000400007BBA1E0B1E015D1B930E9F7A0E011D1CB
:10005000A4E00FD1A7E00DD1A1E00BD1BFE0A0E05B
:1000600008D10FE706BF06E009BF01E003BF78949F
:10007000B1E0A0E009E181D0FCD0B930D9F7662425
:100080007724A89500E1002E8895002D007FE1F7E8
:1000900003FE66C00881E9810E0FEA810E0FEB8135
:1000A0000E0FEC810E17A9F77724CC24DD24EE2463
:1000B000FF24E1E8F0E00081118104831583C00E84
:1000C000D11E0281138106831783E00EF11EE450D6
:1000D000E53689F70981188109871A87C00ED11E74
:1000E0000B811A8111232AF4EE271F770195E10B6A
:1000F0001E2F0B871C87E00EF11E03E0F594E7949A
:10010000D594C7940A95D1F7062D073029F0C984F4
:10011000DA84EB84FC846394D601C0D0B1E0A88576
:10012000A8D0AF81A068A5D0AE81A3D0AD81A1D069
:10013000D701BB2322F4EE27A195EB0BBE2FAED047
:10014000B5E0A88596D0AF81A06893D0AE8191D05C
:10015000AD81FF200AF4A1E08CD004E60ED091CF4F
:100160007394072D0330C9F77A94E2E7F1E07BD06E
:10017000F4CF051D05054F00155BE0E2E5BFE32D5B
:100180008895F32DFE1BF017D8F308950F930FB742
:100190000F93EF93FF932BD001E0401A30F4501AE5
:1001A00020F4EDEEF2E02F013394FF91EF910F91E7
:1001B0000FBF0F911895BB9A00E232C00B34A9F51E
:1001C000BB9800E32DC0F3FD089500E429C0F3FFC0
:1001D000089500E525C0F3FD089501E0202EE0E636
:1001E00022C0F3FF08950630D0F400E719C0E02DD7
:1001F000E07FE030D1F0139491F0012DF6B3E037B9
:10020000A9F0E031C1F2E032C9F2E033E1F2E034CA
:10021000F1F2E03501F3E03621F3E038E9F000E0F7
:10022000E02DE770E02B0E2E11240895F3FD0895C4
:100230000830A8F70430221C00E690F72094E02D47
:10024000E770E05AF0E02192E53600E849F30394C4
:1002500001E0202E00E6E4CFF3FF08950630F8F623
:1002600008E0002E0895B1E0A59103D0B930E1F780
:10027000089518B31C7E18BB30E10B2F05D00A2F50
:1002800003D0C19AB395089528E007FB10F918BB75
:10029000000F2A9536BB36BBC1F70895F8EE03E090
:1002A00017D0EE3709F4E0E0ED83F4E600E010D07B
:1002B0000EE70E27FD810F2B09F4E0E0EE83FAE054
:1002C00000E006D0EF83EA2F0DD0E88788940895E8
:1002D000E0E0AF1BB00B20F0E93041F0E395F9CF3F
:1002E000AF0FB01FEC5EF0E0E491089508940F9119
:0402F0000F910895CD
:00000001FF

Комментарии (35)


  1. VBKesha
    20.03.2016 20:11
    +3

    Странно видеть такую страну на GT тут помоему больше для Хабра.
    Спасибо, давно не читал статей где так всё поробно и хорошо описано.


    1. kdekaluga
      20.03.2016 20:20
      +2

      Возможно, лучше было опубликовать там, сам думал перед публикацией. Но все же тут много уделено именно программированию, а схемотехника практически как в Arduino — соединить все проводками )


  1. aivs
    20.03.2016 21:16

    У меня чето dht22 в связке с raspberry примерно раз в месяц перестает отвечать, питание передернул и еще месяц работает. Никто не сталкивался?


    1. kdekaluga
      20.03.2016 21:56

      У меня пока что термометр включен только неделю, за неделю датчик ни разу не "повисал". Если такое действительно имеет место, можно питать датчик от соседнего выхода МК (raspberry в вашем случае) через небольшой резистор (конденсатор параллельно датчику следует оставить), т.к. заявленное потребление датчика в рабочем состоянии — меньше миллиампера. В этом случае, если датчик перестанет отвечать, можно будет его "перезапустить" программно.


    1. Lachezis
      20.03.2016 23:07

      Иногда его глючит от наводок, мне помогли конденсаторы нф и мкф поближе к его пинанию.


      1. Celtis
        20.03.2016 23:39

        DHT22 в связке с BananaPi M1+ трудится с осени.
        Последняя перезагрузка системы была где-то в январе, но не по вине датчика.
        Подключен даже без резистора.


    1. ittakir
      21.03.2016 06:16

      Любой более-менее сложный датчик может себя так повести. Тот же DS18b20 может начать выдавать +125 градусов.
      В правильной системе нужно иметь возможность отключать питание периферии, особенно если она подключается по длинному кабелю и ловит помехи.


      1. 10s
        21.03.2016 15:57
        +1

        125 градусов он выдает потому что отпадывает провод данных, и контроллер читает кучу единиц, и контроллер интерпретирует это как +125


        1. ploop
          21.03.2016 16:42

          А датчик разве не выдаёт контрольную сумму? По идее должен (по аналогии с DS18B20), и глупо её не проверять.


          1. sim31r
            22.03.2016 00:31

            У него странная контрольная сумма, когда все нули или единицы данные проходят как валидные. Может мои программы направильно что-то считают, но при отключении датчика они показывали что-то вроде 80.0 градусов.


            1. kdekaluga
              22.03.2016 02:40

              У него контрольная сумма считается XOR'ом между битами данных и сдвиговым регистром CRC, который изначально установлен в нули. Если все данные равны нулю, то результирующий CRC тоже получается ноль, т.е. в этом случае он валиден. Надо отдельно проверять это в программе.


        1. ploop
          21.03.2016 16:47
          +2

          Упс, не заметил, речь как раз про DS18B20. Ну тогда +125 это радиус изгиба рук программера :)


  1. ittakir
    20.03.2016 22:41
    +1

    А не пробовали написать аналогичную программу на С, насколько она больше получится?

    Сам когда-то начинал программировать AVR c ассемблера. Но сейчас посмотрел ваш листинг и приуныл. Программа практически не читаема. И дело не в том как она оформлена, с этим все отлично, а в том, что бизнес-логика скрыта ассемблером.


    1. sim31r
      21.03.2016 02:06
      -2

      Вот именно. И название уже не соответствует «Простой цифровой термометр/гигрометр», код уже ни как не простой, а очень специфический. Всё это просто было бы собрать на Arduino Uno. И breadboard бы не понадобился. Arduino Uno мне нравится тем, что там есть поддержка Watch Dog Timer прямо в стандартном ядре (3 строчки кода только добавить, библиотека, конфигрурация и wdtReset), о чем часто ардуинщики забывают, и компрометируют тем, надежную в целом плату.


      1. kdekaluga
        21.03.2016 03:28
        +3

        Не согласен. Специфической была задача написать этот ассемблерный код, но теперь он готов и чтобы повторить устройство, достаточно просто собрать схему. Под "простой" в названии понимается "минимум деталей, минимум расходов".

        Arduino Uno — это отличная плата, но вам не будет жалко отдавать целую плату ради одного термометра? Если это не эксперимент, не устройство, которое потом будет дорабатываться и расширяться, а единовременная разработка, которая потом будет просто стоять на столе и выполнять свою функцию. Да даже если взять ATmega328p отдельно, все равно она чрезмерна для такого простого устройства, функции которого вполне может выполнить и tiny13. В tiny13, кстати, тоже есть watchdog timer)

        А вот почему выбран именно ассемблер — здесь скорее увлечение. Это был первый язык программирования, который я выучил (тогда для Z-80), поэтому мне всегда приятно лишний раз написать на нем что-то стоящее. Кроме того, если программист знает и понимает ассемблер — он будет лучше писать программы на всех остальных языках, правильней выделять память под данные и т.д., т.к. все это в конечном счете компилируется все равно в машинные коды. Поэтому целью данной статьи, кроме, конечно, готового решения, также является желание помочь тем, кто изучает ассемблер сейчас, кто только начал его изучать.


        1. sim31r
          21.03.2016 13:54
          -2

          А у вас breadboard на картинке отдана под конструкцию, что дороже Arduino Uno. И цена Arduino не так велика, стоит копейки при том что на борту блок питания, гребенка контактов, кварц, и USB, и отдавать её не жалко, так как из-за массовости производства плата стоит дешевле, чем компоненты по отдельности, удобства перевешивают минусы. Стоит 3$, куда дешевле, если на борту полный фарш, всё готово? Программировать далее можно хоть на ассеблере, Bascom AVR, Codevision, Atmel studio, micro (pascal, basic, c), iar и прочем, почти все среды поддерживают загрузчик ардуиновский.
          Attiny13 программировал на Bascom AVR (basic), что интересно, почти всё ПО делает код в демоверсии с ограничениями, но эти ограничения перекрывают возможности Attiny13a )
          Тоже выбрал из-за DIP корпуса и цены (нужно было 20 штук), удобнее в отладке.


          1. kdekaluga
            21.03.2016 14:35
            +2

            Макетная плата отдана под временную конструкцию, потом ее заменит обычная печатная или же просто (в данном случае) спаяю все проводами, т.к. паять особо нечего. А стоит она, кстати всего $0.35 )

            Даже если взять Uno за $3, это все равно в шесть больше, чем tiny13 (около $0.5). А представьте, что потом появляется задача сделать 10 таких часов или 100? И размер у ардуины немаленький — моя задумка встроить этот термометр в электронные часы, чтобы они сразу показывали три параметра. Ардуина туда просто не влезет. И, кстати, Uno не содержит блока питания, только стабилизатор на 5 вольт.

            Ну и главное, я хотел показать, что arduino не обязательна для всех задач. Что более простые задачи можно решить на более простом, маленьком и дешевом МК.


            1. kanne
              22.03.2016 00:11
              -1

              в любительских одноразовых конструкциях разница в цене не имеет значения и при всех её недостатках — китайская ардуино-nano в любом случае лучше, нежели голая тини13. программирование для последней — это спортивно, красиво и показательно, но не проще, не нагляднее и не удобнее.
              если же цель сделать 100 часов, то stm8s для партии будет дешевле, опять таки с несопоставимо большими возможностями.


              1. kdekaluga
                22.03.2016 00:28

                Разница в цене имеет значение везде, вопрос в самой разнице) Соглашусь, что в данном случае непринципиально, $0.5 или $3, но тут вступает в дело факт наличия деталей — tiny13 у меня была, а ту же nano надо было бы покупать и ждать доставки. Плюс размер — в часах, куда я собираюсь встроить данный термометр, не так много свободного места, Uno туда точно не влезет, nano — возможно, но не уверен.

                А опубликовал я статью с двумя целями — во-первых, если кто-то будет искать самое простое схемное решение для отображения температуры на основе DHT22 — вот оно, на минимальном МК. Бывает, что такие МК просто лежат у людей без дела годами, поэтому собрать схему и зашить программу (писать-то ее больше уже не надо) будет очень просто. А во-вторых, если кто-то программирует на ассемблере из спортивного или любого другого интереса, данная статья может оказать ему практическую помощь. В этом случае он может, например, взять код опроса датчика и использовать его в другом проекте.


        1. ProstoUser
          21.03.2016 21:50
          +1

          Вообще, при программировании подобных устройств, ассемблер знать, безусловно, надо. Но реально писать на ассемблере имеет смысл только в самых крайних случаях.

          Так что цена — это не аргумент. В данном случае, если устройство делается в единственном экземпляре, гораздо логичнее взять какой-то другой контроллер, ресурсов которого хватило бы для того, чтобы спокойно написать программу на С. Не обязательно даже Ардуино, можно просто контроллер, в котором побольше памяти. На мой взгляд, возиться с ассемблером имеет смысл, только если это образец будущего серийного устройства и пара долларов разницы в цене контроллера имеет значение. Для одного экземпляра и даже для десяти, время потраченное на написание ассемблерной программы многократно перекроет всю экономию на цене микросхемы.

          Другое дело, если просто хочется на ассемблере. Это уже вполне нормальный аргумент. В конце концов, надо же получать удовольствие от того что делаешь!


          1. kdekaluga
            21.03.2016 22:13
            +2

            В данном случае все началось с того, что просто потребовался гигрометр) Вспомнил про DHT22 (были в наличии), почитал даташит, стало интересно, возможно ли сделать функциональный прибор на самом маленьком МК, который был (tiny13). Поэтому сразу же выбрал ассемблер, т.к. ресурсов у МК немного, плюс действительно получил удовольствие, вспомнив те времена, когда серьезные программы можно было писать только на нем (ZX-Spectrum).

            Кстати, большую часть времени потратил не на сам код, а на изучение протокола DTH22 (ведь писалось с нуля) по даташиту и осциллографу. Писать на ассемблере AVR в целом легко, т.к. у него много регистров, на Z-80 было намного сложнее)


      1. ittakir
        21.03.2016 06:02
        +2

        Для Watchdog не нужен ни Arduino Uno, ни ассемблер. На С в IAR, например, сброс делается встроенной командой __watchdog_reset() которая при компиляции развернется в одну ассемблерную инструкцию. Конфигурация также предельно простая.


    1. kdekaluga
      21.03.2016 02:47

      Я не думаю, что будет все очень плохо (касаемо размера кода на С) — возможно, в данном функционале в 1К уместить его получится (т.к. еще есть запас 25%), однако gcc (из состава Atmel Studio 7) иногда выдает не очень логичные конструкции.
      Что касается читабельности программы на ассемблере — она всегда ниже, чем на других языках. Прежде всего потому, что для полного понимания действия каждой команды необходимо помнить, какие данные находятся в каких регистрах процессора. Здесь регистров много (32), помнить приходится больше, поэтому читабельность кода для AVR ниже.


      1. sim31r
        21.03.2016 04:01

        В pure basic сталкивался со странностями при работе со стеком, при прерываниях, вызове подпрограмм крошечного стека ATtiny13 могло не хватать, компиляторам «тяжело» с таким маленьким стеком. Для более старших контроллеров, такой проблемы уже нет.


        1. ittakir
          21.03.2016 06:13

          В IAR по умолчанию стек 16 байт, что уже довольно мало. Вся тяжесть у компилятора связана с количеством локальных переменных. Если их немного, а в данной задаче это так, то и стека нужно ровно столько же.
          Также можно резервировать часть регистров под хранение переменных.


      1. ittakir
        21.03.2016 06:09

        gcc в Atmel Studio настоятельно не рекомендую. Он по какой-то причине выкидывал из программы целые блоки, даже если выключить оптимизацию. Прочитали переменную из регистра, но нигде не используем — выкинуть нафиг, и чтение из регистра тоже. А то что я таким образом пытаюсь отлаживать программу его не волнует. При пошаговом выполнении порядок того, что написано на экране и того, что выполняется разный!
        Зато IAR настоятельно рекомендую. Никогда не было с ним проблем, код получается компактным, все очень гибко настраивается.


        1. kdekaluga
          21.03.2016 14:37

          С IAR пока не работал, но часто слышу хорошие отзывы. Единственный минус, как я понимаю — она платная?


          1. ittakir
            21.03.2016 15:46

            Платность побеждается кряками.
            Самый большой недостаток — очень устаревший встроенный текстовый редактор. Я код пишу в Qt Creator, а компилирую и отлаживаю в IAR.
            На STM32 тоже сначала попробовал Eclipse + GCC. Есть специальная сборка даже именно под STM32. Как-то не пошло, постоянно какие-то проблемы, какие-то конфиги надо править, JTAG отваливается… При каждом обновлении этой студии новые сюрпризы.
            Перешел на IAR, все сразу из коробки работает.


  1. avs24rus
    21.03.2016 07:18

    К чему такой частый опрос датчика, не вносит ли это погрешность в виде нагрева?


    1. kdekaluga
      21.03.2016 14:40

      Не думаю. Датчик потребляет меньше миллиампера в активном режиме (когда его опрашивают), т.е. это до 5mW при питании от 5В. Опрос занимает до 6 миллисекунд, остальное время датчик бездействует, потребляя десятки микроампер. Такая рассеиваемая мощность не внесет погрешность даже на 0.1 градуса.
      А опрашиваю часто просто по причине, чтобы быстрей показания менялись на индикаторе) Если наоборот хочется, чтобы помедленнее, внутри программы можно увеличить задержку.


  1. Maks_K2
    21.03.2016 21:49

    А какое отклонение дает датчик АМ2302 по температуре и влажности в реальности
    просто лежат рядом два DHT11 Так в одном влажность 34% в другом 25%
    понятно что АМ2302 точнее но насколько реальна заявленная в даташит погрешность?


    1. kdekaluga
      21.03.2016 23:56
      +1

      У меня два DHT22 (заказаны были вместе, т.е. из одной партии) показывают оба параметра примерно одинаково (в пределах одного градуса температуры и процента влажности). Разница между DHT22 и старым жидкостным комнатным термометром — где-то 0.5 градуса. Влажность сравнить не с чем.



  1. barmic
    22.03.2016 12:42

    Всё конечно замечательно, но мне не понятно зачем Вы пишите комментарии в коде на иностранном языке? Для понимания начинающего, для них и рассчитана эта статья, можно было описать всё на родном языке, тогда и будет понятно.


    1. kdekaluga
      22.03.2016 12:57
      +1

      Согласен, вот об этом не подумал) Пишу на английском по привычке, чтобы не переключать раскладку. С другой стороны, разработчику английский все равно надо знать, т.к. большинство даташитов именно на нем.