Подключаем датчик температуры ds18b20 к микроконтроллеру.
Ну и по ходу делаем простенький бытовой термометр.

Простенький бытовой термометр

Датчик температуры ds18b20, наверное, один из самых распространённых и хорошо описанных цифровых термометров на сегодняшний день. И все же, владея таким количеством информации на руках, остаются нюансы о которых хотелось бы поговорить и прояснить. Здесь я не буду раскрывать всю глубину и универсальность подключения и работы этого устройства, а просто по-быстрому подключу один датчик к МК и сделаю обыкновенный бытовой термометр.


Начну с того, что этот датчик подключается к микроконтроллеру по протоколу 1-Wire разработанному компанией Dallas Semiconductor в конце 90-х годов прошлого века. На мой взгляд это хоть и не очень скоростное, но довольно изящное решение передачи данных по одному проводу. В пределе, для подключения устройств, по этому протоколу требуется всего два провода (сигнальный и общий). Хорошим примером работы такого подключения является всем известные ключи-таблетки домофонов. Но для упрощения работы я буду подключать прибор по трехпроводной схеме (питание, сигнал, общий). Такое соединение дает возможность контролировать работу подключенного устройства и немного упрощает программирование.

Основой трехпроводного соединения является подтягивающий резистор R1, который удерживает на шине данных высокий потенциал (единицу). Он выбирается исходя из сопротивления передающего кабеля и наличия внешних помех в пределах от 1 кОм до 5,1 кОм. Для кабелей с высоким сопротивлением и минимальными помехами выбирается большее сопротивление, а для линий с большим количеством помех низкое сопротивление. В большинстве случаев и для домашнего использования подойдут 4.7 кОм указанные в даташите.

Из-за того, что данные передаются по одному проводу, говорить о тактовом сигнале, не приходится, поэтому инженеры из Dallas Semiconductor придумали тактировать передаваемые данные подавая на шину низкий сигнал управляющим устройством и соблюдать строго определенные промежутки времени (тайминги) для контроля передоваемой информации. Выдерживание таких таймингов чрезвычайно важно при организации обмена данными по протоколу 1- Wire. И чтобы организовать четко выверенные по времени задержки необходимо либо сильно заморочившись написать на ассемблере специальную программу, либо воспользоваться уже имеющийся библиотекой задержек. Для CV_AVR библиотека задержек с указанным временем подключается с помощью файла:

#include <delay.h>	//Библиотека задержек.

И вот здесь есть один нюанс в котором я так до конца и не разобрался. Дело в том, что для точной работы библиотеки необходимо указать частоту работы микроконтроллера. В других средах разработки частота работы МК указывается через псевдоним:

#define F_CPU 1000000	//Частота работы микроконтроллера 1МГц.

Но в CV_AVR такой макрос не работает. Литература также не дала ответа на вопрос о том, как указать частоту МК для этой библиотеки. Но проведя эксперимент я выяснил, что по умолчанию данная библиотека корректно работает с частотой МК в 8 МГц. На других частотах результат изменяется кратно частоте работы МК. Что собственно и понятно.

Добавлено 24.04.21. Насчет работы библиотеки на частоте 8МГц я до конца не уверен. Нет, я полностью не уверен. Выставив фьюзы в Tiny 25 согласно даташита на 8 МГц у меня все получилось, но проделав всё тоже самое на Mege-8, датчик работать не захотел. И начал работать только после того как я выставил все задержки на 45,8% меньше номинала т.е. delay_us(480) превратилась в задержку delay_us(220) и т.д. Так как Mega-8 изучена мной чуть лучше, предполагаю, что либо Tiny 25 имеет умножитель либо я что-то не то понял и не до конца разобрался с организацией рабочей частоты в Т25. Что более вероятно.

Библиотека <delay.h> содержит две процедуры:

delay_us(количество микросекунд);	//Пауза микросекунд.  
delay_ms(количество миллисекунд);	//Пауза миллисекунд.

При этом функция delay_ms(...) каждую миллисекунду генерирует команду на восстановление сторожевого таймера (wdr). Соответственно, что бы организовать паузу и наглухо загрузить МК на одну секунду необходимо написать в теле программы:

delay_ms(1000);	//Пауза  в одну секунду.

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

Из графика видно, что для сброса необходимо:
      1. Установить на линии низкий потенциал;
      2. В течении 480 микросекунд подержать низкий потенциал на линии;
      3. Установив пин регистра порта на вход;
      4. В течении 70 микросекунд подождать;
      5. Считать потенциал шины данных;
      6. Подождать в течении 410 микросекунд.


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

Теперь рассмотрим график передачи одного бита информации от управляющего устройства (МК) к управляемому (датчику).

Из графика видно, что для передачи в термометр (1) контроллеру необходимо:
      1. Установить на линии низкий потенциал;
      2. В течении 10…15 микросекунд подержать низкий потенциал на линии;
      3. Установить пин регистра порта на вход (подтягивающий резистор восстановит на линии высокий потенциал);
      4. Выждать не менее 50…45 микросекунд перед передачей следующего бита информации.


Для передачи в термометр (0) контроллер должен;
      1. Установить на линии низкий потенциал;
      2. Для верности подождать 120 микросекунд (это максимальное время тайм-слота передачи);
      3. Установить пин регистра порта на вход (подтягивающий резистор восстановит на линии высокий потенциал).


