Делаем устройство для мониторинга концентрации CO2

image-loader.svg


Нередко мы сильно увлекаемся написанием кода настолько, что забываем подышать свежим воздухом, особенно когда нельзя держать окно постоянно открытым (ну, бывают причины). В результате этого в помещении повышается концентрация СО2 и впоследствии начинаются неприятные побочные эффекты в виде сонливости, заторможенности и головной боли. Для решения этой проблемы существуют датчики СО2, которые при достижении определённой концентрации скажут, что пора открыть окно. Готовые варианты — это конечно хорошо, но это слишком просто — сделаем своё хост-устройство для измерения и разомнём извилины.

▍ Введение


Притормозим с открытием САПР для проектирования печатных плат и IDE. Зададим несколько вопросов и соберём нужную нам информацию.

Негативные эффекты от воздействия СО2 начинают проявляться, когда уже всё — приплыли. Даже если после их проявления проветрить помещение, то они сразу не уйдут и будут о себе напоминать ещё некоторое время (в моём случае это минут 20–30). Чтобы этого избежать нужно измерять уровень СО2 и сигнализировать о повышенном уровне заранее.

Концентрация СО2 измеряется в PPM. По сути, PPM это количество молекул СО2 на миллион молекул другого газа, в нашем случае воздуха. Например, если концентрация равна 400ppm, то это значит, что в измеряемом объёме на каждый 1 млн молекул приходится 400 молекул измеряемого газа.

Раз уж мы заговорили о концентрации, то нужно понять, при какой концентрации начинают наступать негативные эффекты. И тут нам поможет вот такая красивая картинка:

image-loader.svg

Концентрация СО2 vs последствия

Как видно из шкалы уже при 1000 PPM начитают проявляться первые негативные эффекты, соответственно при этом значении нужно начинать наводить панику. При достижении 2500 PPM начинаются уже более серьёзные последствия, а 5000 PPM гарантировано заставят вас покинуть помещение и сходить за очередной кружкой чая.

▍ Требования


Хорошо, у нас теперь есть концентрация, от которой мы можем отталкиваться при разработке. Теперь определимся с требованиями к устройству:

  • Компактность. Я думаю никто не хочет видеть у себя на полке огромную коробку.
  • Работа от встроенного АКБ, не люблю батарейки.
  • Автономность. Зарядил и поставил на полку, никаких проводов.
  • Пищалки зло, будем использовать мигающие светодиоды для индикации.
  • Светодиоды должны иметь достаточную яркость, чтобы их заметить, но не освещать всю комнату по ночам.
  • Два уровня индикации: нежелательная и опасная концентрации.
  • Дисплей. Хорошо бы знать текущую концентрацию в помещении.
  • Возможность установки на стену или просто поставить на полку.


В целом достаточно стандартные требования.

▍ Датчик СО2


Начнём с самого основного — измерительного модуля. Разумеется, мы возьмём готовый OEM вариант, т.к. разрабатывать свой ну это крайне сложная задача и требует наличия людей намного умнее меня. Мой выбор пал на NDIR датчик MH-Z19B, т.к. его проще всего достать:

image-loader.svg

MH-Z19B

Относительно остальных компонентов это будет самая дорогая часть устройства не только в плане цены, но и в плане потребления. Давайте глянем его характеристики:

  • Измеряемый диапазон 0–5000 PPM. Нам это подходит.
  • Период измерения 1 секунда и он не настраивается. Это очень плохо, т.к. мы не можем снизить потребление датчика путём уменьшения частоты измерений.
  • Напряжение питания 5В. Тут нужен будет повышающий DC-DC, т.к. работать мы будем от Li-ion АКБ (3.7В).
  • Интерфейс коммуникации UART.
  • Среднее потребление 30 мА. Главный пик тока приходится на вспышку светодиода во время измерения концентрации. Это на самом деле очень печально, хотелось бы меньше, но других бюджетных вариантов нет, которые можно пойти в магазин и купить.


Остальные характеристики нас особо не интересуют. Т.к. датчик китайский, то разумеется там погрешность измерений 0% (сарказм).

Давайте немного поговорим о том, как он работает. Упрощённая схема выглядит так:

image-loader.svgСхема работы датчика газов

Молекулы газа имеют свойство поглощать определённую длину волны. Разумеется, для каждого газа поглощаемая длина волны разная, это позволяет исключить влияние других газов на результат измерения.

