Winsen ZE08-CH2O

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Что делать, если в воздухе появился неприятный запах, а под рукой есть Ардуина? Попробуем разобраться...

sadman41
Онлайн
Зарегистрирован: 19.10.2016
Большая просьба не комментировать этот пост, так как возможны дополнение и актуализация сведений, приведенных в нем.
 
Формальдегид - органическое соединение, бесцветный газ с резким запахом, хорошо растворимый в воде, спиртах и полярных растворителях. Ирритант, контаминант, канцероген, токсичен.
 
Это всё, что мне, почетному троешнику химических наук, понятно из статьи "Формальдегид". Однако, слова "канцероген", "токсичен" и фраза "Основная часть формальдегида используется для производства фенолформальдегидных, карбамидформальдегидных и меламинформальдегидных смол, которые далее идут на производство ДСП, фанеры и мебели", а так же наличие под боком соответствующего производства, всегда пугали. Поэтому приобретение датчика измерения концентрации формальдегида всегда представлялось мне неплохим вложением в дело самообмана и самоуспокоения. 
 
Как уже можно понять из вводных данных - по химическим аспектам мне пояснить нечего. Поэтому перехожу к практическо-инженерной части.
 
Доступные рядовому пользователю (Aliexpress-шопперу) сенсоры CH2O являются электрохимическими: специально обученный электролит при нагреве вступает в химическую реакцию с обнаруживаемым газом, что вызывает пропорциональное изменение протекающего между электродами тока. Ток затекает (но не всегда) в волшебную микросхему, которая измеряет его силу и выдаёт наружу понимаемый Ардуинообразными системами сигнал либо протокол: DAC, PWM, UART, I2C и пр. 
 
 
 
 
Из этого пояснения следует, что:
1) пользоваться датчиком относительно легко;
2) химические фокусы с электролитом выведут датчик из строя через некоторое время (working life).
 
Последнее, конечно, очень расстраивает.
 
Датчики формальдегида включены в состав измерителей качества воздуха уровня "выше среднего" (осторожно: субъективное мнение). Например, такого представителя "заводского китая", как AirMaster AM7 ($220). На немногочисленных картинках видно, что в нём установлен сенсор (черный либо белый круглый четырехухий корпус) производства Dart Sensors LTD, располагающейся в Великобритании. Некоторые китайские производители с гордостью об этом пишут.
 
 
Однако, ссылки, оставшиеся в документации на датчики, приводят на на сайт некоей компании ProSense, расположенной в Shenzhen City, адрес же британского офиса Dart Sensors LTD внезапно становится неотличимо похож на офис продаж ProSense. Такой вот он - чистокровный британский производитель.
 
Впрочем, на этом запутанная история не заканчивается...
 
Итак, моим подопытным стал сенсор от широко известной в узких кругах компании Winsen, знакомой заядлым самодельщикам по такому хиту, как MH-Z19B.  Имя изделия - ZE08-CH2O. У него имеется более дорогой брат ZE07-CH2O (но у него нет защиты от переполюсовки по питанию), который мне показался подозрительно cхожим c WZ-H3-K от компании Prosense. Сравнив же протокольную часть ZE08-CH2O datasheet, WZ-H3-K datasheet и, например, WZ-S datasheet я пришёл к выводу, что с большой долей вероятности папа у них был общий.
 
Посему будем считать, что ZE08-CH2O - родня "британскому" сенсору, который стоит в достаточно дорогих измерителях качества воздуха и доверимся его показаниям. А ещё - запомним, что можно подменять один другим. Протокол-то один и тот же.
 
ZE08-CH2O представляет из себя калиброванное и готовое к использованию изделие (модуль), реагирующее на основной газ CH2O и ряд иных (не знаю, как правильно перевести "Cross interference gas"): NH3, C6H6, Cl2, CH3CL, C3H6O, C2H4O, SO2, H2S, H2, C2H5OH, CH3OH4, CH2O. Все эти жуткие сочетания букв и цифр мне не особо понятны, за исключением, разумеется, C2H5OH. Они несколько изменяют ту концентрацию, о которой отчитывается датчик. Во всяком случае под воздействием паров "жидкости для протирки контактов" выдаваемая концентрация на минуту стала равна 5ppm.
 
