Я писал недавно статью, где подключал к arduino 4-разрядный 7-сегментный индикатор hs420561k-32, тогда упоминал, что хочу сделать градусник с выводом температуры на этот индикатор, только проблема была в отсутствии датчиков. И вот наконец-то приехала посылка с недостающими запчастями и можно продолжить проект. Температурных датчиков у меня три штуки – dht11, LM35 и DS18B20. Но использовать буду только LM35 и DS18B20, поскольку dht11 очень неточный, в даташите написано, что отклонения от реальной температуры составляют плюс-минус два градуса, а так же он работает только с положительной температурой.
Температурный датчик LM35. Подключение к arduino
Первый датчик, который будем использовать – это LM35, использовать его очень просто, тут даже дополнительные библиотеки не требуются, достаточно подключить к нему питание и считать данные на выходе с помощью аналогового пина arduino. LM35 работает с температурами от -55 до 150 градусов Цельсия и если верить даташиту, то погрешность составляет всего плюс-минус 0,25 градуса. Хоть датчик и обрабатывает до +150 градусов, но считать ардуиной сможем только до +110, хотя и это более чем достаточно для домашнего градусника. Поскольку этот датчик имеет высокую точность, но находится в корпусе TO92, без какой-либо дополнительной защиты, использовать его будем для измерения температуры в помещении.
Официальный мануал arduino рекомендует использовать для снятия показаний этого датчика опорное напряжение 1,1 В. Чтобы настроить arduino подобным образом достаточно использовать команду analogReference(INTERNAL) в функции setup. Далее достаточно просто, с нужным интервалом, считывать напряжение с выходной ножки(OUT) датчика. LM35 формирует напряжение 10 милливольт на один градус, таким образом имея опорное напряжение в 1,1 В легко обработать, довольно точно, данные.
void setup() { // меняем опорное напряжение на 1.1 В, относительно которого происходят аналоговые измерения analogReference(INTERNAL); } void loop() { reading = analogRead(A0); // LM35 подключен к пину A0 temperature = (1.1 * reading * 100.0) / 1024; // получаем значение в градусах Цельсия }
Ниже на картинке показано, какая нога датчика LM35 за что отвечает.
Температурный датчик DS18B20. Подключение к arduino
Второй датчик, который будет использован – это цифровой DS18B20. Точность в этом случае не на много ниже — плюс-минус 0,5 градуса, а диапазон измерения температуры практически такой же: от -55 до +125 градусов Цельсия. Большим преимуществом является то, что датчик находится внутри влагозащитного корпуса, а так же имеется кабель длиной 1 метр, что позволяет вынести его на улицу, когда само устройство будет находиться в помещении. Еще из плюсов сюда можно добавить возможность подключение одновременно до 127 датчиков на один пин ардуино, только я даже предположить не могу, где это может пригодиться :).
Считывать данные с DS18B20 немного сложнее, чем с LM35, для удобства можно воспользоваться библиотекой OneWire. В комплекте с библиотекой идет уже готовый пример считывания данных с датчика. Информация о температуре передаются в байтах, которые необходимо сначала запросить, получить и перевести к человеческому виду. В коде примера это подробно прокомментировано, а так же в полном скетче проекта я добавил комментарии.
На картинке ниже показано, как подключать датчик DS18B20. Основным моментом является то, что необходимо использовать резистор сопротивлением 4.7 кОм для соединения провода, передающего данные и плюсовым.
Проблема вывода температуры на индикаторе hs420561k-32
Пока я разбирался с каждым датчиком по отдельности, а также когда подключал к arduino 4-х разрядный 7-сегментнтый индикатор, проблем ни каких не было, все прекрасно работало. Но стоило мне собрать все в кучу, на одну макетную плату и собрать код воедино, как сразу появилась серьезная проблема. Я писал в прошлой статье, что для одновременного вывода данных сразу на 4-х разрядах hs420561k-32 необходимо очень быстро по очереди выводить по одному разряду, тогда создается впечатление одновременного вывода четырех цифр, глаз не успевает уловить смену разрядов. В связи с таким способом вывода возникла сложность с одновременной работой датчика DS18B20, для его опроса требуется чуть больше секунды времени – в двух участках кода используется delay, который заставляет микроконтроллер ждать, 250 и 1000 миллисекунд. Сначала я сделал запрос к датчику раз в 30 секунд, но это не решило проблему – два раза в минуту датчик по секунде показывал непонятно что. Поэтому пришлось отказаться от delay и добавить другую аналогичную конструкцию в код, которая будет выполнять определенные куски кода с задержкой, а остальной код будет выполняться без задержек. Многопоточности в arduino, как оказалось, нет, но есть псевдомногопоточность, добиться ее можно используя не хитрую конструкцию с таймером, который отмеряет время в миллисекундах от старта работы микроконтроллера. Пример такого кода я приведу ниже:
bool flag = false; // флаг unsigned long previousMillis = 0; // время последнего срабатывания const long interval = 1000; // интервал срабатывания кода, задержка. void setup() { // } void loop() { // получаем время в миллисекундах, которое прошло // с момента начала работы МК unsigned long currentMillis = millis(); // проверяем сколько прошло врмени if (currentMillis - previousMillis >= interval) { // если прошло нужное количество миллисекунд, // то записываем в переменную количество прошедшего времени previousMillis = currentMillis; // меняем положение флага, // это может быть вкл и выкл светодиода, например flag = !flag; } }
Что использовалось в проекте:
- Arduino (я использовал arduino nano, но можно любую другую). Покупал тут: arduino nano
- Температурный датчик LM35. Покупал тут: Температурный датчик LM35 5 шт
- Температурный датчик DS18B20. Покупал тут: Температурный датчик DS18B20
- Дисплей hs420561k-32. Покупал тут: аналог дисплея hs420561k-32
- 1 резистор сопротивлением 4.7 кОм. Покупал тут:набор резисторов 700 шт. От 10 Ом до 1 МОм
- 8 резисторов сопротивлением 300 Ом. Покупал тут:набор резисторов 700 шт. От 10 Ом до 1 МОм
- 1 Сдвиговый регистр 74HC595. Покупал тут: сдвиговые регистры 10 штук
- Макетная плата. Я использовал с 830 отверстиями. Покупал тут: макетная плата
- Несколько соединительных проводов. Покупал тут: соединительные провода
Скетч уличного и комнатного термометра на arduino
Все сложные моменты, с которыми возникали сложности, в процессе создания термометра я описал, теперь остается только написать скетч, его код приведен ниже, а также доступен для скачивания тут: скачать.
#include <OneWire.h> // библиотека для работы с датчиком DS18B20 OneWire ds(10); // подключаем уличный датчик к 10 пину //Пин подключен к SH_CP входу 74HC595 int clockPin = 6; //Пин подключен к ST_CP входу 74HC595 int latchPin = 7; //Пин подключен к DS входу 74HC595 int dataPin = 8; int tempHomePin = A0; // градусник в помещении // Пины разрядов цифер int pins_numbers[4] = {2, 3, 4, 5}; // Биты для отображения цифер от 0-9, минуса и символ градуса Цельсия byte numbers_array[22] = { B00111111, B00000110, B01011011, B01001111, // 0 1 2 3 B01100110, B01101101, B01111101, B00000111, // 4 5 6 7 B01111111, B01101111, B01000000, B01100011, // 8 9 - о // цифры с точкой внизу (+12 к индексу элемента) B10111111, B10000110, B11011011, B11001111, // 0 1 2 3 B11100110, B11101101, B11111101, B10000111, // 4 5 6 7 B11111111, B11101111 // 8 9 }; int cel_celsius = 0; // переменная для хранения градусов на улице float tempHome = 0; // переменная для хранения градусов в помещении const long tempInterval = 3000; // интервал запроса актуальной температуры unsigned long previousMillis = 0; // время предыдущего запроса unsigned long previousMillis_delay = 0; // хранения последней даты срабатывания, для второй задержки bool startQuery = false; // флаг, для обозначения начала запроса температуры bool firstQuery = true; // флаг первого запуска, при котором получаем температуру без задержек bool showhome = true; // флаг, который указывают какую температуру показывать - комнату или улицу int sec_show = 5000; // интервал смены отображения погоды unsigned long showhomeMillis_delay = 0; // хранения последней переключения градусников // функция для вывода чисел на индикаторе void showNumber(int numNumber, int number){ // зажигаем нужные сегменты digitalWrite(latchPin, LOW); shiftOut(dataPin, clockPin, MSBFIRST, numbers_array[number]); digitalWrite(latchPin, HIGH); // включаем нужный разряд(одну из четырех цифр) int num_razryad = pins_numbers[numNumber-1]; for(int i; i<4; i++){ // выключаем все digitalWrite(pins_numbers[i], HIGH); } // включаем нужную digitalWrite(num_razryad, LOW); delay(5); } void setup() { //устанавливаем режим OUTPUT pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); for(int i; i<4; i++){ pinMode(pins_numbers[i], OUTPUT); digitalWrite(pins_numbers[i], HIGH); } Serial.begin(9600); // меняем опорное напряжение на 1.1 В, относительно которого происходят аналоговые измерения analogReference(INTERNAL); } void loop() { // зажигаем дисплей только после получения температуры if(!firstQuery){ if(showhome){ // градусник в помещении char digHome[5]; // дробное число переводим в десятичное и разбиваем на символы // умножаем на 100, чтоб откинуть дробную часть sprintf(digHome,"%d", (int)(tempHome * 100)); // тут отрицательных температур не будет // и меньше 10 градусов тоже не будет // поэтому не буду заморачиваться с лишними условиями showNumber(4, digHome[0] - '0'); showNumber(3, (digHome[1] - '0') + 12); // число с точкой внизу showNumber(2, digHome[2] - '0'); }else{ // градусник на улице // разбираем число градусов по одному символу char digStreet[2]; sprintf(digStreet,"%d",abs(cel_celsius)); // включить сразу несколько цифр нельзя, поэтому очень быстро показываем по одной if(abs(cel_celsius) > 9){ showNumber(3, digStreet[0] - '0'); showNumber(2, digStreet[1] - '0'); // если ниже нуля, то вывод знака минуса if(cel_celsius < 0){ showNumber(4, 10); } }else{ showNumber(2, digStreet[0] - '0'); // если ниже нуля, то вывод знака минуса if(cel_celsius < 0){ showNumber(3, 10); } } } showNumber(1, 11); // значок градуса } // переменные для работы с температурным датчиком byte i; byte present = 0; byte type_s; byte data[12]; byte addr[8]; float celsius; // проверяем, пришло ли время получить актуальную температуру unsigned long currentMillis = millis(); if ( (currentMillis - previousMillis >= tempInterval) || startQuery || firstQuery) { previousMillis = currentMillis; // читаем данные от датчика на улицы if ( !ds.search(addr)) { ds.reset_search(); //delay(250); return; } // если ни чего не получили или получили не понятные данные if (OneWire::crc8(addr, 7) != addr[7]) { return; } // читаем первый байт и определяем тип датчика switch (addr[0]) { case 0x10: // DS18S20 type_s = 1; break; case 0x28: // DS18B20 type_s = 0; break; case 0x22: // DS1822 type_s = 0; break; default: return; } // делаем запрос на получение данных от датчика ds.reset(); ds.select(addr); ds.write(0x44); // ждем startQuery = true; // delay(1000); if ( currentMillis - previousMillis_delay >= 1000 ) { previousMillis_delay = currentMillis; }else{ return; } startQuery = false; // и получаем ответ present = ds.reset(); ds.select(addr); ds.write(0xBE); // берем только первые 9 байт for ( i = 0; i < 9; i++) { data[i] = ds.read(); } // данные приходят в битах, переводим их в десятичное число int16_t raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; if (data[7] == 0x10) { raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); if (cfg == 0x00) raw = raw & ~7; else if (cfg == 0x20) raw = raw & ~3; else if (cfg == 0x40) raw = raw & ~1; } celsius = (float)raw / 16.0; // для удобства округляем до целых cel_celsius = round(celsius); Serial.print("DS18B20: "); Serial.println(celsius); // читаем комнатный датчик int reading = analogRead(tempHomePin); // переводим в градусы цельсия tempHome = reading / 9.31; Serial.print("LM35: "); Serial.println(tempHome); firstQuery = false; } // смена отображения градусников: в помещении / на улице if ( currentMillis - showhomeMillis_delay >= sec_show ) { showhomeMillis_delay = currentMillis; showhome = !showhome; } }
Послесловие
Термометр работает, показывает температуры дома и на улице, только вся конструкция собрана на макетной плате, и использовать прибор в таком виде довольно неудобно. Поэтому в планах на будущее будет разработка удобного в эксплуатации устройства – напечатаю на 3D принтере корпус, а все провода нужно будет перенести на печатную плату, так же хочется убрать arduino и использовать atmega8. Это что касается планов на будущее, а пока в завершение статьи приложу видео работы термометра:
Здравствуйте. Так все же как смогли победить моргание семисегментника при опросах датчика 18B20? У меня похожая конструкция но индикатор работает через два сдвиговых регистра. В момент опроса датчика процесс динамической индикации останавливается. Delay не использую, все временные периоды опроса на таких же примерах как у вас (с millis). Такое ощущение что при обмене с датчиком сама библиотека wire содержит в себе delay и чего то ждет.
Спасибо.
Добрый день! Не удалось победить, там действительно в библиотеке есть задержка из-за которой не получается красиво отображать индикатор, пока идет опрос датчика.
Я немного схитрил: у меня на индикаторе выводится два значения температуры(дома и на улице), отображения значения меняются раз в 10 сек, и при смене показаний делаю опрос датчиков, пряча в этот момент показания, выводится один сегмент — черточка
Понятно. Спасибо. Я в своем варианте со сдвиговыми регистрами ушел от датчика DS18B20 и переписал код под 100 кОм термисторы. Сейчас делаю подогрев пола в собачей будке, делаю управление, буду использовать DS18B20 но семисегментный индикатор на 4 разряда сделал на TM1637, он сам поддерживает динамическую индикацию по этому таких проблем нет.