Термодатчики DS18x20, продолжение…

»1 л.с. — это сила, которую развивает 1 лошадь диаметром 1 м и массой 1 кг, в вакууме».
Не первый раз встречается, что люди, видя какую-то техническую задачу, пытаются решить ее сначала с использованием каких-нибудь эмуляторов, причем за основу берутся допущения и упрощения. В итоге приходят к закономерному выводу — «так оно работать не будет! Программа посчитала…»

Если бы мы жили в мире сферических лошадей диаметром 1 м — наверное да, реальность всегда и точно соответствовала бы теоретическим предположениям.
Но, к счастью или к сожалению, оно немножко не так.
И вот пример, снова наши термодатчики (+ немного занудства):

В предыдущей статье написал про работающую у меня («у меня всё работает!» © (тм)) схемку подключения:

defa72f2bed603190f69c6600816d294.pngпояснять, как работает транзистор, надо?

Транзистор, если не вникать в детали, работает очень просто: когда вон на том выводе, посередине слева, затворе,»+» относительно нижнего вывода — он ток проводит, когда »0» или »-» — не проводит. Такая у него базовая фича.

Когда на ножке GPIO логический 0 (0В) — получается, что напряжение на затворе = 3.3В, транзистор открыт, и линия 1-wire закорочена на 0, через транзистор и ножку процессора.

Когда на ножке логическая 1 (3.3В или около того) — получается что напряжение на затворе около 0В, он закрыт, считаем что его вообще нет, и на шине тогда 5В со стабилитрона через резистор R2.

Когда ножка в режиме чтения — напряжение на ней само поднимется до 3.3, логической 1, потому что если оно меньше — транзистор откроется и добавит, а если дошло до 3.3 — закроется. На этой точке оно и будет балансировать.
Если при этом датчик притянет линию к 0 — напряжению будет неоткуда браться, оно упадет до 0.

Поэтому на линии импульсы будут 5В, а на ножке GPIO — не выше 3.3. Всё просто.

и еще одну, тоже реально работающую штуку:

51809c05948ad3844fd0f27b5d67c440.png

Эти схемы — как основа для реального применения, точка опоры, от которой можно отталкиваться. А вот суровая реальность:

Куплена партия датчиков, DS18B20, Китай. Подключение точно по первой схеме, в частности обращу внимание на то что ножки Vcc и Gnd самих датчиков закорочены.
Всё работает идеально.
Если не закоротить — начинаются помехи в линии, отваливаются отдельные датчики.

Куплена партия датчиков, DS18B20, Китай. Подключение точно по первой схеме — глухо, ничего не работает. Берем осциллограф в руки — просадка на линии. Просаживается она как раз при подключении ножки Vcc к Gnd, причем сразу.
Отключаем ножку Vcc — всё работает.

Еще одна линия на датчиках «валялись в коробке», т.е. были куплены непонятно когда, где, с какой партией, вперемешку.
Одни из них начинают работать, когда ножку закоротишь, другие — когда не закоротишь, третьи требуют конденсатора как на второй схеме, иначе не обнаруживаются если провод длиннее метра.
Причем с конденсаторами все работают идеально, а без них — одни да, другие нет.

То есть, при одном и том же названии микросхемы, поведение разных датчиков существенно отличается одно от другого, наверняка и внутренние схемы разные, а значит и токи потребления.
Ну и как это в принципе можно однозначно рассчитать, чтобы с уверенностью потом говорить «будет так!» ?
Да никак. Можно только оценочно прикидывать, и проверять на практике.

Но и для оценочных прикидок надо для начала разобраться, как оно работает в конкретном случае.
Например, допустим, что 1 датчик в момент измерения температуры потребляет 10 мА тока в течении 100 мс (все цифры оценочные, с потолка, т.к. реальных мы не знаем, датчики разные).
Вопрос, какой ток будут потреблять 4 датчика?

Думаете, арифметика вам поможет: 10 мА * 4 = 40 мА?
А кто сказал что они будут потреблять ток одновременно? Или сразу один за другим без пауз?
Это такое допущение, которое на самом деле еще больше с потолка, чем ток потребления неизвестной схемы. В реальности оно будет работать так, как задано процессом опроса датчиков.

И этим мы можем управлять. Например, часто в инструкциях к Адруино приводятся примеры с использованием библиотеки DallasTemperature:

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup(void)
{
  ....
  sensors.begin();
}

void loop(void)
{
  ....
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  ....
}

Действительно, в этом случае в шину 1-wire отправляется команда запуска измерений (0×44) сразу для всех датчиков, после чего запрашивается результат с датчика, который обнаружился и попал в список на этапе инициализации, функция sensors.begin ()

