Measuring CO2 with MQ135

Introduction

A MQ135 sensor on a small PCB.

The MQ135 is a cheap gas sensor that is primarily intended for detecting the presence of flammable gases. It is marketed as a generalized “air quality” sensor, rather than precision device for measuring the concentration of any specific gas. Nevertheless, according to the data sheet the MQ135 is capable of measuring the concentrations of several gases, one of which is CO2. This makes it an attractive low-cost alternative to more specialized (and more expensive) CO2-specific sensors.

Much has been written about using the MQ135 as a CO2 sensor, mostly focusing on using it together with Arduino. This article will, firstly, discuss the working principles of the MQ135 sensor and how to read the data sheet, and secondly present how to use it with the PSLab.

Theory of operation

The MQ135 consists of a surface covered in a thin layer of SnO2, and a heater resistor which serves to raise the temperature of the SnO2 surface to several hundred degrees Celsius. SnO2 is an n-type semiconductor, in which donor electrons are excited to the conduction band at elevated temperatures. However, SnO2 when exposed to air readily adsorbs oxygen onto its surface. The adsorption reaction consumes electrons from the conduction band to form negatively charged oxygen species. Therefore, SnO2 has low conductivity in clean air. [Shimizu]

In the presence of a flammable gas, the gas will also adsorb onto the sensor surface, where it consumes the adsorbed oxygen species to form CO2 and H2O. This reaction releases the donor electrons back into the conduction band, thereby raising the conductivity of the sensor. By quantifying the conductivity response to the presence of various gases, a thin SnO2 surface can be used to determine the concentration of the gas. [Shimizu]

CO2, notably, is not a flammable gas. Here, the sensing mechanism is different; instead of reacting with adsorbed oxygen directly, CO2 sensing relies on water vapor first reacting with adsorbed oxygen to form adsorbed hydroxide (OH). CO2 then in turn reacts with the adsorbed hydroxide, forming carbonate (CO32-). This process ultimately also returns electrons to the SnO2 conduction band, and therefore results in increased conductivity. [Wang et al.]

SnO2 gas sensors can also detect the presence of reducing gases such as NOx. Here, the sensing mechanism is the opposite; the reducing gas adsorbs onto the sensor surface, consuming additional electrons from the conduction band, and thereby lowers the conductivity further. The MQ135 data sheet does not specify response curves for any reducing gases, so while the sensor should be sensitive to such gases, the user would have to experimentally determine the response curves themselves. [Shimizu]

MQ135 gas response curves

The MQ135 data sheet provides a graph showing how the sensor resistance changes with the concentration of several gases. Figure 1 shows a reproduction of the data sheet graph, where the points have been manually extracted from the data sheet graph using WebPlotDigitizer.

Note that the y-axis is Rs/R0, which is the ratio between the sensor resistance at any given moment (Rs) and the sensor resistance at a specific set of conditions (R0). This is because the sensor resistance depends on the exact amount of active material on the sensor surface, which varies between individual sensors. Therefore, the resistance response must be normalized against a predefined state in order for results from different sensors to be comparable. For the MQ135, this predefined state is 100 ppm ammonia vapor in otherwise clean air at 20 °C and 65% relative humidity. The sensor resistance at this state must be known in order for the curves in Figure 1 to apply. This may seem like a problem, since most people will not be able to reproduce those conditions. Fortunately, there are other ways to derive R0. More on that later.

The responses show clearly linear behavior in loglog-scale, which means that the relationship between resistance and concentration can be expressed as:

RS / R0 = a * cb    (1)

where a and b are constants, and c is gas concentration in ppm.

The curves in the Figure 1 have been fitted using exponential regression, specifically scipy.optimize.curve_fit.

Figure 2: Data sheet gas response values (points) and fitted curves (lines).

Equation 1 gives the sensor resistance as a function of gas concentration, but we are interested in the opposite relation, i.e. gas concentration as a function of sensor resistance. A little elementary algebra gets us there:

c = (1 / a)1/b * (RS / R0)1/b = A * (RS / R0)B    (2)

With that, we can correlate sensor resistance with gas concentration for any of the gases in Figure 1. Now, we need a way to determine the sensor resistance.

MQ135 electrical characteristics

As previously mentioned, the MQ135 behaves as a variable resistor when exposed to different gas concentrations. In order to measure gas concentration using equation 2, we need a way to measure the sensor resistance.

Figure 3: Circuit diagram of a typical sensing circuit using a MQ135. Note the voltage divider in the upper path.

By connecting a resistor in series with the sensor and measuring the voltage between them we can create a voltage divider, as in figure 3. The expression for the output voltage from a voltage divider is:

Vout = RL / (RS + RL) * Vin    (3)

Rearrange to isolate the sensor resistance:

RS = RL * (Vin / Vout – 1)    (4)

Understanding R0

While equation 4 provides a way to measure the sensor resistance, the sensor resistance alone is not enough to determine the gas concentration. This is because every individual sensor has a unique resistance response, based on the amount and distribution of active material on the sensor surface. Therefore, in order to compare the response of one sensor with other sensors, the response must be normalized. R0 is the normalization factor, and is defined as the sensor resistance at a known set of conditions, i.e. a certain temperature, relative, humidity, and most importantly a certain gas concentration. The ratio of the sensor resistance and R0 can be compared between sensors.

The MQ135 data sheet defines R0 as the sensor resistance at 100 ppm ammonia at 20 °C and 65% relative humidity. Of course, it is possible to redefine R0 at some other set of conditions, but if R0 is redefined, the curves in Figure 2 no longer apply! The only situation where it makes sense to redefine R0 is if you have the means to create new response curves for the gas you wish to measure. Otherwise, keeping R0 defined as above is the best available option.

