Термометр с помощью arduino и датчиков LM35 и DS18B20

Термометр на arduino

Я писал недавно статью, где подключал к 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 за что отвечает.

Ноги датчика LM35

Температурный датчик DS18B20. Подключение к arduino

Второй датчик, который будет использован – это цифровой DS18B20. Точность в этом случае не на много ниже — плюс-минус 0,5 градуса, а диапазон измерения температуры практически такой же: от -55 до +125 градусов Цельсия. Большим преимуществом является то, что датчик находится внутри влагозащитного корпуса, а так же имеется кабель длиной 1 метр, что позволяет вынести его на улицу, когда само устройство будет находиться в помещении. Еще из плюсов сюда можно добавить возможность подключение одновременно до 127 датчиков на один пин ардуино, только я даже предположить не могу, где это может пригодиться :).
Считывать данные с DS18B20 немного сложнее, чем с LM35, для удобства можно воспользоваться библиотекой OneWire. В комплекте с библиотекой идет уже готовый пример считывания данных с датчика. Информация о температуре передаются в байтах, которые необходимо сначала запросить, получить и перевести к человеческому виду. В коде примера это подробно прокомментировано, а так же в полном скетче проекта я добавил комментарии.
На картинке ниже показано, как подключать датчик DS18B20. Основным моментом является то, что необходимо использовать резистор сопротивлением 4.7 кОм для соединения провода, передающего данные и плюсовым.

Подключение датчика DS18B20

Проблема вывода температуры на индикаторе 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

Все сложные моменты, с которыми возникали сложности, в процессе создания термометра я описал, теперь остается только написать скетч, его код приведен ниже, а также доступен для скачивания тут: скачать.

#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)

Термометр с помощью arduino и датчиков LM35 и DS18B20: 3 комментария

  1. Здравствуйте. Так все же как смогли победить моргание семисегментника при опросах датчика 18B20? У меня похожая конструкция но индикатор работает через два сдвиговых регистра. В момент опроса датчика процесс динамической индикации останавливается. Delay не использую, все временные периоды опроса на таких же примерах как у вас (с millis). Такое ощущение что при обмене с датчиком сама библиотека wire содержит в себе delay и чего то ждет.
    Спасибо.

    1. Добрый день! Не удалось победить, там действительно в библиотеке есть задержка из-за которой не получается красиво отображать индикатор, пока идет опрос датчика.
      Я немного схитрил: у меня на индикаторе выводится два значения температуры(дома и на улице), отображения значения меняются раз в 10 сек, и при смене показаний делаю опрос датчиков, пряча в этот момент показания, выводится один сегмент — черточка

      1. Понятно. Спасибо. Я в своем варианте со сдвиговыми регистрами ушел от датчика DS18B20 и переписал код под 100 кОм термисторы. Сейчас делаю подогрев пола в собачей будке, делаю управление, буду использовать DS18B20 но семисегментный индикатор на 4 разряда сделал на TM1637, он сам поддерживает динамическую индикацию по этому таких проблем нет.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

code