MiCS-VZ-89 Air Quality Sensor

A low power sensor that can measure at the same time both carbon dioxide (CO2) and Volatile Organic Compounds (tVOC) is probably too good to be true. Yet the MiCS-VZ-89 does this, among some other appealing features like 3.3V supply (in the T variant), I2C communication, calibration-free high sensitivity and a small size factor. Its price is also a plus.
I got the chance to give it a closer look while designing the Portable Environmental Monitor project of this year’s Hackaday Prize .
My circuit’s I2C line was already serving a BMP180 temperature and barometric pressure sensor, but the MiCS_VZ_89T got along just fine. Using the datasheet, I was able to find the I2C Address (set to 0x70), its commands and parameters. The precious air quality data is returned as a 6 bytes array:

3.2. 0b00001001: Get VZ89 status:
This command is used to read the VZ89 status coded with 6 bytes:
D1 (8bits) represent the CO2-equivalent signal value [13..242].
D2 (8bits) represent the VOC-short signal value [13..242].
D3 (8bits) represent the VOC-long signal value [13..242].
D4 (8bits) represent the 1st byte of raw sensor resistor value (LSB).
D5 (8bits) represent the 2nd byte of raw sensor resistor value.
D6 (8bits) represent the 3rd byte of raw sensor resistor value (MSB).

Full details in this datasheet: MICS-VZ-89-I2C-specs-rev-A .
The code implementation is as follows:

#define VZ89_ADDR (0x70<<1) //0x70 default I2C address

