Работа с датчиком температуры DS18b20

Выдержка из даташита:

DS18B20 цифровой термометр с программируемым разрешением, от 9 до 12–bit, которое может сохраняться в EEPROM памяти прибора. DS18B20 обменивается данными по 1-Wire шине и при этом может быть как единственным устройством на линии так и работать в группе. Все процессы на шине управляются центральным микропроцессором.
Диапазон измерений от –55°C до +125°C и точностью 0.5°C в диапазоне от –10°C до +85°C. В дополнение, DS18B20 может питаться напряжением линии данных (“parasite power”), при отсутствии внешнего источника напряжения.
Каждый DS18B20 имеет уникальный 64-битный последовательный код, который позволяет, общаться с множеством датчиков DS18B20 установленных на одной шине. Такой принцип позволяет использовать один микропроцессор, чтобы контролировать множество датчиков DS18B20, распределенных по большому участку. Приложения, которые могут извлечь выгоду из этой особенности, включают системы контроля температуры в зданиях, и оборудовании или машинах, а так же контроль и управление температурными процессами.

Схема подключения датчика:

Основной задачей DS18B20 является определение температуры и преобразование полученного результата в цифровой вид. Мы можем самостоятельно задать необходимое разрешение, установив количество бит точности – 9, 10, 11 и 12. В этих случаях разрешение датчика будет соответственно равно 0,5С, 0,25С, 0,125С и 0,0625С.

Во время включения питания датчик находится в состоянии покоя. Для начала измерения контроллер Ардуино выполняет команду «преобразование температуры». Полученный результат сохранится в 2 байтах регистра температуры, после чего датчик вернется в первоначальное состояние покоя. Однако на преобразование температуры уходит достаточно много времени - при максимальном разрешении 750 мс. Это определяет некоторые особенности работы с датчиком.

Перейдем к практике.

Ждать получения данных после запроса 750 милисекунд - значит блокировать работу микроконтроллера на это время.

Здесь я приведу пример простой работы с одним датчиком в неблокирующем режиме. В этом случае при обращении к датчику мы считываем температуру, которая была пролучена на предыдущий запрос и тут же даем датчику команду на очередное преобразование. Его результат будет считан при следующем обращении. Таким образом работа микроконтроллера во время преобразования температуры датчиком блокироваться не будет, правда, ценой некоторого запаздывания изменений температуры.

Для работы с датчиком понадобится скачать и установить библиотеку OneWire - GitHub - PaulStoffregen/OneWire: Library for Dallas/Maxim 1-Wire Chips

#include <OneWire.h> // https://github.com/PaulStoffregen/OneWire

const byte DS18B20_PIN = 8; // пин датчика DS18b20
byte addr[8];               // адрес датчика температуры
float temperature = 0;      // переменная, в которую записываются показания датчика

const uint32_t temp_interval = 3000; // интервал между обращениями к датчику

OneWire ds(DS18B20_PIN);

// функция обращения к датчику - считывание показаний и команда на следующую конвертацию данных
void getTemperature()
{
  // считываем показания датчика после предыдущей конвертации
  int temp;
  ds.reset();
  ds.select(addr);
  ds.write(0xBE);                      // Считывание значения с датчика
  temp = (ds.read() | ds.read() << 8); // Принимаем два байта температуры
  temperature = (float)temp / 16.0;

  // даем команду на конвертацию для следующего запроса
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);
}

void setup()
{
  Serial.begin(9600);

  // инициализация датчика
  ds.reset();     // сброс шины
  ds.write(0x7F); // установить точность измерения: 0,5гр = 1F; 0,25гр = 3F; 0,125гр = 5F; 0,0625гр = 7F;
  ds.search(addr);
  // команда на конвертацию для первого запроса температуры
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);
}

