ARDUINO Несколько термодатчиков DS18B20 на одном проводе, работа с ними по номеру датчика

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,301
Москва
Подключил для примера 3 датчика по схеме :cxema.jpg

Из стандартных библиотек подключил только OneWire. Написал класс для удобства.
Программу и класс предоставляю на обзор.
C++:
#include <OneWire.h>
#include "term18b20.h"

uint32_t StTime;
OneWire  ds(3); //пин куда полдключены термодатчики
// адреса подключенных термодатчиков
byte TerAdr[] = {0x28, 0xAA, 0x19, 0x85, 0x55, 0x14, 0x01, 0x9D};
byte TerAdr2[] = {0x28, 0xAA, 0xAB, 0x3E, 0x55, 0x14, 0x01, 0x54};
byte TerAdr3[] = {0x28, 0xAA, 0x73, 0x40, 0x55, 0x14, 0x01, 0x89};

// 3 объекта 18B20
OneTerm MyTerm(TerAdr);
OneTerm MyTerm2(TerAdr2);
OneTerm MyTerm3(TerAdr2);

void PrintBalcon(int16_t T) // тестовая функция обратного вызова, печатает только измененное значение
{
  static float oldtm;
  float tm = (float)T / 16.0;
  if (oldtm != tm)
  {
    Serial.print("Температура на балконе: ");
    Serial.println(tm);
    oldtm = tm;
  }
}

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

  delay(205);
  MyTerm.SetPrecession(ds, Max_Prec);
  String("Room ").toCharArray(MyTerm.t_name, 8);
  MyTerm.SetMode(2);

  MyTerm2.SetPrecession(ds, Max_Prec);
  String("Kitch ").toCharArray(MyTerm2.t_name, 8);
  MyTerm2.SetMode(2);

  MyTerm3.SetPrecession(ds, Max_Prec);
  String("Bath ").toCharArray(MyTerm3.t_name, 8);
  MyTerm3.func_callback = PrintBalcon;
  MyTerm3.SetMode(3);
  MyTerm3.SetFollow(true);

  StTime = millis();  //отсчет секунд с начала работы,
}
uint32_t Cntr = 0;
float newt3, oldt3 = 0;
void loop() {
  Cntr++; // количество циклов loop за определенное время (изначально за 10 сек)

  MyTerm.Tick(ds);
  if (MyTerm.IsReady())
  {
    Serial.print(MyTerm.t_name);
    Serial.println(MyTerm.Celsius());
  };

  MyTerm2.Tick(ds);
  if (MyTerm2.IsReady())
  {
    Serial.print(MyTerm2.t_name);
    Serial.println(MyTerm2.Celsius());
  };

  MyTerm3.Tick(ds);

  if ((millis() - StTime) > 10000)
  {
    Serial.print("Кол-во ");
    Serial.println(Cntr);
    Cntr = 0;
    StTime = millis();
    MyTerm.SetMode(2);
    MyTerm2.SetMode(2);
  };
}

C++:
#include <OneWire.h>

typedef void (*callback_term)(int16_t);

void PrintAdr(byte a[8])  // вывод 16-отчного адреса на экран. теоретически не нужна
{
  for (int i = 0; i < 8; i++)
  {
    if (a[i] < 16)Serial.print("0");
    Serial.print(a[i], HEX);
  }
};

#define Max_Prec 3
#define High_Prec 2
#define Low_Prec 1
#define Min_Prec 0


#define Max_Time 760
#define High_Time 380
#define Low_Time 190
#define Min_Time 95
#define Term_OFF 0
#define Term_long 1
#define Term_wait 2
#define Term_Callback 3
#define Term_Follow 4 // если флаг установлен, то режим не сбрасывается. сочетается  Term_long Term_wait и Term_Callback
#define Term_ready 128 // бит выставляется по готовности результатов, после чтения Celsius сбрасывается, но всегда можно читать еще раз последнее значение


class OneTerm
{
  public:
    char t_name[8];           //отображаемое название датчика
    byte addres[8];           //адрес датчика
    byte mode = 0b01100000;           // задержка по времени, изначально самая большая. после 1-го получения температуры меняется на актулаьную
    uint32_t start_time = 0;  // время начала запроса температуры
    int16_t Temperatura = 0;  // здесь температура хранится после получения.
    callback_term func_callback=NULL;  // эта функция будет вызываиться после получения температуры
    OneTerm(byte a[8]);       // конструктор
    bool IsReady();           // true если запрос получения температуры обработан
    int Term_Delay();         // возвращает время обработки запроса температуры датчиком. изначально максимальное время.
    void TermRequest(OneWire D_Wire);  // запрос подготовки данных о температуре датчика
    void TermReceive(OneWire D_Wire);  // получение данных с датчика , только при условии пройденного времени
    void Tick(OneWire D_Wire);        // выполняет действия согласно регистру mode
    float Celsius();                //
    void SetMode(byte m);
    void SetPrecession(OneWire D_Wire, byte m);
    bool isFollow();
    void SetFollow(bool f);
};
OneTerm::OneTerm(byte a[8]) // создаем экземпляр класс с указанным адресом термодатчика;
{
  for (int i = 0; i < 8; i++) {
    addres[i] = a[i];
  };
}
bool OneTerm::IsReady() {
  return (mode &  Term_ready);
};

int OneTerm::Term_Delay() //озвращает время обработки запроса температуры датчиком в милисекундах.
{
  switch ((mode & 96) >> 5)
  {
    case 0: return Min_Time;
    case 1: return Low_Time;
    case 2: return High_Time;
    case 3: return Max_Time;
  }
};

