Блок с датойБлок с временемБлок с возрастом сайта
Mr.ALB

    Анатолий Беляев (aka Mr.ALB). Персональный сайт

    Да пребудут с вами Силы СВЕТА!

     

    Ардуино (Arduino). #34

    Столкнулся с такой проблемой: как подключить сразу несколько датчиков температуры DS18B20 на одну линию, но так, чтобы можно было управлять каждым из них, то есть считывать и записывать каждый индивидуально. Порыскав в Интернете и посмотрев много разных примеров и библиотек обнаружил, что эта тема как-то не очень и раскрыта. Многие начинающие программисты Ардуино испытывают трудности. Далее эти трудности преодалеваю.



    Датчик DS18B20
    Несколько штук на одну линию


    Описание

    Начнём с того, что познакомимся с самим датчиком.

    Датчик температуры DS18B20 разработан фирмой DALLAS SEMICONDUCTOR.

    DS18S20 – старый тип, DS18B20 – современный, часто применяемый в конструкциях самодельщиков c точностью ±0.5°С, DS1821 и DS1822 – новые типы с уменьшеной точность: ±1.0°С и ±2.0°С.

    Каждый датчик имеет уникальный 64-битный последовательный код, который позволяет, общаться со множеством датчиков DS18B20 установленных на одной шине в качестве 1-проводного устройства, с использованием протокола, подробно описанного в руководствах по 1-проводной системе. Такой принцип позволяет использовать один вывод микроконтроллера, чтобы контролировать множество датчиков DS18B20, распределенных по большому участку.

    Наименее значимые 8 бит памяти ROM содержат код семейства 1-Wire. Для DS18S20: 10h, для DS18B20: 28h, для DS18B22: 22h. Следующие 48 бит содержат уникальный серийный номер. Наиболее значимые 8 бит данных содержат байт контрольного кода проверки циклической избыточности (CRC), который вычисляется из первых 56 бит кода данных.

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

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

    Чаще всего используются датчики в корпусе TO-92. Для подключения датчик имеет три вывода. Первый соединяется с GND, второй является информационным выходом DQ, а третий – выводом для подключения питания Vcc.

    DS18B20. Расположение выводов
    Pic 1. DS18B20. Расположение выводов

    Для точных измерений рекомендуется использовать схему подключения с внешним источником питания, так как при использовании паразитного питания диапазон измерения температуры от -10°С до +100°С, поэтому нужно учитывать при проектировании прибора способ питания датчика. На мой взгляд, лучше использовать внешнее питание.

    DS18B20. Схема подключения
    Pic 2. DS18B20. Схема подключения

    Таблица распределения данных

    Нас в основном тут интересует температурный регистр, который хранит значение температуры по окончании температурного преобразования. Он состоит из двух байт: байт 0 - LSB (младший разряд значения температуры), байт 1 - MSB (старший разряд значения температуры).

    Ещё интересен байт 8 - CRC с контрольным кодом достоверности измеренных данных.

    Возможно кому-то понадобится ещё два однобайтовых регистра контроля температуры, где байт 2 - TH – это контрольный уровень вверху (может устанавливаться пользователем), байт 3 - TL – контрольный уровень температуры снизу (может устанавливаться пользователем).

    И ещё внимания требует байт 4 – регистр конфигурации, в который записывается точность измерения датчика.

    TH, TL и регистр конфигурации энергонезависимы (EEPROM), таким образом они сохраняют данные на время отключения датчика от питания.

    DS18B20. Таблица распределения данных
    Pic 3. DS18B20. Таблица распределения данных

    Регистр конфигурации

    Байт 4 памяти содержит регистр конфигурации, который организован, как иллюстрировано ниже.

    Регистр конфигурации позволяет устанавливать разрешающую способность цифрового преобразователя температуры 9, 10, 11, или 12 бит, что влияет на время преобразования и точность измерения температуры.

    Пользователь может настроить разрешающую способность DS18B20, используя биты R0 и R1 в этом регистре, как показано в Таблице 3. Значение по умолчанию этих битов: R0 = 1 и R1 = 1 (12-битовая разрешающая способность). Обратите внимание, что есть прямая зависимость между разрешающей способностью и временем преобразования. Бит 7 и биты от 0 до 4 в регистре конфигурации зарезервированы для внутреннего использования устройством и не могут быть изменены или использованы.

    DS18B20. Регистр конфигурации
    Pic 4. DS18B20. Регистр конфигурации

    Скетч

    В Интернете есть разные библиотеки для работы с датчиком DS18B20. Наиболее популярная это DallasTemperature. Однако она довольно большая, особенно для проектов, в которых помимо датчика температуры есть много других устройств. Ещё есть библиотека от Gyver-а microDS18B20. Она имеет компактные размеры, но и уменьшенный функционал при работе с однопроводной линией.

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

    В данном случае отказался от этих библиотек...

    И пришёл к тому, что нужно написать свои функции для работы с датчиком DS18B20 именно для работы с несколькими датчиками, подключенными к одному выводу Ардуино.

    В итоге написано несколько функций по управлению однопроводной линией, к которой подключаю несколько датчиков. У меня есть 5 датчиков, на них и опробован скетч.

    Скетч написан как пример использования этих функций. Хорошо закомментирован и с пониманием его работы трудностей не должно быть. Вывод информации идёт в последовательный порт (Serial).

    Что в скетче интересного?

    В функции setup() идёт поиск всех датчиков, которые подключены к линии. В первых версиях скетча не делал ограничение на максимальное количество датчиков. Программа искала неограниченное количество подключенных датчиков. Однако потом мне понадобилась сортировка найденных адресов и решил добавить ограничение на максимальное количество, что и определяет размеры массивов адресов.

    У найденного датчика проверяется нулевой байт ROM, который указывает на тип найденного устройства. Как ранее уже указывал для датчиков DS18B20 это значение 0х28.

    Для проверки программирования точности измерения, у первого найденного датчика устанавливается разрешение в 12 бит. Этим примером легко перепрограммировать датчики, у которых ранее было запрограммировано не максимальное разрешение. У второго найденного датчика устанавливается самое низкое разрешение в 9 бит, которое, как пример, при измерении будет перепрограммировано в разрешение 11 бит.


    DS18B20. Работа программы
    Pic 5. DS18B20. Работа программы

    Поиск датчиков идёт в некотором роде случайно, какой датчик быстрее ответит на запрос, тот и будет первым и так далее, но бывает нужно знать конкретно датчик, с которого идёт показания температуры. Для этих целей создана функция сортировки адресов датчиков. Для неё нужны значения двух последних байтов в адресе подключенного датчика. Эти адреса можно узнать при первом подключении к линии. При поиске датчиков выводятся все их адреса. Значения последних двух байт указываем в соответствующем двухмерном массиве byte indic[MAX_DS][2] PROGMEM. Порядок значений этого массива будет указывать на порядок датчиков при измерении.

    Необходимо заметить, что количество строк в массиве byte indic[MAX_DS][2] должно совпадать с максимальным количеством датчиков MAX_DS, при этом количество подключенных к линии датчиков может быть от 1 до MAX_DS. Программа автоматически находит все подключенные датчики и сортирует их как задано.

    /********************************************************* 
       Программирование датчика DS18B20 
       Пример использования нескольких датчиков на одном выводе 
       2024-03-01 Mr.ALB v2.0 Второй заход, первый неудачно 
       2024-03-02 Mr.ALB v2.1 Работает 
       2024-03-02 Mr.ALB v2.2 Оптимизация 
       2024-03-02 Mr.ALB v3.0 Создание функций 
       2024-03-02 Mr.ALB v4.0 Оптимизация. Условная компиляция 
      
       Датчик DS18B20 или подобный 
         1 - GND - GND 
         2 - DQ  - DS_PIN 
         3 - VCC - 5V 
       От выхода DQ резистор 4.7kOhm на VCC. 
       Один на все датчики. 
    
     *********************************************************/
    
    // Настройки функций работы с DS18B20
    #define PRINT_CRC         // 1 Вывод CRC
    #define SERIAL_PRINT_DATA // 2 Вывод данных
    #define READ_RESOLUTION   // 3 Чтение точности
    #define SET_RESOLUTION    // 4 Запись точности
    #define SORTIROVKA        // 5 Сортировка адресов
    
    #define PWR_TYPE  0   // Тип питания: 
    ;                     //    0-внешнее,
    ;                     //    1-паразитное
    
    #define DS18S20   1   // Тип старый
    
    // Scratchpad locations
    #define TEMP_LSB        0
    #define TEMP_MSB        1
    #define CONFIGURATION   4
    
    #ifdef READ_RESOLUTION
    #define COUNT_REMAIN    6
    #define COUNT_PER_C     7
    #endif
    
    #include <OneWire.h>  // Подключение библиотеки 1-Wire
    
    #define MAX_DS    5   // Сколько всего датчиков
    #define DS_PIN    10  // Pin - Выход датчика DQ
    
    char strv[] = "v4.1";
    
    byte sens[MAX_DS][8]; // Массив адресов всех датчиков
    byte type_s[MAX_DS];  // Тип датчика
    byte addr[8];         // Адрес датчика
    byte buffData[9];     // Буфер данных
    byte t = 0;           // Номер подключенного датчика
    
    float temperature;    // Измеренная температура
    
    OneWire ds (DS_PIN);  // Создание однопроводной линии
    
    #ifdef SORTIROVKA
    // Массив с указанием последних двух байтов
    // в адресах датчиков, для сортировки адресов
    //
    const byte indic[MAX_DS][2] PROGMEM =
    {
      {0x02, 0xF0},
      {0x3C, 0xDD},
      {0x3C, 0x30},
      {0x3C, 0x42},
      {0x3C, 0x0E}
    };
    #endif
    
    /***********************
        Функция настройки
      **********************/
    void setup()
    {
      Serial.begin(9600); // Инициализируем порт, 9600 бод
      delay(300);         // Задержка 300 мс
    
      Serial.print("Поиск датчиков ");
      Serial.println(strv);
      fnLine1(18, true);
    
      // Цикл поиска всех датчикав на линии
      do
      {
        if (ds.search(addr))  // Поиск датчика
        {
          // Если найден датчик
          Serial.println();
          Serial.print("sens #");
          Serial.println(t);
    
    #ifdef SERIAL_PRINT_DATA
          Serial.print("ROM =");
          fnPrintData(addr, 8, false); // Выводим данные в порт
    #endif
          // Адрес в массив адресов
          for (byte i = 0; i < 8; i++)
            sens[t][i] = addr[i];
    
          fnRequestTemp();  // Команда на чтение данных
          fnReadData(addr, buffData); // Чтение данных
    
          // Массив строк типов DS18x20
          const char* chipDs[] =
          {
            " Chip = ",
            "DS18S20",
            "DS18B20",
            "DS1822",
            "is not type of DS18x20"
          };
    
          // The first ROM byte indicates which chip
           // Первый байт адреса указывает на тип чипа
          Serial.print(chipDs[0]);
          switch (addr[0])
          {
            case 0x10:
              // DS18S20 или старый тип DS1820
              Serial.println(chipDs[1]);
              type_s[t] = 1;
              break;
            case 0x28:
              Serial.println(chipDs[2]);
              type_s[t] = 2;
              break;
            case 0x22:
              Serial.println(chipDs[3]);
              type_s[t] = 3;
              break;
            default:
              Serial.println(chipDs[4]);
              type_s[t] = 4;
              while (1); // Остановка программы
          }
    
    #ifdef SERIAL_PRINT_DATA
          Serial.print("Data=");
          fnPrintData(buffData, 9, true); // Выводим данные в порт
    #endif
          // Проверка CRC с 9 байтом данных
          if (fnCheckCRC(buffData))
          {
            // Данные правильные, выводим температуру
            Serial.print(" T= ");
            Serial.print(fnGetTemperC(type_s[t], buffData));
            Serial.print("°C");
    
    #ifdef READ_RESOLUTION
            // Выводим точность
            Serial.print(" Resolution= ");
            Serial.println(fnReadResolution(buffData));
    #else   Serial.println();
    #endif
    
    #ifdef SET_RESOLUTION
            // Проверяем программируемость датчиков
            // 1-го и 2-го
            // Для 1-го выставляем 12 бит
            // Для 2-го выставляем 9 бит
            if (t == 0 || t == 1)
            {
              Serial.print("Programing sens #");
              Serial.println(t);          // Номер датчика
              // Установка новой точности измерения
              byte res = 9;
              if (t == 0) res = 12; // Первому датчику 12 бит
              fnSetResolution(addr, res); // Установка точности
    
              // Проверяем, что записалось
              fnReadData(addr, buffData); // Чтение данных
    #ifdef SERIAL_PRINT_DATA
              Serial.print("Data=");
              fnPrintData(buffData, 9, true); // Выводим данные в порт
    #endif
              // Проверка контрольного кода CRC
              bool crc = fnCheckCRC(buffData);
    
    #ifdef READ_RESOLUTION
              Serial.print("New resolution= ");
              Serial.println(fnReadResolution(buffData));
    #endif
            }
    #endif
          }
          t++;  // Инкремент количества найденных датчиков
          if (t == MAX_DS)break;
        }
        else
        {
          // Если датчиков больше нет,
          // то выход из цикла поиска датчиков
          break;
        }
      } while (1);
      ds.reset_search();  // Сброс поиска датчиков
    
    #ifdef SORTIROVKA
      // Сортировка адресов, чтобы расположить датчики
      // в нужном порядке.
      if (t > 1)
      {
        Serial.print("Сортировка...");
        fnSortArr();  // Сортировка адресов
      }
    #endif
    
      Serial.println();
      Serial.print  ("На линии обнаружено датчиков: ");
      Serial.println(t, DEC);
      fnLine1(32, true);
    
      Serial.println("И З М Е Р Е Н И Е");
    } // end setup()
    
     /***********************
        Основная функция
      **********************/
    void loop()
    {
      // Перебор датчиков
      for (byte j = 0; j < t; j++)
      {
        if (t > 1) // Если больше 1-го датчика на линии
        {
          if (j == 0) fnLine1(30, true);  // ---
          Serial.print("sens #");
          Serial.println(j);
        }
    
        // Загрузка адреса датчика j в массив адреса
        for (byte i = 0; i < 8; i++)
          addr[i] = sens[j][i];
    
        fnRequestTemp();  // Команда на чтение данных
        fnReadData(addr, buffData); // Чтение данных
    
    #ifdef SERIAL_PRINT_DATA
        Serial.print("ROM =");
        fnPrintData(addr, 8, true); // Выводим данные в порт
    #endif
    
    #ifdef SERIAL_PRINT_DATA
        Serial.print("Data=");
        fnPrintData(buffData, 9, true); // Выводим данные в порт
    #endif
    
        // Проверка CRC
        if (fnCheckCRC(buffData))
        {
          // Данные правильные. Выводим температуру
          Serial.print("T= ");
          Serial.print(fnGetTemperC(type_s[j], buffData));
          Serial.print("°C");
    
    #ifdef READ_RESOLUTION
          // Проверка установленной точности измерения
          Serial.print(" Resolution= ");
          byte rs = fnReadResolution(buffData);
          Serial.println(rs);
    #else Serial.println();
    #endif
    
    #ifdef SET_RESOLUTION
           // Если точность меньше 11 бит, то, к примеру,
           // выставить 11 бит
          if (rs < 11)
          {
            // Установка точности 11 бит
            fnSetResolution(addr, 11);
    
            Serial.print("Programing sens #");
            Serial.println(j);
    
            // Проверяем, что записалось
            fnReadData(addr, buffData);     // Чтение данных
    
    #ifdef SERIAL_PRINT_DATA
            Serial.print("Data=");
            fnPrintData(buffData, 9, true); // Выводим данные в порт
    #endif
    
            // Проверка контрольной суммы
            bool crc = fnCheckCRC(buffData);
    
    #ifdef READ_RESOLUTION
            // Выводим установленную точность измерения
            Serial.print("New resolution= ");
            Serial.println(fnReadResolution(buffData));
    #endif
          }
    #endif
        } // end Проверки CRC
      }   // end Перебора датчиков
    }     // end loop()
    
    
     /********************************************************
        Ф У Н К Ц И И
        для DS18B20
        2024-03-02 Mr.ALB
        https://mralb.ru/sections/programming/arduino.php  
     
       // Чтение данных 
       void fnReadData(byte * adr, byte * buff) 
      
       // Проверка CRC 
       bool fnCheckCRC(byte * buff) 
      
       // Команда на измерение 
       void fnRequestTemp(void) 
      
       // Вычисления температуры -> temper °C 
       float fnGetTemperC(byte type, byte *buff) 
      
       // Вывод данных в последовательный порт 
       #define SERIAL_PRINT_DATA 
       void fnPrintData(byte * buff, byte len, bool flag) 
      
       // Чтение точности -> 9...12 
       #define READ_RESOLUTION 
       byte fnReadResolution(byte * buff) 
      
       // Запись точности в датчик 
       #define SET_RESOLUTION 
       void fnSetResolution(byte * adr, byte rs) 
      
       // Линия 
       void fnLine1(byte len, bool flag) 
     ********************************************************/ 
    
     /********************************** 
       Функция: Чтение данных 
       2024-03-02 Mr.ALB 
     **********************************/
    void fnReadData(byte * adr, byte * buff)
    {
      // Чтение памяти датчика
      ds.reset();               // Сброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0xBE, PWR_TYPE); // Команда чтения памяти датчика
      ds.read_bytes(buff, 9);   // Чтение памяти датчика, 9 байт
      delay(50);
    }
    
    /***********************************
       Функция: Проверка CRC
       2024-03-03 Mr.ALB
     **********************************/
    bool fnCheckCRC(byte * buff)
    {
      // Считывание CRC
      byte crc = OneWire::crc8(buff, 8);
      if (buff[8] == crc)
      {
    #ifdef PRINT_CRC
        // Выводим CRC
        Serial.print("Data[8]= ");
        if (buff[8] < 16) Serial.print("0");
        Serial.print(buff[8], HEX); // Выводим 9-й байт
        Serial.print(" CRC= ");
        Serial.println(crc, HEX);
    #endif
        return true;
      }
      else
      {
    #ifdef PRINT_CRC
        // Ошибка CRC
        Serial.println("Read error");
    #endif
        return false;
      }
    }
    
    /**********************************************
       Функция: Команда на измерение
       2024-03-03 Mr.ALB
     **********************************************/
    void fnRequestTemp(void)
    {
      // Подготовка измерения данных датчика
      ds.reset();                 // Сброс шины
      ds.select(addr);            // Выбор датчика по адресу
      ds.write(0x44, PWR_TYPE);   // Команда измерения
      delay(900);                 // Пауза 0,9 сек
    }
    
    /**********************************************
       Функция: Вычисления температуры
       2024-03-02 Mr.ALB
        type - указатель на тип датчика
        buff - указатель на массив данных
     **********************************************/
    float fnGetTemperC(byte type, byte *buff)
    {
      // Начинаем процесс преобразования полученых данных
      // в фактическую температуру, которая хранится
      // в 0 и 1 байтах считанных данных.
      // Объединяем эти два байта в одно 16-ти битное число
      int16_t raw = (buff[TEMP_MSB] << 8) | buff[TEMP_LSB];
    
    #ifdef READ_RESOLUTION
      // Точность измерения датчика
      byte rs = fnReadResolution(buff);
    
      // В зависимости от типа датчика,
      // вычисление температуры будет
      // проходить по-разному, так как DS18B20 и DS1822
      // возвращают 12-ти битное значение,
      // а DS18S20 - 9-ти битное
    
      if (type == DS18S20)
      { // Если датчик относится к типу DS18S20
        // Не проверено, т.к. нет живого DS18S20 (Mr.ALB)
        raw = raw << 3;       // значение равно 9 бит
        if (buff[COUNT_PER_C] == 0x10)
        {
          raw = (raw & 0xFFF0) + 12 - buff[COUNT_REMAIN];
        }
      }
      else if (rs != 12) // Если точность не 12 бит
      {
        // При более низких разрешениях можно обнулять
        // младшие биты, так как они всё равно не определены
        if (rs == 9)       raw = raw & ~7; //9  бит (93.75 мс)
        else if (rs == 10) raw = raw & ~3; //10 бит (187.5 мс)
        else if (rs == 11) raw = raw & ~1; //11 бит (375 мс)
        // По умолчанию 12 бит (750 мс)
      }
    #endif
    
      // Возвращаем значение температуры
      return (float)raw * 0.0625; // Делим на 16.0;
    }
    
    #ifdef SERIAL_PRINT_DATA
    /***************************************
        Функция: Вывод данных датчика в порт
        2024-03-02 Mr.ALB
        flag - ставить в конце .println();
      **************************************/
    void fnPrintData(byte * buff, byte len, bool flag)
    {
      for (byte i = 0; i < len; i++)
      {
        Serial.print(" ");          // Промежуток
        if (buff[i] < 16) Serial.print("0");
        Serial.print(buff[i], HEX); // Данные из буфера
      }
      if (flag)
        Serial.println();
    }
    #endif
    
    #ifdef READ_RESOLUTION
    /***************************************************
        Функция: Чтение установленной точности измерения
        2024-03-02 Mr.ALB
         Возвращает точность измерения: 9...12
      **************************************************/
    byte fnReadResolution(byte * buff)
    {
      // Код точности из данных
      byte rs = (buff[CONFIGURATION] & 0x60);
    
      if (rs == 0)         rs = 9;
      else if (rs == 0x20) rs = 10;
      else if (rs == 0x40) rs = 11;
      else if (rs == 0x60) rs = 12;
      return rs;
    }
    #endif
    
    #ifdef SET_RESOLUTION
    /********************************************
       Функция: Запись новой точности измерения
       2024-03-02 Mr.ALB
         adr - указатель на массив адреса
         rs  - новая точность 9...12
     ********************************************/
    void fnSetResolution(byte * adr, byte rs)
    {
      // Загрузка данных в ОЗУ
      ds.reset();               // Cброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0x4E, PWR_TYPE); // Команда на запись памяти
      ds.write(0xFF, PWR_TYPE); // TH-верхний порог температуры
      ds.write(0, PWR_TYPE);    // TL-нижний порог температуры
    
      // Установка новой точности
      if (rs == 9)       ds.write(0x1F, PWR_TYPE);
      else if (rs == 10) ds.write(0x3F, PWR_TYPE);
      else if (rs == 11) ds.write(0x5F, PWR_TYPE);
      else if (rs == 12) ds.write(0x7F, PWR_TYPE);
      delay(20);
    
      // Загрузка данных в EEPROM
      ds.reset();               // Сброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0x48, PWR_TYPE); // Запись EEPROM
      delay(20);
    }
    #endif
    
    #ifdef SORTIROVKA
    /*******************************************************
       Функция: Сортировка адресов датчиков в нужном порядке
       2024-03-08 Mr.ALB
     *******************************************************/
    void fnSortArr(void)
    {
      byte sort[MAX_DS][8]; // Вспомогательный массив
      byte index = 0;
    
      // Ищем совпадение указанных адресов с найденными
      for (byte i = 0; i < MAX_DS; i++)
      {
        byte b_0 = pgm_read_byte(&indic[i][0]),
             b_1 = pgm_read_byte(&indic[i][1]);
    
        // Перебор найденных датчиков
        for (byte ii = 0; ii < t; ii++)
        {
          if (sens[ii][6] == b_0 && sens[ii][7] == b_1)
          {
            // Записываем адрес датчика по порядку indic
            for (byte iii = 0; iii < 8; iii++)
              sort[index][iii] = sens[ii][iii];
            index++;
          }
        }
      }
      // Переписываем sens массив адресов найденных датчиков
      // в порядке, как указано в массиве indic
      for (byte i = 0; i < t; i++)
        for (byte ii = 0; ii < 8; ii++)
          sens[i][ii] = sort[i][ii];
    
      Serial.println("Ok");
    }
    #endif
    
    // Функция: линия из: ----
    void fnLine1(byte len, bool flag)
    {
      for (byte i = 0; i < len; i++)
        Serial.print("-");
      if (flag)Serial.println();
    }
    

    Скетч для измерения одним датчиком

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

    Попутно замечу, что объём скетча ещё зависит от количества используемых комад для вывода в последовательный порт. Если использовать вывод, к примеру, на дисплей LCD или на семисегментные индикаторы, то соответственно можно убрать все команды на последовательный порт и саму инициализацию этого порта в функции setup(). В этом случае код будет меньше.

    В данном скетче убрал функцию сортировки, так как тут в ней нет необходимости.

    /*********************************************************
      Программирование датчика DS18B20
      Пример использования нескольких датчиков на одном выводе
      2024-03-01 Mr.ALB v2.0 Второй заход, первый неудачно
      2024-03-02 Mr.ALB v2.1 Работает
      2024-03-02 Mr.ALB v2.2 Оптимизация
      2024-03-02 Mr.ALB v3.0 Создание функций
      2024-03-02 Mr.ALB v4.0 Оптимизация. Условная компиляция
      2024-03-02 Mr.ALB v5.0 Минимизация
      2024-03-02 Mr.ALB v6.0 Минимизация для одного датчика
    
      Датчик DS18B20 или подобный
        1 - GND - GND
        2 - DQ  - DS_PIN
        3 - VCC - 5V
      От выхода DQ резистор 4.7kOhm на VCC.
      Один на все датчики.
    
      v6.0
        Простое измерение одного датчика. Код минимальный
      Скетч использует 4274 байт (13%) памяти устройства. 0
      Скетч использует 4398 байт (13%) памяти устройства. 3
      Скетч использует 4466 байт (13%) памяти устройства. 3,4
    *********************************************************/
    
    /***************************************
       Настройки функций работы с DS18X20
     **************************************/
    //#define PRINT_CRC         // 1 Вывод CRC
    //#define SERIAL_PRINT_DATA // 2 Вывод данных
    #define READ_RESOLUTION   // 3 Чтение точности
    #define SET_RESOLUTION    // 4 Запись точности
    
    #define PWR_TYPE  0   // Тип питания: 
    ;                     //    0-внешнее,
    ;                     //    1-паразитное
    
    #define DS18S20   1   // Тип старый
    
    // Scratchpad locations
    #define TEMP_LSB        0
    #define TEMP_MSB        1
    #define CONFIGURATION   4
    
    #ifdef READ_RESOLUTION
    #define COUNT_REMAIN    6
    #define COUNT_PER_C     7
    #endif
    
    #define DS_PIN    10  // Pin - Выход датчика DQ
    
    #include <OneWire.h>
    OneWire ds (DS_PIN);  // Создание однопроводной линии
    
    byte addr[8];         // Адрес датчика
    byte buffData[9];     // Буфер данных
    float temperature;    // Измеренная температура
    
    /***********************
       Функция настройки
     **********************/
    void setup()
    {
      Serial.begin(9600); // Инициализируем порт, 9600 бод
      delay(300);         // Задержка 300 мс
    
      ds.search(addr);    // Поиск датчика
    #ifdef SET_RESOLUTION
      fnSetResolution(addr, 12);
    #endif
    }
    
    /***********************
       Основная функция
     **********************/
    void loop()
    {
      fnRequestTemp();  // Команда на чтение данных
      fnReadData(addr, buffData); // Чтение данных
    
      // Выводим температуру
      if (fnCheckCRC(buffData))
      {
        Serial.print(" T = ");
        Serial.print(fnGetTemperC(2, buffData));
        Serial.print("°C");
    #ifdef READ_RESOLUTION
      Serial.print(" Resolution= ");
      Serial.print(fnReadResolution(buffData));
    #endif 
      }
      else
      {
        Serial.print(" T = Error");
      }
      Serial.println();
    } // end loop()
    
    /********************************************************
       Ф У Н К Ц И И
       для DS18B20
       2024-03-02 Mr.ALB
    
      // Чтение данных
      void fnReadData(byte * adr, byte * buff)
    
      // Проверка CRC
      bool fnCheckCRC(byte * buff)
    
      // Вычисления температуры -> temper °C
      float fnGetTemperC(byte type, byte *buff)
    
      // Вывод данных в последовательный порт
      #define SERIAL_PRINT_DATA
      void fnPrintData(byte * buff, byte len)
    
      // Чтение точности -> 9...12
      #define READ_RESOLUTION
      byte fnReadResolution(byte * buff)
    
      // Запись точности в датчик
      #define SET_RESOLUTION
      void fnSetResolution(byte * adr, byte rs)
    ********************************************************/
    
     /********************************** 
       Функция: Чтение данных 
       2024-03-02 Mr.ALB 
     **********************************/
    void fnReadData(byte * adr, byte * buff)
    {
      // Чтение памяти датчика
      ds.reset();               // Сброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0xBE, PWR_TYPE); // Команда чтения памяти датчика
      ds.read_bytes(buff, 9);   // Чтение памяти датчика, 9 байт
      delay(50);
    }
    
    /***********************************
       Функция: Проверка CRC
       2024-03-03 Mr.ALB
     **********************************/
    bool fnCheckCRC(byte * buff)
    {
      // Считывание CRC
      byte crc = OneWire::crc8(buff, 8);
      if (buff[8] == crc)
      {
    #ifdef PRINT_CRC
        // Выводим CRC
        Serial.print("Data[8]= ");
        if (buff[8] < 16) Serial.print("0");
        Serial.print(buff[8], HEX); // Выводим 9-й байт
        Serial.print(" CRC= ");
        Serial.println(crc, HEX);
    #endif
        return true;
      }
      else
      {
    #ifdef PRINT_CRC
        // Ошибка CRC
        Serial.println("Read error");
    #endif
        return false;
      }
    }
    
    /**********************************************
       Функция: Команда на измерение
       2024-03-03 Mr.ALB
     **********************************************/
    void fnRequestTemp(void)
    {
      // Подготовка измерения данных датчика
      ds.reset();                 // Сброс шины
      ds.select(addr);            // Выбор датчика по адресу
      ds.write(0x44, PWR_TYPE);   // Команда измерения
      delay(900);                 // Пауза 0,9 сек
    }
    
    /**********************************************
       Функция: Вычисления температуры
       2024-03-02 Mr.ALB
        type - указатель на тип датчика
        buff - указатель на массив данных
     **********************************************/
    float fnGetTemperC(byte type, byte *buff)
    {
      // Начинаем процесс преобразования полученых данных
      // в фактическую температуру, которая хранится
      // в 0 и 1 байтах считанных данных.
      // Объединяем эти два байта в одно 16-ти битное число
      int16_t raw = (buff[TEMP_MSB] << 8) | buff[TEMP_LSB];
    
    #ifdef READ_RESOLUTION
      // Точность измерения датчика
      byte rs = fnReadResolution(buff);
    
      // В зависимости от типа датчика,
      // вычисление температуры будет
      // проходить по-разному, так как DS18B20 и DS1822
      // возвращают 12-ти битное значение,
      // а DS18S20 - 9-ти битное
    
      if (type == DS18S20)
      { // Если датчик относится к типу DS18S20
        // Не проверено, т.к. нет живого DS18S20 (Mr.ALB)
        raw = raw << 3;       // значение равно 9 бит
        if (buff[COUNT_PER_C] == 0x10)
        {
          raw = (raw & 0xFFF0) + 12 - buff[COUNT_REMAIN];
        }
      }
      else if (rs != 12) // Если точность не 12 бит
      {
        // При более низких разрешениях можно обнулять
        // младшие биты, так как они всё равно не определены
        if (rs == 9)       raw = raw & ~7; //9  бит (93.75 мс)
        else if (rs == 10) raw = raw & ~3; //10 бит (187.5 мс)
        else if (rs == 11) raw = raw & ~1; //11 бит (375 мс)
        // По умолчанию 12 бит (750 мс)
      }
    #endif
    
      // Возвращаем значение температуры
      return (float)raw * 0.0625; // Делим на 16.0;
    }
    
    #ifdef SERIAL_PRINT_DATA
    /***************************************
        Функция: Вывод данных датчика в порт
        2024-03-02 Mr.ALB
        flag - ставить в конце .println();
      **************************************/
    void fnPrintData(byte * buff, byte len, bool flag)
    {
      for (byte i = 0; i < len; i++)
      {
        Serial.print(" ");          // Промежуток
        if (buff[i] < 16) Serial.print("0");
        Serial.print(buff[i], HEX); // Данные из буфера
      }
      if (flag)
        Serial.println();
    }
    #endif
    
    #ifdef READ_RESOLUTION
    /***************************************************
        Функция: Чтение установленной точности измерения
        2024-03-02 Mr.ALB
         Возвращает точность измерения: 9...12
      **************************************************/
    byte fnReadResolution(byte * buff)
    {
      // Код точности из данных
      byte rs = (buff[CONFIGURATION] & 0x60);
    
      if (rs == 0)         rs = 9;
      else if (rs == 0x20) rs = 10;
      else if (rs == 0x40) rs = 11;
      else if (rs == 0x60) rs = 12;
      return rs;
    }
    #endif
    
    #ifdef SET_RESOLUTION
    /********************************************
       Функция: Запись новой точности измерения
       2024-03-02 Mr.ALB
         adr - указатель на массив адреса
         rs  - новая точность 9...12
     ********************************************/
    void fnSetResolution(byte * adr, byte rs)
    {
      // Загрузка данных в ОЗУ
      ds.reset();               // Cброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0x4E, PWR_TYPE); // Команда на запись памяти
      ds.write(0xFF, PWR_TYPE); // TH-верхний порог температуры
      ds.write(0, PWR_TYPE);    // TL-нижний порог температуры
    
      // Установка новой точности
      if (rs == 9)       ds.write(0x1F, PWR_TYPE);
      else if (rs == 10) ds.write(0x3F, PWR_TYPE);
      else if (rs == 11) ds.write(0x5F, PWR_TYPE);
      else if (rs == 12) ds.write(0x7F, PWR_TYPE);
      delay(20);
    
      // Загрузка данных в EEPROM
      ds.reset();               // Сброс шины
      ds.select(adr);           // Выбираем датчик по адресу
      ds.write(0x48, PWR_TYPE); // Запись EEPROM
      delay(20);
    }
    #endif
    

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


    Практическое применение

    Как пример практического применения выше рассмотренных скетчей в Приложении размещаю скетч термометра для измерения внешней уличной температуры и температуры в доме. Вывод температур осуществляется на дисплей Nokia-5110/3310.

    У меня дисплей Nokia-LCD5110. У него разрешение 84 * 48 точек. При включении устройства вначале выводится заставка. Название, версия и автор.

    Термометр. Заставка
    Pic 6. Термометр. Заставка

    Далее выводятся значения с двух датчиков. У меня всё устройство на столе и отрицательная температура через симуляцию. Просто умножаю на -1. Это как пример, если датчик вынести на улицу, то само собой будет выводится реальная температура на улице.

    Термометр. Измерение двух температур
    Pic 7. Термометр. Измерение двух температур

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

    Термометр. Обрыв внешнего датчика
    Pic 8. Термометр. Обрыв внешнего датчика

    Данный пример показывает, что на основе написанных функций можно легко сделать термометр со множеством датчиков.

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


    Приложение

    Материалы для повторения:

    Анатолий Беляев.

    . Mr.ALB
    Предыдущая страница Страница 35 Далее