Пока датчик прикручен к ноутбуку, история показаний живёт ровно до закрытия монитора порта: скетч из первого урока раздела честно печатает строки, но листаются они в никуда. Мне нужно было суточное логирование температуры в кладовке с графиком и фильтрами, а из инструментов был Excel, который уже стоит на любой рабочей машине. Копипаст из монитора руками работает до второго часа: строки прилипают, глаза устают, половина замеров забывается. Ниже — маршрут, который я в итоге застолбил: плата печатает готовые CSV-строки, Excel съедает их как таблицу, и всё логирование сводится к двум вещам — договориться о формате строки и не делить порт между двумя читателями.
Разберу три вещи: как выглядит строка t,temp,hum и почему поля идут именно в таком порядке, какими тремя дорогами эти строки попадают в Excel (надстройка Data Streamer для живого потока, вставка с разбором по разделителю для истории) и почему в русской локали запятая-разделитель превращается в головную боль, у которой два честных лечения. Сам датчик я подключал в уроке про DHT11 — там распиновка и подтяжка; здесь он просто источник двух чисел в секунду.
Строка t,temp,hum: договор до кода
CSV — формат, при котором одна строка текста равна одной записи таблицы, а поля внутри разделяет запятая: t,temp,hum — время в секундах, температура, влажность. Формату больше тридцати лет, его открывают Excel, LibreOffice, Google Таблицы и любая база, поэтому лог в CSV переживёт и смену ноутбука, и остывание интереса к проекту. Первое поле я делаю секундами с millis(), а не часами из RTC: для суточного лога этого хватает, а привязку к стенному времени можно пришить при разборе.
Число полей фиксируйте заранее и не меняйте посреди лога: Excel растянет столбцы по первой строке-шапке, и если десятая запись вдруг пойдёт с четырьмя полями, она либо съедет по столбцам, либо потеряет хвост. Шапку t,temp,hum я печатаю один раз в setup() — она становится заголовками столбцов, и никаких «Столбец1», «Столбец2» в файле не заводится. Тот же принцип «одна строка = одна запись» мы проговаривали в уроке про таблицу данных — там он обслуживал монитор порта, здесь ту же строку читает Excel.
// Логгер CSV: DHT11 на пине 2, запись раз в 2 секунды.#include "DHT.h"DHT dht(2, DHT11); unsigned long poslednyaya = 0; // момент прошлой строки void setup() { Serial.begin(9600); dht.begin(); Serial.println("t,temp,hum"); // шапка = заголовки столбцов} void loop() { if (millis() - poslednyaya >= 2000) { // 2 секунды между записями poslednyaya = millis(); float temp = dht.readTemperature(); float hum = dht.readHumidity(); Serial.print(millis() / 1000); // секунды с включения Serial.print(","); Serial.print(temp, 1); // один знак после запятой Serial.print(","); Serial.println(hum, 0); // влажность — целым числом }}Два решения в скетче экономят вечера. Первое — пауза на millis(), а не delay(2000): loop() остаётся живым, и в те же две секунды можно опрашивать кнопку, мигать светом и слушать порт; механику планировщика разобрал урок про millis вместо delay. Второе — второй аргумент в Serial.print(temp, 1): печать с одним знаком после запятой. Без него float лезет в строку целиком со своим хвостом вида 23.60000038, и столбец температуры разбухает втрое.
Интервал 2000 мс взят не с потолка: DHT11 обновляет показания раз в секунду, DHT22 — раз в две, и опрашивать их чаще — печатать одно и то же число по два раза. Две секунды закрывают оба сенсора с запасом. Строка при этом короткая: t,temp,hum с числами — байт двадцать, то есть примерно 10 байт в секунду. Порт на 9600 бод тащит порядка 960 байт в секунду — канал занят на один процент, и про переполнение приёмного буфера можно не вспоминать.
Третья мелочь — какими бывают поля. Секунды печатаю целым числом: millis() живёт до 49 суток, целого хватает, а деление на тысячу в момент печати не накапливает погрешности. Температуру — с одним знаком после запятой, влажность — целой: у DHT11 сотые всё равно фиктивны, паспортная точность по влажности ±5 %. Каждое поле — ровно то, что хочется видеть в столбце: без пересчётов на стороне Excel и без столбцов-мутантов из шести цифр.
Три дороги в Excel
Дорога первая — Data Streamer, бесплатная надстройка Microsoft для Excel из состава Microsoft 365: она сама открывает COM-порт и стримит строки прямо в лист, обновляя ячейки на лету. Включается через вкладку «Вставка» → «Надстройки», после установки заводится своя вкладка Data Streamer, где выбирают устройство и скорость порта — те же 9600, что в Serial.begin(). Дальше Excel превращается в живой дашборд: строки льются, график по выделенному диапазону перерисовывается сам. Для многодневного лога это неудобно — поток перезаписывает лист, а не копит историю, — но для демонстрации датчика на проекционнике вещь незаменимая.
Дорога вторая — тупо и железобетонно: включить лог в мониторе порта, выделить, скопировать, вставить в лист. Если столбцы слиплись в один, это лечится без переписывания скетча: Данные → Текст по столбцам → «с разделителями» → указать запятую. Третья дорога — мост на Python через pyserial, который пишет CSV-файл сам, без ручного копипаста; скелет такого скрипта разобран в уроке про Arduino и Python, и когда логов становится много, это дорога номер один.
| Дорога | Что даёт | Когда беру |
|---|---|---|
| Data Streamer (Microsoft 365) | живой поток в лист, график перерисовывается сам | демо на экране, короткие живые замеры |
| Копипаст + Текст по столбцам | история без единой строки кода на компьютере | одноразовый лог на полчаса-час |
| Python + pyserial | файл CSV пишется сам, работает сутками | недельные логи, дюжина файлов |
| SD-карта на плате | лог без компьютера вообще | установка в шкафу, куда ноутбук не занести |
Мелочь, которая портит готовые файлы: Uno перезагружается при каждом открытии порта, и после повторного подключения лог снова получает строку шапки. В суточной выгрузке накопится несколько t,temp,hum посреди чисел — при разборе их отсеивает простое правило: запись начинается с цифры, шапка — с буквы. Тот же фильтр улавливает пустые ответы датчика: если DHT не ответил, печатайте строку с пустыми полями вместо нулей — пустая ячейка честно покажет провал, а ноль в столбце температуры нарисует ложный обвал графика.
Прежде чем строить таблицы, проверьте сырую строку глазами: включите монитор порта и убедитесь, что записи идут ровные, по одному числу на поле, без хвостов вида nan. Десять секунд на этот тест экономят вечер разбора файла, в котором половина строк — мусор из-за болтающегося провода сенсора. Ни одна надстройка не спасёт лог от плохого источника.
Лог, который копится неделю, удобнее делить по дням: файл с датой в имени не так страшно открыть, как месячный монолит в сто мегабайт. В вставке через монитор это одна выгрузка в день, в Python-мосте — имя файла с датой, которое скрипт собирает сам. Excel откроет оба варианта, но привычка выгружать по дням стоит ноль рублей и спасает нервные клетки, когда файл на половину листа открывается минуту.
Локаль: куда девается запятая
Грабля, на которой я просидел минут сорок: в русской Windows Excel считает запятую десятичным разделителем — число 23,5 пишется с запятой, поэтому запятая между полями для него разделителем не служит. Файл открывается, но вся строка падает в один столбец. Выхода два, оба честные. Разовый: Текст по столбцам с ручным выбором запятой — Excel разрежет как надо. Системный: пусть плата печатает точку с запятой — Serial.print(";") вместо запятой — и русская локаль разбирает файл влет, а англоязычная требует настройки. Для логов на свою машину я беру запятую и разовый разбор; для файлов в чужие руки — точку с запятой.
И арифметика объёма, чтобы месяц спустя никто не удивился. Строка раз в 2 секунды — 43 200 записей в сутки, файл CSV на них весит меньше мегабайта. Лист xlsx вмещает 1 048 576 строк: непрерывный лог упирается в потолок листа за 24 суток. Мой практический предел для Excel-лога — неделя: файл в 200-300 тысяч строк ещё листается, дальше фильтры задумываются по несколько секунд. Логам длиннее этого предела место на SD-карте — в уроке про логирование на SD та же CSV-строка пишется прямо на плате, без компьютера в цепочке.
Пара слов про выборку в Data Streamer, если надстройка легла в вашу цепочку. Частота опроса у неё своя и не обязана совпадать с темпом платы: для DHT с интервалом в две секунды гонять стрим чаще нет смысла — датчик между записями молчит. Живой поток направляйте на отдельный участок листа, а разбор и графики держите на соседнем: так стрим не затрёт накопленную статистику в момент, когда вы отвернулись.
- 1. Строка лога идёт раз в 2 секунды. Сколько записей наберётся за сутки?
- 2. Почему в русской локали Excel склеивает CSV-строку в один столбец?
- 3. Что случится, если открыть монитор порта при работающем Data Streamer?
- 4. Зачем в логгере пауза на millis(), а не delay(2000)?
- 5. Что напечатает Serial.print(temp, 1), если temp равен 23.60000038?
- Одна строка CSV равна одной записи: t,temp,hum с шапкой в setup() — и в Excel вырастают готовые столбцы.
- Интервал 2000 мс на millis(): закрывает и DHT11 с его секундой, и DHT22 с его двумя, а loop() живёт между записями.
- Три дороги в Excel: Data Streamer для живого потока, копипаст с Текст по столбцам для истории, Python для многодневных логов.
- Порт открывает один читатель: монитор и Data Streamer одновременно не живут.
- Русская локаль съедает запятую: разовый разбор по столбцам или печать через точку с запятой.
Частые вопросы по теме урока
- Строка лога идёт раз в 2 секунды. Сколько записей наберётся за сутки?
- В сутках 86 400 секунд; делите на интервал 2 — получаете 43 200 строк. Лист xlsx с его 1 048 576 строками выдержит такой лог 24 суток подряд. 43 200
- Почему в русской локали Excel склеивает CSV-строку в один столбец?
- В русской Windows десятичный разделитель — запятая, поэтому на роль разделителя полей назначена точка с запятой. Лечится разбором через Текст по столбцам или печатью через точку с запятой. запятая занята: в этой локали она отдана десятичной дроби
- Что случится, если открыть монитор порта при работающем Data Streamer?
- Порт открывает один читатель. Второй получит отказ — то же правило ломает загрузку скетча при открытом мониторе. второй читатель порт не получит: COM-порт открывает один
- Зачем в логгере пауза на millis(), а не delay(2000)?
- delay() замораживает loop() на все две секунды. Планировщик на millis() держит скетч отзывчивым — тот же приём, что в мигалке без delay. loop() остаётся живым: кнопку и порт опрашиваем между записями
- Что напечатает Serial.print(temp, 1), если temp равен 23.60000038?
- Второй аргумент — число знаков после запятой: один знак даёт 23.6 и держит столбец компактным. 23.6