void loop()
{
  static uint32_t timer = 0;

  // опрос датчика; здесь вызывается один раз в три секунды; при слишком частом опросе микросхема датчика может вносить искажения в температуру за счет собственного разогрева
  if (millis() - timer >= temp_interval)
  {
    timer = millis();
    getTemperature();
  }

  Serial.println(temperature);
}

Первые три секунды в монитор порта будет выводиться 0,00 градусов.

А как ты его получаешь/задаёшь? Что то не вижу…

ds.search(addr);

Или это и есть получение адреса?

Да, адрес получается автоматически. Если есть что получать, конечно :slightly_smiling_face:

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

Эти проблемы я решаю небольшим классом

ds1820.h
/* Небольшая библиотека для работы с датчиками температуры DS1820, DS18s20, DS18b20 или DS1822;
   Работает только с одним датчиком, выдает температуру в градусах Цельсия в формате float, имеет контроль наличия датчика на линии, в случае, если датчика нет, выдает -127 градусов.

   Для работы требует наличие библиотеки OneWire.h - https://github.com/PaulStoffregen/OneWire

   Методы библиотеки

   DS1820 temp_sensor(data_pin) - конструктор, data_pin - пин, к которому подключен датчик (наличие резистора 4.7кОм между пином данных и VCC обязательно)

   void readData() - опрос датчика; следует вызывать не чаще одного раза в секунду, а лучше реже, т.к. при слишком частом опросе микросхема датчика начинает вносить искажения в температуру за счет собственного разогрева; считанные данные помещаются в поле temp;

   float getTemp() - получение ранее считанной из дачика температуры;
*/
#pragma once
#include <Arduino.h>
#include <OneWire.h> // https://github.com/PaulStoffregen/OneWire

#define ERROR_TEMP -127.0

class DS1820 : public OneWire
{
private:
  float temp = ERROR_TEMP;
  byte addr[8];
  byte type_c = 10;

  bool checkData(byte *data)
  {
    // проверка данных на валидность - сначала по контрольной сумме;
    // в случае отсутствия датчика data[] заполняется нулями, поэтому
    // проверка CRC ничего не даст - CRC суммы нулей равно нулю, а
    // именно нуль будет записан в ячейке CRC массива data[] в этом
    // случае; поэтому проверяем ячейку, в которой указано разрешение
    // конвертации, оно нулю равняться не должно
    bool result = OneWire::crc8(data, 8) == data[8];
    if (result && data[8] == 0)
    {
      result = (!type_c && data[7] != 0x00) && (type_c && data[4] != 0x00);
    }

    return (result);
  }

public:
  DS1820(byte data_pin) : OneWire(data_pin)
  {
    OneWire::reset();
    // если датчик найден
    if (OneWire::search(addr))
    {
      // если адрес верифицирован
      if (OneWire::crc8(addr, 7) == addr[7])
      {
        // определяем тип чипа
        switch (addr[0])
        {
        case 0x10: // Chip = DS18S20 или старый DS1820
          type_c = 0;
          break;
        case 0x28: // Chip = DS18B20
        case 0x22: // Chip = DS1822
          type_c = 1;
          break;
        }
      }
      if (type_c < 2)
      {
        temp = 0;
        OneWire::reset();
        OneWire::select(addr);
        OneWire::write(0x44, 1);
      }
    }
  }

  /**
   * @brief считывание показаний датчика; вызывать не чаще одного раза в секунду
   *
   */
  void readData()
  {
    if (type_c < 2)
    {
      byte data[9];
      // считывание данных из датчика
      OneWire::reset();
      OneWire::select(addr);
      OneWire::write(0xBE);
      for (byte i = 0; i < 9; i++)
      {
        data[i] = OneWire::read();
      }

      if (!checkData(data))
      {
        temp = ERROR_TEMP;
      }
      else
      {
        int16_t raw = (data[1] << 8) | data[0]; // считываем два байта температуры
        if (!type_c)
        // для датчиков DS1820 и DS18s20
        {
          raw = raw << 3; // разрешение по умолчанию 9 бит
          if (data[7] == 0x10)
          {
            // «количество оставшихся» дает полное 12-битное разрешение
            raw = (raw & 0xFFF0) + 12 - data[6];
          }
        }
        else
        // для датчиков DS18b20 и DS1822
        {
          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
        }
        temp = raw / 16.0;
      }

      // даем команду на конвертацию для следующего запроса
      OneWire::reset();
      OneWire::select(addr);
      OneWire::write(0x44, 1);
    }
  }