Выглядит датчик так:
 
Время жизни (working life) - 2 years(in air). Что означает загадочное китайское "In Air" - пока не понял. Родственники от ProSense и Dart имеют, согласно документации, 3 и 5 лет жизни in air.
 
Питание модуля осуществляется напряжением 3.7V...9V, но TTL уровень интерфейса UART - 3.3V и он нетолерантен к 5V. Просто  издевательство какое-то. Обещанный в разделе "Pin Description" руководства PWM, к сожалению, не обнаружен.
 
Разъём подключения с шагом 1.27мм, в комплекте с модулем иногда поставляется (уточняйте у продавца) кабель, который можно отрезать с одной стороны и использовать для заделки в человеческий разъём шага 2.54мм. Проверка выявила, что кабель от MH-Z19B v2.0 (см. ниже информацию о совместимости) точно такой же, как и для ZE08-CH2O. Отсюда небольшой хинт: один кабель можно разрезать пополам и спокойненько подключить оба датчика в свою конструкцию. 
 
Так же очень удачно, что по последней парижской моде сенсор умеет работать и включается в активном режиме, т.е. периодически сам отсылает пакет данных микроконтроллеру. Таким образом - на согласователе уровней можно сэкономить, если подойти к вопросу аккуратно.
 
В интернетах мне не удалось сходу найти беспроблемные библиотеки под Arduino, поэтому самостоятельно изобразил функционал получения данных:
 
#include <SoftwareSerial.h>

// Activate debug messages output
#define DEBUG_ON

#define WINSEN_ZE08_CH2O_UART_TX_PIN                            (2)
#define WINSEN_ZE08_CH2O_UART_SPEED                             (9600)
#define WINSEN_ZE08_CH2O_UART_DEFAULT_READ_TIMEOUT              (2500UL)
#define WINSEN_ZE08_CH2O_UART_START_BYTE                        (0xFF)
#define WINSEN_ZE08_CH2O_UART_GAS_NAME                          (0x17)
#define WINSEN_ZE08_CH2O_UART_GAS_UNIT                          (0x04)

// We can use 0xFFFF as error code because sensor's ppb range is 0x00...0x1388 (0...5000)
#define WINSEN_ZE08_CH2O_READ_ERROR_CODE                        (0xFFFF)

// Store all metric's value in the pretty struct
typedef struct {
  uint8_t gasName;
  uint8_t gasUnit;
  uint8_t noDecimalByte;
  uint8_t concentrationHighByte, concentrationLowByte;
  uint8_t fullRangeHighByte, fullRangeLowByte;
  uint8_t checkSum;
} Ze08Ch2OData_t;