Какие тут минусы?
— измерение производится сразу всеми датчиками (уточню: — получившими команду, а это не обязательно все на линии!)
— в момент измерения датчики потребляют больше, чем во всё оставшееся время. Именно тот случай когда несколько датчиков способны разрядить всю линию.
— этот процесс занимает некоторое время, ну пусть всё-таки 100 мс. А потом идет запрос к конкретному датчику, чтобы он передал температуру — это еще время. То есть, на этом участке исполнения программы МК задерживается, ждет пока там отработают датчики.
— мы не можем сказать какой именно из »287d9244060000c7»,»285bf044d4e13cab»,»28f4b844d4e13c04» будет в списке первый, какой второй и т.д., нужно работать с конкретными адресами, а для этого надо их сначала узнать.
— главный минус: всё это работает с теми датчиками, которые определились сразу, в первый раз. То есть если какой-то не определился, или добавили-удалили потом — мы этого уже не узнаем, до перезагрузки.

Этого всего можно избежать, если снимать показания немного по-другому.

Во-первых, можно измерять температуру не всеми датчиками одновременно, а по очереди. Для этого надо выбирать конкретный датчик, и отправлять команду ему.
Во-вторых, можно не ждать пока он наизмеряет — отправили команду, вернулись с вопросами через некоторое время.
Для этого нужно иметь список устройств, а вот обнаруживать устройства можно в процессе заново, т.е. появляется возможность добавлять-удалять датчики на лету, не перезагружая контроллер и не меняя прошивку.
И наконец, можно сделать так, чтобы обработка результатов измерения происходила тогда, когда они получены, и если получены.

За основу взята библиотека OneWire, которая содержит низкоуровневые команды управления 1-wire, поверх нее был написан класс:

....
  
#define DS_INIT     0
#define DS_SEARCH   1
#define DS_CONVERT  2
#define DS_RESULT   3
#define DS_REPORT   4

....
  
class owds : public OneWire {

    int ds_count;                               // device count
    unsigned long ds_timer;                     // loop timer
    unsigned long ds_period;                    // loop period
    int ds_mode;                                // current mode
    int ds_sel;                                 // selected device

    owds_Addr ds_list[DS_COUNT];
    owds_Name names[DS_COUNT];

    int fill_mac(char * mac, int i);

  public:

    owds(int pin);                                          // init

    void loop();                                            // main procedure at system loop

    const char * find_name(const char * mac);               // search name for MAC
    void set_name(const char * mac, const char * name);     // set name for MAC
    inline void clear_names() {                             // clear names
      memset(names,0,sizeof(names));
    }
    inline String get_names(){                              // get all names
      String ret = "{";
      for(int i=0; i< DS_COUNT; i++){
        if(strcmp(names[i].name,"") != 0){
          if(i > 0) ret += ",";
          ret += "\"";
          ret += names[i].mac;
          ret += "\":\"";
          ret += names[i].name;
          ret += "\"";
        }
      }
      ret += "}";
      return ret;
    }
    void save_config(File & file);                          // save names to config file (JSON)
    int load_config(File & file);                           // read names from config file (JSON)

    virtual void report(const char * str) {};               // when reporting for all devices (JSON)
    virtual void processing(const char * id, float t) {};   // when processing one device (MAC, TEMP)
    virtual void debug(const char * str) {};                // hook for debug

};
.....

Принцип заключается в том, что выделены 5 режимов работы:
1 — инициализация переменных, «начинаем с начала»
2 — поиск устройств на шине
3 — для каждого устройства запрашивается измерение
4 — для каждого устройства выводятся данные
5 — отправляется итоговый отчет
И всё повторяется через некоторое время.

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

#define PIN_OW   5

// OneWire settings =======================================
#include 

// Create own class based on owds, define user functions and play
class myowds : public owds {
  public:
  myowds(int pin) : owds(pin){};

  // report all found devices at once, JSON string
  void report(const char * str){
    Serial.println("report:");
    Serial.println(str);
  }

  // report for each found device 
  void processing(const char * mac, float t){
    Serial.println("processing:");
    Serial.print(mac);
    Serial.print(" = ");
    Serial.println(t);
  }

  // just debug message
  void debug(const char * str){
    Serial.print("debug: ");
    Serial.println(str);
  }

};

myowds  oneWire(PIN_OW);

void setup() {

  Serial.begin(19200);

}


void loop() {

  oneWire.loop();

  delay(100);

}

Всё, теперь при удачном обнаружении датчиков будут вызываться соответствующие функции. Можно просто сбрасывать данные в MQTT, можно обрабатывать на месте, если заранее известен MAC датчика (manufacturer address code, он же ROM, он же ID) и его назначение.

Именно с ее помощью и реализован у меня контроль зон. Там действительно получается всё просто: что определилось — то будет работать, а если не работает — надо лезть с осциллографом и смотреть почему

075cdd3646a9e6d4ae0ebe83564545a6.png

Библиотеку полностью, если кому надо — закинул на GitHub.

© Habrahabr.ru