Нередко мы сильно увлекаемся написанием кода настолько, что забываем подышать свежим воздухом, особенно когда нельзя держать окно постоянно открытым (ну, бывают причины). В результате этого в помещении повышается концентрация СО₂ и впоследствии начинаются неприятные побочные эффекты в виде сонливости, заторможенности и головной боли. Для решения этой проблемы существуют датчики СО₂, которые при достижении определённой концентрации скажут, что пора открыть окно. Готовые варианты конечно хорошо, но это слишком просто — сделаем своё хост-устройство для измерения и разомнём извилины.
▍ Введение
Притормозим с открытием САПР для проектирования печатных плат и IDE. Зададим несколько вопросов и соберём нужную нам информацию.
Негативные эффекты от воздействия СО₂ начинают проявляться, когда уже всё — приплыли. Даже если после их проявления проветрить помещение, то они сразу не уйдут и будут о себе напоминать ещё некоторое время (в моём случае это минут 20-30). Чтобы этого избежать нужно измерять уровень СО₂ и сигнализировать о повышенном уровне заранее.
Концентрация СО₂ измеряется в PPM. По сути, PPM это количество молекул СО2 на миллион молекул другого газа, в нашем случае воздуха. Например, если концентрация равна 400ppm, то это значит, что в измеряемом объёме на каждый 1млн молекул приходится 400 молекул измеряемого газа.
Раз уж мы заговорили о концентрации, то нужно понять, при какой концентрации начинают наступать негативные эффекты. И тут нам поможет вот такая красивая картинка:
Концентрация СО₂ vs последствия
Как видно из шкалы уже при 1000 PPM начитают проявляться первые негативные эффекты, соответственно при этом значении нужно начинать наводить панику. При достижении 2500 PPM начинаются уже более серьёзные последствия, а 5000 PPM гарантировано заставят вас покинуть помещение и сходить за очередной кружкой чая.
▍ Требования
Хорошо, у нас теперь есть концентрация, от которой мы можем отталкиваться при разработке. Теперь определимся с требованиями к устройству:
- Компактность. Я думаю никто не хочет видеть у себя на полке огромную коробку.
- Работа от встроенного АКБ, не люблю батарейки.
- Автономность. Зарядил и поставил на полку, никаких проводов.
- Пищалки зло, будем использовать мигающие светодиоды для индикации.
- Светодиоды должны иметь достаточную яркость, чтобы их заметить, но не освещать всю комнату по ночам.
- Два уровня индикации: нежелательная и опасная концентрации.
- Дисплей. Хорошо бы знать текущую концентрацию в помещении.
- Возможность установки на стену или просто поставить на полку.
В целом достаточно стандартные требования.
▍ Датчик СО₂
Начнём с самого основного — измерительного модуля. Разумеется, мы возьмём готовый OEM вариант, т.к. разрабатывать свой ну это крайне сложная задача и требует наличия людей намного умнее меня. Мой выбор пал на NDIR датчик MH-Z19B, т.к. его проще всего достать:
MH-Z19B
Относительно остальных компонентов это будет самая дорогая часть устройства не только в плане цены, но и в плане потребления. Давайте глянем его характеристики:
- Измеряемый диапазон 0-5000 PPM. Нам это подходит.
- Период измерения 1 секунда и он не настраивается. Это очень плохо, т.к. мы не можем снизить потребление датчика путём уменьшения частоты измерений.
- Напряжение питания 5В. Тут нужен будет повышающий DC-DC, т.к. работать мы будем от Li-ion АКБ (3.7В).
- Интерфейс коммуникации UART.
- Среднее потребление 30мА. Главный пик тока приходится на вспышку лампочки во время измерения концентрации. Это на самом деле очень печально, хотелось бы меньше, но других бюджетных вариантов нет, которые можно пойти в магазин и купить.
Остальные характеристики нас особо не интересуют. Т.к. датчик китайский, то разумеется там погрешность измерений 0% (сарказм).
Давайте немного поговорим о том, как он работает. Упрощённая схема выглядит так:
Схема работы датчика газов
Молекулы газа имеют свойство поглощать определённую длину волны. Разумеется, для каждого газа поглощаемая длина волны разная, это позволяет исключить влияние других газов на результат измерения.
В измеряемый объём помещается газ, и делается вспышка источником света. Свет проходит через измеряемый газ, и излучение поглощается молекулами по пути к детектору. Перед детектором стоит фильтр, который пропускает только нужную нам длину волны. В данном случае это длина волны, поглощаемая молекулами СО₂, и, если я не ошибаюсь, это 4.26мкм (поправьте, пожалуйста, если я неправ). Чем больше молекул СО₂ в измеряемом объёме, тем больше поглощение — мы получаем зависимость поглощения от концентрации.
Выглядит всё достаточно просто, но на самом деле это очень сложное устройство не только в плане обработки данных, но и в плане изготовления. По пути до детектора излучение должно поглощаться только молекулами газа, соответственно требуется обеспечить максимальное отражение нужного нам излучения от внутренних стенок ёмкости с газом. Это одна из многих проблем при производстве подобных устройств.
Продолжим ковырять наше устройство дальше.
▍ Выбор железа
Я решил использовать STM32F030 в качестве микроконтроллера для обработки данных с датчика. Да, у STM есть L версии микроконтроллеров, которые в разы меньше потребляют, но и цена у них в разы выше. На самом деле у меня их целая коробка и нужно их куда-то определять.
В качестве DC-DC для питания устройства возьмём L6920DTR. Не очень дорогой и требует минимум обвязки, да и эффективность заявлена неплохая — порядка 95%. С питанием тут не всё просто. Для питания датчика нужно 5В, а для питания МК 3.3В. Соответственно проще всего будет поднять напряжение до 5В при помощи DC-DC, а для питания МК опустить напряжение обычным линейным стабилизатором. При токах питания МК потери на стабилизаторе будут минимальные.
Для зарядки АКБ используем микросхему STC4054GR. Она имеет все необходимые нам плюшки в виде установки тока заряда и индикации. Сам аккумулятор выберем позже после разработки ПП, чтобы это всё дело влезло в корпус с максимальным использованием свободного места.
Дисплей возьмём 0.96 дюйма на базе SSD1306:
Дисплей на базе SSD1306
▍ Разработка железа
Вжух и схема готова.
Местами есть конденсаторы на 25В, которые стоят в цепи 3.3В — это нормально. Дело в том, что при разработке схем я сначала отталкиваюсь от своих запасов и только потом иду в магазин.
Схема (часть 1)
Схема (часть 2)
В целом тут нечего особенного. Самая печатная плата получилась достаточно компактной и имеет размеры 55х45. Около 50% занимает сам датчик СО₂:
3D модель печатной платы
▍ Разработка корпуса
Тут уже интереснее — нужно всё это уместить в компактном виде. В качестве материала будем использовать PLA пластик и FDM печать. Размеры в сборе 51х64х30 (ШхВхГ). Корпус получился достаточно толстым, но в целом приемлемо и на полке смотрится неплохо:
3D модель корпуса
На лицевой части имеются 3 отверстия для светодиодов. Два верхних для индикации концентрации: жёлтый и красный. Жёлтый означает, что пора открыть окно, красный — пора бежать :) Третье отверстие для светодиода индикации процесса заряда АКБ.
Также спереди и снизу имеются отверстия для циркуляции воздуха. Изначально я хотел сделать принудительную циркуляцию при помощи вентилятора. Микроконтроллер периодически запускал бы его и продувал датчик, но впоследствии отказался от этого. Не хочу, чтобы ночью он внезапно начал шуршать вентилятором, да и механические детали не очень надёжны (скрипят, шумят, в них постоянно что-то попадает).
Сзади имеется паз под саморезвинтболтгвоздь, на который его можно повесить на стену где-нибудь в офисе, например.
Устройство собирается как бутерброд. Сначала ставятся светодиоды и дисплей. Дисплей крепится за направляющие путём их оплавления, т.е. снять его без разрушения элементов корпуса не получится. Не очень хорошее решение, но если подумать «А зачем его вообще снимать оттуда?»
Крепление дисплея к корпусу
Дальше устанавливается основная плата. Она уже крепится на саморезы, т.к. в процессе отладки приходилось её часто снимать.
Установка ПП в корпус
Дальше ставится АКБ на плату и прижимается задней крышкой. Кстати, АКБ я выбрал на 1200мА — это самый толстый АКБ, который влез в этот корпус.
АКБ
Задняя крышка устройства
▍ Код, код, код
Я не буду описывать весь код (драйвер 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, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79. На неё датчик отвечает 0xFF, 0x01, CONC, CONC. В последних двух байтах содержится нужное нам значение концентрации. В главном цикле это значение отображается на дисплей и принимается решение о включении нужных светодиодов. В целом всё достаточно просто.
▍ Результаты
Устройство получилось достаточно компактным, вполне можно брать с собой и носить в кармане. По потреблению немного скудно — 3 дня без подзарядки. Очень хотелось бы уменьшить частоту измерений до 0.1Гц, это позволило бы снизить потребление в разы, но увы. Ток заряда 300мА и заряжается до 100% за несколько часов.
Итоговая стоимость без учёта моего времени порядка 2000р (2021 год), из которых 60% это стоимость датчика СО₂.
В целом устройство выполняет свою функцию — напоминает о необходимости проветрить помещение и ситуаций вида «тут уже дышать нечем» стало намного меньше. Почему они не исчезли вовсе? «Вот сейчас допишу этот кусок функционала и прервусь», «Да-да ещё чуть-чуть» и.т.п (чтобы меня выгнать из-за компа, это нужно постараться) :)
Всем спасибо за внимание и дышите свежим воздухом.
Автор: Алексей