Мой покупной уличный термометр жил на балконной раме и врал двумя способами сразу: ночью показывал честные −22, а днём, когда солнце переворачивалось на его сторону, вдруг выдавал −8 при реальных −12. Тёмный корпус на прямом свете греется, стекло дышит комнатой с обратной стороны — верить такой цифре нельзя было в принципе. Захотелось честного термометра на экране, да ещё с памятью: минимум и максимум за сезон, чтобы зимы можно было сравнивать между собой, а не по ощущениям.
Проект складывается из четырёх задач: довести шину 1-Wire по кабелю 10-30 м через окно без потерь, закрыть датчик от солнца белым экраном, сгладить показания средним из 16 замеров и записать рекорды в EEPROM. Кнопка листает экран между «сейчас», «мин» и «макс», светодиод загорается, когда мороз перевалил за −25, — цифру видно даже из кухни. Электрика зонда тут та же, что в уроке про подключение DS18B20, а всё остальное — уличная специфика. Журнал t= в мониторе ведёт хронику всей зимы — сохраняйте его в файл, в феврале будет чем похвастаться перед самим собой.
Кабель через окно и экран от солнца
Кабель на 10-30 м — это уже взрослая линия 1-Wire: ёмкость кабеля рыхлит фронты, и без подготовки показания рвутся. Лечение приходит из урока про несколько DS18B20 и длинные линии: витая пара для данных с землёй и подтяжка 2,2 кОм у платы вместо 4,7. Мой рабочий вариант — пара жил из старого Ethernet-кабеля на 15 метров: за две зимы ни одного промаха. Откуда берётся шум в длинных проводах, мы разбирали в уроке про наводки на датчиках — улица добавляет к нему ветер и помехи от соседских блоков питания.
Через окно кабель веду щелью 3-4 мм, просверленной в раме, снаружи капля герметика. Закладывать его в уплотнитель под защёлкой не стоит: рама давит на кабель, и через сезон резина перетирает оплетку до меди. От стекла зонд держу на 5-10 см: стекло зимой холодное, летом тёплое, и датчик, приклеенный к раме, измеряет раму, а не воздух. И снимайте прибор на зиму только для ремонта: рекорды собираются тогда, когда термометр висит на месте месяцами, а не зимует в ящике.
Солнце — главный враг уличного термометра. Тёмная колба на прямом свете прогревается на 5-10 °C выше воздуха, и слепой датчик честно отдаёт эту ерунду в журнал. Лечение старое, как метеорология: белый экран. Мне хватило белого пластикового бокса с прорезями по бокам — тот же принцип, что у метеорологических клеток: белые стенки отражают излучение, воздух гуляет сквозь щели. Плюс правило установки: северная сторона, где солнце не бывает часами. Готовая клетка стоит как пара зондов, но и бокс от обуви у меня живёт третий год.
Собственный шум DS18B20 скромный, десятые доли градуса, но улица добавляет порывы: ветер у стены, струйка тёплого воздуха из щели, солнечный блик через экран. Среднее из 16 замеров гасит разброс примерно вчетверо — корень из числа замеров; механику мы собирали в уроке про фильтр среднего, там он чистил АЦП, здесь работает по цифровой шине. Источник другой, приём тот же: копите замеры, делите на количество, печатаете одно спокойное число.
Цена вопроса — время. При 12 битах 16 замеров съедают 16 × 750 мс = 12 секунд на обновление. Компромисс, на котором я осел: setResolution(11) — шаг 0,125 °C, конверсия 375 мс, полный цикл шесть секунд. Для улицы, где рекорды пишутся за сезон, а не за минуту, этого с запасом; дрожащая последняя цифра на экране исчезает именно из-за усреднения, а не из-за разрешения.
Проверить фильтр глазами легко, если печатать рядом оба числа: сырое и усреднённое. В моём журнале сырое прыгало в диапазоне 0,3-0,4 °C при ветре из щели, среднее ходило на десятую и выглядело как линия, а не как кардиограмма. Как только усреднённое ведёт себя спокойнее сырого на порядок — фильтр настроен. Если оба пляшут одинаково, дело не в шуме, а в физике: ищите сквозняк, обогреватель за шторой или соседский кондиционер, дующий прямо на ваш экран.
| Симптом на улице | Причина | Лечение |
|---|---|---|
| днём скачки на +5-10 °C в солнечные часы | прогрев колбы излучением | белый экран с прорезями, северная сторона, отступ от стекла |
| промахи раз в час на длинной линии | ёмкость кабеля и наводки | витая пара данные+земля, подтяжка 2,2 кОм у платы |
| последняя цифра на экране дрожит | шум сенсора плюс порывы ветра | среднее из 16 замеров, 11 бит вместо 12 |
| −127 после ледяного дождя | влага в клеммнике у зонда | пайка или гермоклеммы, кабель отводить петлёй вниз |
Рекорды сезона и минус на экране
Минимум и максимум живут в EEPROM и переживают и отключение света, и ваш отпуск. Ресурс ячейки — около 100 тысяч записей, поэтому пишу только при новом рекорде и только методом update: он трогает ячейку, лишь когда байт изменился — разницу с write мы считали в уроке про ресурс ячеек. Хранение умещается в один байт: байт = температура + 55, шкала −55…+70 °C. Целых градусов для рекорда сезона достаточно: разница между −27 и −27,4 не меняет ни одежду, ни планы.
Чистая плата приходит с ячейками, залитыми 0xFF, — это флаг «рекордов ещё нет». Первый замер засевает оба значения текущей температурой, дальше они ползут сами: вниз — к морозным ночам, вверх — к июльской жаре. Кнопка листает три страницы экрана; я повесил её на D3 — D2 занят шиной 1-Wire, и такой выбор одинаков в симуляторе и на живой плате.
Минус на экране — отдельная песня. Печать отрицательных float сама ставит минус перед числом, придумывать ничего не надо. Грабля в другом: после −9,5 значение ползёт к −12,3, и старая цифра 9 продолжает торчать рядом с новой. Лечение — хвост из пробелов после каждого числа: мусор исчезает. Символ градуса печатается write(223) — глиф знакогенератора HD44780. Экран я не очищаю между обновлениями: clear() раз в шесть секунд даёт видимое мерцание, мы его разбирали в уроке про мерцание LCD.
#include <OneWire.h>#include <DallasTemperature.h>#include <LiquidCrystal_I2C.h>#include <EEPROM.h> OneWire oneWire(2);DallasTemperature sensors(&oneWire);LiquidCrystal_I2C lcd(0x27, 16, 2); const int KNOPKA = 3; // D2 занят шиной 1-Wireconst int LED = 12; // горит при морозе ниже -25const int ADR_MIN = 0; // минимум сезонаconst int ADR_MAX = 1; // максимум сезона int tMin = 999; // 999 — рекордов ещё нетint tMax = -999;int stranica = 0; // 0: сейчас, 1: мин, 2: максint prevKn = HIGH; void setup() { Serial.begin(9600); pinMode(KNOPKA, INPUT_PULLUP); pinMode(LED, OUTPUT); lcd.init(); lcd.backlight(); sensors.begin(); sensors.setResolution(11); // 375 мс вместо 750 byte bMin = EEPROM.read(ADR_MIN); // чистая плата: 255 byte bMax = EEPROM.read(ADR_MAX); if (bMin != 255) tMin = bMin - 55; // байт = градусы + 55 if (bMax != 255) tMax = bMax - 55; Serial.print("min="); Serial.print(tMin); Serial.print(" max="); Serial.println(tMax);} void loop() { float summa = 0; for (int i = 0; i < 16; i = i + 1) { sensors.requestTemperatures(); // 16 × 375 мс = 6 с на цикл summa = summa + sensors.getTempCByIndex(0); } float t = summa / 16.0; int ti = (int)(t < 0 ? t - 0.5 : t + 0.5); // целые для рекордов if (tMin == 999) tMin = ti; // первый замер сезона if (tMax == -999) tMax = ti; if (ti < tMin) { tMin = ti; EEPROM.update(ADR_MIN, ti + 55); } if (ti > tMax) { tMax = ti; EEPROM.update(ADR_MAX, ti + 55); } digitalWrite(LED, ti <= -25 ? HIGH : LOW); // мороз — видно из кухни int kn = digitalRead(KNOPKA); if (kn == LOW && prevKn == HIGH) { stranica = stranica + 1; if (stranica > 2) stranica = 0; } prevKn = kn; lcd.setCursor(0, 0); lcd.print("Ulitsa "); lcd.print(t, 1); lcd.write(223); lcd.print("C "); lcd.setCursor(0, 1); if (stranica == 0) { lcd.print("Seichas "); lcd.print(t, 1); lcd.print(" "); } else if (stranica == 1) { lcd.print("Min sez "); lcd.print(tMin); lcd.print(" "); } else { lcd.print("Max sez "); lcd.print(tMax); lcd.print(" "); } Serial.print("t="); Serial.print(t, 1); Serial.print(" min="); Serial.print(tMin); Serial.print(" max="); Serial.println(tMax); delay(200);}Цикл с 16 замерами блокирует скетч на шесть секунд, и кнопка отвечает с той же ленью — для термометра приемлемо, для живого меню уже нет. Стандартная дорога дальше — разнести замеры и опрос кнопки по кооперативному циклу: кнопка начнёт отвечать мгновенно, а серия замеров станет очередным делом расписания. Сверка с эталоном простая: соседний спиртовой термометр в тот же теневой час; расхождение больше градуса — возвращайтесь к месту установки и экрану, а не к датчику.
Светодиод мороза я вынес на подоконник отдельными проводами длиной два метра: обычный индикаторный LED с резистором 220 Ом берёт 10 мА, и за столько метров падение напряжения никого не волнует. Ночью красная точка в окне читается через двор — соседи думают, что это гирлянда, а я знаю, что за окном минус двадцать пять. Экран остаётся дома на полке у окна: он для цифр и рекордов, светодиод — для мгновенного ответа на вопрос «одеваться теплее».
- Линия 10-30 м — витая пара и подтяжка 2,2 кОм у платы; сквозь раму — щель 3-4 мм с герметиком, не под защёлку.
- Белый экран с прорезями и северная сторона: тёмная колба на солнце прогревается на 5-10 °C выше воздуха.
- Среднее из 16 замеров гасит дрожь вчетверо; на 11 битах полный цикл — шесть секунд.
- Рекорды — в EEPROM через update и только при новом достижении; байт = градусы + 55, шкала −55…+70.
- Минус печатается сам; хвост пробелов стирает остатки прошлой цифры, а clear() между обновлениями — это мерцание.
- 1. Почему днём на солнце уличный датчик врёт вверх?
- 2. Во сколько раз падает разброс при среднем из 16 замеров?
- 3. Почему рекорд пишется через EEPROM.update, а не write?
- 4. Как температура прячется в байт EEPROM со сдвигом?
- 5. Кнопка читается раз в цикл. Что с ней не так при серии из 16 замеров?
Частые вопросы по теме урока
- Почему днём на солнце уличный датчик врёт вверх?
- Излучение греет корпус зонда, а не воздух. Лечится белым экраном с прорезями и северной стороной; протокол и библиотека тут ни при чём. Тёмная колба прогревается излучением на 5-10 °C
- Во сколько раз падает разброс при среднем из 16 замеров?
- Случайный шум при усреднении падает как корень из N: 16 замеров — вчетверо. Независимые промахи частично гасят друг друга, но не все сразу. В 4 раза — как корень из числа замеров
- Почему рекорд пишется через EEPROM.update, а не write?
- Каждая запись расходует цикл перезаписи, ресурс — около 100 тысяч. update трогает ячейку только при новом байте, а рекорд меняется по пальцам раз в сезон. update пишет только отличия и бережёт ресурс ячейки
- Как температура прячется в байт EEPROM со сдвигом?
- Сдвиг уводит шкалу в положительные: −55 даёт ноль, +70 — 125. Один байт вместо четырёх, и рекорды соседних зим сравниваются в уме. Байт = температура + 55, шкала −55…+70 °C
- Кнопка читается раз в цикл. Что с ней не так при серии из 16 замеров?
- Пока скетч собирает серию замеров, digitalRead не вызывается; нажатие прочтут только в конце цикла. Лечение — кооперативный цикл, как для любых долгих дел. Она отвечает с ленью до шести секунд: цикл блокирован