Термометр на DS18B20 + 1-Wire на AVR

Запилил себе термометр на базе DS18B20.



Под катом еще картинки и описание кода…


Схема

  • Питание через разъем и LDO на 5V 0.5A MC33269DT-5.0
  • Контроллер — ATTiny2313, без кварца (точность нам без надобности)
  • Сегментный индикатор с общим катодом CC56-12SRWA
  • Для управления сегментами используется сдвиговый регистр 74HC164N
  • Сегменты питаются через N-канальные полевые транзисторы, управляемые с МК (марку не помню, валяются для такого случая)
  • На плату, кроме разъема для подключения 1-Wire, выведены UART и SPI-разъем для программирования

Обратная сторона

Схема простенькая (ИМХО).

Прошивка чуть сложнее. Я первый раз работал с 1-Wire, собрал несколько граблей.

Инициализация.
init:
			CLI

			//Настройка частоты(делителя частоты)
			OUTP 	CLKPR, 0x80
			OUTP 	CLKPR, 0x00

			//Инициализация стека
			OUTP 	SPL, Low(RAMEND)

			//Настройка портов
			OUTP 	DDRB, 0x0F
			OUTP 	DDRD, (1<<DDD3) | (1<<DDD6)
			CBI 	PORTD, 2

			//Настройка USART
			.equ 	baudrate = 9600
			.equ 	bauddivider = XTAL/(16*baudrate)-1
	
			OUTP 	UBRRH, high(bauddivider)
			OUTP 	UBRRL, low(bauddivider)
			OUTP 	UCSRA, 0
			; Прерывания запрещены, прием-передача разрешен.
			OUTP 	UCSRB, (1<<TXEN)|(1<<UDRIE)
			; Формат кадра - 8 бит, пишем в регистр UCSRC, за это отвечает бит селектор
			OUTP 	UCSRC, (1<<USBS)|(3<<UCSZ0)

			; Иницилизация буфферов:
			CLR	ZH
			STS	OUT_PTR_S, ZH
			STS	OUT_PTR_E, ZH
			STS	OUT_FULL, ZH

			; Инициализация индикатора
			CLR	pos

			CLR	LED1
			CLR	LED2
			CLR	LED3
			
			LDI	ZH, 0xC3		
			MOV	LED4, ZH
			
			//Настройка таймера
			OUTP	TCCR0A, 2			;Режим CTC
			OUTP	TCCR0B, 4			;Предделитель 256
			OUTP	OCR0A, 124			;125 тиков, потом прерывание
			OUTP	TIMSK, (1<<OCIE0A)  ;Разрешаем прерывания

			LDI	counter, 250

			CLT
			
			//Настройка температуры
			CLR	TH
			CLR	TL

			SEI

			//init_sleep();


Частота 8MHz от RC-генератора.
UART — 9600. Работа по прерыванию (только отправка)
Чистим состояние индикатора.
Настраиваем таймер Timer0 на тики каждые 4мс. Там мы будем отрисовывать сегменты и выставлять флажок для основного кода.
Очищаем остальные переменные…

Главная часть.
MAIN:		
			//SLEEP

			BRTC	MAIN
			
			CLT
			RCALL	ONEWIRE_WORK
			
			RJMP	MAIN


Да. Вот так все просто:
если выставлен флаг T (флаг выставляет таймер 1 раз в секунду) — идем работать с 1-Wire.

Обработчик UART взят отсюда

Таймер
TIMER:
			PUSH	XH
			IN 	XH, SREG
						
//Отрисовка очередной цифры...
			PUSH	R19
			PUSH	ZH
			PUSH	ZL
			PUSH	R15
			
			IN	R19, PINB        ; запоминаем состояние порта
			CLR	ZH
			OUT	PORTB, ZH        ; отключаем вывод на индикатор, чтобы не мерцало
			LSL	R19              ; сдвигаем отображаемый сегмент
			OR	pos, pos         ; если позиция 0
			BRNE	NOT_ZERO				
			LDI	R19, 1		 ; то начинаем с первого				
NOT_ZERO:
			//Подготовка данных
			;R15 = LED[pos];
			LDI	ZL, LOW(LED)
			CLR	ZH
			ADD	ZL, pos
			LD	R15, Z            ; загрузили что надо показывать

			LDI	ZL, 8				;for (zl=8; zl > 0; zl--)
;-----------------------------------------------------------------
DrawLoop:
			CLK_LOW
			
			DATA_LOW
			SBRC	R15, 0
			DATA_HIGH

			CLK_HIGH
			LSR	R15

			DEC	ZL
			BRNE	DrawLoop