That means we need a way to calculate R0, since most people will not be able to recreate the conditions at which it is defined. By rearranging equation 2, we get:

R0 = RS * (1 / A * c)-1/B    (5)

Since we can measure the sensor resistance, that means the only unknown term in the RHS of equation 5 is the concentration. In other words, if we can measure the sensor resistance in any known gas concentration, we can calculate R0.

As it happens, we do know the concentration of one of the gases in figure 2: Carbon dioxide. The concentration of CO2 in outside air is around 400 ppm (and rising, check co2.earth for up to date values if you are reading this some years after 2021).

Accounting for temperature and humidity

The sensor response also depends on ambient temperature and humidity. The data sheet specifies the effect on sensor response at four different temperatures and two different humidity levels for ammonia vapor. Figure 4 shows a reproduction of the data sheet graph, plus fitted curves.

Figure 4: Data sheet temperature / humidity corrections (points) and fitter curves (lines).

From figure 4 we can see that the sensor resistance reaches a minimum at some temperature TM and then starts to increase. The reason the resistance decreases as the temperature increases toward TM is that higher surface temperature contributes to faster reaction rates between the gas and the adsorbed oxygen. The increase in sensor resistance at temperatures above TM can be explained by faster re-adsorption of oxygen at higher temperatures, which means more oxygen will cover the sensor surface for the same flammable gas concentration. [Shimizu]

We can also see that sensor resistance seems to decrease with at higher relative humidity. The mechanism for this is less clear. On the one hand, water vapor can adsorb onto the sensor surface, thereby decreasing the oxygen coverage and increasing sensor conductivity. On the other hand, water vapor can heal defective adsorption sites, which increases the surface area available for oxygen adsorption and therefore decreases sensor conductivity. [Wicker et al.] Additionally, recall that the CO2 sensing mechanism is highly dependent on the presence of water. Wang et al. reports a non-linear sensor response to CO2 with respect to humidity, with a response peak at 34% RH. With that in mind, the data sheet humidity dependence graph (which only has two data points and therefore must be assumed to be linear) is of limited utility when measuring CO2.

With that, we have all the pieces we need to use the MQ135 to measure CO2 concentration. Or any other of the gases in figure 2, for that matter. Enough theory, let’s take a look at how to do it in practice using the Pocket Science Lab!

Using the MQ135 with PSLab

If you have just the sensor itself, connect it as shown in figure 5, where the sensor is represented by a SparkFun gas sensor breakout board. CH1 is used to monitor the output voltage of the voltage divider between the sensor and the load resistor (which is 22K in this exaple circuit). Additionally, CH2 is used to monitor the voltage level of the PSLab’s +5V line. It is important that the voltage on the +5V line does not drop below 4.9 V, as the MQ135 will not reach its operating temperature otherwise. The MQ135 heater resistor draws up to 200 mA of current, which may be more power than the USB port powering the PSLab can supply. The PSLab does not regulate the +5V line, so it is up to the USB port to deliver enough current.

Figure 5: Guide to connect MQ135 to PSLab.
Figure 6: A MQ135 mounted on a PCB with a built-in load resistor (highlighted in red), as well as some other circuitry.

If you are using a SNS-MQ135 board, shown in figure 6, simply connect the AO pin to CH1, without the external load resistor. The SNS-MQ135 has its own load resistor, which unfortunately is only 1K. Such a small load resistor means the output voltage on AO will be very small when measuring CO2, which limits measurement accuracy. If possible, you should desolder the built-in SNS-MQ135 load resistor (outlined in red in figure 6), and use an external load resistor as in figure 5 instead. The caveat about the +5V level also applies to the SNS-MQ135.

The MQ135 must be allowed to heat to its operating temperature before it can be used. The data sheet recommends a heating period of at least 24h before use. Additionally, if the sensor hasn’t been used for a long time (or ever, if it is new), it must be allowed to burn in for even longer (>72h) in order to burn off various substances which have reacted with the surface during that time.

After the MQ135 has been allowed to heat up and burn in properly, we can use pslab-python 2.1.0+ to measure the ambient CO2 concentration.

Start by creating a multimeter to measure the voltage level on the +5V line:

>>> import pslab
>>> multimeter = pslab.Multimeter()
>>> multimeter.measure_voltage(“CH2”)
4.961014

If the voltage is at least 4.9 V, the next step is to determine the sensor’s R0. It is a good idea to use an average value over a period of time (this only needs to be done once for each sensor):

>>> import time
>>> from pslab.external.gas_sensor import MQ135
>>> mq135 = MQ135(gas=”CO2”, r_load=22e3)
>>> r0 = []
>>> for i in range(3600):
>>>     r0.append(mq135.measure_r0(400))
>>>     time.sleep(1)
>>> mq135.r0 = sum(r0) / len(r0)

Finally:

>>> mq135.measure_concentration()
418.067514310052683

Done!

References

Shimizu Y. (2014) SnO2 Gas Sensor. In: Kreysa G., Ota K., Savinell R.F. (eds) Encyclopedia of Applied Electrochemistry. Springer, New York, NY. https://doi.org/10.1007/978-1-4419-6996-5_475

Wang et al. (2016) CO2-sensing properties and mechanism of nano-SnO2 thick-film sensor, Sensors and Actuators B: Chemical, Volume 227, Pages 73-84, ISSN 0925-4005, https://doi.org/10.1016/j.snb.2015.12.025

Wicker et al. (2017) Ambient Humidity Influence on CO Detection with SnO2 Gas Sensing Materials. A Combined DRIFTS/DFT Investigation, The Journal of Physical Chemistry C, Volume 121, Number 45, Pages 25064-25073, https://doi.org/10.1021/acs.jpcc.7b06253

Continue Reading Measuring CO2 with MQ135

Performing Custom Experiments with PSLab

