DS18B20: повторим пройденное.

Тема в разделе "Схемотехника, компоненты, модули", создана пользователем ИгорьК, 2 дек 2014.

  1. ИгорьК

    ИгорьК Давно здесь

    С датчиками DS18B20 имел дело неоднократно (раз, два). Но каждый раз проект увлекал больше, чем эта маленькая козявка. Поэтому хватал чужой код, не вникая в суть.
    Чем датчики хороши? Две вещи - точность 0.5 градуса и линия 1Wire, которая позволяет (теоретически) работать на удалении до 300 м. У меня работает метрах на десяти, по трем шлейфовым проводам, причем на улице, и этого вполне достаточно.
    Про датчик в интернете написано много, в том числе и кода для Ардуино. Что можно внести нового? Ничего, кроме комментариев к отдельным кускам, которые раньше были не ясны, а именно:
    Код (C):
    #include <Arduino.h>
    #include <OneWire.h>
    #include <stdint.h>

    OneWire  ds(2);                // Завесим датчики на pin 2 (и обязательно подтянем к плюсу 4.7K резистором)
    byte i;                        // Любимый счетчик
    byte data[8];                  // Сюда попадают данные из датчиков
    byte addr[8];                  // Здесь хранятся адреса датчиков
    float celsius;
                                    // Всякая хрень для работы с температурой
    int signBit, tc_100, whole, fract;


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

    void loop(void) {
                                    // Функция последовательно ищет адреса устройств
        if (!ds.search(addr)) {    // и запоминает их в addr. Если очередного устройства не найдено
        ds.reset_search();          // поиск сбрасывается и все начинается сначала
        delay(250);
        return;
        }
        ds.reset();                  // Сбрасываем линию
        ds.select(addr);              // Выбираем найденный адрес
        ds.write(0x44, 1);            // Пишем 0x44 - команда на расчет температуры и 1 - если паразитное питание, если обычное (соединение три провода) то 0.

        delay(1000);                  // Для преобразования при паразитном питании надо 750ms, берем с запасом. Если питание обычное - достаточно 100

        ds.reset();                  // Опять сброс и выбор адреса - так положено по инструкции.
        ds.select(addr);
        ds.write(0xBE);              // Команда датчику, чтобы он начал отдавать данные.

        Serial.print("  Data = ");
        Serial.print(" ");
        for ( i = 0; i < 9; i++) {    // Читаем 8 байт. А зачем нам все 8? Достаточно первых двух!
        data[i] = ds.read();
        Serial.print(data[i], HEX);
        Serial.print(" ");
        }

        int16_t raw = (data[1] << 8) | data[0]; // int кодировка сотоит из двух байт, причем каждая единичка значит 0.0625 градуса

        celsius = (float)raw / 16.0;            // Превращаем int во float и делим на 16, что равно умножению на 0.0625, потому что
        Serial.print("First Temperature = ");  // 1 делить на 16 равно 0.0625.
        Serial.print(celsius);

        signBit = raw & 0x8000;                  // Проверяем самый левый бит: 0x8000= 0b10000000 00000000
        if (signBit)                            // Если там единица - число отрицательное и его надо преобразовать
        {                                        // Стандартное преобразование отрицательного числа, которое в микроконтроллере в дополнительной кодировке
            raw = (raw ^ 0xffff) + 1;              // Путем исключающего ИЛИ плюс единица http://www.inf1.info/additionalcode
        }
        tc_100 = (6 * raw) + raw / 4;            // Это хитрая запись умножения на 6.25 - поиграйте с дробями на бумажке: (25raw)/4
                                                // Вообще то, нужно было умножать на 0.0625, но число наше - int, и тогда мы потеряем
                                                // все данные. Поэтому умножили еще на 100.
        whole = tc_100 / 100;                      // Делим на сто, и получаем целое число (помним, что int отбросит дробь)
        fract = tc_100 % 100;                    // Остаток от деления на 100 будет дробной частью.
        Serial.print("Second Temperature = ");  // И напечатаем все это для сравнения.
        if(signBit) {
            Serial.print("-");
        }
        Serial.print(whole);
        Serial.print(".");
        if(fract < 10) {
            Serial.print("0");
        }
        Serial.println(fract);
    }
     
    Надеюсь этот конспект поможет кому-нибудь лучше понять что там происходит.
     
    Последнее редактирование: 29 авг 2016
    alp69, Radius, 9xA59kK и 3 другим нравится это.
  2. Tomasina

    Tomasina Иномирянин

    кроме хороших комментариев, актуально следующее - не ждать 750 мс, пока датчики вернут показания, а идти по программе дальше, а данные забрать в следующем цикле.
    При работе с кнопками и дисплеем это замирание особенно ощутимо.
     
  3. ИгорьК

    ИгорьК Давно здесь

    750 мс большая пауза, ничто не мешает вместо нее работать через таймер mills(). Причем, суть измерения заключается в том, что по его результатам заполняются две ячейки памяти термометра и сохраняются там до тех пор, пока не будут заменены новым измерением. То есть время обращения после необходимой задержки не слишком важно.
    Это же просто пример.
    Другой вариант - не использовать паразитное питание. Согласно даташиту,
    Если устройство используется в режиме паразитного питания, то в пределах не позже 10 мкс (максимальный) после подачи команды устройство управления должно установить высокий уровень на шине на время продолжительности преобразование (tconv).
    Если DS18B20 питается от внешнего источника питания, главное устройство может считывать состояние шины после команды Конвертирования температуры [44h]. Если на шине логический «Ноль» - это значит, что DS18B20 выполняет температурное преобразование. Если на шине логическая «Единица» – это значит, что преобразование окончено и можно, считывать данные
    .
     
    Последнее редактирование: 2 дек 2014
  4. Dmr

    Dmr Нуб

    Получение удвоенной температуры датчика с минимальными ресурсами:
    static inline byte DoubleTemp(byte byte0, byte byte1)
    {
    // Удвоенная температура (точность датчика - 0.5 градуса, так что потери точности не будет)
    /* Как считаем:
    Два байта в обратной записи (сначала - второй, потом - первый);
    Цена единицы с датчика - 0,0625 градуса, что то же самое, что 1/16 (для удвоенного значения - 1/8);
    Т.е. для получения удвоенного значения температуры в градусах:
    - второй байт сдвигаем влево на 8 и прицепляем к нему первый байт;
    - итог делим на 8 (или сдвигаем вправо на три - т.е. второй байт можно сдвинуть влево не на 8, а на 8-3=5)
    */
    return((byte1 << 5) | (byte0 >> 3));
    }

    static inline byte getDoubleProbeTemp(byte data[])
    {
    // Упрощенная для кода процедура
    return DoubleTemp(data[0], data[1]);
    }
     
  5. AlexVS

    AlexVS Гик

    Скажите пожалуйста, после того как температура уже получена
    Код (C++):
     celsius = (float)raw / 16.0;            // Превращаем int во float и делим на 16, что равно умножению на 0.0625, потому что
    Зачем нужны остальные манипуляции?
    Код (C++):
        signBit = raw & 0x8000;                  // Проверяем самый левый бит: 0x8000= 0b10000000 00000000
        if (signBit)                            // Если там единица - число отрицательное и его надо преобразовать
        {                                        // Стандартное преобразование отрицательного числа, которое в микроконтроллере в дополнительной кодировке
            raw = (raw ^ 0xffff) + 1;              // Путем исключающего ИЛИ плюс единица http://www.inf1.info/additionalcode
        }
        tc_100 = (6 * raw) + raw / 4;            // Это хитрая запись умножения на 6.25 - поиграйте с дробями на бумажке: (25raw)/4
                                                // Вообще то, нужно было умножать на 0.0625, но число наше - int, и тогда мы потеряем
                                                // все данные. Поэтому умножили еще на 100.
        whole = tc_100 / 100;                      // Делим на сто, и получаем целое число (помним, что int отбросит дробь)
        fract = tc_100 % 100;
    Ведь переменная celsius уже и так содержит значение в градусах до сотых с учетом знака.
    Или я что то упустил?
     
  6. ИгорьК

    ИгорьК Давно здесь

    Это верно для положительной температуры. Если температура отрицательная начинаются дальнейшие злодейства.
     
  7. AlexVS

    AlexVS Гик

    Странно, вчера специально запихал далласа в морозилку и результат для
    Код (C++):
    Temperature =  (data[0] | (data[1] << 8)) / 16.0;
    вполне адекватный и естественно отрицательный, потом залил код из стандартных примеров из DallasTemperature, цифры получились такие же.
    Более того: при отрицательной температуре результат
    Код (C++):
     int16_t raw = (data[1] << 8) | data[0];
    также отрицательный, в BIN он выглядит как 11111111111111111111111010010101
    а после
    Код (C++):
    raw = (raw ^ 0xffff) + 1;
    он просто становится положительным и в BIN выглядит как 101101011.
    Приходится умножать на -1(минус 1), что бы значение стало отрицательным. Естественно что signBit при этом =1.
    Вот как то так.
     
    Последнее редактирование: 16 сен 2016
  8. #include <OneWire.h>
    OneWire ds(4);

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

    void loop() {

    byte data[2];
    ds.reset();
    ds.write(0xCC);
    ds.write(0x44);
    delay(750);
    ds.reset();
    ds.write(0xCC);
    ds.write(0xBE);
    data[0] = ds.read();
    data[1] = ds.read();
    int Temp = (data[1]<< 8)+data[0];
    Temp = Temp>>4;
    Serial.println(Temp);
    }

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

    Dmr Нуб

    Дело в том, что отрицательные числа во всех современных процессорах записываются не так, как мы привыкли в десятичной системе. Сложное описание того, как выдает датчик отрицательную температуру - это описание той самой записи, которую используют для отрицательных чисел все процессоры. Так что никаких преобразований и проверок не требуется - для процессора полученные два байта и есть отрицательное число (посмотрите "Дополнительный код" в википедии).
    Добавлю еще, что эти байты должны идти в обратном порядке - 1-й, потом 0-й - это тоже особенность всех интеловских микропроцессоров еще с 70-х годов прошлого века. Поэтому - (data[1]<< 8)+data[0].
     
  10. Delit

    Delit Нерд

    Дополнительные преобразования это всего лишь пример как получить значения в виде отдельных цифр например для вывода на ряд семисегментных светодиодных индикаторов
     
  11. Danil25

    Danil25 Нуб

    Знатаки!) Подскажите! Я хочу подключить датчиками температуры Ds18b20 к Arduino! http://proumnyjdom.ru/kontrollery/ds18b20-podklyuchenie-k-arduino.html Если еще добавить дисплей это будет здорово! Только как это все лучше срастить? И не пойму почему у меня сейчас сам датчик температуру не точно показывает( погрешность в 2 градуса это норм?
     
  12. ImrDuke

    ImrDuke Гик

    После месяца проведенного в аквариуме датчик стал показывать только значение 85.00С
    Переключал на другой порт ардуины - результат тот же. Датчик получается сдох?

    Вопрос снимается! Не особо то он оказался герметичным. Попала вода и отгнили ножки у датчика.
     
  13. ИгорьК

    ИгорьК Давно здесь

    Да. 85 - это число выдается когда датчик не может совершить измерение. Если раньше все работало - это однозначно поломка.
    Причем связь с датчиком есть а вычислений - нет.
     
  14. ИгорьК

    ИгорьК Давно здесь

    Хотя, проверьте подтяжку - мало ли что.
     
  15. ImrDuke

    ImrDuke Гик

    Поломка. Попала вода, сгнила ножка.
     
  16. ИгорьК

    ИгорьК Давно здесь

    Быстро однако. У меня на улице уже года три висит самодельный - пайка и термоусадка и все.
     
  17. =ENOT=

    =ENOT= Нуб

    Для вывода отрицательной температуры изменил строку tt = (tt ^ 0xffff) +2 . Вывод на экран организован следующим способом
    LCD_DAT(whole/10+0x30);
    LCD_DAT(whole%10+0x30);
    LCD_DAT('.');
    LCD_DAT(fract/10+0x30);
    В железе не пробовал, а в протеусе с точностью до 0.1 отображает адекватно
     
  18. Sharkman89

    Sharkman89 Нуб

    Уважаемые форумчане прошу вашей помощи, использую распространенный скетч

    на дисплее отображается температура с сотыми частями, необходимо выводить только целое число.

    заранее благодарен!!!


    Код (C++):
    Уважаемые прошу вашей помощи, использую распространенный скетч

    на дисплее отображается температура с сотыми частями, необходимо выводить только целое число.

    заранее благодарен!!!
    /*
    Тестировалось на Arduino IDE 1.6.12
    Дата тестирования 23.12.2016г.
    */

    #include <Wire.h>  // Подключаем библиотеку Wire
    #include <LiquidCrystal_I2C.h>  // Подключаем библиотеку LiquidCrystal_I2C
    #include <DallasTemperature.h>  // Подключаем библиотеку DallasTempature
    #define DS18B20 2  // Указываем, к какому выводу подключена DQ
    byte simvol[8]  = {B11100,B10100,B11100,B00000,B00000,B00000,B00000,B00000,}; // Символ градуса

    LiquidCrystal_I2C lcd(0x3f,16,2);  // Задаем адрес и размер дисплея
    OneWire oneWire(DS18B20);  
    DallasTemperature sensors(&oneWire);


    void setup()
    {

      sensors.begin();  // Запуск библиотеки, по умолчанию 9 бит
      lcd.init();  // Инициализация lcd  
      lcd.backlight();  // Включаем подсветку
      lcd.setCursor(2,0);  // Устанавливаем курсор на 1 строку, ячейка 2
      lcd.print("TEMP");  // Выводим текст
      lcd.setCursor(2,1);  // Устанавливаем курсор на 2 строку, ячейка 2
      lcd.print("www.rchip.ru");  // Выводим текст
    }
    void loop()
    {
      lcd.createChar(1, simvol);  // Создаем символ под номером 1
      sensors.requestTemperatures();  // Считываем показания температуры
      lcd.setCursor(7,0);  // Устанавливаем курсор на 1 строку, ячейка 7
      lcd.print(sensors.getTempCByIndex(0));  // Выводим показания температуры
      lcd.setCursor(14,0);  // Устанавливаем курсор на 1 строку, ячейка 12
      lcd.print("\1");  // Выводим символ градуса
      lcd.setCursor(15,0);  // Устанавливаем курсор на 1 строку, ячейка 13
      lcd.print("C");  // Выводим текст
    }
     
  19. Tomasina

    Tomasina Иномирянин

    Код (C++):
    lcd.print(int(sensors.getTempCByIndex(0) - 0.5));
     
    Sharkman89 нравится это.
  20. tungsten3200

    tungsten3200 Нуб

    Приветствую!
    Кто нибудь знает, насколько они безопасны, для взаимодействия с едой (например, с горячим чаем)?