  /**
   * @brief получение ранее считанной температуры
   *
   * @return float
   */
  float getTemp() 
  { 
    return (temp); 
  }
};

Этот код нужно скопироаать в файл ds1820.h и сохранить его рядом с файлом скетча.

Теперь скетч будет выглядеть так

#include "ds1820.h"

const byte DS18B20_PIN = 8; // пин датчика DS18b20

const uint32_t temp_interval = 3000; // интервал между обращениями к датчику

DS1820 ds(DS18B20_PIN);

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  static uint32_t timer = 0;

  // опрос датчика; здесь вызывается один раз в три секунды; при слишком частом опросе микросхема датчика может вносить искажения в температуру за счет собственного разогрева
  if (millis() - timer >= temp_interval)
  {
    timer = millis();
    ds.readData();
  }

  Serial.println(ds.getTemp());
}

При обрыве датчика будет выводиться очень отрицательная температура )) -127 градусов

А не целесообразнее ли строку 47 (пост №2) перенести внутрь getTemperature()? (для 7 поста это строка 19)

Это уже по вкусу. Вызов getTemperature() все равно останется в лупе, только будет вызываться много-много-много раз )). А в случае с классом придется еще поле интервала вводить с соответствующими геттерами/сеттерами

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

Дело не во времени реакции датчика, а

при слишком частом опросе микросхема датчика начинает вносить искажения в температуру за счет собственного разогрева

Да и нагляднее, когда интервал вручную выдерживается. А ежели пользователь сумеет разобраться, то уже сам решит, как ему делать )

1 лайк

@andriano , @v258 показал свой код, причём в плане обучения и «бери-пользуйся». Если есть возражения - предоставь свой код. Кто-то (возможно я, или ещё кто-то) посмотрит, скажет - «о, это идеально!» и добавит в главную тему.

1 лайк

Еще раз: я предпочитаю идеи коду, т.к. ценность представляют именно идеи, а код по готовой идее может написать любой (разумеется, остается вопрос с константным обеспечением, но мы считаем, что это - часть идеи).

Пользователь, который сам сумеет разобраться, не нуждается в чужом коде. Так для кого именно мы пишем?

  • для того, кто может разобраться сам (IMHO это ему не нужно),
  • для того, кто сам разобраться не может (IMHO для него надо обеспечивать, чтобы класс датчика сам регулировал частоту физического опроса).

@andriano , Вы не прониклись данным разделом. Указывать я Вам не могу, но было бы не плохо Вам не писать в этом разделе. Спасибо. (Не указываю, Боже упаси).

Если Вам плохо, можете в этот раздел не заглядывать (ни в коей мере не пытаюсь Вам указывать, что делать, просто пытаюсь подсказать, как сохранить свое душевное здоровье).

1 лайк

Собственно как и Вы.

А мне наличие ответов от других пользователей не доставляет душевных страданий (при условии, разумеется, что они не переходят на личности).

PS. Тем не менее надеюсь, что сообщения, не относящиеся к обсуждению работы с датчиками, из темы будут вскоре удалены.

Чавой-та? Я этот код не сам придумал, а из какого-то библиотечного примера адаптировал. Т.е. и код не помешал, и разобрался быстрее, чем по даташитам было бы.

Имхо, это вкусовщина. Таки опрос - это задача программиста, датчик сам себе ничего решать не должен :slightly_smiling_face:

Мальчишки, не ссорьтесь. Два мнения обычно лучше, чем одно