uint16_t getZe08Ch2OMetric(const uint8_t _rxPin, const uint8_t _txPin) {
  uint8_t headerDetected = false,
          writePos = 0x00,
          checkSum = 0x00;
  uint16_t rc = WINSEN_ZE08_CH2O_READ_ERROR_CODE;
  uint32_t readStartTime;;

  Ze08Ch2OData_t Ze08Ch2OData;
  uint8_t* ptrRawBuffer = (uint8_t*) &Ze08Ch2OData;

  SoftwareSerial swSerial(_rxPin, _txPin);
  swSerial.begin(WINSEN_ZE08_CH2O_UART_SPEED);
  readStartTime = millis();
  while ((sizeof(Ze08Ch2OData) > writePos) && (WINSEN_ZE08_CH2O_UART_DEFAULT_READ_TIMEOUT > millis() - readStartTime)) {
    if (!swSerial.available()) {
      continue;
    }
    ptrRawBuffer[writePos] = swSerial.read();
#if defined(DEBUG_ON)
    //Serial.print("currentChar (# "); Serial.print(writePos);  Serial.print(") => 0x"); Serial.println((byte) ptrRawBuffer[writePos], HEX);
#endif
    if (!headerDetected) {
      headerDetected = (WINSEN_ZE08_CH2O_UART_START_BYTE == ptrRawBuffer[0x00]);
    } else {
      writePos++;
    }
  }
  // Reading is not finished sucessfully: not all bytes recieved or wrong Gas ID / Unit ID contained in the packet
  if (writePos < sizeof(Ze08Ch2OData) ||
      WINSEN_ZE08_CH2O_UART_GAS_NAME != Ze08Ch2OData.gasName ||
      WINSEN_ZE08_CH2O_UART_GAS_UNIT != Ze08Ch2OData.gasUnit) {
    goto finish;
  }
  // Calculate checksum.
  //Start byte & recieved checksum is not taken in account. The first one is dropped in the read procedure and the second one just will skipped in calculation
  for (uint8_t i = 0x00; i < sizeof(Ze08Ch2OData) - 1; i++) {
    checkSum += ptrRawBuffer[i];
#if defined(DEBUG_ON)
    Serial.print(" 0x"); Serial.print(ptrRawBuffer[i], HEX);
#endif
  }
  checkSum = (~checkSum) + 1;
#if defined(DEBUG_ON)
  Serial.print("\nRecieved / calculated checksum: 0x");
  Serial.print(Ze08Ch2OData.checkSum, HEX); Serial.print(" / 0x"); Serial.println(checkSum, HEX);
#endif
  if (checkSum == Ze08Ch2OData.checkSum) {
#if defined(DEBUG_ON)
    // rc = (Ze08Ch2OData.fullRangeHighByte << 0x08) + Ze08Ch2OData.fullRangeLowByte; Serial.print("Full range (ppb): "); Serial.println(rc);
#endif
    rc = (Ze08Ch2OData.concentrationHighByte << 0x08) + Ze08Ch2OData.concentrationLowByte;
  }

finish:
  swSerial.~SoftwareSerial();
  return rc;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Winsen ZE08-CH2O sensor reading demo (UART, active mode)"); ;
}

void loop() {
  delay(2000);
  uint16_t ch2O = getZe08Ch2OMetric(WINSEN_ZE08_CH2O_UART_TX_PIN, WINSEN_ZE08_CH2O_UART_TX_PIN);
  Serial.print("["); Serial.print(millis()); Serial.print("] CH2O: ");

  if (WINSEN_ZE08_CH2O_READ_ERROR_CODE != ch2O) {
    Serial.print(ch2O); Serial.print(" ppb, \t"); Serial.print(ch2O / 1000.0, 3); Serial.println(" ppm");
  } else {
    Serial.println("read error");
  }
}

Отчёт с испытаний - несколько секунд подержал над чужой бутылкой с чем-то "протирающим" (на этикетке написано "Раствор антисептический, медицинский. Этанол 95%", хозяин утверждает, что содержимое соответствует):

Winsen ZE08-CH2O sensor reading demo (UART, active mode)
[2611] CH2O: 0 ppb,	0.000 ppm
[4827] CH2O: 0 ppb,	0.000 ppm
[7043] CH2O: 0 ppb,	0.000 ppm
[9261] CH2O: 0 ppb,	0.000 ppm
[11476] CH2O: 496 ppb,	0.496 ppm
[13692] CH2O: 1459 ppb,	1.459 ppm
[15908] CH2O: 2643 ppb,	2.643 ppm
[18124] CH2O: 3828 ppb,	3.828 ppm
[20340] CH2O: 4840 ppb,	4.840 ppm
[22554] CH2O: 4692 ppb,	4.692 ppm
[24770] CH2O: 4240 ppb,	4.240 ppm
[26988] CH2O: 3626 ppb,	3.626 ppm
[29204] CH2O: 2870 ppb,	2.870 ppm
[31420] CH2O: 2042 ppb,	2.042 ppm
[33636] CH2O: 1322 ppb,	1.322 ppm
[35852] CH2O: 760 ppb,	0.760 ppm
[38068] CH2O: 483 ppb,	0.483 ppm
[40284] CH2O: 270 ppb,	0.270 ppm
[42500] CH2O: 116 ppb,	0.116 ppm
[44716] CH2O: 39 ppb,	0.039 ppm
[46931] CH2O: 17 ppb,	0.017 ppm
[49147] CH2O: 0 ppb,	0.000 ppm
[51363] CH2O: 0 ppb,	0.000 ppm

 

