Барометр/барограф на Arduino v.2 (ESP32) с дополнительными функциями

Предыдущая версия барометра исправно служит уже почти три года, претензий к устройству у меня нет, но недавно попался в руки прибор для мониторинга углекислого газа, штука показалась мне полезной и интересной. Но нельзя же просто так взять и купить, это не интересно, особенно посетителям этого сайта, да и барометров с аналогичным функционалом с момента сборки предыдущего устройства, в продаже тоже больше не стало. Поэтому было решено доработать барометр и интегрировать туда мониторинг уровня СО2. 

Возможности прибора:

  • Построение графика изменения давления за последние сутки (интервал замера 11 минут 15 секунд)
  • Отображение температуры
  • Отображение влажности 
  • Отображение концентрации CO2
  • Сохранение массива измерений давления (для защиты от сброса графика при случайном отключении питания)
  • Подсветка экрана
  • Возможность проверки всех показаний с обоих датчиков 
  • Возможность настройки диапазона давления отображаемого на экране (минимум и максимум)

Логика работы прибора довольно простая, при включении он начинает измерять параметры окружающей среды и выводит их на экран. По результатам измерения давления за сутки, на экране строится график. Шаг графика по горизонтали 11,25 минут (11 мин. 15 сек), всего 128 позиций, что в сумме дает сутки. Шаг по вертикали 1 мм.рт.ст., а отображаемый диапазон 40 мм.рт.ст. По умолчанию минимальное давление, отображаемое на экране, 730 мм.рт.ст. максимальное, соответственно, 770, но, если в вашей местности диапазон колебаний давления отличается, есть возможность это скорректировать в коде прошивки (можно изменить минимально давление, максимум автоматически пересчитается, диапазон не изменится). Раз в час данные массива давления сохраняются в постоянную память, а при включении, из памяти считываются (эту опцию можно отключить). Как уже писал выше, это сделано для предотвращения потери данных при отключении питания, хотя при этом и возможно смещение графика относительно реальной картины атмосферного давления, но, как показала практика, это смещение обычно заметно слабо, зато это позволяет сохранить и понять общую динамику изменения давления. Если прибор долго был отключен, и данные в памяти стали не актуальны, можно сбросить сохраненный массив данных (подать питание на устройство с зажатой кнопкой),ну или просто оставить его работать, и в течение суток массив обновится самостоятельно. Помимо этого, в рабочем режиме однократное нажатие кнопки включает подсветку экрана на 10 секунд, если кнопку зажать, то включится подсветка и на экране появятся все данные со всех датчиков. Для BME280 это давление, температура и влажность, для SCD41 это концентрация СО2, температура и влажность. Это позволяет проверить, оценить и сравнить показания обоих датчиков, сделать выводы об их корректности. Если же подать питание на выключенное устройство с зажатой кнопкой, то, как было описано выше, это приведет к сбросу массива данных давления.

Для начала список комплектующих. Основой проекта будет отладочная плата ESP32.

В качестве дисплея я решил использовать LCD, монохромный, графический 12864-21m. (У китайцев есть разные варианты плат, могут незначительно отличаться внешне)

Пару слов о выборе дисплея, больше всего мне нравятся как выглядят VFD и OLED дисплеи, однако, для данного проекта есть некоторые ограничения. Дело в том, что на дисплей будет выводиться, фактически, статичная картинка графика давления, а вышеуказанные дисплеи не очень любят такое, т.к. постоянно светящиеся пиксели у них со временем деградируют, и из-за статичного изображения это будет происходить не равномерно (например в фабричном варианте прибора, который мне попался в руки, как раз был установлен OLED экран, но там отображались только цифры, и для того чтоб избежать неравномерного выгорания, изображение постоянно постепенно смещалось по экрану в разные стороны, «плавало»). Также хорошим вариантом может быть E-INK экран, возможно как-нибудь попробую и его, особенно если потребуется собрать автономное устройство с питанием от аккумуляторов.

В качестве датчиков используются BME280 (3.3 вольтовая версия) для мониторинга температуры, влажности и давления и SCD41 для отслеживания уровня углекислого газа.

BME280 это уже проверенная классика, тут нечего добавить, касательно мониторинга CO2, рассматривал разные варианты, остановился на SCD41, т.к. во-первых, он достаточно точный, и во-вторых, не требует калибровки и в-третьих, работает по I2C, что очень удобно. 