PSLab has the capability to perform a variety of experiments. The PSLab Android App and the PSLab Desktop App have built-in support for about 70 experiments. The experiments range from variety of trivial ones which are for school level to complicated ones which are meant for college students. However, it is nearly impossible to support a vast variety of experiments that can be performed using simple electronic circuits.

So, the blog intends to show how PSLab can be efficiently used for performing experiments which are otherwise not a part of the built-in experiments of PSLab. PSLab might have some limitations on its hardware, however in almost all types of experiments, it proves to be good enough.

  • Identifying the requirements for experiments

    • The user needs to identify the tools which are necessary for analysing the circuit in a given experiment. Oscilloscope would be essential for most experiments. The voltage & current sources might be useful if the circuit requires DC sources and similarly, the waveform generator would be essential if AC sources are needed. If the circuit involves the use and analysis of data of sensor, the sensor analysis tools might prove to be essential.
    • The circuit diagram of any given experiment gives a good idea of the requirements. In case, if the requirements are not satisfied due to the limitations of PSLab, then the user can try out alternate external features.
  • Using the features of PSLab

  • Using the oscilloscope
    • Oscilloscope can be used to visualise the voltage. The PSLab board has 3 channels marked CH1, CH2 and CH3. When connected to any point in the circuit, the voltages are displayed in the oscilloscope with respect to the corresponding channels.
    • The MIC channel can be if the input is taken from a microphone. It is necessary to connect the GND of the channels to the common ground of the circuit otherwise some unnecessary voltage might be added to the channels.

  • Using the voltage/current source
    • The voltage and current sources on board can be used for requirements within the range of +5V. The sources are named PV1, PV2, PV3 and PCS with V1, V2 and V3 standing for voltage sources and CS for current source. Each of the sources have their own dedicated ranges.
    • While using the sources, keep in mind that the power drawn from the PSLab board should be quite less than the power drawn by the board from the USB bus.
      • USB 3.0 – 4.5W roughly
      • USB 2.0 – 2.5W roughly
      • Micro USB (in phones) – 2W roughly
    • PSLab board draws a current of 140 mA when no other components are connected. So, it is advisable to limit the current drawn to less than 200 mA to ensure the safety of the device.
    • It is better to do a rough calculation of the power requirements in mind before utilising the sources otherwise attempting to draw excess power will damage the device.

  • Using the Waveform Generator
    • The waveform generator in PSLab is limited to 5 – 5000 Hz. This range is usually sufficient for most experiments. If the requirements are beyond this range, it is better to use an external function generator.
    • Both sine and square waves can be produced using the device. In addition, there is a feature to set the duty cycle in case of square waves.
  • Sensor Quick View and Sensor Data Logger
    • PSLab comes with the built in support for several plug and play sensors. The support for more sensors will be added in the future. If an experiment requires real time visualisation of sensor data, the Sensor Quick View option can be used whereas for recording the data for sensors for a period of time, the Sensor Data Logger can be used.
  • Analysing the Experiment

    • The oscilloscope is the most common tool for circuit analysis. The oscilloscope can sample data at very high frequencies (~250 kHz). The waveform at any point can be observed by connecting the channels of the oscilloscope in the manner mentioned above.
    • The oscilloscope has some features which will be essential like Trigger to stabilise the waveforms, XY Plot to plot characteristics graph of some devices, Fourier Transform of the Waveforms etc. The tools mentioned here are simple but highly useful.
    • For analysing the sensor data, the Sensor Quick View can be paused at any instant to get the data at any instant. Also, the logged data in Sensor Data Logger can be exported as a TXT/CSV file to keep a record of the data.
  • Additional Insight

    • The PSLab desktop app comes with the built-in support for the ipython console.
    • The desired quantities like voltages, currents, resistance, capacitance etc. can also be measured by using simple python commands through the ipython console.
    • A simple python script can be written to satisfy all the data requirements for the experiment. An example for the same is shown below.

This is script to produce two sine waves of 1 kHz and capturing & plotting the data.

from pylab import *
from PSL import sciencelab
I=sciencelab.connect()
I.set_gain('CH1', 2) # set input CH1 to +/-4V range
I.set_gain('CH2', 3) # set input CH2 to +/-4V range
I.set_sine1(1000) # generate 1kHz sine wave on output W1
I.set_sine2(1000) # generate 1kHz sine wave on output W2
#Connect W1 to CH1, and W2 to CH2. W1 can be attenuated using the manual amplitude knob on the PSlab
x,y1,y2 = I.capture2(1600,1.75,'CH1') 
plot(x,y1) #Plot of analog input CH1
plot(x,y2) #plot of analog input CH2
show()

 

References

Continue Reading Performing Custom Experiments with PSLab

SPI Communication in PSLab

PSLab supports communication using the Serial Peripheral Interface (SPI) protocol. The Desktop App as well as the Android App have the framework set-up to use this feature. SPI protocol is mainly used by a few sensors which can be connected to PSLab. For supporting SPI communication, the PSLab Communication library has a dedicated class defined for SPI. A brief overview of how SPI communication works and its advantages & limitations can be found here.

The class dedicated for SPI communication with numerous methods defined in them. The methods required for a particular SPI sensor may differ slightly, however, in general most sensors utilise a certain common set of methods. The set of methods that are commonly used are listed below with their functions.

In the setParameters method, the SPI parameters like Clock Polarity (CKP/CPOL), Clock Edge (CKE/CPHA), SPI modes (SMP) and other parameters like primary and secondary prescalar which are specific to the device used.

Primary Prescaler (0,1,2,3) for 64MHz clock->(64:1,16:1,4:1,1:1)

Secondary prescaler (0,1,..7)->(8:1,7:1,..1:1)