;-----------------------------------------------------------------

			OUT	PORTB, R19        ; Индикатор снова показывает

			INC	pos               ; Передвигаем позицию
			ANDI	pos, 3

			CLK_LOW
			DATA_LOW
			

;-----------------------------------------------------------------
			DEC	counter		    ; Счетчик тиков таймера.

			BRNE	SKIP_RESET_COUNTER          ; Прошла еще секунда...

			LDI	counter, 250
			ORI	XH, 0x40	    ; при каждом сбросе - начинаем работу с 1-wire	

SKIP_RESET_COUNTER:

			POP	R15
			POP	ZL
			POP	ZH
			POP	R19

			OUT 	SREG, XH			; Выходим, достав все из стека
			POP 	XH

			reti						; выходим


Вспомогательные функции
Перевод температуры из 1/16 в 1/10 градуса
.MACRO		CONVERT2DEC //S0:S1 TMP0:TMP1
			//T *= 5
			//Y = T
			MOV	@2, @0
			MOV	@3, @1

			//Y *= 2
			CLC
			ROL	@3
			ROL	@2
			//Y *= 2
			CLC
			ROL	@3
			ROL	@2

			//T += Y
			ADD	@1, @3
			ADC	@0, @2

			//T /= 8
			ROR	@0
			ROR	@1
			ROR	@0
			ROR	@1
			ROR	@0
			ROR	@1
			ANDI	@0, 0x03
.ENDM

Тут мы должны умножить на 10 и разделить на 16, или умножить на 5, разделить на 8.
Умножение на 5 делаем как сложение A + A*4 == A + (A << 2)
Деление на 8 делаем сдвигами. Я не сталчистить флаг переноса каждый раз, что приводит к мусору в старших битах. Его я чищу операцией AND, т.к. знаю, что отрицательных температур быть не может.

Перевод в десятичное представление
.MACRO    SubAny2	//@0:@1	@2 @3
	                CLR	@2
SubAny2_while:
                        SUBI 	@0, @3		
	                SBCI 	@1, 0		
	                BRCS 	SubAny2_exit	;while (tmp.VAL>0)
	
                        INC 	@2				;[tmp.Z]++;  
    
	                RJMP 	SubAny2_while

SubAny2_exit:
	                SUBI 	@0, (-@3)	;Корректируем значение, т.к. мы его загнали в минус...
	                SBCI	@1, 0
.ENDM


Из пары регистров, в цикле вычитается третий, пока число не станет меньше вычитаемого. Каждое вычитание увеличивает счетчик (4-й параметр).
По сути DIVMOD (получаем частное и остаток) для чисел до 1000.
Используется так:
SubAny2 YL, YH, R4, 100

Тут мы быстро получаем кол-во сотен в числе (первую десятичную цифру числа).

.macro    SubAny	//@0 @1 @2
	                CLR 	@1		;[tmp.Z]=0;
SubAny_while:
                        SUBI 	@0, @2		;VAL-=@2;		
	                BRCS 	SubAny_exit	;while (tmp.VAL>0)
	
                        INC 	@1		;[tmp.Z]++;  
    
	                RJMP 	SubAny_while

SubAny_exit:
	                SUBI 	@0, (-@2)	;Корректируем значение, т.к. мы его загнали в минус...
.endm

Аналогично, только для чисел меньше 100. Получаем сразу 2 цифры числа.

Циклы ожидания
.MACRO DELAY_US
			LDI	ZL, low((@0) * 2 - 1)
			LDI	ZH, high((@0) * 2 - 1)

DELAY_US_LOOP:
			SUBI	ZL, 1
			SBCI	ZH, 0

			BRCC	DELAY_US_LOOP
.ENDM

Цикл ожидания @0 микросекунд. Точность +- 3 такта.

.MACRO DELAY_US_SMALL
			LDI	ZH, low((@0) * 8/3 - 2)

DELAY_US_SMALL_LOOP:
			SUBI	ZH, 1

			BRCC	DELAY_US_SMALL_LOOP
.ENDM

Если надо ждать меньше 97 микросекунд, то можно использовать другой макрос. Он короче и использует только 1 регистр.

Ожидание сигнала присутствия устройств на линии 1-Wire
.MACRO	ONEWIRE_WAIT_EXISTS
			
			DELAY_US @0
			
			ONEWIRE_HIGH
			
			DELAY_US_SMALL 2

			LDI	ZH, 80

ONEWIRE_WAIT_LOOP:
			SBIC	PIND, 2
			RJMP	ONEWIRE_WAIT_CONTIMUE