Кнопка, корпус внешнее оформление, тут дело вкуса, я использовал то, что было в запасах. Так же понадобится N-канальный мосфет для управления подсветкой дисплея, я использовал 2N7002, но подойдет любой похожий по характеристикам. 

Схема подключения компонентов:

Как и для большинства Arduino проектов, печатная плата здесь не требуется, модули просто спаиваются между собой проводами. Питается дисплей от платы ESP32 (Пин Vin), датчики подключены к 3,3в и шине I2S. Продублирую информацию со схемы текстом.

Подключение дисплея :

  • GND__GND (Земля)
  • VCC___Vin (Vin на плате ESP32)
  • RS____D4  (CS)
  • RW___D23 (MOSI)
  • E_____D18 (SCK)
  • PSB__GND (Включение режима SPI)
  • BLA__Vin   (Vin на плате ESP32)
  • BLK__LCD_K (На землю через резистор и мосфет)

Подключение датчиков:

  • VCC(VDD)___3.3v
  • GND________GND (Земля)
  • SCL________D22
  • SDA________D21

Подключение кнопки:

  • PIN1___D13
  • PIN2___GND

После того как все собрано согласно схеме, необходимо открыть скетч, установить необходимые библиотеки:

 #include <U8g2lib.h>               // Подключаем библиотеку U8g2 для работы с графическими экранами #include <Wire.h>                  // Подключаем библиотеку Wire для работы с I2C #include <Adafruit_Sensor.h>       // Подключаем общую библиотеку для сенсоров #include <Adafruit_BME280.h>       // Подключаем библиотеку для работы с BME280 #include <Preferences.h>           // Сохранение настроек в память (для массива давления) #include <SensirionI2cScd4x.h>     // Подключаем библиотеку для работы с датчиком CO2 #include <EncButton.h>             // Подключаем библиотеку от Гайвера для кнопки 

При необходимости скорректировать константы:

 #define MIN_PRES 30                // Минимум давления (30 = 730 мм.рт.ст.) #define BUTTON_PIN 13              // Пин кнопки #define USE_MEMORY                 // Использование памяти для хранения массива давления. Закомментировать эту строку, чтобы отключить автосохранение данных

Минимальное давление отображаемое на графике по умолчанию 730 мм.рт.ст. Я рассчитывал диапазон исходя из статистических данных атмосферного давления по Москве, если вы живете у моря, или наоборот где то по выше, можно скорректировать минимум давления (30 это 730 мм.рт.ст., 40 это 740мм.рт.ст., 10 это 710мм.рт.ст. и т.д.) Максимум рассчитается автоматически (+ 40 мм.рт.ст.) диапазон не поменяется (40 мм.рт.ст.). Также здесь можно изменить пин кнопки и отключить использование памяти, закомментировав строку  #define USE_MEMORY.

После этого можно скомпилировать и загрузить прошивку. 

Кратко опишу логику работа программы.

После запуска, первым делом программа пытается проверить работоспособность датчиков, функция firstScreen(). Датчик BME280, обычно готов к работе сразу, а вот датчику углекислого газа SCD41 для этого требуется некоторое время, обычно порядка 10 секунд. Функция checkSCD41() пытается подключиться к этому датчику и получить с него данные, максимально на это отведено 10 попыток с интервалом три секунды. Если за 10 попыток (30 сек) этого сделать не удалось, то функция просто возвращает false. Результат проверки датчиков отображается на экране как OK или Err. Без датчика СО2 программа работать будет, если же не обнаружен датчик BME280, то дальше программа не запускается, т.к. это основной датчик без него работа не имеет смысла.

После проверки датчиков, идет работа с массивом давления, он обнуляется и дальше, если использование памяти активировано, загружаются ранее сохраненные данные (если они есть и это не первое включение). Настройка на этом заканчивается.

Далее в основном цикле работает функция отрисовки экрана drawScreen(), функции опроса кнопки b.tick() и ее обработки b.click() и b.hold(), и три таймера.  Первый таймер myTimer1, основной, срабатывает каждые 11,25 минут (11 минут 15 секунд) считывает данные с датчиков и передает новое значение в кольцевой буфер массива давления. Всего в буфере хранится 128 значений что в сумме дает ровно сутки измерений. 

