«1 л.с. — это сила, которую развивает 1 лошадь диаметром 1 м и массой 1 кг, в вакууме».
Не первый раз встречается, что люди, видя какую‑то техническую задачу, пытаются решить ее сначала с использованием каких‑нибудь эмуляторов, причем за основу берутся допущения и упрощения. В итоге приходят к закономерному выводу — «так оно работать не будет! Программа посчитала...»
Если бы мы жили в мире сферических лошадей диаметром 1м — наверное да, реальность всегда и точно соответствовала бы теоретическим предположениям.
Но, к счастью или к сожалению, оно немножко не так.
И вот пример, снова наши термодатчики (+ немного занудства):
В предыдущей статье написал про работающую у меня ( «у меня всё работает!» © (тм) ) схемку подключения:
пояснять, как работает транзистор, надо?
Транзистор, если не вникать в детали, работает очень просто: когда вон на том выводе, посередине слева, затворе, "+" относительно нижнего вывода - он ток проводит, когда "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. Всё просто.
и еще одну, тоже реально работающую штуку:
Эти схемы — как основа для реального применения, точка опоры, от которой можно отталкиваться. А вот суровая реальность:
Куплена партия датчиков, 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мс. А потом идет запрос к конкретному датчику, чтобы он передал температуру — это еще время. То есть, на этом участке исполнения программы МК задерживается, ждет пока там отработают датчики.
Мы не можем сказать какой именно из «287d9 244 060 000c7», «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 <OWDS.h>
// 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) и его назначение.
Именно с ее помощью и реализован у меня контроль зон. Там действительно получается всё просто: что определилось — то будет работать, а если не работает — надо лезть с осциллографом и смотреть почему
Библиотеку полностью, если кому надо — закинул на GitHub.
Комментарии (4)
REPISOT
19.01.2025 18:17Ну и как это в принципе можно однозначно рассчитать, чтобы с уверенностью потом говорить "будет так!"?
Ну, судя по всему, вы покупаете не оригинальный датчик DS18B20, поведение которого строго соответствует даташиту, а какую-то китайскую реплику.
Некоторые производители делают клоны, рабочие по протоколу, и это работает, пока вы подаете им питание по 3-му проводу. Но это не гарантирует работу в паразитном режиме. Потому что некоторые клоны могут быть сделаны на базе программируемых матриц. Как тут с FT232.
Для оригинала рекомендуется соединять VDD и GND для паразитного питания. А вот что рекомендуют производители копий, никто не знает.
Из даташита DS18B20
Разные копии.
JBFW Автор
19.01.2025 18:17Совершенно верно.
Поэтому делаю для них питание через внешний конденсатор, в соответствии с функциональной схемой оригинального чипа - и они работают.
Listenernow
Может 3 провода проще?
JBFW Автор
Как выяснилось - далеко не факт.
Пробовал, как-то оно неудобно выходило.
Само по себе внешнее питание решает ровно одну проблему, собственно питание, при этом БП лучше ставить нормальный, иначе какой смысл.
Проблемы с работой самой сети, накладками сигналов и прочего оно не решает, но вдобавок пришлось вести третье подключение (скрутки, клеммы), которое к тому же нельзя замыкать.
В смысле, двухпроводную линию можно в любом месте невозбранно порезать и подключить туда отвод, а с питанием это уже обесточивать надо чтобы не спалить.
Двухпроводку не страшно водой залить, не страшно передавить - максимум кусок работать не будет.
Вот такие какие-то мелочи, вроде небольшие, но в итоге перевесило, убрал питание и всё.
А так-то да, можно конечно.