В процессе изучения справочных материалов выяснилось, что датчики MH-Z14A, MH-Z19B v2.0 (опознаётся по наличию доп.разъема на отламываемой площадке текстолита) и ZE08-CH2O имеют практически одинаковую распиновку одного из выходных разъёмов (в MH-Z19B, например, не выведен PWM, но UART/питание совпадают у всех трёх), формат пакетов обмена по UART в пассивном режиме работы (запрос-ответ), алгоритм расчёта контрольной суммы (несмотря на то, что приведённые в datasheet примеры выглядят по-разному). Это факт можно учитывать при сборке измерителя уровня CO2+формальдегид.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Comment #1 reserved for future use.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Comment #2 reserved for future use.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Вот интересно, как авторы тем представляют себе эту "просьбу не комментировать".

Вот, скажем, прочитал я основной пост темы.

Все 3-4 экрана.

Потом автору захотелось добавить на 2-м и 4-м экранах по одному предложению.

Т.е. по мнению автора я буду перечитывать эти 4 экрана, чтобы найти изменения?

Мне кажется, сама эта идея - дополнять первый пост - порочная. Все дополнения следует делать отдельными постами. А если тема вдруг, наконец, обретет свой окончательный вид, то перевыложить все в последнем посте, либо создать новую - в окончательном варианте.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

А у меня другое соображение: пришёл читатель в тему, ознакомился с первопостом, а потом ещё нужно все 100500 страниц изучить, чтобы узнать, где была допущена опечатка, например? 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Не, ну как вариант:

1. Исправления в основном тексте выделяются цветом. Через пару дней цвет меняется, а через неделю - становится обычным.

2. Исправления дублируются новым постом.

 

И, кстати, как я могу узнать:

- что в текст пробралась опечатка?

- что она была исправлена?

- в каком месте основного поста ее следует искать?

sadman41
Онлайн
Зарегистрирован: 19.10.2016

При всём уважении, andriano, мне бы не хотелось переводить общение по теме в спор о том, какой способ нарезания подаваемого за стол хлеба более правильный: вдоль, поперёк или по диагонали. Вашу позицию я понял (хотя и не во всём согласен), свою объяснил.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Это все понятно.

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

Ты - услышал. Те, кто будет создавать подобные темы (а в том, что они нужны и очень полезны, ни у кого сомнения нет) - тоже, надеюсь, услышали. Так что теперь, по-хорошему, надо бы посты с №4 по №8 удалить. Ну или постепенно перевести их в резерв аналогично №2-3.

Еще раз прошу прощения.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

В отвлечённых было что-то по оформлению постов - можно туда переместиться.

Coldman
Offline
Зарегистрирован: 18.05.2019

Спасибо, заработало сразу как надо, вот только иногда проскаивает:

02:13:56.114 -> [119054] CH2O: 47 ppb,	0.047 ppm
02:13:58.107 -> [121055] CH2O: 47 ppb,	0.047 ppm
02:14:00.099 -> [123056] CH2O: 65535 ppb,	65.535 ppm
02:14:02.094 -> [125056] CH2O: 47 ppb,	0.047 ppm
02:14:04.093 -> [127057] CH2O: 47 ppb,	0.047 ppm

 

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Да, вижу ошибку. До компьютера доберусь - исправлю.

...ошибка, похоже, по стечению обстоятельств, не влияла на корректность результата. Тем не менее - код в посте немного изменил, добавил отладочного вывода. Нужно проверить, что из датчика прилетает (строки #70 и #71).

Впрочем, я вижу, что вывод отличается от моего - может Вы неправильно рассматриваете return code функции? 0xFFFF, он же 65535 - код ошибки, который говорит о том, что сенсор не ответил или ответил некорректным пакетом.