Содержание
- Обзор
- Технические характеристики
- Подключение к Arduino
- Работа с готовыми библиотеками
- Пример использования
- Часто задаваемые вопросы
Обзор цифровой датчик температуры DS18B20
DS18B20 – это полноценный цифровой термометр, способный измерять температуру в диапазоне от -55оС до +125оС с программируемой точностью 9-12 бит. При изготовлении на производстве, каждому датчику присваивается свой уникальной 64-битный адрес, а обмен информацией с ведущим устройством (микроконтроллером или платой Arduino) осуществляется по шине 1-wire. Такой подход позволяет подключать к одной линии целую группу датчиков, вплоть до 264.
Технически, датчики DS18B20 выпускаются в 3-х корпусах: ТО-92, SO, uSOP. Внешний вид и распиновка корпусов показаны на рисунке №1.
Рисунок №1 – виды корпусов и распиновка DS18B20
Для того, чтобы понять принципы общения с датчиком, необходимо ознакомиться с его внутренней составляющей. Эта на вид маленькая микросхема содержит в себе целый ряд электронных блоков и модулей. На рисунке №2 показана структурная схема датчика DS18B20.
Рисунок №2 – структурная схема DS18B20
Первое на что хотелось бы обратить внимание – это способ питания микросхемы. Их всего два: режим прямого питания (напряжение подаётся на выводы Vdd и GND) и режим паразитного питания (датчик запитывается от линии данных). Более подробно эти режимы будут рассмотрены ниже в статье.
Из структурной схемы видно, что за прямое питание отвечает блок (POWER SUPPLY SENSE), а режим паразитного питания обеспечивает блок (PARASIT POWER CIRCUIT) в составе которого основную роль играет конденсатор Срр, как питающий буфер.
Далее следует модуль “64-BIT ROM AND 1-WIRE PORT”. В нём содержится информация об уникальном адресе каждого устройства. Как было сказано выше данный код записывается заводом-изготовителем во внутреннюю энергонезависимую память датчика (EEPROM) и никогда не повторяется. Здесь же размещена и подсистема взаимодействия с 1-Wire интерфейсом.
Блок “MEMORY CONTROL LOGIC” осуществляет взаимосвязь между командами интерфейса 1-Wire и внутренней памятью датчика SCRATCHPAD. Данная память, в свою очередь, взаимодействует с несколькими специализированными регистрами DS18B20, а именно:
- TEMPERATURE SENSOR. Позволяет считывать преобразованное значение температуры.
- ALARM HIGH TRIGGER и ALARM LOW TRIGGER. Эти регистры позволяют выставить верхний и нижний порог срабатывания сигналов тревоги при выходе температуры за указанные пределы.
- CONFIGURATON REGISTER. Данный регистр предназначен для настройки разрешающей способности температурного датчика. Регистр может быть сконфигурирован на выполнение преобразования температуры с точностью 9 бит, 10 бит, 11 бит или 12 бит, что соответствует точности измерения 0.5оС, 0.25оС, 0.125оС и 0.0625оС.
- 8-BIT CRC GENERATOR. Данный регистр предназначен для генерации контрольной суммы с целью повышения защиты данных.
Технические характеристики DS18B20
После поверхностного знакомства с температурным датчиком DS18B20 можно приступать к его поэтапному освоению. Для начала будет нелишним свести в единый список все технические характеристики данного прибора.
- Напряжение питания: 3V-5.5V;
- Протокол обмена данными: 1-Wire;
- Способ подключения: прямой / по одной линии с паразитным питанием;
- Разрешение преобразования температуры: 9 бит – 12 бит;
- Диапазон измерения температуры: от -55 до +125 оС;
- Период измерения температуры при максимальной точности 12 бит: 750 мС;
- Тип индексации на линии 1-Wire: уникальный 64-битный адрес;
- Есть возможность программирования диапазона тревожного сигнала.
Подключение к плате Arduino
Как упоминалось выше, температурный датчик DS18B20 может быть подключен к плате Arduino двумя способами (прямым и с паразитным питанием). Кроме того, на один вход Arduino можно повесить как один, так и целую группу датчиков. Для начала рассмотрим самый простой вариант. На рисунке ниже показана схема прямого подключения одиночного датчика к Arduino Nano.
Рисунок №3 – схема прямого подключения одиночного датчика
Здесь всё довольно просто. Запитываем DS18B20 от самой платы Arduino, подавая 5V на вывод Vdd датчика. Аналогичным образом соединяем между собой выводы GND. Средний вывод термодатчика подключим, например, к выводу D2 нашей Arduino Nano. Подключать вывод данных (DQ) можно практически на любой вход Arduino, предварительно прописав его номер в скетче. Единственный и самый важный момент, на который следует обратить внимание – это наличие резистора номиналом 4,7k между плюсом питания и линией данных термодатчика. Этот резистор служит для подтяжки линии данных к логической единице и его отсутствие вызовет сбой в работе алгоритма обмена информацией. Значение 4,7k не сильно критично и в некоторых пределах его можно изменять, главное не увлекаться.
С прямым подключением одного датчика всё понятно, теперь рассмотрим прямое подключение группы датчиков к одному выводу Arduino. На рисунке №4 показан пример подключения 5-ти датчиков DS18B20. Это количество может быть любым и ограничивается только рамками временем на опрос каждого из них (750мС).
Рисунок №4 – подключение группы датчиков DS18B20
Как видно из вышеприведенного рисунка, абсолютно все датчики на шине подключены параллельно и на всю группу идёт один подтягивающий резистор. Хоть изменения в схеме логичны и минимальны, но работа с несколькими термодатчиками немного сложнее в плане составления программы. В этом случае необходимо обращаться к каждому в отдельности, используя уникальные адреса. Вопрос программирования каждого из режимов будет рассмотрен позже.
Режим паразитного питания отличается от прямого тем, что датчики получают энергию непосредственно с линии данных, без использования прямых 5V. При этом выводы Vdd и GNG каждого термодатчика соединяются между собой. Более наглядно этот процесс отражён на рисунке №5.
Рисунок №5 – подключение одиночного датчика и группы датчиков в режиме паразитного питания от линии данных.
Как и в предыдущих схемах, здесь присутствует резистор 4,7k, который в данном случае играет двойную роль, а именно: подтяжка линии данных к логической «1» и питание самого датчика. Возможность такого включения обеспечивает встроенная в DS18B20 специальная схема и буферный конденсатор Срр (рисунок №2). Иногда это позволяет сэкономить 1 провод в общем шлейфе подключения группы термодатчиков, что в некоторых проектах играет существенную роль.
После рассмотрения схем включения, самое время перейти к программированию и здесь можно пойти тремя путями:
- Использовать готовые, проверенные библиотеки для работы с DS18B20;
- Общаться с датчиком напрямую через перечень установленных команд;
- Написать свою низкоуровневую библиотеку, включая функции передачи битов данных по тайм-слотам, приведённым в технической документации.
Третий вариант наиболее сложен и требует изучения большого объёма информации. В рамках этой статьи будут рассмотрены первых два варианта.
В нашем магазине вы можете купить модули Arduino, а так же различные датчики.
Работа с готовыми библиотеками
Итак, для работы с датчиками температуры DS18B20 в сети можно найти огромное множество библиотек, но как правило, применяют две самые популярные. Это библиотека OneWire.h и библиотека DallasTemperature.h. Причём вторая библиотека является более удобной надстройкой над первой и без неё использоваться не может. Другими словами, перед подключением библиотеки DallasTemperature.h, необходимо также подключить OneWire.h. Как устанавливать те или иные библиотеки в среде Arduino IDE можно узнать на официальном сайте сообщества.
Библиотека OneWire.h
Рассмотрим для начала работу с библиотекой OneWire.h. Ниже приведён перечень её функций с кратким описанием.
- OneWire temperatureSensor (uint8_t pinNumber)
Данная функция является конструктором класса OneWire и создаёт объект temperatureSensor, т.е. открывает канал связи с датчиком или групой датчиков на выводе pinNumber. В наших примерах (рисунки 3-5) это вывод “D2” Arduino Nano. Именно к нему мы подключали шину данных DQ DS18B20.
Пример:
OneWire temperatureSensor(D2); // Датчик или группа датчиков подключены к выводу D2
- uint8_t search(addrArray)
Функция ищет очередное устройство на шине 1-Wire и при его обнаружении заносит значение адреса в массив addrArray, возвращая при этом true. Так как уникальный адрес каждого датчика составляет 64-бит, то массив addrArray должен иметь размерность 8 байт. В случае неудачного поиска, функция возвращает false. Следует отметить, что при подключении нескольких термодатчиков к одной шине, каждый вызов функции search будет адресован к следующему датчику, затем следующему и т.д., пока не будут перебраны все устройства на шине. Особенность данной функции – запоминать уже обработанные адреса. Для сброса очереди необходимо вызвать функцию reset_search(), речь о которой пойдёт ниже.
Пример
byte addrArray[8]; // Массив для хранения 64-битного адреса // Если устройсто вообще отсутствует на шине или все устройства перебраны // выводим соответствующую информацию в монитор порта if(!temperatureSensor.search(addrArray)) Serial.println("No more addresses."); // Иначе, если очередное устройство откликнулось на запрос присутствия, // выводим в монитор порта его 64-битный адрес else { for(i = 0; i < 8; i++) Serial.print(addrArray[i], HEX); }
voidreset_search()
Как говорилось выше, данная функция сбрасывает очередь опроса устройств на шине 1-Wire в самое начало. Её всегда необходимо использовать в паре с функцией search, когда последняя возвращает значение false. Например для нашего случая с 5-ю датчиками на шине, вызвав функцию search 5 раз, мы можем получить 5 адресов. На шестой раз функция search вернёт нам false и будет делать это при каждом следующем опросе до момента сброса очереди. На это следует обращать внимание, чтобы избежать непонятных ситуаций.
Пример:
byte addrArray[8]; // Массив для хранения 64-битного адреса // Если устройсто вообще отсутствует на шине или все устройства перебраны // сбрасываем очередь опроса для повторного цикла поиска if(!temperatureSensor.search(addrArray)) temperatureSensor.reset_search();
- uint8_t reset()
Функция сброса шины 1-Wire инициализирует процесс обмена данными. Она вызывается каждый раз, когда мы хотим общаться с датчиком температуры. В качестве возвращаемых значений могут быть true или false. Значение true мы получим, если хотя-бы один датчик на шине ответит на сброс импульсом присутствия. В противном случае получим false;
Пример
if(!temperatureSensor.reset()) Serial.println("No sensors on bus"); else Serial.println("sensor is detected");
- void select(addrArray)
Функция позволяет выбрать конкретное устройство, с которым в данный момент мы хотим работать. Выбор происходит путём явного указания 64-битного адреса, занесённого в массив addrArray. Адрес можно задать явно, прописав в массиве или использовать ранее считанный функцией search. Следует отметить, что перед вызовом функции select следует вызвать функцию reset. При очередном сбросе, связь с выбранным датчиком разрывается до следующего вызова select.
Пример:byte addrArray[8]; // Массив для хранения 64-битного адреса // Если устройсто вообще отсутствует на шине или все устройства перебраны // выводим соответствующую информацию в монитор порта if(!temperatureSensor.search(addrArray)) Serial.println("No more addresses."); // Иначе, если очередное устройство откликнулось на запрос присутствия, // выбираем его для последующей работы else { temperatureSensor.reset(); // Не забываем подавать команду сброса temperatureSensor.select(addrArray); // Указываем массив со считанным адресом }
- voidskip()
Функция актуальна только при работе с одним датчиком на шине и просто пропускает выбор устройства. Другими словами можно не использовать поиск функцией search, и следовательно быстро получить доступ с своему единственному датчику.
Пример:
temperatureSensor.reset(); // Сбрасываем шину temperatureSensor.skip(); // Выбираем единственный датчик для дальнейшей работы с ним
- voidwrite(uint8_t byte, uint8_t powerType = 0)
Функция посылает байт данных byte выбранному устройству на шине. Аргумент powerType указывает тип питания датчиков (0 – датчики питаются напрямую от внешнего источника; 1 – используется подключение с паразитным питанием). Второй параметр можно не указывать, если используется внешнее питание, так как он по умолчанию равен 0.
Пример:
temperatureSensor.reset(); // Сбрасываем шину temperatureSensor.skip(); // Выбираем единственный датчик для последующей работы с ним // Посылаем команду на преобразование температуры, // используя подключение с паразитным питанием от шины данных temperatureSensor.write(0x44, 1);
- uint8_t read()
Данная функция считывает один байт данных, посланный ведомым устройством (датчиком) на шину 1-Wire.
Пример:
// Считываем 9 байт данных с шины 1-Wire и результат заносим в массив array byte array[9]; for(uint8_t i = 0; i < 9; i++) { array[i] = temperatureSensor.read(); }
- static uint8_t crc8(const uint8_t * addr, uint8_t len);
Функция предназначена для вычисления контрольной суммы. Предназначена для проверки правильного обмена данными с датчиком температуры. Здесь аргумент addr является указателем на масив данных, а len определяет число байтов.
Пример:
byte addrArray[8]; // Массив для хранения 64-битного адреса // Если устройсто вообще отсутствует на шине или все устройства перебраны // выводим соответствующую информацию в монитор порта if(!temperatureSensor.search(addrArray)) Serial.println("No more addresses."); // Иначе, если очередное устройство откликнулось на запрос присутствия, // проверяем контрольную сумму его адреса else { // Если контрольная сумма не совпадает, выводим сообщение об ошибке if(OneWire::crc8(addrArray, 7) != addrArray[7]) { Serial.println("CRC is not valid!"); } }
Мы рассмотрели каждую функцию библиотеки OneWire.h в отдельности и чтобы закрепить метериал, ниже я приведу скетч для считывания температуры с группы термодатчиков DS18B20, которые будут подключены к выводу D2 с использованием схемы паразитного питания. В скетче будут подробные комментарии по всем необходимым моментам.#include <OneWire.h> // Подключаем библиотеку для работы с термодатчиками DS18B20 OneWire ds(2); // Датчик или группа датчиков подключены к выводу D2 Arduino // ФУНКЦИЯ ПРЕДВАРИТЕЛЬНЫХ УСТАНОВОК void setup(void) { Serial.begin(9600); // Инициализация работы с Serial-портом } // ОСНОВНОЙ ЦИКЛ void loop(void) { byte i; // Вспомогательная переменная для циклов byte present = 0; // Переменная для определения готовности датчика к общению byte type_s; // Переменная для определения типа термодатчика на шине byte data[12]; // Массив для хранения принятой от датчика информации byte addr[8]; // Массив для хранения 64-битного адреса датчика float celsius, fahrenheit; // Переменные для вычисления температуры // Если устройства на шине не найдены или перебраны все устройтсва на шине // выводим соответствующую информацию в монитор порта, сбрасываем очередь // и осуществляем повторный поиск, выждав 250мС if (!ds.search(addr)) { Serial.println("No more addresses."); Serial.println(); ds.reset_search(); delay(250); return; } // Если очередное устройство на шине найдено, выводим его уникальный адрес // в монитор порта в 16-тиричном виде Serial.print("ROM ="); for( i = 0; i < 8; i++) { Serial.write(' '); Serial.print(addr[i], HEX); } // Проверяем контрольную сумму адреса найденного устройства // и если она не совпадает, выводим соответствующую инфорацию if (OneWire::crc8(addr, 7) != addr[7]) { Serial.println("CRC is not valid!"); return; } Serial.println(); // Проверяем нулевой байт адреса, в котором содержится информация // о конкретном типе температурного датчика. В зависимости от значения нулевого // байта, выводим в монитор порта серию чипа. Если нулевой байт содержит неизвестное // значение - выводим сообщение о неизвестном семействе термодатчика. switch (addr[0]) { case 0x10: Serial.println(" Chip = DS18S20"); type_s = 1; break; case 0x28: Serial.println(" Chip = DS18B20"); type_s = 0; break; case 0x22: Serial.println(" Chip = DS1822"); type_s = 0; break; default: Serial.println("Device is not a DS18x20 family device."); return; } ds.reset(); // Сбрасываем шину для инициализации обмена данными ds.select(addr); // Выбираем датчик с текущим адресом для работы с ним // Подаём команду на преобразование температуры (по документации 0х44) // Не забываем про второй параметр "1", так как мы передаём данные по // линии с паразитным питанием. ds.write(0x44, 1); // Датчик начинает преобразование, которое согласно документации занимает макс. 750мС // Для перестраховки организовуем паузу, длительностью в ё секунду delay(1000); // Повторно сбрасываем шину для считываения информации с датчика // сохраняем ответ функции reset() в переменную present для дальнейшой работы с ней present = ds.reset(); ds.select(addr);// Повторно выбираем датчик по его адресу, так как был импульс сброса // Команда 0хBE согласно технической документации, разрешает чтение внутренней памяти // термодатчика (Scratchpad), которая состоит из 9-ти байт. ds.write(0xBE); // Считываем и выводим в монитор порта 9 байт из внутренней памяти термодатчика Serial.print(" Data = "); Serial.print(present, HEX); Serial.print(" "); for (i = 0; i < 9; i++) { data[i] = ds.read(); Serial.print(data[i], HEX); Serial.print(" "); } // Проверяем и выводим в монитор порта контрольую сумму полученных данных Serial.print(" CRC="); Serial.print(OneWire::crc8(data, 8), HEX); Serial.println(); // Начинаем процесс преобразования полученых данных в фактическую температуру, // которая храниться в 0 и 1 байтах считанной памяти. Для этого объединяем эти два // байта в одно 16-ти битное число int16_t raw = (data[1] << 8) | data[0]; // Перед дальнейшим преобразованием, на потребуется определить семейство, к которому // относится данный датчиик (ранее мы сохраняли релультат в переменной type_s). // В зависимости от семейства, вычисление температуры будет проходить по-разному, // так как DS18B20 и DS1822 возвращают 12-ти битное значение, а DS18S20 - 9-ти битное if (type_s) { // Если датчик относится к семейтсву DS18S20 raw = raw << 3; // разрешение по умолчанию равно 9 бит if(data[7] == 0x10) { raw = (raw & 0xFFF0) + 12 - data[6]; } } else { // Определяем на какую точночть измерения сконфигурирован данный датчик byte cfg = (data[4] & 0x60); // При более низких разрешениях можно обнулять младшие биты, // так как они всё-рано не определены if (cfg == 0x00) raw = raw & ~7; // 9 бит (преобразование занимает 93.75 ms) else if (cfg == 0x20) raw = raw & ~3; // 10 бит (преобразование занимает 187.5 ms) else if (cfg == 0x40) raw = raw & ~1; // 11 бит (преобразование занимает 375 ms) // По умолчанию установлена точность 12 бит (преобразование занимает 750 ms) } // Вычисляем и выводим значения температуры в монитор порта celsius = (float)raw / 16.0; fahrenheit = celsius * 1.8 + 32.0; Serial.print(" Temperature = "); Serial.print(celsius); Serial.print(" Celsius, "); Serial.print(fahrenheit); Serial.println(" Fahrenheit"); }
Если всё сделано правильно, то в окне монитора порта мы должны будем увидеть примерно следующее (рисунок №6):
Рисунок №6 – результат работы с библиотекой OneWire.h
Библиотека DallasTemperature.h
Данная библиотека основана на предыдущей и немного упрощает процесс программирования за счёт более понятных для восприятия функций. После установки, вы получите доступ к 14 примерам хорошо документированного кода на все случаи жизни. В рамках этой статьи будет рассмотрен пример работы одним датчиком.
// Подключаем необходимые библиотеки #include <OneWire.h> #include <DallasTemperature.h> // Шину данных подключаем к выводу №2 Arduino #define ONE_WIRE_BUS 2 // Создаём экземпляр класса для нашей шины и ссылку на него OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); // ФУНКЦИЯ ПРЕДУСТАНОВОК void setup(void) { Serial.begin(9600); // Инициализация серийного порта sensors.begin(); // Инициализация шины } // ОСНОВНОЙ ЦИКЛ void loop(void) { Serial.print("Reading Temperature..."); // Подаём команду на чтение sensors.requestTemperatures(); Serial.println("Read"); Serial.print("Sensor Temperature 1: "); // Отображаем значение температуры Serial.print(sensors.getTempCByIndex(0)); }
Результат работы программы показан на рисунке №7 Рисунок №7 – результат чтения температуры при помощи библиотеки DallasTemperature.h
Где можно использовать
На первый взгляд может показаться, что код для работы с датчиками DS18B20 слишком сложен для понимания, но это только поначалу. С ростом вашего опыта будет расти и сложность проектов, в которых эти датчики используются. Высокая точность измерения и надёжность позволяют применять их в таких проектах, как инкубаторы, климат-контроллеры теплиц, аквариумов, различных бытовых помещений и многое другое, на что способна ваша фантазия.FAQ. Часто задаваемые вопросы
Ответ: Необходимость применения подтягивающего резистора обусловлена особенностями протокола 1-Wire. Номинал резистора можно менять в разумных пределах +/- 2 кОм.
Вопрос: Как применять подобные датчики для измерения температур в жидкой или агрессивной среде?
Ответ: Существуют в продаже варианты с герметичным корпусом из нержавеющей стали. Главное, чтобы температура среды не выходила за пределы измерения самого датчика.
Вопрос: Какой может быть максимальная длина шлейфа для шины данных.
Вопрос: Датчик постоянно или периодически выдаёт температуру 85оС
Ответ: Здесь может быть несколько вариантов. Температура 85оС установлена у DS18B20 по умолчанию. Следовательно любой сбой по питанию, некачественный кабель, значительное удаление от контроллера или выход из строя самого датчика возвратят указанное значение температуры.
Вопрос: Возможно ли подключение нескольких датчиков на разные выводы Arduino?
Ответ: Да, возможно. В этом случае для каждой шины данных создаётся свой экземпляр класса библиотеки OneWire или DallasTemperature.
Вопрос: Где найти коды команд для работы с DS18B20 и их значение?
0x44 – команда инициализирует измерение температуры.
0хВЕ – команда на считывание всей оперативной памяти, включая код CRC.
0х4Е – команда записи в оперативную память (байты №2, 3, 4, TH, TL и регистр конфигурации)
0х48 – команда копирования памяти TH, TL, и регистра конфигурации в EEPROM датчика
0хВ8 – команда перегружает TH, TL, и регистр конфигурации из EEPROM в оперативную память.
0хВ4 – команда для определения режима питания термодатчика