Второй таймер myTimer2 на 10 секунд обеспечивает чтение и отображение текущих данных, а именно давления, влажности и температуры с BME280 и концентрации СО2 с SCD41. Здесь же определяется разница давления, которая рассчитывается за сутки и отображается на экране со знаком «+» или «-» соответственно. Она вычисляется как разница между первым и последним значением буфере давления, т.е. смыл эта цифра будет иметь только после суток работы прибора, когда наберется полный буфер. Так же этот таймер гасит подсветку, если она до этого горела. 

Третий таймер myTimer3 настроен на 1 час (3600000 мс). Он сохраняет текущее состояние буфера в энергонезависимую память. Использование памяти можно отключить, если есть опасения по поводу ее долговечности, т.к. в ESP32 используется Flash у которой ресурс меньше, чем у EEPROM. В программе для работы с памятью используется стандартная для ESP32 библиотека Preferences.h для сохранения настроек, и вроде в ней есть оптимизации позволяющие продлить срок службы памяти, поэтому думаю, что ее должно хватить на долго.

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

Пойдем по порядку:

  • Пользовательский класс RingBuffer, создает кольцевой буфер на 128 элементов для хранения и обработки массива данных давления. У класса есть методы: push для добавления элемента в буфер, peek для чтения конкретного элемента из буфера, а также методы saveBufferToEEPROM и loadBufferFromEEPROM для сохранения данных в память и чтения их оттуда. Использование кольцевого буфера позволяет повысить скорость обработки данных, т.к. при каждом новом измерении переписывается только один элемент (а не весь массив, как стандартном подходе когда, все ячейки сдвигаются на одну позицию назад, записывается новая и последняя удаляется).
     
  • Функция checkSCD41 проверяет работоспособность датчика углекислого газа и получает от него данные (концентрацию СО2, влажность и температуру), т.к. датчик при включении требует некоторого времени для запуска, функция делает 10 попыток с интервалом 3 секунды, если подключиться не удалось, возвращает false.
     
  • Функция readAllSensors читает данные с датчиков и выводит их на экран, все сразу. Можно использовать для сравнения данных, если есть сомнения в корректной работе какого-то из них, в нормальных условия показания датчиков (влажность, температура) отличаются не сильно, обычно менее чем на градус или процент. 
     
  • Функция firstScreen используется при запуске устройства в секции setup, она проверяет работоспособность датчиков, и выводит информацию на экран. «ОК» если все хорошо и «Err» если датчик неисправен. Если обнаружились проблемы в работе датчика SCD41  то функция просто отобразит сообщение об ошибке и продолжит работу, если же не удалось обнаружить датчик BME280, то программа зависает, т.к. дальнейший запуск без этого датчика не имеет смысла.
     
  • Функция readCO2 проверяет готовы ли данные у датчика углекислого газа и читает их, если все хорошо она возвращает концентрацию СО2, иначе возвращает значение 0xFFFF, что дальше в коде интерпретируется как ошибка.
     
  • Функция drawScreen основная функция для отрисовки данных на экране, работает в основном с методами библиотеки U8g2. Т.к. библиотека работает только со строками, функция преобразует все переменные в строки с помощью sprintf и выводит данные на экран с использованием указанных шрифтов. Также она отрисовывает график давления на основе данных кольцевого буфера и горизонтальные и вертикальные линии разметки на экране с помощью функций drawDashedLine и drawVerticalDashedLine.
     
  • Функции  drawDashedLine и drawVerticalDashedLine отрисовывают линии разметки на экране пунктиром, чтоб они были видны на любом фоне.
     
  • Функция  getData() читает данные с датчиков, рассчитывает разницу давления.
     
  • Функция resetBuffer() обнуляет буфер с данными давления забивая его минимальным значением указанным в константе MIN_PRES.

Фото готового устройства:

На этом, пожалуй, все. Исходный код в приложенном архиве. Он максимально структурирован и подробно прокомментирован, разобраться не составит труда, если есть какие то вопросы, пишите, поясню, расскажу. 

Список радиоэлементов

Обозначение Тип Номинал Количество Примечание Магазин Мой блокнот
ESP32 Микроконтроллер ESP32 1 ESP-WROOM-32 DevKit v1
12864-21M LCD-дисплей 12864-21M 1
BME280 Датчик температуры BME280 1 версия на 3.3 вольта
SCD41 Датчик СО2 SCD41 1 Датчик CО2
2N7002 MOSFET-транзистор 2N7002 1
Кнопка тактовая Кнопка Кнопка тактовая 1