Skip navigation
> RoadTest Reviews

Omron Sensor Evaluation Board 2JCIE-EV - Review


Product Performed to Expectations: 10
Specifications were sufficient to design with: 10
Demo Software was of good quality: 8
Product was easy to use: 8
Support materials were available: 10
The price to performance ratio was good: 7
TotalScore: 53 / 60
  • RoadTest: Omron Sensor Evaluation Board 2JCIE-EV
  • Buy Now
  • Evaluation Type: Development Boards & Tools
  • Was everything in the box required?: Yes
  • Comparable Products/Other parts you considered: Omron 2JCIE-BU01, 2JCIE-BL01, Individual Breakout Boards (e.g. Pmods, Click, Grove, etc).
  • What were the biggest problems encountered?: Demo code does not follow ordinary Arduino library convention.

  • Detailed Review:

    Omron 2JCIE-EV-AR1 Sensor Evaluation Board +
    Omron B5W-LD0101 Air Quality Sensor RoadTest

    By Gough Lui – August 2020


    In the modern “internet-of-things” paradigm, it seems that sensor networks are a common theme. Owing to the propensity of small, low-cost, low-power sensors which can detect acceleration, gyroscopic forces, magnetic fields, temperature, humidity, air-pressure, sounds, gas and particulate matter concentrations, one common application is for use in indoor environment monitoring for comfort and wellbeing. Of course, such sensors can also be deployed in many different areas, including predictive maintenance scenarios such as Industry 4.0. It really depends on your imagination.


    But if you have little experience in designing sensor platforms and just want to get a prototype off-the-ground, there’s only a few choices available. You could opt for individual break-out modules for the sensors you’re interested in, but be left with a jumble of wires to sort out and perhaps not be well supported on the software front. Or you could opt for a more “finished” product such as the Omron 2JCIE-BU01 or 2JCIE-BL01 I reviewed earlier, only to have to interface with the sensors through USB or Bluetooth Low-Energy rather than doing so natively, which comes with potential performance penalties and the need to implement your own interface code.


    Omron’s 2JCIE-EV series of boards are a happy “middle-ground”, providing a neat PCB design integrating a range of useful sensors and demonstration code to get a prototype off-the-ground quickly. Thanks to element14 and Omron, I am able to RoadTest the 2JCIE-EV-AR1 which is intended for Arduino MKR-style boards (e.g. Vidor 4000, Wi-Fi 1010, ZERO) along with the B5W-LD0101 air quality sensor. They are also available for Raspberry Pi and Adafruit Feather type development boards as well.



    Introduction to Indoor Air Quality Sensing

    Because of recent concerns about indoor air quality, a variety of sensors are commonly employed to gauge the condition and whether it is safe or comfortable for occupants. This can be especially important when many people gather in an enclosed space.


    Sensors most applicable for indoor air quality include:

    • Temperature – to identify the potential for heat stroke and determine if occupants are comfortable.
    • Relative Humidity – mainly for comfort, as occupants may be sensitive to air that is too dry or too moist.
    • Volatile Organic Compounds – these are emitted by furnishings and new materials and can lead to sick building syndrome. Continuous exposure could cause acute health effects.
    • CO2 or CO2-eq – to gauge whether ventilation is sufficient to bring in sufficient fresh air to avoid the potential for impaired function due to high carbon-dioxide concentrations which could arise due to too many people in an enclosed space or due to combustion of fossil fuels in unflued heaters.
    • CO – for safety, to identify dangerous build-up of carbon monoxide which could cause impairment or unconsciousness.
    • Particulate Matter – to detect the presence of fine dust and particles which can cause respiratory discomfort and have been linked to a number of different health conditions.


    The Omron 2JCIE-EV-01 allows for the evaluation of temperature, relative humidity and particulate matter sensors. In the case of particulate matter, an external B5W-LD0101-01 sensor and harness is required. This particular sensor is a “dust” sensor which uses LED detection, providing outputs for two different particle size thresholds. The sensor itself is simpler in construction than a laser-based particulate matter sensor which is much more expensive and often uses a fan, resulting in some noise. Other sensors can be incorporated into an Arduino solution with the expansion I2C connector on the MKR board.


    Unboxing Introduction

    The Omron 2JCIE-EV-AR01 board (~AU$175.08) is packaged in a small cardboard box. The front side of the box is a green and white line-art depiction of the board, while the rear provide links to the product information available on their website. The side of the box claims the board to be Made in Japan, with a serial number label. Disposal warnings are provided on the other side, but as is common with evaluation boards, there is no safety approval marks.

    The board comes with a stern warning not to make available or sell to third parties any product, system or service in which this product is integrated – likely due to a lack of safety certifications. Inside the box are two bubble-wrapped packages, one containing a pair of header pins, the other containing the evaluation board.

    The board itself contains a number of sensors, including:


    Curiously, the majority of the sensors on the board do not come from Omron, thus we are actually evaluating the performance of third-party parts. The board itself does not guarantee the datasheet specifications for these sensors will be maintained, due in part to the possibility of heating from the microcontroller causing elevated temperature measurements. Aside from the above components, there are a number of level shifters for digital and analog signals as well as an RGB LED and drive circuitry as a visual display. The underside has some jumpers for configuration purposes.


    The key feature, however, is the presence of a number of external sensor sockets which accept connections to a variety of Omron sensors. This is especially useful if you’re interested in evaluating the supported MEMS thermal, MEMS flow, light convergent reflective and air quality sensors from Omron.


    Based on what the board contains and what it does, the price seems to be somewhat high but perhaps acceptable for those who are looking for a rapid route to getting a demo up and running or for a one-off evaluation of the onboard or compatible external sensors in a commercial setting. I cannot see hobbyists opting for it or it becoming a high-volume product, however.


    For this RoadTest, I opted to test the Omron B5W-LD0101 Air Quality Sensor as the external sensor, as I have an interest in particulate matter sensing. This unit was separately ordered (~AU$22.62) and is just one of a number of sensors supported by the 2JCIE-EV-AR01.

    The B5W-LD0101 consists of a black plastic chamber which houses the LED light source and sensor and is shaped with a unique air-flow path. This is attached to a PCB which contains some analog circuitry to provide a pulse output based on particle detection. One trimpot on the rear is pre-set from the factory and should not be touched.

    Unlike some other particulate monitor sensors which contain a fan for airflow, this one uses a resistor heating up the air instead, as is used in some thermostats. This method ensures the unit is silent in operation, however, also means that the unit must be mounted vertically to ensure accurate readings. The exhaust is to the left side of the unit. The design of the sensor provides two locations for mounting screws to hold the unit, however, one awkward aspect for prototyping is the tall electrolytic capacitor on the PCB which is vulnerable to being knocked off.


    The sensor must be connected to the 2JCIE-EV-AR01 through the use of an appropriate harness. These are ordered separately (~AU$18.01) and have a length of approximately 50cm, using the appropriate connectors to be a plug-and-play experience as long as the user had consulted the datasheets to understand which harness part number is required for their sensor of choice.

    Finally, for the purposes of this RoadTest, I was supplied with an Arduino MKR Wi-Fi 1010. Those who are purchasing the 2JCIE-EV-AR01 should note that they will need to supply a compatible board themselves as it is not included.

    The MKR Wi-Fi 1010 is a compact microcontroller board featuring a Microchip ATSAMD21 32-bit ARM Cortex M0+ clocked at 48MHz, paired with an ESP32-based Ublox NINA-W102 module for wireless connectivity and an ATECC508 secure element.

    The board comes pre-populated with stackable headers, poked into protective foam. A sheet of stickers and a warranty leaflet are supplied, as is customary for genuine Arduino products.

    The compact board really packs a punch, featuring an onboard battery charger and a special I2C expansion bus connector. Unlike previous generations of Arduinos, this one features an actual DAC, as well as capability to run I2S for audio applications but is limited to 3.3V I/O only.

    Helpfully, the header pin numbers and functions are labelled on the sides for quick reference. The stackable header design is somewhat awkward if not plugging into a breadboard and the width of the board makes use on a breadboard somewhat less convenient due to the lack of free holes for wires.

    The board require a micro-USB B cable for power and programming. Of note is the Wi-Fi module which has a rather “fragile” antenna sticking up. Owners are cautioned not to touch it to avoid damage, paying particular attention when attempting to detach shields. They seem to have put some hot glue on the antenna to provide some mechanical support.


    Documentation, Arduino Library Set-Up & Demo Code

    The documentation can be downloaded from Omron’s website, consisting of a datasheet, a user’s manual, schematics and CAD data. The quality of the documentation is good, with only minor issues with English expression. The datasheet overviews the main features of the board, the external sensors it is compatible with, the headers to which these sensors connect to and the harness cables necessary for each sensor. The user manual gives a detailed walkthrough for setting up the board, which includes soldering the pin-headers, setting up the Arduino IDE, downloading the support code from GitHub (external sensors have separate repositories, e.g. for the air quality sensor), installing the libraries and loading the demo applications.

    The schematics provide a clearer understanding of what the board itself does – namely, it provides some level conversion for both analog and digital signals for some external sensors, while otherwise providing direct connections to SPI and I2C sensors onboard and an RGB LED for feedback. The CAD data is also helpful in case you would like to model the board in 2D or 3D environments. In all, having this information provided is very helpful in case you would like to eventually spin your own PCB with just the sensors you need.


    To begin, the user must first solder the header pins to the PCB. For someone experienced with soldering, this is not a difficult task, however it is a task best done with a fine tip and some care due to the narrowness of the lands and the proximity of sensor components which may be affected by excess heat or flux. The board can then be mounted to the Arduino and any external sensors attached with the supplied jumper cables.

    As I am experienced with the Arduino environment, I got started right away with a copy of the latest Arduino 1.8.13 IDE at the time of writing this review. The next step was to install the necessary libraries and board support packages.

    As I was using a Wi-Fi 1010 board, I took the opportunity to update the Wi-Fi module firmware as well – although for some reason, I didn’t have access to the latest version 1.4.0, so had to settle for 1.3.0. Doing so should reduce the possibility of hiccups when using the Wi-Fi later.

    To get started with the sample code, it is as easy as browsing the example sketches –

    One downside I quickly realised when loading the example sketches is that the provided sample code does not really adhere to the Arduino library convention of “hiding” the code in the library, exposing just a “pretty” set of functions being called to make the sensor work.

    Instead, the code is perhaps more akin to what you might expect from a more traditional C-developer, including the addition of additional .h header files that have to be placed in the sketch directory to compile. As a result, the code is a little bit messy, making use of plenty of defines, macros and specially defined functions to handle the data formats from the sensors. There are a few sparse comments, but for the most part, the code is fairly self-explanatory.


    It should be noted that according to the manual …

    While the code is provided, the user is ultimately left to fend for themselves in case of problems. Another thing that is worthy of mention is the fact the board is only warranted for defects for a period of one week, distinct from other products which usually have longer warranty periods due to the nature of the product. Finally, the product should not be integrated into a product that is sold or given to anyone else – this is probably as the evaluation board does not carry any safety regulatory compliances.

    On the whole, the provided demo code loaded and ran successfully first-time, which is a good result. The printed format is inconsistent between examples – some are rather verbose with units while others are without such information. There are also minor typos, such as “retun code”. This suggests to me that the code may have been developed by different teams. Examining the header files for some of the examples showed only one or two #define statements that could have very easily just been in the sketch itself, saving the complexity of having a separate header file.


    While running the code for the air quality sensor, I discovered that the code itself is based on a polling loop looking for pulse transitions on the analog inputs, which may not be quite ideal in terms of sample rate but also has a disadvantage of being blocking code, meaning that nothing else can happen during the 20-second sampling period. This is a common disadvantage of analog pulse-output “dust” sensors without their own integrated microcontroller.

    The I2S microphone was the only peripheral without their own dedicated sample code, instead, users are directed to use the example code from Arduino instead.

    I did run the code, which seemed to work and respond to audio, however, closer examination of the code seems to suggest that its default settings regarding sample rate, bit depth and I2S mode don’t match the expected rates the Knowles datasheet has for this part. The fact that it works is perhaps more of a fluke than anything else – in fact, I also noticed a subtle bug with the code which would skip printing out any sample if it just so happened to be a perfect zero. Whoops. But that does make something perhaps worthy of investigation later in this RoadTest!


    Experiment: Comparison with Bosch CISS & Sensirion SPS30

    While the sensors have much in common with those used on the 2JCIE-BU01 and 2JCIE-BL01 reviewed prior, I still felt it worthwhile to compare the performance of the sensor with others in my collection. For this, I employed the Harting MICA Industrial Computer, along with a Bosch CISS module to provide a temperature, humidity, pressure and light reference point. The air quality sensor would be compared to a Sensirion SPS30 laser particulate matter sensor which is connected to an Adafruit Feather Huzzah, running some code of my own that spits out the readings on a one-second interval over UDP.


    For the 2CJIE-EV, I developed this “monolithic” (no header files) version of the code based on copying and pasting the provided demo code that operates with the temperature, humidity, pressure, light and dust sensors. This code connects to Wi-Fi and fires off UDP packets containing the values in ASCII formatted as “OMRON,nnn.nnn …., nnn.nnn#\r\n”. This would happen once every 20-seconds or so, due to the sampling time required for the dust sensor. To their credit, I was able to finish development of this within the first night of unpacking the unit, which really speaks to how simple it was to work with the unit.


    A script was run on the Harting MICA that collected the UDP broadcasts from both the SPS30 and 2JCIE-EV platforms, timestamping the readings into separate .csv files for later comparison.

    // OMRON 2JCIE-EV Environmental Sensors (Temp, Hum, Press, Light) + B5W-LD Dust Sensor
    // Amalgamated code - Serial + Wi-Fi UDP for MKR Wi-Fi 1010 by Gough Lui ( - Aug 2020
    // No warranties provided or implied. Based on Omron sample code from:
    // &
    // Reformatted for wide-screens & removed all local header dependencies
    #include <Wire.h>
    #include <Arduino.h>
    #include <SPI.h>
    #include <WiFiNINA.h>
    #include <WiFiUdp.h>
    #define SHT30_ADDR  0x44
    #define SHT30_STATUSMASK  0xFC1F
    #define SHT30_READSTATUS           0xF32D
    #define SHT30_CLEARSTATUS          0x3041
    #define SHT30_SOFTRESET            0x30A2
    #define SHT30_HEATEREN             0x306D
    #define SHT30_HEATERDIS            0x3066
    #define SHT30_MEAS_HIGHPRD         0x2334
    #define SHT30_READ_PERIODIC        0xE000
    #define BARO_2SMPB02E_CHIP_ID     0x5C
    #define BARO_2SMPB02E_ADDRESS (0x56)
    #define BARO_2SMPB02E_REGI2C_PRES_TXD2 0xF7
    #define BARO_2SMPB02E_REGI2C_IO_SETUP 0xF5
    #define BARO_2SMPB02E_REGI2C_CTRL_MEAS               0xF4
    #define BARO_2SMPB02E_REGI2C_IIR 0xF1
    #define BARO_2SMPB02E_REGI2C_CHIP_ID 0xD1
    #define BARO_2SMPB02E_REGI2C_COEFS 0xA0
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_0001MS ((uint8_t)0x00)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_0125MS ((uint8_t)0x20)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_0250MS ((uint8_t)0x40)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_0500MS ((uint8_t)0x60)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_1000MS ((uint8_t)0x80)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_2000MS ((uint8_t)0xA0)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_4000MS ((uint8_t)0xC0)
    #define BARO_2SMPB02E_VAL_IOSETUP_STANDBY_8000MS ((uint8_t)0xE0)
    #define BARO_2SMPB02E_VAL_TEMPAVERAGE_01 ((uint8_t)0x20)
    #define BARO_2SMPB02E_VAL_TEMPAVERAGE_02 ((uint8_t)0x40)
    #define BARO_2SMPB02E_VAL_TEMPAVERAGE_04 ((uint8_t)0x60)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_01 ((uint8_t)0x04)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_02 ((uint8_t)0x08)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_04 ((uint8_t)0x0C)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_08 ((uint8_t)0x10)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_16 ((uint8_t)0x14)
    #define BARO_2SMPB02E_VAL_PRESAVERAGE_32    ((uint8_t)0x18)
    #define BARO_2SMPB02E_VAL_POWERMODE_SLEEP ((uint8_t)0x00)
    #define BARO_2SMPB02E_VAL_POWERMODE_FORCED ((uint8_t)0x01)
    #define BARO_2SMPB02E_VAL_POWERMODE_NORMAL ((uint8_t)0x03)
    #define BARO_2SMPB02E_VAL_IIR_OFF ((uint8_t)0x00)
    #define BARO_2SMPB02E_VAL_IIR_02TIMES ((uint8_t)0x01)
    #define BARO_2SMPB02E_VAL_IIR_04TIMES ((uint8_t)0x02)
    #define BARO_2SMPB02E_VAL_IIR_08TIMES ((uint8_t)0x03)
    #define BARO_2SMPB02E_VAL_IIR_16TIMES ((uint8_t)0x04)
    #define BARO_2SMPB02E_VAL_IIR_32TIMES ((uint8_t)0x05)
    #define BARO_2SMPB02E_COEFF_S_A1   ((double)( 4.3E-04))
    #define BARO_2SMPB02E_COEFF_A_A1 ((double)(-6.3E-03))
    #define BARO_2SMPB02E_COEFF_S_A2   ((double)( 1.2E-10))
    #define BARO_2SMPB02E_COEFF_A_A2 ((double)(-1.9E-11))
    #define BARO_2SMPB02E_COEFF_S_BT1  ((double)( 9.1E-02))
    #define BARO_2SMPB02E_COEFF_A_BT1  ((double)( 1.0E-01))
    #define BARO_2SMPB02E_COEFF_S_BT2  ((double)( 1.2E-06))
    #define BARO_2SMPB02E_COEFF_A_BT2  ((double)( 1.2E-08))
    #define BARO_2SMPB02E_COEFF_S_BP1  ((double)( 1.9E-02))
    #define BARO_2SMPB02E_COEFF_A_BP1  ((double)( 3.3E-02))
    #define BARO_2SMPB02E_COEFF_S_B11  ((double)( 1.4E-07))
    #define BARO_2SMPB02E_COEFF_A_B11  ((double)( 2.1E-07))
    #define BARO_2SMPB02E_COEFF_S_BP2  ((double)( 3.5E-10))
    #define BARO_2SMPB02E_COEFF_A_BP2 ((double)(-6.3E-10))
    #define BARO_2SMPB02E_COEFF_S_B12  ((double)( 7.6E-13))
    #define BARO_2SMPB02E_COEFF_A_B12  ((double)( 2.9E-13))
    #define BARO_2SMPB02E_COEFF_S_B21  ((double)( 1.2E-14))
    #define BARO_2SMPB02E_COEFF_A_B21  ((double)( 2.1E-15))
    #define BARO_2SMPB02E_COEFF_S_BP3  ((double)( 7.9E-17))
    #define BARO_2SMPB02E_COEFF_A_BP3  ((double)( 1.3E-16))
    #define OPT3001_ADDR  0x45
    #define OPT3001_REG_RESULT          0x00
    #define OPT3001_REG_CONFIG          0x01
    #define OPT3001_REG_LOLIMIT         0x02
    #define OPT3001_REG_HILIMIT         0x03
    #define OPT3001_REG_MANUFACTUREID   0x7E
    #define OPT3001_DEVICEID            0x7F
    #define OPT3001_CMD_CONFIG_MSB      0xC6
    #define OPT3001_CMD_CONFIG_LSB      0x10
    #define PIN_VOUT1     13
    #define PIN_VOUT2     14
    #define PIN_VTH       15
    #define PIN_EN        A3
    #define conv16_u8_h(a) (uint8_t)(a >> 8)
    #define conv16_u8_l(a) (uint8_t)(a & 0xFF)
    #define conv8s_u16_be(b, n) (uint16_t)(((uint16_t)b[n] << 8) | (uint16_t)b[n + 1])
    #define conv8s_s24_be(a, b, c) (int32_t)((((uint32_t)a << 16) & 0x00FF0000) | (((uint32_t)b << 8) & 0x0000FF00) | ((uint32_t)c & 0x000000FF))
    #define halt(a) {Serial.println(a); while (1) {}}
    typedef struct baro_2smpb02e_setting {
        double _A0, _A1, _A2;
        double _B00, _BT1, _BP1;
        double _B11, _BT2, _BP2;
        double _B12, _B21, _BP3;
    } baro_2smpb02e_setting_t;
    baro_2smpb02e_setting_t baro_2smpb02e_setting;
    int status = WL_IDLE_STATUS;
    char ssid[] = "YOUR_SSID"; // EDIT THIS LINE!!!
    char pass[] = "YOUR_PASSWORD"; // EDIT THIS LINE!!!
    unsigned int localPort = 19999;
    char packetBuffer[512];
    WiFiUDP Udp;
    bool i2c_write_reg16(uint8_t slave_addr, uint16_t register_addr, uint8_t *write_buff, uint8_t len) {
        if (len > 0) {
            for (uint8_t i = 0; i < len; i++) {
        return false;
    bool i2c_read_reg16(uint8_t slave_addr, uint16_t register_addr, uint8_t *read_buff, uint8_t len) {
        i2c_write_reg16(slave_addr, register_addr, NULL, 0);
        Wire.requestFrom(slave_addr, len);
        if (Wire.available() != len) {
            return true;
        for (uint16_t i = 0; i < len; i++) {
            read_buff[i] =;
        return false;
    bool i2c_write_reg8(uint8_t slave_addr, uint8_t register_addr, uint8_t *write_buff, uint8_t len) {
        if (len != 0) {
            for (uint8_t i = 0; i < len; i++) {
        return false;
    bool i2c_read_reg8(uint8_t slave_addr, uint8_t register_addr, uint8_t *read_buff, uint8_t len) {
        i2c_write_reg8(slave_addr, register_addr, NULL, 0);
        Wire.requestFrom(slave_addr, len);
        if (Wire.available() != len) {
            return true;
        for (uint16_t i = 0; i < len; i++) {
            read_buff[i] =;
        return false;
    void sht30_setup() {
        i2c_write_reg16(SHT30_ADDR, SHT30_SOFTRESET, NULL, 0);
        uint16_t stat = 0;
        i2c_write_reg16(SHT30_ADDR, SHT30_CLEARSTATUS, NULL, 0);
        int retry = 10;
        do {
            stat = sht30_readstatus();  // check status
        } while (((stat & SHT30_STATUSMASK) != 0x0000) && (retry-- > 0));
        if (((stat & SHT30_STATUSMASK) != 0x0000) || (retry == 0)) {
            halt("cannot detect SHT30 working.");
        i2c_write_reg16(SHT30_ADDR, SHT30_MEAS_HIGHPRD, NULL, 0);
    uint16_t sht30_readstatus(void) {
        bool result;
        uint8_t readbuffer[3] = {0, 0, 0};
        uint16_t stat = 0xFFFF;
        result = i2c_read_reg16(SHT30_ADDR, SHT30_READSTATUS, readbuffer, 3);
        if (!result) {
            stat = conv8s_u16_be(readbuffer, 0);
        return stat;
    int sht30_readTempHumi(int32_t* humi, int32_t* temp) {
        bool result;
        uint8_t readbuffer[6];
        result = i2c_read_reg16(SHT30_ADDR, SHT30_READ_PERIODIC, readbuffer, 6);
        if (result) {
            return 1;
        if (readbuffer[2] != sht30_crc8(readbuffer, 2)) {
            return 2;
        if (readbuffer[5] != sht30_crc8(readbuffer + 3, 2)) {
     return 3;
        uint16_t ST, SRH;
        ST = conv8s_u16_be(readbuffer, 0);
        SRH = conv8s_u16_be(readbuffer, 3);
        double stemp = (double)ST * 17500.0 / 65535.0 - 4500.0;
        *temp = (int32_t)stemp;
        double shum = (double)SRH * 10000.0 / 65535.0;
        *humi = (int32_t)shum;
        return 0;
    uint8_t sht30_crc8(const uint8_t *data, int len) {
        const uint8_t POLYNOMIAL(0x31);
        uint8_t crc(0xFF);
        for (int j = len; j; --j) {
            crc ^= *data++;
            for (int i = 8; i; --i) {
                crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
        return crc;
    bool baro_2smpb02e_setup(void) {
        bool result;
        uint8_t rbuf[32] = {0};
        uint8_t ex;
        result = i2c_read_reg8(BARO_2SMPB02E_ADDRESS, BARO_2SMPB02E_REGI2C_CHIP_ID, rbuf, 1);
        if (result || rbuf[0] != BARO_2SMPB02E_CHIP_ID) {
            halt("cannot find 2SMPB-02E sensor, halted...");
        result = i2c_read_reg8(BARO_2SMPB02E_ADDRESS,
                BARO_2SMPB02E_REGI2C_COEFS, rbuf, 25);
        if (result) {
            halt("failed to read 2SMPB-02E coeffients, halted...");
        ex = (rbuf[24] & 0xf0) >> 4;
        baro_2smpb02e_setting._B00 = baro_2smpb02e_conv20q4_dbl(rbuf, ex, 0);
        baro_2smpb02e_setting._BT1 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_BT1, BARO_2SMPB02E_COEFF_S_BT1, rbuf, 2);
        baro_2smpb02e_setting._BT2 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_BT2, BARO_2SMPB02E_COEFF_S_BT2, rbuf, 4);
        baro_2smpb02e_setting._BP1 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_BP1, BARO_2SMPB02E_COEFF_S_BP1, rbuf, 6);
        baro_2smpb02e_setting._B11 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_B11, BARO_2SMPB02E_COEFF_S_B11, rbuf, 8);
        baro_2smpb02e_setting._BP2 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_BP2, BARO_2SMPB02E_COEFF_S_BP2, rbuf, 10);
        baro_2smpb02e_setting._B12 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_B12, BARO_2SMPB02E_COEFF_S_B12, rbuf, 12);
        baro_2smpb02e_setting._B21 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_B21, BARO_2SMPB02E_COEFF_S_B21, rbuf, 14);
        baro_2smpb02e_setting._BP3 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_BP3, BARO_2SMPB02E_COEFF_S_BP3, rbuf, 16);
        ex = (rbuf[24] & 0x0f);
        baro_2smpb02e_setting._A0 = baro_2smpb02e_conv20q4_dbl(rbuf, ex, 18);
        baro_2smpb02e_setting._A1 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_A1, BARO_2SMPB02E_COEFF_S_A1, rbuf, 20);
        baro_2smpb02e_setting._A2 = baro_2smpb02e_conv16_dbl(BARO_2SMPB02E_COEFF_A_A2, BARO_2SMPB02E_COEFF_S_A2, rbuf, 22);
        rbuf[0] = BARO_2SMPB02E_VAL_IOSETUP_STANDBY_0125MS;
        i2c_write_reg8(BARO_2SMPB02E_ADDRESS, BARO_2SMPB02E_REGI2C_IO_SETUP, rbuf, sizeof(rbuf));
        rbuf[0] = BARO_2SMPB02E_VAL_IIR_32TIMES;
        i2c_write_reg8(BARO_2SMPB02E_ADDRESS, BARO_2SMPB02E_REGI2C_IIR, rbuf, sizeof(rbuf));
        result = baro_2smpb02e_trigger_measurement(BARO_2SMPB02E_VAL_MEASMODE_ULTRAHIGH);
        if (result) {
            halt("failed to wake up 2SMPB-02E sensor, halted...");
        return false;
    static double baro_2smpb02e_conv16_dbl(double a, double s, uint8_t* buf, int offset) {
        uint16_t val;
        int16_t ret;
        val = (uint16_t)((uint16_t)(buf[offset] << 8) | (uint16_t)buf[offset + 1]);
        if ((val & 0x8000) != 0) {
            ret = (int16_t)((int32_t)val - 0x10000);
        } else {
            ret = val;
        return a + (double)ret * s / 32767.0;
    static double baro_2smpb02e_conv20q4_dbl(uint8_t* buf, uint8_t ex, int offset) {
        int32_t ret;
        uint32_t val;
        val = (uint32_t)((buf[offset] << 12) | (buf[offset + 1] << 4) | ex);
        if ((val & 0x80000) != 0) {
            ret = (int32_t)val - 0x100000;
        } else {
            ret = val;
        return (double)ret / 16.0;
    static bool baro_2smpb02e_trigger_measurement(uint8_t mode) {
        uint8_t wbuf[1] = {(uint8_t)(mode | BARO_2SMPB02E_VAL_POWERMODE_NORMAL)};
        i2c_write_reg8(BARO_2SMPB02E_ADDRESS, BARO_2SMPB02E_REGI2C_CTRL_MEAS, wbuf, sizeof(wbuf));
        return false;
    int baro_2smpb02e_read(uint32_t* pres, int16_t* temp, uint32_t* dp, uint32_t* dt) {
        bool ret;
        uint8_t rbuf[6] = {0};
        uint32_t rawtemp, rawpres;
        ret = i2c_read_reg8(BARO_2SMPB02E_ADDRESS, BARO_2SMPB02E_REGI2C_PRES_TXD2, rbuf, sizeof(rbuf));
        if (ret) {
            return 1;
        *dp = rawpres = conv8s_s24_be(rbuf[0], rbuf[1], rbuf[2]);
        *dt = rawtemp = conv8s_s24_be(rbuf[3], rbuf[4], rbuf[5]);
        return baro_2smpb02e_output_compensation(rawtemp, rawpres, pres, temp);
    bool baro_2smpb02e_output_compensation(uint32_t raw_temp_val, uint32_t raw_press_val, uint32_t* pres, int16_t* temp) {
        double Tr, Po;
        double Dt, Dp;
        Dt = (int32_t)raw_temp_val - 0x800000;
        Dp = (int32_t)raw_press_val - 0x800000;
        baro_2smpb02e_setting_t* c = &baro_2smpb02e_setting;
        Tr = c->_A0 + c->_A1 * Dt + c->_A2 * (Dt * Dt);
        Po = c->_B00 + (c->_BT1 * Tr) + (c->_BP1 * Dp) + (c->_B11 * Tr * Dp) + c->_BT2 * (Tr * Tr) + (c->_BP2 * (Dp * Dp)) + (c->_B12 * Dp * (Tr * Tr)) + (c->_B21 * (Dp * Dp) * Tr) + (c->_BP3 * (Dp * Dp * Dp));
        *temp = (int16_t)(Tr / 2.56);
        *pres = (uint32_t)(Po * 10.0);
        return false;
    void opt3001_setup(void) {
        uint8_t wbuf[2];
        wbuf[0] = OPT3001_CMD_CONFIG_MSB;
        wbuf[1] = OPT3001_CMD_CONFIG_LSB;
        i2c_write_reg8(OPT3001_ADDR, OPT3001_REG_CONFIG, wbuf, sizeof(wbuf));
    int opt3001_read(uint16_t* light) {
        bool result;
        uint8_t rbuf[2];
        uint16_t raw_data;
        result = i2c_read_reg8(OPT3001_ADDR, OPT3001_REG_CONFIG, rbuf, sizeof(rbuf));
        if (result) {
            return 1;
        if ((rbuf[1] & 0x80) == 0) {
            return 2;  // sensor is working...
        result = i2c_read_reg8(OPT3001_ADDR, OPT3001_REG_RESULT, rbuf, sizeof(rbuf));
        if (result) {
            return 100;
        raw_data = conv8s_u16_be(rbuf, 0);
        *light = (uint16_t)(opt3001_convert_lux_value_x100(raw_data) / 100);
        return 0;
    uint32_t opt3001_convert_lux_value_x100(uint16_t value_raw) {
        uint32_t value_converted = 0;
        uint32_t exp;
        uint32_t data;
        exp = (value_raw >> 12) & 0x0F;
        exp = 1 << exp;
        data = value_raw & 0x0FFF;
        value_converted = (uint32_t)(exp * data);
        return value_converted;
    void setup() {
        pinMode(PIN_VOUT1, INPUT);
        pinMode(PIN_VOUT2, INPUT);
        pinMode(PIN_EN, OUTPUT);
        digitalWrite(PIN_EN, HIGH);
        pinMode(PIN_VTH, OUTPUT);
        analogWrite(PIN_VTH, 0.5 / (3.3 / 1024));
        if (WiFi.status() == WL_NO_MODULE) {
          halt("Communication with WiFi module failed!");
        while (status != WL_CONNECTED) {
          Serial.print("Attempting to connect to SSID: ");
          status = WiFi.begin(ssid, pass);
        Serial.println("Connected to wifi");
    void loop() {
        int32_t humi, temp;
        uint32_t pres, dp, dt;
        int16_t temp2;
        uint16_t illm;
        sht30_readTempHumi(&humi, &temp);
        baro_2smpb02e_read(&pres, &temp2, &dp, &dt);
        uint8_t prv_gpio1 = LOW;
        uint8_t prv_gpio2 = LOW;
        int now = millis(), prev = now;
        int counts_vout1 = 0;
        int counts_vout2 = 0;
        while (now - prev < 20000) {   // Measurement interval = 20000 [msec]
            delayMicroseconds(400);   // Sampling period about 400 [usec]
            uint8_t cur_gpio1 = digitalRead(PIN_VOUT1);
            uint8_t cur_gpio2 = digitalRead(PIN_VOUT2);
            if (cur_gpio1 == HIGH && prv_gpio1 == LOW) {
            if (cur_gpio2 == HIGH && prv_gpio2 == LOW) {
            prv_gpio1 = cur_gpio1;
            prv_gpio2 = cur_gpio2;
            now = millis();
        Serial.print(temp / 100.0);
        Serial.print(humi / 100.0);
        Serial.print(temp2 / 100.0);
        Serial.print(pres / 1000.0);
        int msglen = sprintf(packetBuffer,"OMRON,%f,%f,%f,%f,%d,%d,%d#\r\n",temp/100.0,humi/100.0,temp2/100.0,pres/1000.0,illm,counts_vout1,counts_vout2);

    Testing was performed with the Omron air quality sensor mounted upright, as required, on the cardboard box that the RoadTest items were shipped in. The Sensirion SPS30 was hung by its wires nearby. The Arduino remained attached to the breadboard, placed a short distance away, to which the Bosch CISS was also placed with the sensing head face up to match the orientation of the sensors on the Omron 2JCIE-EV.


    The experiment was left unattended for several days, however, it was found that the Arduino stopped sending data after almost five days of operation. A conclusive cause was not determined – perhaps the module lost Wi-Fi connectivity and failed to reconnect, as restarting the Arduino bought it back to life. Regardless, this provided plenty of data for analysis. The data was imported into MATLAB and the Timetables feature was used to synchronise all data together, linearly interpolating the nearest samples from other data sources to match the timestamps of the Arduino solution. The raw data looks something like this:

    A glitch is seen in the Bosch CISS barometer channel, with one erroneous reading reported possibly due to a serial communication interface issue. The other data looks to be as expected, however, the particle numbers, temperature seem to be varying extremely just before the data from the Arduino was lost. Perhaps there was something wrong with the power or Wi-Fi connection to the board.

    The first check was on the temperature as recorded by the board. The 2JCIE-EV-AR01 has two sources of temperature, one from the environment sensor that provides relative humidity and another from the sensor that provides barometric pressure. A look at these two sensors shows that they have fair linearity over the indoor room temperature range, but a temperature offset of about 1.4 degrees Celsius, probably due to heat coming from the Arduino platform that runs the unit. Considering the SHT30 as the main source of temperature, when compared with the Bosch CISS, the temperature was pretty much identical although the linearity seemed to suffer somewhat. On the whole, the temperature readings from the 2JCIE-EV-AR01 seem to be reliable in the tested range.

    Testing of relative humidity shows the same trend as identified in testing the 2JCIE-BU01/BL01 units where the reported trend is virtually the same but offset by a couple of percentage points. The Omron appears to react faster than the CISS, producing some high-frequency variations that the CISS misses out on. This implies the relative humidity sensing function is quite good.

    Comparison of the barometric pressure readings (zooming in to exclude the one-off outlier from the Bosch CISS) shows that the relationship is very much linear and both sensors are in good agreement on an absolute basis.

    The light trends are also very much coincident, although as reported in previous testing, the CISS has a fairly low resolution, thus the Omron provides a more fine-grained detail as to light levels by comparison. However, it does prove that both sensors are in agreement and the demonstration code appears to work well.

    Testing of the dust sensor versus my Sensirion SPS30 laser particulate-matter sensor was complicated by the fact that the two sensors, while seemingly belonging to the same sort of measurement, actually measure two different things. The SPS30 measures particles of a given size and smaller, down to 0.3µm, thus a count for PM10 is 0.3µm to 10µm particles. The Omron B5W-LD0101, however, gives two outputs which are for >0.5µm and >2.5µm with no upper bound in size provided (although practically speaking, there probably is).


    Thus, for the VOUT1 output which provides >0.5µm readings, I compared it with the PM10 readings from the SPS30 for the closest match. The resultant counts from the B5W based on 20-second sampling interval shows a “floor” value which is significantly higher than that reported by the SPS30. The visible trends from the SPS30 are reflected in the B5W, but not at the same magnitude and couched in some measurement noise. The SPS30’s data looks much cleaner, but the difference is surprising.


    When looking at the VOUT2 output which provides >2.5µm readings, I instead compared it with the SPS30’s PM10 reading minus the PM2.5 reading, thus reading particles in the range of 2.5µm to 10µm. This suggests that the frequency of larger particles is actually extremely low – at most, the B5W reported ten counts, but frequently the counts ranged from zero to one. The SPS30’s data provides floating point values which show a similar trend but far below the one-count-per-cm³ range.


    In light of this, the “floor” count reading on the VOUT1 output is not likely due to large particles >2.5µm, so instead it is reflecting a heightened detection of particles in the 0.5µm to 2.5µm range which is entirely covered by the SPS30. As to which sensor is correct on an absolute basis, I cannot be entirely sure – perhaps some of the VOUT1 pulse counts may be noise, or perhaps the SPS30 has an internal baseline correction that is taking some of that away. However, for qualitative trending, it seems that the B5W can provide similar trending data to those obtained by more expensive laser particulate monitor sensors.


    Some readers may wonder why I didn’t compare the 2JCIE-EV’s readings to the 2JCIE-BU01 or 2JCIE-BL01 – the reason is that both of these boards have been given away to a friend to evaluate their suitability for development of a sensor platform for a commercial/government partnership project. As a result, they are both no longer in my possession.


    Test: Power Consumption of the MKR Wi-Fi 1010, 2JCIE-EV-AR01 & B5W Sensor

    Testing of the power consumption used the same sketch as above, instead, the unit was powered through my own USB Kelvin breakout, attached to a Rohde & Schwarz NGM202 power supply using statistics mode to determine average power consumption. Three cases were considered – the MKR board alone, the MKR board with the 2JCIE-EV board and finally the MKR board with the 2JCIE-EV and the B5W sensor.

    With the MKR board alone, the average current consumption was 18.02mA. This increased to 51.68mA with the Omron 2JCIE-EV board attached, implying that around 33.66mA would be attributed to the 2JCIE-EV although in reality some of this difference will be due to the increased work from the microcontroller as well, since the sketch will not be “halted”. Adding the B5W sensor pushes the average current consumption up to 126.74mA, suggesting the B5W sensor adds another 75.06mA of consumption. This is not unsurprising, as the sensor has a heater resistor and a sensing LED that need to be powered for operation.


    Experiment: Recording from the I2S Microphone

    Something I did not propose in my RoadTest application, but thought it might be a good stretch goal was to see whether I could use the I2S microphone in some way as the Arduino MKR-series boards have some support for I2S. I would try to read from the I2S microphone, using the 32-64kHz sampling rate at 24-bit resolution that the datasheet specifies as the nominal for this microphone, passing the raw data over the USB-serial interface to “record” audio.


    Unfortunately, I found that the I2S library is quite poorly documented, with the reference missing any reference to the read() function (which I’ve seen others online struggle with). Assuming it inherits from the Stream class libraries, I wrote some code that would check for something to read using available() and read it in using read(). Unfortunately, this exposed something strange – there always seemed to be something to read, but many of the samples were zero rather than an actual sample value, resulting in a spiky graph. I suppose that’s the reason why the example code checks if the sample value is zero and drops it quietly if it is! It does introduce a bug that an all-zero sample will be lost, but now I can see it seems to be a workaround for some potentially broken code or some odd microphone behaviour when the sample rate is below the minimum stated in the datasheet (randomly going to sleep?). Playing with the sample rates also resulted in some conditions where the data flow from the board would run for a while and then randomly cease without any pattern – if it were a memory overflow, I would expect it to happen after a consistent amount of operating time. It was a frustrating discovery.


    Another thing that was found was that changing the I2S modes or bit depths prevented the initialisation from completing, and/or resulted in issues where the output would be one consistent value. Again, these values were left at the defaults … and it seemed to work. Using this broken method, the modified code is as follows:

    #include <I2S.h>
    void setup() {
      while (!I2S.begin(I2S_PHILIPS_MODE, 16000, 32)) {
    void loop() {
      int sample =;
      if (sample) {


    I attempted to speed it up by dropping the least significant byte, as this would have been padded up with zeroes as the microphone only emits 24-bits. Examining the results in an audio editor that can import raw samples, the result is not intelligible. It sounds sped up somewhat but contains parts of syllables mashed in with others. It seems that changing the I2S sample rate higher didn’t make any difference whatsoever. This suggests to me that the I2S library or the Serial library or Arduino in general is not efficient enough given the CPU cycles available to process data in such a naïve and straightforward way.


    I also tried using looking for a better library and came across the Adafruit ZeroI2S library which seems to have some DMA code as well. Unfortunately, it too is not well documented and the examples were only for transmitting while I needed receiving. It seems to support this, but only as a “stereo” two-channel mode. The pin definitions were also nowhere to be found – I merely guessed some of the pins but I really have no idea what the final value should be for the RX pin! I examined the DMA example but I really couldn’t make heads or tails of it … so I tried a similar approach with the following code -


    #include <Arduino.h>
    #include <Adafruit_ZeroI2S.h>
    Adafruit_ZeroI2S i2s(3, 2, A6, A6);
    int32_t left;
    int32_t right;
    void setup() {
    void loop() {, &right);
      if(!left==0) {
        Serial.write((char) (left >> 24));
        Serial.write((char) (left >> 16));
        Serial.write((char) (left >> 8));
        //Serial.write((char) left);
      if(!right==0) {
        Serial.write((char) (right >> 24));
        Serial.write((char) (right >> 16));
        Serial.write((char) (right >> 8));
        //Serial.write((char) right);


    Interestingly, the results were a little different – I could choose a sample rate even up to 48kHz but the output would be continuously sampled, just at a rate less than 8kHz. Loading it into the audio editor at 8kHz, my voice is intelligible but sounds a bit like I’m on helium. I’d estimate the effective sample rate is somewhere between 4kHz to 6kHz, again suggesting that such a naïve implementation on Arduino doesn’t work for continuous audio sampling.


    While this is a disappointment, as I had hoped I2S would be straightforward to use on the Arduino, the fact that it isn’t is perhaps not Omron’s fault and perhaps is down to inefficiencies of naïve code implementation which doesn’t take advantage of the SAMD’s hardware DMA capabilities and perhaps quirks with the microphone if it’s not clocked just right.



    The Omron 2JCIE-EV-AR01 is a neat solution, fitting onto an Arduino MKR-series board and providing a set of sensors including the Sensirion SHT30 Temperature/Humidity, Texas Instruments OPT3001 Ambient Light, Omron 2SMPB Barometric Pressure, STMicroelectronics LIS2DW Accelerometer and Knowles SPH0645 Microphone. It provides expansion connectors supporting a variety of Omron MEMS thermal, MEMS flow, light convergent reflective and air quality sensors with available pre-terminated wiring harnesses. For those who wish to evaluate these sensors on an open platform (as opposed to using more finished products such as the 2JCIE-BU01) and do not have any hardware expertise, or just want to do so neatly with the minimum of fuss, the 2JCIE-EV-AR01 offers a solution with sample code available and documentation that steps the user through the setup process. As proof of this, I was able to get up and running with my own code within an hour of receiving the kit. Soldering experience is necessary, however, as the header pins are not pre-populated.


    The downsides are the price of the board and the conditions that prohibit its integration into any product or service, ensuring that this unit really only appeals to those who are evaluating the sensors “in-house” or building a private proof-of-concept. The sample code also did not conform to the regular Arduino library conventions, exposing much of their internal workings and requiring additional header files to be added to the sketch. While no major issues were detected with the code, it is important to understand that the code is not warranted by Omron. Finally, as the air quality sensor has only analog outputs, measuring from the sensor requires the microcontroller’s full attention, preventing other code from running alongside.


Also Enrolling

Enrollment Closes: Dec 29 
Enrollment Closes: Dec 6 
Enrollment Closes: Dec 18 
Enrollment Closes: Dec 15