Со стороны датчика изменение на шине выглядят таким образом. После появления на линии низкого потенциала через 15 микросекунд датчик в течении 45 микросекунд считывает потенциал шины. Если на шине высокий потенциал, то в память записывается бит (1) если низкий, то (0). При передачи данных необходимо помнить о том, что данные передаются младшим битом вперед.

Чтение бита информации из датчика происходит следующим образом.
      1. Установить на линии низкий потенциал;
      2. В течении 10…15 микросекунд подержать низкий потенциал на линии;
      3. Установив пин регистра порта на вход;
      4. Подождать в течении 2…5 микросекунд;
      5. Считать потенциал шины;
      6. Выждать не менее 45 микросекунд перед приемом следующего бита информации.


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

Подпрограмма сброса. Она не только сбрасывает термометр, но и проверяет правильность подключения:

//Подпрограмма сброса
unsigned char reset_DS18B20(void){
	unsigned char f_err = OFF;	//флаг ошибки
	DQ_RORT_DOWN;			//выставляем ноль на пин порта	
	DQ_DDR_OUT;			//устанавливаем вывод на передачу     
	delay_us(480);			//пауза  
	DQ_DDR_IN;			//переводим выход на прием   
	delay_us(70);			//пауза  
	if(DQ_PIN_ON) f_err = ON;	//поднимаем флаг ошибки если сигнал "1" (термометр не отвечает)
	delay_us(410);			//пауза  
	if(!(DQ_PIN_ON)) f_err = ON;	//поднимаем флаг ошибки если сигнал "О" (оторван резистор)
	return (f_err);
}

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

//подпрограмма передачи команды в термометр (один байт)
void write_data_DS18B20(unsigned char d_comand){
	unsigned char c_temp = ZERO;		//временная переменная 
	DQ_RORT_DOWN;				//выставляем ноль на пин порта	
	for (c_temp; c_temp < ONE_BYTE; c_temp++) { 		
		if (d_comand & (1<<ZERO_BIT)){	//если нулевой бит (1)
			DQ_DDR_OUT;		//устанавливаем вывод на передачу      
			delay_us(15);		//пауза 
			DQ_DDR_IN;		//переводим выход на прием    
			delay_us(45);		//пауза 
		}
		else{		//если бит (0)
			DQ_DDR_OUT;		//устанавливаем вывод на передачу  
			delay_us(120);		//пауза
			DQ_DDR_IN;		//переводим выход на прием       
		};
		d_comand >>= ONE_STEP;		//смещения на один шаг
		delay_us(5);			//пауза 
	}; 
}
//подпрограмма считывания одного байта 
	unsigned char read_data_DS18B20(void){
	unsigned char c_temp = ZERO;		//временная переменная 
	unsigned char d_temp = CLEAN;		//возвращаемая переменная даты  
	DQ_RORT_DOWN;				//выставляем ноль на пин порта	
	for (c_temp; c_temp < ONE_BYTE; c_temp++) {     
		DQ_DDR_OUT;			//устанавливаем вывод на передачу   
		delay_us(15);			//пауза
		DQ_DDR_IN;			//переводим выход на прием  
		delay_us(2);			//пауза 		
		if (DQ_PIN_ON) d_temp |= (1<<c_temp);	//если бит (1) устанавливаем его в текущую позицию
		delay_us(45);			//пауза   
	}; 
	return (d_temp);
}

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

Еще немного хочу сказать о переводе числа со знаком (записанного в дополнительном коде) в целое положительное для его отображения. Для начала необходимо определить знак полученного значения температуры. Затем выставить флаг в случае если оно отрицательно, затем из полученного значения необходимо вычесть единицу и инвертировать его. При этом мы получаем положительное целое число для его отображения на индикаторе.

//определение отрицательной температуры
	if(data_temp & (1<<LAST_BIT_TWO_BYTE)){
		f_minus = TRUE;		//устанавливаем флаг отрицательной температуры
		data_temp --;		//вычитаем единицу
		data_temp = ~data_temp;	//инвертируем 
	}
	else f_minus = FALS;		//снимаем флаг отрицательной температуры 

Ну вот с программной частью вроде все. Как я уже писал, весь проект для CV_AVR можно посмотреть в прилагаемых файлах.

Т.к. это устройство было задумано в качестве тестового образца для подключения термометра, то я решил, что HX1230 и tiny_25 это отличный тандем для реализации этого проекта. Я не пожалел о выборе, но в процессе программирования чуть не уперся в нехватку Flash памяти МК. Ее хватило в притык. Очень много уходит на разного рода украшательства вроде надписи и больших цифр. Подсветку дисплея я включил на постоянку. Но если кто будет повторять этот проект, то может наоборот отключить ее совсем или приделать кнопочку для включения по нажатию. Это ведь очевидно. И еще дисплей HX1230 не любит 5-ти вольтовое питание. Для него это очень много. Поэтому я рекомендую вместо диода поставить стабилизатор на 3.3V. Это положительно скажется на работе дисплея и не как не повлияет на работу устройства в целом.

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

После чего заменил выносной датчик стационарным в корпусе ТО-92. И распечатал простенький корпус. Т.к. термометр очень чувствительный, то замена сразу сказалась на показаниях. Если выносной термометр мерил реальную температуру того места где находился, то стационарный из-за близости к плате дает погрешность.

А на сегодня всё.

05.02.21


Если вдруг найдете в статье неточности или заблуждения. Напишите мне об этом. Я подправлю.

Приложения:
Скачать схему, плату, даташит, проект в CV_AVR с .hex файлом, а также файлы корпуса..