The values of CKP/CPOL and CKE/CPHA needs to set using the following convention and according to our requirements.

  • At CPOL=0 the base value of the clock is zero, i.e. the idle state is 0 and active state is 1.
    • For CPHA=0, data is captured on the clock’s rising edge (low→high transition) and data is changed at the falling edge (high→low transition).
    • For CPHA=1, data is captured on the clock’s falling edge (high→low transition) and data is changed at the rising edge (low→high transition).
  • At CPOL=1 the base value of the clock is one (inversion of CPOL=0), i.e. the idle state is 1 and active state is 0.
    • For CPHA=0, data is captured on the clock’s falling edge (high→low transition) and data is changed at the rising edge (low→high transition).
    • For CPHA=1, data is captured on the clock’s rising edge (low→high transition) and data is changed at the falling edge (high→low transition).

public void setParameters(int primaryPreScalar, int secondaryPreScalar, Integer CKE, Integer CKP, Integer SMP) throws IOException {
        if (CKE != null) this.CKE = CKE;
        if (CKP != null) this.CKP = CKP;
        if (SMP != null) this.SMP = SMP;

        packetHandler.sendByte(commandsProto.SPI_HEADER);
        packetHandler.sendByte(commandsProto.SET_SPI_PARAMETERS);
        packetHandler.sendByte(secondaryPreScalar | (primaryPreScalar << 3) | (this.CKE << 5) | (this.CKP << 6) | (this.SMP << 7));
        packetHandler.getAcknowledgement();
    }

 

The start method is responsible for sending the instruction to initiate the SPI communication and it takes the channel which will be used for communication as input.

public void start(int channel) throws IOException {
        packetHandler.sendByte(commandsProto.SPI_HEADER);
        packetHandler.sendByte(commandsProto.START_SPI);
        packetHandler.sendByte(channel);
    }

 

The setCS method is responsible for selecting the slave with which the SPI communication has to be done. This feature of SPI communication is known as Chip Select (CS) or Slave Select (SS). A master can use multiple Chip/Slave Select pins for communication whereas a slave utilises just one pin as SPI is based on single master multiple slaves principle. The capacity of PSLab is limited to two slave devices at a time.

public void setCS(String channel, int state) throws IOException {
        String[] chipSelect = new String[]{"CS1", "CS2"};
        channel = channel.toUpperCase();
        if (Arrays.asList(chipSelect).contains(channel)) {
            int csNum = Arrays.asList(chipSelect).indexOf(channel) + 9;
            packetHandler.sendByte(commandsProto.SPI_HEADER);
            if (state == 1)
                packetHandler.sendByte(commandsProto.STOP_SPI);
            else
                packetHandler.sendByte(commandsProto.START_SPI);
            packetHandler.sendByte(csNum);
        } else {
            Log.d(TAG, "Channel does not exist");
        }
    }

 

The stop method is responsible for sending the instruction to the stop the communication with the slave.

public void stop(int channel) throws IOException {
        packetHandler.sendByte(commandsProto.SPI_HEADER);
        packetHandler.sendByte(commandsProto.STOP_SPI);
        packetHandler.sendByte(channel);
    }

 

PSLab SPI class has methods defined for sending either 8-bit or 16-bit data over SPI which are further classified on whether they request the acknowledgement byte (it helps to know whether the communication was successful or unsuccessful) or not.

The methods are so named send8, send16, send8_burst and send16_burst . The burst methods do not request any acknowledgement value and as a result work faster than the normal methods.

public int send16(int value) throws IOException {
        packetHandler.sendByte(commandsProto.SPI_HEADER);
        packetHandler.sendByte(commandsProto.SEND_SPI16);
        packetHandler.sendInt(value);
        int retValue = packetHandler.getInt();
        packetHandler.getAcknowledgement();
        return retValue;
    }

 

Resources:

Continue Reading SPI Communication in PSLab

I2C Communication in PSLab

PSLab supports communication using the I2C protocol and both the Desktop App and the Android App have the framework set-up to use the I2C protocol. I2C protocol is mainly used by sensors which can be connected to PSLab. For supporting I2C communication, PSLab board has a separate block for I2C communication and has pins named 3.3V, GND, SCL and SDA. A brief overview of how I2C communication works and its advantages & limitations compared to SPI communication can be found here.

The PSLab Python and Java communication libraries have a class dedicated for I2C communication with numerous methods defined in them. The methods required for a particular I2C sensor may differ, however, in general most sensors utilise a certain common set of methods. The set of methods that are commonly used are listed below with their functions. For utilising the methods, the I2C bus is first notified using the HEADER byte (it is common to all the methods) and then a byte to uniquely determine the method in use.

The send method is used to send the data over the I2C bus. First the I2C bus is initialised and set to the correct slave address using I2C.start(address) followed by this method. The method takes the data to be sent as the argument.

def send(self, data):
    try:
        self.H.__sendByte__(CP.I2C_HEADER)
        self.H.__sendByte__(CP.I2C_SEND)
        self.H.__sendByte__(data)  # data byte
        return self.H.__get_ack__() >> 4
    except Exception as ex:
        self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name)

 

The read method reads a fixed number of bytes from the I2C slave. One can also use I2C.simpleRead(address,  numbytes) instead to read from the I2C slave. This method takes the length of the data to be read as argument.  It fetches length-1 bytes with acknowledge bits for each.

def read(self, length):
     data = []
     try:
        for a in range(length - 1):
             self.H.__sendByte__(CP.I2C_HEADER)
             self.H.__sendByte__(CP.I2C_READ_MORE)
             data.append(self.H.__getByte__())
             self.H.__get_ack__()
       self.H.__sendByte__(CP.I2C_HEADER)
       self.H.__sendByte__(CP.I2C_READ_END)
       data.append(self.H.__getByte__())
       self.H.__get_ack__()
    except Exception as ex:
       self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name)
   return data

 