void OneTerm::TermRequest(OneWire D_Wire)
{
  bitClear(mode, 7);
  D_Wire.reset();
  D_Wire.select(addres);
  D_Wire.write(0x44);
  start_time = millis();
};

void OneTerm::TermReceive(OneWire D_Wire)
{
  uint32_t ctim = millis();
  // Serial.println((ctim - start_time));
  if ((ctim - start_time) < Term_Delay()) return;
  byte i;
  byte present = 0;
  byte data[12];

  present = D_Wire.reset();
  D_Wire.select(addres);
  D_Wire.write(0xBE);
  for ( i = 0; i < 9; i++) {
    data[i] = D_Wire.read();
  };
  Temperatura = (data[1] << 8) | data[0];

  mode = (mode & 0b10011111) | (data[4] & 0b01100000);

  bitSet(mode, 7);
};

float OneTerm::Celsius()
{
  bitClear(mode, 7);
  return (float)Temperatura / 16.0;
}

void OneTerm::SetMode(byte m)
{
  if ((m < 0) && (m > 2)) return;
  mode = (mode & 248) | m;
}

void OneTerm::SetPrecession(OneWire D_Wire, byte m)
{
  if ((m < 0) | (m > 3)) return;
  m = m << 5;
  mode = (mode & 0b10011111) | m ;
  D_Wire.reset();
  D_Wire.select(addres);
  D_Wire.write(0x4E);
  D_Wire.write(0x7F); D_Wire.write(0xFF); D_Wire.write(m << 1);
};

bool OneTerm::isFollow()
{
  return (mode & Term_Follow);
};
void OneTerm::SetFollow(bool f)
{
  if (f) bitSet(mode, 2); else bitClear(mode, 2);
};



void OneTerm::Tick(OneWire D_Wire)
{
  byte smode = mode & 3;
  //Serial.println(smode);
  switch (smode)
  {
    case 0: return;
    case 1: {
        TermRequest(D_Wire);
        delay(Term_Delay() + 5);
        TermReceive(D_Wire);
        if (! isFollow()) mode = mode & 248; //если не стоит следить, то очистить режим.
        return;
      };
    case 2: {
        if (start_time == 0)
          TermRequest(D_Wire);
        else
        {
          TermReceive(D_Wire);
          if (IsReady())
          {
            if (!isFollow()) mode = mode & 248; //если не стоит следить, то очистить режим.
            start_time = 0;
          }
        };
        return;
      }
    case 3: {
        if (start_time == 0)
          TermRequest(D_Wire);
        else
        {
          TermReceive(D_Wire);
          if (IsReady())
          {

            if (!isFollow()) mode = mode & 120; //если не стоит следить, то очистить режим и готовность, т.к. сразу вызываем функцию
            start_time = 0;
            if (func_callback!=NULL) func_callback(Temperatura);
          }
        };
        return;
      }
  }
};

У класса есть 4 режима работы, 0 - выключено; 1 - запрос температуры и ожидание результат , вешает всю систему delay-ем; 2 - запрос температуры, по готовности выставляет флаг и можно считывать данные; 3 - то же что и 2, но по готовности вызывает CallBack функцию и передает туда температуру в "сыром виде", не преобразованную.
Есть возможность настроить каждый режим для разового и постоянного опрашивания. В примере 1 -ый и 2-ой датчик включаются по таймеру раз в 10 секунд, датчки 3 использует функцию обратного вызова и следит за температурой непрерывно.
Реализована функция выставления точности датчика, но не сделал сохранение установки в самом датчике. При перезагрузке точность надо выставлять заново. Изначально стоит самая высокая точность, после 1-го считывания температуры точность в любом случае будет выставлена по точности датчика.

Подключенные по такой схеме 3 датчика в режиме слежения дают порядка 7000 циклов loop в секунду, чем выше точность, тем больше циклов пройдет.

Не уверен, что будет работать со схемой паразитного подключения.
Функция обратного вызова назначается вручную
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,301
Москва
А теперь вопросы...
Вроде как должен быть какой то указатель , который никуда не показывает.. что то типа nothin или null , никак не могу найти. Буду искать, но если кто то знает, то напишите. Надо что бы если CallBack функция не была инициализирована, то не вызывать ее, а то мало ли что будет
 

Александр Симонов

★★★★✩✩✩
2 Авг 2018
727
208
А теперь вопросы...
Вроде как должен быть какой то указатель , который никуда не показывает.. что то типа nothin или null , никак не могу найти. Буду искать, но если кто то знает, то напишите. Надо что бы если CallBack функция не была инициализирована, то не вызывать ее, а то мало ли что будет
Пустой указатель это NULL (большими буквами)
 
  • Лойс +1
Реакции: Старик Похабыч

kostyamat

★★★★★★✩
29 Окт 2019
1,098
632
@Старик Похабыч, ,а зачем на delay делать? Непорядок это.
Там в самой OneWire делаев хватает.
Надо отправить запрос на подготовку данных, потом по millis забрать. Я так в своем проекте делал.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,301
Москва
@kostyamat, 1-ый режим работы с ожиданием ответа с приостановкой программы, 2 - получение по готовности (выставляю флаг, да по миллис), 3 -ий вариант с функцией обратного вызова, похож на 2-ой, но в лупе кроме тика ничего больше не надо. я им пользуюсь обычно
 

kostyamat

★★★★★★✩
29 Окт 2019
1,098
632
@Старик Похабыч, ааа... Ну тогда ясно, а то я в код не смотрел, а описание читал, и то похоже особо не вник.
А чем стандартная библа не устроила?
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,263
1,301
Москва
Разбирался как самому сделать. Но основное заточить именно под обратный вызов. Да и в СИ потренироваться надо, язык не родной для меня