В измеряемый объём помещается газ, и делается вспышка источником света. Свет проходит через измеряемый газ, и излучение поглощается молекулами по пути к детектору. Перед детектором стоит фильтр, который пропускает только нужную нам длину волны. В данном случае это длина волны, поглощаемая молекулами СО2, и, если я не ошибаюсь, это 4.26 мкм (поправьте, пожалуйста, если я неправ). Чем больше молекул СО2 в измеряемом объёме, тем больше поглощение — мы получаем зависимость поглощения от концентрации.

Выглядит всё достаточно просто, но на самом деле это очень сложное устройство не только в плане обработки данных, но и в плане изготовления. По пути до детектора излучение должно поглощаться только молекулами газа, соответственно требуется обеспечить максимальное отражение нужного нам излучения от внутренних стенок ёмкости с газом. Это одна из многих проблем при производстве подобных устройств.

Продолжим ковырять наше устройство дальше.

▍ Выбор железа


Я решил использовать STM32F030 в качестве микроконтроллера для обработки данных с датчика. Да, у STM есть L версии микроконтроллеров, которые в разы меньше потребляют, но и цена у них в разы выше. На самом деле у меня их целая коробка и нужно их куда-то определять.

В качестве DC-DC для питания устройства возьмём L6920DTR. Не очень дорогой и требует минимум обвязки, да и эффективность заявлена неплохая — порядка 95%. С питанием тут не всё просто. Для питания датчика нужно 5В, а для питания МК 3.3В. Соответственно проще всего будет поднять напряжение до 5В при помощи DC-DC, а для питания МК опустить напряжение обычным линейным стабилизатором. При токах питания МК потери на стабилизаторе будут минимальные.

Для зарядки АКБ используем микросхему STC4054GR. Она имеет все необходимые нам плюшки в виде установки тока заряда и индикации. Сам аккумулятор выберем позже после разработки ПП, чтобы это всё дело влезло в корпус с максимальным использованием свободного места.

Дисплей возьмём 0.96 дюйма на базе SSD1306:

image-loader.svg
Дисплей на базе SSD1306

▍ Разработка железа


Вжух и схема готова.

Местами есть конденсаторы на 25В, которые стоят в цепи 3.3В — это нормально. Дело в том, что при разработке схем я сначала отталкиваюсь от своих запасов и только потом иду в магазин.
image-loader.svg

Схема (часть 1)

image-loader.svg

Схема (часть 2)

В целом тут нечего особенного. Самая печатная плата получилась достаточно компактной и имеет размеры 55×45. Около 50% занимает сам датчик СО2:

image-loader.svg

3D модель печатной платы

▍ Разработка корпуса


Тут уже интереснее — нужно всё это уместить в компактном виде. В качестве материала будем использовать PLA пластик и FDM печать. Размеры в сборе 51×64х30 (ШхВхГ). Корпус получился достаточно толстым, но в целом приемлемо и на полке смотрится неплохо:

image-loader.svg

3D модель корпуса

На лицевой части имеются 3 отверстия для светодиодов. Два верхних для индикации концентрации: жёлтый и красный. Жёлтый означает, что пора открыть окно, красный — пора бежать :) Третье отверстие для светодиода индикации процесса заряда АКБ.

Также спереди и снизу имеются отверстия для циркуляции воздуха. Изначально я хотел сделать принудительную циркуляцию при помощи вентилятора. Микроконтроллер периодически запускал бы его и продувал датчик, но впоследствии отказался от этого. Не хочу, чтобы ночью он внезапно начал шуршать вентилятором, да и механические детали не очень надёжны (скрипят, шумят, в них постоянно что-то попадает).

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

Устройство собирается как бутерброд. Сначала ставятся светодиоды и дисплей. Дисплей крепится за направляющие путём их оплавления, т.е. снять его без разрушения элементов корпуса не получится. Не очень хорошее решение, но если подумать «А зачем его вообще снимать оттуда?»

image-loader.svg
Крепление дисплея к корпусу

Дальше устанавливается основная плата. Она уже крепится на саморезы, т.к. в процессе отладки приходилось её часто снимать.

image-loader.svg
Установка ПП в корпус

Дальше ставится АКБ на плату и прижимается задней крышкой. Кстати, АКБ я выбрал на 1200 мА — это самый толстый АКБ, который влез в этот корпус.

image-loader.svg
АКБ

image-loader.svg
Задняя крышка устройства

▍ Код, код, код