The readBulk method reads the data from the I2C slave. This takes the I2C slave device address, the address of the device from which the data is to be read and the length of the data to be read as argument and the returns the bytes read in the form of a list.

def readBulk(self, device_address, register_address, bytes_to_read):
        try:
            self.H.__sendByte__(CP.I2C_HEADER)
            self.H.__sendByte__(CP.I2C_READ_BULK)
            self.H.__sendByte__(device_address)
            self.H.__sendByte__(register_address)
            self.H.__sendByte__(bytes_to_read)
            data = self.H.fd.read(bytes_to_read)
            self.H.__get_ack__()
            try:
                return [ord(a) for a in data]
            except:
                print('Transaction failed')
                return False
        except Exception as ex:
           self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name)

 

The writeBulk method writes the data to the I2C slave. It takes address of the particular I2C slave for which the data is to be written and the data to be written as arguments.

def writeBulk(self, device_address, bytestream):
        try:
            self.H.__sendByte__(CP.I2C_HEADER)
            self.H.__sendByte__(CP.I2C_WRITE_BULK)
            self.H.__sendByte__(device_address)
            self.H.__sendByte__(len(bytestream))
            for a in bytestream:
                self.H.__sendByte__(a)
            self.H.__get_ack__()
        except Exception as ex:
  self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name)

 

The scan method scans the I2C port for connected devices which utilise I2C as a communication mode. It takes frequency as an argument to set the frequency of the communication and is by default set to 100000. An array containing the addresses of the connected devices (which are integers) is returned.

def scan(self, frequency=100000, verbose=False):
        self.config(frequency, verbose)
        addrs = []
        n = 0
        if verbose:
            print('Scanning addresses 0-127...')
            print('Address', '\t', 'Possible Devices')
        for a in range(0, 128):
            x = self.start(a, 0)
            if x & 1 == 0:  # ACK received
                addrs.append(a)
                if verbose: print(hex(a), '\t\t', self.SENSORS.get(a, 'None'))
                n += 1
            self.stop()
       return addrs

 

Additional Sources

  1. Learn more about the principles behind i2c communication https://learn.sparkfun.com/tutorials/i2c
  2. A simple experiment to demonstrate use of i2c communication with Arduino http://howtomechatronics.com/tutorials/arduino/how-i2c-communication-works-and-how-to-use-it-with-arduino/
  3. Java counterpart of the PSLab I2C library https://github.com/fossasia/pslab-android/blob/master/app/src/main/java/org/fossasia/pslab/communication/peripherals/I2C.java

Continue Reading I2C Communication in PSLab

The Pocket Science Lab: Who Needs it, and Why

Science and technology share a symbiotic relationship. The degree of success of experimentation is largely dependent on the accuracy and flexibility of instrumentation tools at the disposal of the scientist, and the subsequent findings in fundamental sciences drive innovation in technology itself. In addition to this, knowledge must be free as in freedom. That is, all information towards constructing such tools and using them must be freely accessible for the next generation of citizen scientists. A common platform towards sharing results can also be considered in the path to building a better open knowledge network.

But before we get to scientists, we need to consider the talent pool in the student community that gave rise to successful scientists, and the potential talent pool that lost out on the opportunity to better contribute to society because of an inadequate support system. And this brings us to the Pocket Science Lab

How can PSLab help electronics engineers & students?

This device packs a variety of fundamental instruments into one handy package, with a Bill-of-materials that’s several orders of magnitude less than a distributed set of traditional instruments.

It does not claim to be as good as a Giga Samples Per second oscilloscope, or a 22-bit multimeter, but has the potential to offer a greater learning experience. Here’s how:

  • A fresh perspective to characterize the real world. The visualization tools that can be coded on an Android device/Desktop (3D surface plots, waterfall charts, thermal distributions etc ), are far more advanced than what one can expect from a reasonably priced oscilloscope. If the same needs to be achieved with an ordinary scope, a certain level of technical expertise is expected from the user who must interface the oscilloscope with a computer, and write their own acquisition & visualization app.
  • Reduce the entry barrier for advanced experiments.: All the tools are tightly integrated in a cost-effective package, and even the average undergrad student that has been instructed to walk on eggshells around a conventional scope, can now perform elaborate data acquisition tasks such as plotting the resonant frequency of a tuning fork as a function of the relative humidity/temperature. The companion app is being designed to offer varying levels of flexibility as demanded by the target audience.
  • Is there a doctor in the house? With the feature set available in the PSlab , most common electronic components can be easily studied , and will save hours while prototyping new designs.  Components such as resistors, capacitors, diodes, transistors, Op-amps, LEDs, buffers etc can be tested.

How can PSLab help science enthusiasts ?

Physicists, Chemists and biologists in the applied fields are mostly dependent on instrument vendors for their measurement gear. Lack of an electronic/technical background hinders their ability to improve the gear at their disposal, and this is why a gauss meter which is basically a magnetometer coupled with a crude display in an oversized box with an unnecessarily huge transformer can easily cost upwards of $150 . The PSLab does not ask the user to be an electronics/robotics expert , but helps them to get straight to the acquisition part. It takes care of the communication protocols, calibration requirements, and also handles visualization via attractive plots.

A physicist might not know what I2C is , but is more than qualified to interpret the data acquired from a physical sensor, and characterize its accuracy.

  • The magnetometer (HMC5883L) can be used to demonstrate the dependence of the axial magnetic field on distance from the center of a solenoid
  • The pressure,temperature sensor (BMP280) can be used to verify the gas laws, and verify thermodynamic phenomena against prevalent theories.

