Я писал недавно статью, где подключал к 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. Это что касается планов на будущее, а пока в завершение статьи приложу видео работы термометра:


(7 оценок, среднее: 3,86 из 5)
Здравствуйте. Так все же как смогли победить моргание семисегментника при опросах датчика 18B20? У меня похожая конструкция но индикатор работает через два сдвиговых регистра. В момент опроса датчика процесс динамической индикации останавливается. Delay не использую, все временные периоды опроса на таких же примерах как у вас (с millis). Такое ощущение что при обмене с датчиком сама библиотека wire содержит в себе delay и чего то ждет.
Спасибо.
Добрый день! Не удалось победить, там действительно в библиотеке есть задержка из-за которой не получается красиво отображать индикатор, пока идет опрос датчика.
Я немного схитрил: у меня на индикаторе выводится два значения температуры(дома и на улице), отображения значения меняются раз в 10 сек, и при смене показаний делаю опрос датчиков, пряча в этот момент показания, выводится один сегмент — черточка
Понятно. Спасибо. Я в своем варианте со сдвиговыми регистрами ушел от датчика DS18B20 и переписал код под 100 кОм термисторы. Сейчас делаю подогрев пола в собачей будке, делаю управление, буду использовать DS18B20 но семисегментный индикатор на 4 разряда сделал на TM1637, он сам поддерживает динамическую индикацию по этому таких проблем нет.