ONEWIRE_WAIT_OK:
			SBIS	PIND, 2
			RJMP	ONEWIRE_WAIT_OK

			RJMP	ONEWIRE_WAIT_EXIT

ONEWIRE_WAIT_CONTIMUE:
			DEC	ZH
			BRNE	ONEWIRE_WAIT_LOOP
			RJMP	@1

ONEWIRE_WAIT_EXIT:	
.ENDM

Ожидает заданное время (первый параметр) окончания «reset», отпускает линию и ждет сигнала присутствия в течении 80 микросекунд. Если нет сигнала, переходит на адрес ошибки (второй параметр)

Отправка бита 1-Wire
SEND_BIT:
			// Отправляет один бит
			//IN CARRY-FLAG 0-1

			ONEWIRE_LOW

			BRCS	SEND_ONE

			DELAY_US_SMALL 90
			ONEWIRE_HIGH
			DELAY_US_SMALL 5

			RET

SEND_ONE:
			DELAY_US_SMALL 5
			ONEWIRE_HIGH
			DELAY_US_SMALL 90

			RET

Значение отправляемого бита лежит во флаге переноса (CARRY). Очень удобно — в цикле сдвиг через перенос и вызов функции…

Отправка байта 1-Wire
SEND_BYTE:
			//IN R19
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			ROR	R19
			RCALL	SEND_BIT
			RET


Чтение бита 1-Wire
READ_BIT:
			// читает значение бита, передаваемое уйстройством.
			//OUT CARRY-FLAG 0-1
			PUSH	R10

			ONEWIRE_LOW

			DELAY_US_SMALL 2

			ONEWIRE_HIGH

			DELAY_US_SMALL 8

			IN	R10, PIND

			DELAY_US_SMALL 79

			ROR	R10
			ROR	R10
			ROR	R10

			POP	R10

			RET

Результат чтения возвращается во флаге переноса (CARRY)

Чтение байта 1-Wire
READ_BYTE:
			//OUT R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RCALL	READ_BIT
			ROR	R19
			RET


Расчет CRC по стандарту 1-Wire
UPDATE_CRC:
			//IN	R19, CRC
			//OUT	CRC

			LDI	ZL, 8
			LDI	YH, 0x8C

CRC_LOOP:
			MOV	ZH, CRC
			EOR	ZH, R19

			LSR	CRC
			LSR	R19
			
			SBRC	ZH, 0
			EOR	CRC, YH

			DEC	ZL
			BRNE	CRC_LOOP
			
			RET


Реализация алгоритма:

uchar crc;		
void onewire_crc_update(uchar inData) {
  for (uchar p = 8; p; p--) {
    uchar tmp = crc ^ inData;
    crc >>= 1;
    inData >>= 1;
    if (tmp & 1) crc ^= 0x8C;
  }
}


Таблица кодов для индикатора (только цифры)
; LED-Table ==============================================
DIGIT_TABLE:
			.db 0xBB, 0x88 // (48) 0 // (49) 1 
			.db 0xF1, 0xE9 // (50) 2 // (51) 3 
			.db 0xCA, 0x6B // (52) 4 // (53) 5 
			.db 0x7B, 0x8B // (54) 6 // (55) 7 
			.db 0xFB, 0xEB // (56) 8 // (57) 9


Перевод температуры в сигналы индикатора, вывод в USART
TEMP_TO_LED:
			//Пересчитываем отображаемые значения  
			MOV	YH, TH
			MOV	YL, TL

			SubAny2 YL, YH, R4, 100
			SubAny 	YL,	R5, 10

			//Convert to LED
			LDI	R19, 0x0D
			RCALL	Buff_Push
			LDI	R19, 't'
			RCALL	Buff_Push
			LDI	R19, ':'
			RCALL	Buff_Push

			OR	R4, R4
			BREQ	SKIP_FIRST_CONVERT
			MOV	R19, R4
			ORI	R19, 0x30
			RCALL	Buff_Push

			CLR	ZH
			LDI	ZL, LOW(2*DIGIT_TABLE)
			ADD	ZL, R4

			LPM	R4, Z
			
SKIP_FIRST_CONVERT:

			MOV	R19, R5
			ORI	R19, 0x30
			RCALL	Buff_Push
			LDI	R19, '.'
			RCALL	Buff_Push
			MOV	R19, YL
			ORI	R19, 0x30
			RCALL	Buff_Push
			LDI	R19, 'C'
			RCALL	Buff_Push
			LDI	R19, 0x0D
			RCALL	Buff_Push

			CLR	ZH
			LDI	ZL, LOW(2*DIGIT_TABLE)
			ADD	ZL, R5
			LPM	R19, Z

			LDI	ZL, LOW(2*DIGIT_TABLE)
			ADD	ZL, YL
			LPM	YL, Z

			ORI	R19, 0x04			; Добавляем точку
			
			LDI	ZH, 0xC3		
			
			//Set LED
			CLI
			
			MOV	LED1, R4
			MOV	LED2, R19
			MOV	LED3, YL
			MOV	LED4, ZH

			SEI
			
			RET