Similarly, a chemist can use an RGB sensor (TCS3200) to put the colour of a solution into numbers, and develop a colorimeter in the process. Colorimeters are quite handy for determining molality of coloured solutions., and commercial ones are rather expensive. What it also needs is a set of LEDs with known wavelengths, and most manufacturers offer proper characterisation information.

What does it mean for the hobbyist?

It is capable of greatly speeding up the troubleshooting process . It can also instantly characterize the expected data from various sensors so that the hobbyist can code accordingly. For example, ‘beyond what tilt threshold & velocity should my humanoid robot swing its arms forward in order to prevent a broken nose?’ . That’s not a question that can be easily answered by said hobbyist who is currently in the process of developing his/her own acquisition system.

How can we involve the community?

The PSLab features an experiment designer that speeds acquisition by providing spreadsheets, analytical tools, and visualisation options all in one place. An option for users to upload their new experiments/utilities to the cloud, and subject those to a peer-review process has been planned. Following which , these new experiments can be pumped back into the ecosystem which will find more uses for it, improve it, and so on.

For example , a user can combine the waveform generator with an analog multiplier IC, and develop a spectrum analyzer.

The case for self-reliance

The average undergraduate laboratory currently employs dedicated instruments for each experiment as prescribed by the curriculum. These instruments often only include the measurement tools essential to the experiment, and students merely repeat the procedure verbatim. That’s not experimentation, it’s rather just verification. PSLab offers a wide array of additional instruments that can be employed by the student to enhance the experiment with their own inputs.

For example, a commonly used diode IV curve-tracer kit usually has a couple of power supplies, a voltmeter, and an ammeter. But, if a student wishes to study the impact of temperature on the band gap, he will hard pressed for the additional tools, and software to combine the acquisition process. With the PSLab, however , he/she can pick from a variety of temperature sensors (LM35, BMP180, Si7021 .. ) depending on the requirement, and explore beyond the book. They are thus better prepared to enter research labs .

And in conclusion , this project has immense potential to help create the next generation of scientists, engineers and creators.

Resources

Continue Reading The Pocket Science Lab: Who Needs it, and Why

Real time Sensor Data Analysis on PSLab Android

PSLab device has the capacity to connect plug and play sensors through the I2C bus. The sensors are capable of providing data in real time. So, the PSLab Android App and the Desktop app need to have the feature to fetch real time sensor values and display the same in the user interface along with plotting the values on a simple graph.

The UI was made following the guidelines of Google’s Material Design and incorporating some ideas from the Science Journal app. Cards are used for making each section of the UI. There are segregated sections for real time updates and plotting where the real time data can be visualised. A methods for fetching the data are run continuously in the background which receive the data from the sensor and then update the screen.

The following section denotes a small portion of the UI responsible for displaying the data on the screen continuously and are quite simple enough. There are a number of TextViews which are being constantly updated on the screen. Their number depends on the type and volume of data sent by the sensor.

<TextView
       android:layout_width="wrap_content"
       android:layout_height="30dp"
       android:layout_gravity="start"
       android:text="@string/ax"
       android:textAlignment="textStart"
       android:textColor="@color/black"
       android:textSize="@dimen/textsize_edittext"
       android:textStyle="bold" />

<TextView
       android:id="@+id/tv_sensor_mpu6050_ax"
       android:layout_width="wrap_content"
       android:layout_height="30dp"
       android:layout_gravity="start"
       android:textAlignment="textStart"
       android:textColor="@color/black"
       android:textSize="@dimen/textsize_edittext"
       android:textStyle="bold" />

 

The section here represents the portion of the UI responsible for displaying the graph. Like all other parts of the UI of PSLab Android, MPAndroidChart is being used here for plotting the graph.

<LinearLayout
       android:layout_width="match_parent"
       android:layout_height="160dp"
       android:layout_marginTop="40dp">

       <com.github.mikephil.charting.charts.LineChart
               android:id="@+id/chart_sensor_mpu6050"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#000" />
</LinearLayout>

 

Since the updates needs to continuous, a process should be continuously run for updating the display of the data and the graph. There are a variety of options available in Android in this regard like using a Timer on the UI thread and keep updating the data continuously, using ASyncTask to run a process in the background etc.

The issue with the former is that since all the processes i.e. fetching the data and updating the textviews & graph will run on the UI thread, the UI will become laggy. So, the developer team chose to use ASyncTask and make all the processes run in the background so that the UI thread functions smoothly.

A new class SensorDataFetch which extends AsyncTask is defined and its object is created in a runnable and the use of runnable ensures that the thread is run continuously till the time the fragment is used by the user.

scienceLab = ScienceLabCommon.scienceLab;
i2c = scienceLab.i2c;
try {
    MPU6050 = new MPU6050(i2c);
} catch (IOException e) {
    e.printStackTrace();
}
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        while (true) {
            if (scienceLab.isConnected()) {
                try {
                    sensorDataFetch = new SensorDataFetch();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                sensorDataFetch.execute();
            }
        }
    }
};
new Thread(runnable).start();

 

The following is the code for the ASyncTask created. There are two methods defined here doInBackground and onPostExecute which are responsible for fetching the data and updating the display respectively.

The raw data is fetched using the getRaw method of the MPU6050 object and stored in an ArrayList. The data type responsible for storing the data will depend on the return type of the getRaw method of each sensor class and might be different for other sensors. The data returned by getRaw is semi-processed and the data just needs to be split in sections before presenting it for display.

The PSLab Android app’s sensor files can be viewed here and they can give a better idea about how the sensors are calibrated, how the intrinsic nonlinearity is taken care of, how the communication actually works etc.

After the data is stored, the control moves to the onPostExecute method, here the textviews on the display and the chart are updated. The updation is slowed down a bit so that the user can visualize the data received.