Я не буду описывать весь код (драйвер USART, работа с дисплеем и т.п.), расскажу о работе с MH-Z19B. Весь код можно глянуть на моём Github: github.com/NeoProg2013/CO2_sensor

Исходник
static uint16_t concentration = 0;
static bool is_data_ready = false;

//  ***************************************************************************
/// @brief  Initialize CO2 sensor driver
/// @param  none
/// @return none
//  ***************************************************************************
void co2_sensor_init(void) {
    usart1_callbacks_t callback;
    callback.frame_received_callback = frame_received_callback;
    usart1_init(9600, &callback);
    
    // Disable ABC
    const uint8_t disable_abc_cmd[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86}; 
    uint8_t* tx_buffer = usart1_get_tx_buffer();
    memcpy(tx_buffer, disable_abc_cmd, sizeof(disable_abc_cmd));
    usart1_start_tx(sizeof(disable_abc_cmd));
    
    // Delay for send command
    uint64_t start_time = get_time_ms();
    while (get_time_ms() - start_time < 1000);
}

//  ***************************************************************************
/// @brief  Read concentration from CO2 sensor
/// @param  none
/// @return true - success, false - error
//  ***************************************************************************
bool co2_sensor_read_concentration(void) {
    const uint8_t meas_cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}; 
    uint8_t* tx_buffer = usart1_get_tx_buffer();
    uint8_t* rx_buffer = usart1_get_rx_buffer();
    
    is_data_ready = false;
    
    // Send measurement command
    memcpy(tx_buffer, meas_cmd, sizeof(meas_cmd));
    usart1_start_rx();
    usart1_start_tx(sizeof(meas_cmd));
    
    // Wait response
    uint64_t start_time = get_time_ms();
    while (!is_data_ready) {
        if (get_time_ms() - start_time > 5000) {
            return false;
        }
    }
    
    // Process response
    concentration = (uint16_t)(rx_buffer[2] << 8) | (rx_buffer[3] << 0);
    if (concentration > 9999) { 
        concentration = 9999;
    }
    return true;
}

//  ***************************************************************************
/// @brief  Get concentration value
/// @param  none
/// @return concentration valuenone
//  ***************************************************************************
uint16_t co2_sensor_get_concentration(void) {
    return concentration;
}

//  ***************************************************************************
/// @brief  USART frame received callback
/// @param  frame_size: received frame size
/// @return none
//  ***************************************************************************
static void frame_received_callback(uint32_t frame_size) {
    is_data_ready = true;
}


Я выкинул из кода всё лишнее для краткости. Давайте по порядку. Первым делом мы выключаем ABC (Auto background calibration), зачем это нужно и что это такое? ABC это и полезная вещь и ружьё, которые обязательно стрельнёт вам в ногу. Принцип её работы (ABC, не ноги) заключается в нахождении минимальной концентрации за определённый период и установки её в качестве «фона» — 400 PPM.Т. е. если вы надышали в пакет и положили туда датчик, то через неделю на датчике можно будет увидеть 400 PPM, вместо 10 000. ABC предполагает, что измеряемый объём проветривается и как бы запоминает значение концентрации уличного воздуха. Нам не нужна эта недокументированная магия, поэтому вырубаем это дело.

В будущем нужно будет предусмотреть принудительную калибровку, т.к. качество воздуха везде разное. Где-то может 1000 PPM быть на улице (надеюсь нет) и ниже этого значения концентрация никогда не упадёт. Всё относительно.

Дальше в коде имеется функция co2_sensor_read_concentration, которая дёргается каждые 3 секунды. Команда для чтения концентрации имеет следующий вид: 0xFF, 0×01, 0×86, 0×00, 0×00, 0×00, 0×00, 0×00, 0×79. На неё датчик отвечает 0xFF, 0×01, CONC, CONC. В последних двух байтах содержится нужное нам значение концентрации. В главном цикле это значение отображается на дисплей и принимается решение о включении нужных светодиодов. В целом всё достаточно просто.

▍ Результаты


Устройство получилось достаточно компактным, вполне можно брать с собой и носить в кармане. По потреблению немного скудно — 3 дня без подзарядки. Очень хотелось бы уменьшить частоту измерений до 0.1Гц, это позволило бы снизить потребление в разы, но увы. Ток заряда 300 мА и заряжается до 100% за несколько часов.

Итоговая стоимость без учёта моего времени порядка 2000р (2021 год), из которых 60% это стоимость датчика СО2.

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

Всем спасибо за внимание и дышите свежим воздухом.

image-loader.svg

© Habrahabr.ru