Основной код работы с 1-Wire
ONEWIRE_WORK:
			//RESET+WAIT
			ONEWIRE_LOW
			ONEWIRE_WAIT_EXISTS 640, ONEWIRE_EXIT_ERR

			//Выбираем все устройства
			LDI	R19, 0xCC
			RCALL	SEND_BYTE 

			//Читаем результаты измерений...
			LDI	R19, 0xBE
			RCALL	SEND_BYTE

			CLR	CRC
			//
			RCALL	READ_BYTE
			MOV	TL, R19
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			MOV	TH, R19
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			RCALL   UPDATE_CRC
			//
			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			//
			RCALL	READ_BYTE
			RCALL   UPDATE_CRC

			//Check CRC
			OR	CRC, CRC
			BRNE	ONEWIRE_EXIT_CRC

			//RESET+WAIT
			ONEWIRE_LOW

			//Пересчитываем все, пока время идет...
			LDI	R19, 'R'
			RCALL	Buff_Push
			MOV	R19, TL
			RCALL	Buff_Push
			MOV	R19, TH
			RCALL	Buff_Push

			CONVERT2DEC TH, TL, YH, YL
			
			LDI	R19, 'D'
			RCALL	Buff_Push
			MOV	R19, TL
			RCALL	Buff_Push
			MOV	R19, TH
			RCALL	Buff_Push

			RCALL	TEMP_TO_LED 

			ONEWIRE_WAIT_EXISTS 610, ONEWIRE_EXIT_ERR

			//Выбираем все устройства
			LDI	R19, 0xCC
			RCALL	SEND_BYTE 

			//Начинаем измерение			
			LDI	R19, 0x44
			RCALL	SEND_BYTE 

			RET

ONEWIRE_EXIT_ERR:
			LDI	ZH, 0x73		
			MOV	LED4, ZH
			
			LDI	ZH, 0x04
			EOR	LED2, ZH

			RET

ONEWIRE_EXIT_CRC:
			LDI	ZH, 0x33		
			MOV	LED4, ZH
			
			LDI	ZH, 0x04
			EOR	LED2, ZH

			RET
Файлы в топике: Temp1W.zip

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

RSS свернуть / развернуть
Ого, в наши времена редко увидишь ассемблерные листинги. Необходимость в прецизионном ногодрыге повлияла на его выбор?
0
Нет. Просто ради разминки ) Сто лет не писал на асме. А последние 2 года вообще на питоне…
+1
Мне только интересно, их хоть кто-то изучил внимательно или только глазами по каментам пробежали?)
+2
Если кто-то захочет содрать код, то может и изучит, а так — зачем? Верим автору наслово, что все работает)
+1
Работает.
А листинги это скорее для тех, кто будет ковырять 1-Wire (делая очередной термометризучая протокол).
0
Ну кагбэ листинги тут теоретически не для украшения, а чтобы их изучить и либо чему-то поучиться, либо обосрать. Вот мне и интересно, сколько тут народа, которому не лень листинг на асме изучить.
0
Какой размер прошивки получился?
Как впечатления от асма?
Почему предпочел ногодрыг для 1W, а не полуаппаратную реализацию через UART?

Алсо, нафига тебе термометр? Удивляет популярность сабжа среди радиолюбителей, притом что китайские готовые дешевле чем один лишь датчик и при этом годами пашут на таблетке. Аналогично с часами, но их я и сам сделать хочу — с блэкджеком и шлюхами.
0
  • avatar
  • Vga
  • 05 мая 2015, 09:27