private class SensorDataFetch extends AsyncTask<Void, Void, Void> {
   MPU6050 MPU6050 = new MPU6050(i2c);
   ArrayList<Double> dataMPU6050 = new ArrayList<Double>();

   private SensorDataFetch(MPU6050 MPU6050) throws IOException {
   }

   @Override
   protected Void doInBackground(Void... params) {
       try {
           if (MPU6050 != null) {
               dataMPU6050 = MPU6050.getRaw();
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
           return null;
   }

   protected void onPostExecute(Void aVoid) {
       super.onPostExecute(aVoid);
       tvSensorMPU6050ax.setText(String.valueOf(dataMPU6050.get(0)));
       tvSensorMPU6050ay.setText(String.valueOf(dataMPU6050.get(1)));
       tvSensorMPU6050az.setText(String.valueOf(dataMPU6050.get(2)));
       tvSensorMPU6050gx.setText(String.valueOf(dataMPU6050.get(3)));
       tvSensorMPU6050gy.setText(String.valueOf(dataMPU6050.get(4)));
       tvSensorMPU6050gz.setText(String.valueOf(dataMPU6050.get(5)));
       tvSensorMPU6050temp.setText(String.valueOf(dataMPU6050.get(6)));
   }
}

The detailed implementation of the same can be found here.

Additional Resources

  1. Learn more about how real time sensor data analysis can be used in various fields like IOT http://ieeexplore.ieee.org/document/7248401/
  2. Google Fit guide on how to use native built-in sensors on phones, smart watches etc. https://developers.google.com/fit/android/sensors
  3. A simple starter guide to build an app capable of real time sensor data analysis http://developer.telerik.com/products/building-an-android-app-that-displays-live-accelerometer-data/
  4. Learn more about using AsyncTask https://developer.android.com/reference/android/os/AsyncTask.html

Continue Reading Real time Sensor Data Analysis on PSLab Android

Packing and Unpacking Data in PSLab Android App

In PSLab we communicate with PSLab Hardware device, to exchange data, i.e we give a command or instruction and it responses accordingly. So this giving and receiving is in terms of packed byte string. Thus, we need some solid knowledge to pack and unpack data. In python communication library, there is struct module available. In JAVA we can use NIO’s ByteBuffer or implement our own functions. In this blog post I discuss both methods.  

In Python we have struct module for packing data in byte strings. As different languages interpret data types differently like Java takes 4 bytes for int and C++ takes 2 bytes for int. To send and receive data properly, we pack data in a byte string and unpack on other side with it’s data type properties. In PSLab, we have to communicate with device for various applications like getting calibration data during power up time as raw data doesn’t make much sense until calibration is applied on it.

You also need to take care of order of sequence of bytes like there are generally two types of order in which a sequence of bytes are stored in memory location:

  • Big – Endian: In which MSB is stored first.

    Source: Wikipedia
  • Little – Endian: In which LSB is stored first.

    Source: Wikipedia

In Python

The standard sizes and format characters of particular data type can be seen in the image below.

Format C Type Python Type Standard
x Pad byte No value
c char string of length 1 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
f float float 4
d double float 8
s char[] string
p char[] string
P void* integer

Source: Python Docs

For Packing data

import struct
struct.Struct(“B”).pack(254)   # Output ->  b’\xfe’
a = struct.Struct(“I”).pack(2544)   # Output -> b’\xf0\t\x00\x00′

Now a is the byte string that has packed value as 2544, this can be send to some device byte by byte and reconstructed on receiving side by knowing how many bytes does the data type received contains.

For Unpacking data

import struct
struct.unpack(“I”,a)  # Output -> (2544,)

In JAVA

For Packing data

Suppose you have to pack an integer, in java int takes 32 bits (4 bytes)

Using JAVA’s NIO’s ByteBuffer

byte[] bytes = ByteBuffer.allocate(4).putInt(2544).array();

If you want hardcore method to see what exactly is happening, use

byte[] intToByteArray(int value){
 return new byte[]{
     (byte)value >>> 24,
     (byte)value >>> 16,
     (byte)value >>> 8,
     (byte)value
  };
}

“>>>” is used for unsigned shifting, you can use according to your requirements.

After you have your byte array, you can easily create a string out of it and transmit.

For Unpacking data

Using JAVA’s NIO’s ByteBuffer

int fromByteArray(byte[] bytes){
int a = ByteBuffer.wrap(bytes).getInt();
return a;
}

It assumes that byte array is stored as Big Endian, if bytes in byte array is stored as Little Endian, add order() after wrap()

int fromByteArray(byte[] bytes){
int a = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
return a;
}

Note: Make sure the bytes array that you provide has same number of bytes as that of the data type that you are trying to unpack. For example: if you want int, bytes array should have 4 bytes as int type in JAVA has 4 bytes. If you want short, bytes array should have 2 bytes as short type in JAVA has 2 bytes.

To visualise underlying implementation, see

int from byteArray(byte[] bytes){
return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];
}

In all above implementation big-endian order was assumed, you can modify function if you are using little-endian or some other sequence.

References

Continue Reading Packing and Unpacking Data in PSLab Android App

Communication by pySerial python module in PSLab

In the PSLab Desktop App we use Python for communication between the PC and PSLab device. The PSLab device is connected to PC via USB cable. The power for the hardware device is provided by the host through USB which in this case is a PC. We need well structured methods to establish communication between PC and PSLab device and this is where pySerial module comes in. We will discuss how to communicate efficiently from PC to a device like PSLab itself using pySerial module.

How to read and write data back to PSLab device?

pySerial is a python module which is used to communicate serially with microcontroller devices like Arduino, RaspBerry Pi, PSLab (Pocket Science Lab), etc. Serial data transfer is easier using this module, you just need to open a port and obtain serial object, which provides useful and powerful functionality. Users can send string (which is an array of bytes) or any other data type all data types can be expressed as byte string using struct module in python, read a specific number of bytes or read till some specific character like ‘\n’ is encountered. We are using this module to create custom read and write functions.

How to Install pySerial and obtain serial object for communication?

You can install pySerial using pip by following command

pip install pyserial

Once it’s installed we can now import it in our python script for use.

Obtain Serial Object

In Linux

>>> import serial
>>> ser = serial.Serial(‘/dev/ttyUSB0’)

In Windows

>>> ser = serial.Serial()
>>> ser.baudrate = 19200
>>> ser.port = ‘COM1’

Or

>>> ser = serial.Serial(‘COM1’, 19200)

You can specify other properties like timeout, stopbits, etc to Serial constructor.

Complete list of parameters is available here. Now this “ser” is an object of Serial class that provides all the functionalities through its interface. In PSLab we obtain a serial object and implement custom methods to handle communication which isn’t directly provided by pySerial, for example if we need to implement a function to get the version of the PSLab device connected. Inside the version read function we need to send some bytes to the device in order to obtain the version string from device as a byte response.

What goes under the hood?

We send some sequence of bytes to PSLab device, every sequence of bytes corresponds to a unique function which is already written in device’s firmware. Device recognises the function and responses accordingly.

Let’s look at code to understand it better.

ser.write(struct.Struct(‘B’).pack(11))  #  Sends 11 as byte string
ser.write(struct.Struct(‘B’).pack(5))   #  Sends 5 as bytes string
x = ser.readline()                      #  Reads bytes until ‘\n’ is encountered   

To understand packing and unpacking using struct module, you can have a read at my other blog post Packing And Unpacking Data in JAVA in which I discussed packing and unpacking of data as byte strings and touched a bit on How it’s done in Python.  

You can specify how many bytes you want to read like shown in code below, which is showing and example for 100 bytes :

x = ser.read(100)

After your communication is complete you can simply close the port by:

ser.close()

Based on these basic interface methods more complex functions can be written to handle your specific needs. More details one how to implement custom methods is available at python-communication-library of PSLab which uses pySerial for communication between Client and PSLab device.

An example of custom read function is suppose I want to write a function to read an int from the device. int is of 2 bytes as firmware is written in C, so we read 2 bytes from device and unpack them in client side i.e on PC. For more such custom functions refer packet_handler.py of PSLab python communication library.

def getInt(self):
      “””
      reads two bytes from the serial port and
      returns an integer after combining them
      “””
      ss = ser.read(2)  # reading 2 bytes from serial object
      try:
          if len(ss) == 2:
              return CP.ShortInt.unpack(ss)[0]  # unpacking bytes to make int
      except Exception as ex:
          self.raiseException(ex, “Communication Error , Function : get_Int”)

Resources

Continue Reading Communication by pySerial python module in PSLab

Analyzing Sensor Data on PSLab

PSLab Android App and Desktop app have the functionality of reading data from the sensors. The raw sensor data received is in the form of a long string and needs to parsed to understand what the data actually conveys.

The sensor data is unique in terms of volume of data sent, the units of measurement of the data etc., however none of this is reflected in the raw data. The blog describes how the sensor data received by the Android/Desktop app is parsed, interpreted and finally presented to the user for viewing.

The image below displays the raw data sent by the sensors

blog_post_9_2

Fig: Raw Sensor data displayed below the Get Raw button