#define VZ89_CMD_SETPPMCO2 	0x8	// This command is used to send a ppmCO2 value from an analyser to the VZ89 in order to recalibrate its outputs.
#define VZ89_CMD_GETSTATUS	0x9	// This command is used to read the VZ89 status coded with 6 bytes:
// i2c read
void VZ89::readmem(uint8_t reg, uint8_t buff[], uint8_t bytes) {
	uint8_t i =0;
	i2c_start_wait(VZ89_ADDR | I2C_WRITE);
	i2c_rep_start(VZ89_ADDR | I2C_READ);
	for(i=0; i

Where read raw returns the 6 bytes carrying what we need. A quick test gave me the three bytes starting at 13 right away, and spraying a little butane from a lighter showed the numbers change.
All good. Except I didn't have a way to interpret them into actual gas concentrations. I contacted the manufacturer, and they replied (in less than 24hours), indicating where I can find the information. The interpretation of the I2C data can be seen here: Preliminary Datasheet MiCS-VZ-86 and VZ-89 rev 6 (page 3).
The instructions were translated into code as follows:

void VZ89::readRaw(uint8_t rawData[6]) {
	static uint8_t buff[6];
	memset(buff, 0, sizeof(buff));

	//read raw temperature
	readmem(VZ89_CMD_GETSTATUS, rawData, 6);

During “Functional Test Mode” only “Raw sensor” and “VOC_short” data are available. “VOC_short” is
an image of sensor reactivity and can then be used for functional test.
Out of this initial period, the device will have the I2C data CO2 equivalent [ppm] and tVOC equivalent
referred to the isobutylene sensitivity unit [ppb].

D1:Data_byte_1: CO2_equ: [13…242] -> CO2_equ [ppm] = (D1 -13) * (1600/229) + 400
D2: Data_byte_2: VOC_short [13…242]
D3: Data_byte_3: tVOC: [13…242] -> tVOC [ppb] = (D3 -13) * (1000/229)
D4: Data_byte_4: Raw sensor first byte (LSB)
D5: Data_byte_5: Raw sensor second byte
D6: Data_byte_6: Raw sensor third byte (MSB) -> Resistor value [W] = 10*(D4 + (256*D5) + (65536*D6))

return CO2 equivalent [ppm] and tVOC equivalent referred to the isobutylene sensitivity unit [ppb].
bool VZ89::read(float *co2, uint8_t *reactivity, float *tvoc) {
	uint8_t data[6];
	if (data[0] < 13 || data[1] < 13 || data[2] < 13) return false;
	// convert data to meaningful values
	*co2 = (data[0] - 13) * (1600.0 / 229) + 400; // ppm: 400 .. 2000
	*reactivity = data[1];
	*tvoc = (data[2] - 13) * (1000.0/229); // ppb: 0 .. 1000

	//uint32_t resistor = 10 * (data[3] +256 * data[4] + 65536 * data[5]);
	return true;

And the result produced the CO2 concentration as ppm, respectively the tVOC as ppb:

Get the code

Mics-vz-89t code is released as open source. Get it here or on Github.

This article has 13 Comments

  1. This seems a very nice sensor, I am trying to use MG811 co2 sensor for a project, but this seems a better alternative. I couldn’t find many distributors for the sensor, where did you ordered it?

  2. Radu, Thanks for sharing! useful code that worked right away!

    For those who would like to see the signals as reference for ‘Reading VZ89 Status’ , http://snag.gy/wYrh4.jpg (it’s not in the datasheet)

    I am evaluating this sensor to replace the iAQ-Cores sensor from appliedsensors.



  3. Dear Radu
    My name is Minh and I come from Viet Nam. I saw your project on: https://hackaday.io/project/4977-portable-environmental-monitor. Your project was amazing and it is very useful about environment.
    I read carefully your project but when I access in open source on links:
    or https://github.com/radhoo/uradmonitor_d/tree/master/enclosure
    and https://github.com/radhoo/uradmonitor_d/tree/master/pcb/102
    and https://github.com/radhoo/uradmonitor_d/tree/master/pcb/102/gerber
    This system was shut down and I don’t know why?
    Can you share open source this project for me? Thank you for your helping.
    Best regard, Minh Hoang.

  4. How to print CO2 and VOC data on OLED display by using MICS VZ-89T interfaced with Arduino Uno3.

    I have worked on VZ-89T and OLED individually both are working fine. but when I want to display data on OLED its not printing anything.

  5. You’ll need to do your own debugging for that, it’s hard for me to guess what goes wrong there, it might be something related to your I2C channels. Hope you manage to fix it soon, if you wish include a few pics with your setup.

  6. Hi Radu Motisan, very nice tutorial and thanks for that. I have tried similar logic on Arduino with the same sensor. I get TVOC value as 242 (constant always) all the time, even long after warm up time of the sensor. 242 is the highest value of the sensor and it really does not make sense (as I’m still alive, pun intended) as I’m working in the controlled environment. Also CO2 measurements are rarely correct and most of the time I get around 400 PPM which does not match with standard measuring device AirMentor I’m using. I’m validating both CO2 and TVOC values with standard device AirMentor and CO2 values match rarely but TVOC values never match. Please enlighten me more about this issue. Looking forward for your reply. Thanks!!

  7. Hello Vinay. The CO2 is not a real measurement, but an indirect approximation from the tVOC levels. 400ppm means initialisation threshold, and again is not a real value. As for tVOC being 242, you might have damaged your sensor, I see no other explanation.

    Nevertheless VOC doesn’t have a measurement unit, so you can’t really compare it from a device to another (MiCS-VZ-89 vs AirMentor). That simply doesn’t make sense.

    CO2 is a different story, as you have clear concentration measurements (ppm).

    Hope this helps.

  8. Hi Radu,

    Thanks for sharing! I wanted to ask your opinion on this sensor. I’ve now got it running but I’m not sure how relevant its tVOC sensor is, or even the CO2 sensor given your previous comment. It is factory calibrated, but without much insight into what levels of tVOCs are impacting you it seems a little useless.. ? e.g. distinction between formaldehyde and benzene is probably important?

    I that you’ve worked with different chips, and curious to hear where this one sits. e.g. compared to bosch or electrochemical sensors.


  9. SGX is a well established sensor manufacturer, honestly I was surprised they came up with this sensor. It is clearly better than electrochemical sensors, since it is compensated to temperature and humidity. As you know, the regular analogue electrochemical sensors have a considerable drift in readings and you need to compensate that by using additional temperature and humidity readings.

    On the cons side, the stability of the MiCS-VZ-89 was really poor. In about one week, without any contamination, simply running the sensor in normal room conditions, the values drifted towards to top interval. Obviously, this can’t be used not even for relative observations, like comparing readings from one unit to another. (See early build vairants of my uradmonitor D to see the sensor in action).

    The BME680 on the other hand is a little gem, working great where the MiCS failed. But with VOC, the lack of a standardised M.U. is somewhat of a problem. We only got scores, trends, and relative comparisons.

    Hope this helps.

Leave a Reply