Ну ты знаешь, сейчас практически что ни возьми, то можно задешево купить в Китае. А как же удовольствие от процесса и получения работающего устройства?)
+2
Несомненно. Но стопятисотый термометр без какой-либо изюминки — это как-то неинтересно, на мой взгляд. Мне вот, например, термометр делать скучно — как раз из-за этого.
0
P.S. Это не критика, а именно вопрос — чем заинтересовала столь обыденная задача.
0
Нужен термометр. :)
А тут 4 выходных… Вот и запилил :)
0
Прошивка 780 байт.
Многие моменты можно сделать оптимальнее:
чтение в и запись в цикле.
ожидания сделать не макросами, а функциями.
и т.д.
Но зачем?
А в целом: могу только согласиться с 1essor1 :
А как же удовольствие от процесса и получения работающего устройства?)
+1
Но зачем?
Чисто из спортивного интереса, очевидно же.
А в целом: могу только согласиться с 1essor1:
Неужели за устройством не стоит никакого практического применения?
0
Нет. Тут как раз наоборот. Нужен термометр. А ждать посылку — 2-3 недели. А 4 выходных — делать особо нечего…
0
Ну, купить-то при желании и местно можно. Я как раз местно и покупал (температуру за окном мерять). У самопала существенный минус для такого применения — сложно сделать такой, чтобы на батарейке годами работал. А лишнюю розетку занимать не хочется.

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

Ну это как посмотреть и к делу подойти вот (http://radiokot.ru/circuit/digital/home/97/) сделал себе ради прикола, третий год, полет нормальный, работает. Достаточно одной таблэтки :)
0
Я предпочитаю постоянную индикацию (китаец висит на стене и считывается с метра-двух), а это автоматически означает микропотребляющий МК и безмозглую сегментную ЖК-стекляшку, которую не столь просто найти в продаже.
0
Да и с 1-Wire никогда не работал, было интересно разобраться, потрогать руками…
0
Алсо, нафига тебе термометр?

Вспомнилось:

«В жизни каждого эмбеддера наступает момент, когда он понимает, что ему пора сделать термометр...»
(С) _YS_
+4
Я еще не делал, но уверен что этот момент еще наступит =)
+1
Обязательно наступит :)

Просто у каждого свой термометр и в свое время. История постов на этом портале это подтверждает. Кто-то пишет свой термометр на ASM («Просто ради разминки»), кто-то на Lua с использованием промежуточного сервера на PHP для отправки сообщений по SMTP, кто-то использует нестандартные средства измерения (например, уход частоты watchdog таймера от температуры). Но через «сделать свой термометр» рано или поздно проходят все :)
+1
И я уже готовлюсь к этому событию) И планирую не то чтобы термометр, а что-то типа выносной метеостанции с уличным «клиентом» и домашним «сервером», а также радиоканалом и еще кой-какими фичами. Даже загодя дисплейчик под это дело заказал. А вот когда этому настанет время, это уже совсееем другой вопрос =)
+1
Удачи!
Мой термометр был тоже «внешний», с автономным питанием и радиоканалом на CC1101 :)
0
Меня все вопрос мучает — а индуктивная зарядка пробьет через стеклопакет? Так можно как от проводов на улицу уйти, так и автономного питания по идее.
0
Должна. Разве что с КПД похуже станет, но это решаемо экспериментальным путем.
0
Ну кпд не проблема я думаю. Я этот вопрос еще не разбирал, она пол ампера где дает на выходе? Если так, то даже 100мА будет за глаза.
0
Я бы скорее рассматривал аналогии с NFC и RFID. На расстоянии в 20-30см энергии вполне хватает для работы экономичного чипа.
А вообще, компании вроде WiTricity обещают передачу вполне солидной мощности (десятки ватт) на расстояние 2-3 метра, и уже демонстрируют определенные в том успехи. Так что передать требуемую термометру мощность вполне реально (заодно можно и сигнал передавать тем же каналом, как это делает RFID).
0
В этом случае «кормушке» придется выполнять обязанности гейта. Что не всегда хорошо.
0
Нашел вот такую приблуду. Оно? Заявленно 5мм всего.
И там как я понял вкладыш с NFC IC Chip 011-12090068BK. Только как с таким чудом общаться непонятно.
0
Нашел инфу, что работает до 4см. А внутри вполне конкретные Техасские микросхемы. Правда отсутствие каких-либо интерфейсов намекает на невозможность передачи своей инфы.
0
как ни странно, но сделал свой для того, чтобы видеть температуру))) Табло такое заказали)
0
От чего питание?
Я делал питание от li-ion, плюс автоматическая зарядка от USB, с индикацией приближающегося разряда, плюс step-UP до 5 вольт для стабильного питания LCD 16х2. Ну и кнопка для активации. Измерение каждые 2 сек в активном режиме и раз в минуту в неактивном.
Писал на Си, ибо.
0
Какая емкость акума? И сколько держит?
0
2200 мАч, 18650. От полной зарядки до 3 вольт проходит больше месяца, скорее ближе к полутора. При устловии перехода в активный режим пару раз в день. Точнее не мерял, меня такой результат более чем устраивает.
Если поставить дохленький, от ноутбука, то 3-4 недели.
0
А может поделитесь схемой — исходниками?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.