  • In order to understand the data sent from the sensor, we need to understand what the sensor does.
    • For example, HMC5883L is a 3-axis magnetometer and it returns the value of the magnetic field in the x, y & z axes in the order of nanoTeslas.
    • Similarly, the DAC of PSLab – MCP4728 can also be used like other sensors, it returns the values of channels in millivolts.
    • The sensor MPU6050 being 3-axes accelerometer & gyroscope which returns the values of acceleration & angular momentum of the x, y & z axes in their SI units respectively.
  • Each sensor has a sensitivity value. The sensitivity of the sensor can be modified to adjust the accuracy of the data received. For PSLab, the data returned is a float number with each data point having 4 bytes of memory with the highest sensitivity. Although sensitivity is not a reliable indicator of the accuracy of the data. Each value received has a lot of trailing values after the decimal and it is evident that no sensor can possibly achieve accuracy that high, so the data after 2-3 decimal places is garbage and not taken into consideration.
  • Some sensors are configurable up to a great extent like MPU6050 where limits can also be set on the range of data, volume of data sent etc. whereas some are not configurable and are just meant for sending the data at regular intervals.
  • In order to parse the above data, if the sensor returns a single value, then the data is ready to be used. However, in most cases like above where the sensors return multiple values, the data stream can be divided into equal parts since each value occupies equal space and each value can be stored in different variables.
  • The stored data has to be presented to the user in a better understandable format where it is clear that what each value represents. For example, in case of the 3 axes sensors, the data of each axis must be distinctly represented to the user.

Shown below are the mock-ups of the sensor UIs in which each value has been distinctly represented.

         

Fig: Mock-ups for the sensor UIs (a) – HMC5883L (b) – MPU6050

Each UI has a card to display those values. These values are updated in real time and there are additional options to plot the data received in real time and in some cases also configure the sensor. In addition to that there are features for data logging where the data is recorded for a given time interval specified by the user and on completion of recording, calculations like the mean, standard deviation etc. are presented to the user.

Additional Resources

  1. Analyzing sensor data using Arduino, similar to method for PSLab – http://tronixstuff.com/2014/01/21/online-data-analysis-arduino-plotly/
  2. YouTube video to understand analysis of data from MPU6050 in Arduino – https://www.youtube.com/watch?v=taZHl4Mr-Pk

Continue Reading Analyzing Sensor Data on PSLab