Thursday, March 31, 2016

Mailbag - Si7021 / SHT21 Temperature/Humidity sensor








And the story behind: 

Somebody sent to me a nice & tiny temperature/humidity sensor breakout board based on the Si7021 IC:

Si7021 Module - Top View

On the Top side we have Si7021 Sensor only.


Si7021 Module - Bottom View

Bottom Side, a 3.3V LDO and a Voltage level shifting circuit that probably makes it 5V tolerant on I2C bus. We will use it at 3.3V so nothing to worry about.



DESCRIPTION

    The  Si7021  I2C  Humidity  and  Temperature  Sensor  is  a  monolithic  CMOS  IC integrating   humidity   and   temperature   sensor   elements,   an   analog-to-digital converter, signal processing, calibration data, and an I2C Interface.

    The patented use of industry-standard, low-K polymeric dielectrics for sensing humidity enables the  construction  of  low-power,  monolithic  CMOS  Sensor  ICs  with  low  drift  and hysteresis, and excellent long term stability.

   The  humidity  and  temperature  sensors  are  factory-calibrated  and  the  calibration data is stored in the on-chip non-volatile memory.  This ensures that the sensors are fully interchangeable, with no recalibration or software changes required.

   The Si7021 offers an accurate, low-power, factory-calibrated digital solution ideal for measuring humidity, dew-point, and temperature, in applications ranging from HVAC/R and asset tracking to industrial and consumer platforms.


Si7021 - Block Diagram


Nice. Looks more or less like SHT21 from Sensirion. And we will see that it is quite compatible (at least on the temp/humidity reading procedure side) with small differences for the rest of registers.




FEATURES
  • Precision Relative Humidity Sensor - ± 3% RH (max), 0–80% RH
  • High Accuracy Temperature Sensor - ±0.4 °C (max), –10 to 85 °C
  • 0 to 100% RH operating range
  • Up to –40 to +125 °C operating range
  • Wide operating voltage (1.9 to 3.6 V) (SHT21 - 2.1V min !)
  • Low Power Consumption:
         - 150 μA active current
         - 60 nA standby current
  • Factory-calibrated 
  • I2C Interface
  • Integrated on-chip heater
  • 3x3 mm DFN Package
  • Excellent long term stability
  • Optional factory-installed cover
        - Low-profile
        - Protection during reflow
        - Excludes liquids and particulates



Si7021 - Pinout Diagram



Typical Application Circuit for Relative Humidity and Temperature Measurements




For more details please take a look at the Si7021 Datasheet.



What we will need:

  • ESP8266 nEXT EVO Board
  • Si7021 Module as the one from above
  • For programming and uploading the driver and the software we will continue to use the LuaUploader as before.  
Connection with ESP8266 nEXT EVO Board is pretty straight-forward as the module is fully pin-to-pin compatible with the availavble nEXT Bus connector.

 
Si7021 Board connected with ESP8266 nEXT EVO DevBoard - TOP view




Si7021 Board connected with ESP8266 nEXT EVO DevBoard - 45 deg view





Software implementation

 The Si7021 communicates with the host controller over a digital I2C interface. The 7-bit base slave address is 0x40

 Master I2C devices communicate with the Si7021 using a command structure. The commands are listed below in the I2C command  table.  
 Commands  other  than  those  documented  below  are  undefined  and  should  not  be  sent  to  the device.


 
I2C Command Table



Issuing a Measurement Command
 
   The measurement commands instruct the Si7021 to perform one of two possible measurements: Relative Humidity or  Temperature. 

   The  procedure  to  issue  any  one  of  these  commands  is  identical.  While  the  measurement  is  in progress, the option of either clock stretching (Hold Master Mode) or Not Acknowledging read requests (No Hold Master  Mode)  is  available  to  indicate  to  the  master  that  the  measurement  is  in  progress. The  chosen  command code determines which mode is used.

   Optionally,  a  checksum  byte  can  be  returned  from  the  slave  for  use  in  checking  for  transmission  errors.  The checksum  byte  will  follow  the  least  significant  measurement  byte  if  it  is  acknowledged  by  the  master.  


   The checksum  byte  is  not  returned  if  the  master  “not  acknowledges”  the  least  significant  measurement  byte.  The checksum byte is calculated using a CRC generator polynomial of x^8+ x^5 + x^4 + 1, with an initialization of 0x00.

   The  checksum  byte  is  optional  after  initiating  an  RH  or  temperature  measurement  with  commands  0xE5,  0xF5,0xE3, and 0xF3. It is required for reading the electronic ID with commands 0xFA 0x0F and 0xFC 0xC9. 


  For all other commands, the checksum byte is not supported.









1. Init I2C bus/interface

 Standard I2C Bus Initialisation function:

function init_I2C()
    i2c.setup(bus, sda, scl, i2c.SLOW)
end

2.  Write Si7021 Register Function


    write_Si_Reg = function (dev_addr, set) 
          i2c.start(0x0)
          i2c.address(0x0, dev_addr ,i2c.TRANSMITTER)
          i2c.write(0x0,set)
          i2c.stop(0x0)
          tmr.delay(5000)
     end

 3.  Read Si7021 Register Function 


     read_Si_Reg = function (dev_addr)          
          i2c.start(0x0)
          i2c.address(0x0, dev_addr,i2c.RECEIVER)
          tmr.delay(5000)
          c = i2c.read(0x0,2)
          i2c.stop(0x0)

          rval = (bit.lshift(string.byte(c, 1), 8) + string.byte(c, 2))
          status = bit.band(rval,3)    --save status bits
          rval = bit.band(rval,65532)  --clear status bits
          return rval, status
     end

 4. Measuring Relative Humidity
 

         Once  a  relative  humidity  measurement  has  been  made,  the  results  of  the  measurement  
     may  be  converted  to percent relative humidity by using the following expression:

             hum = -6.0+125.0/65536.0*rval

       A humidity measurement will always return XXXXXX10 in the LSB field -> Status bit = 1 -> 

     marking a Humidity measurement data.

function read_hum()
   write_Si_Reg(dev_addr, RHumidityHoldCmd)
   tmr.delay(10000)
   read_Si_Reg(dev_addr)       
   hum = -6.0+125.0/65536.0*rval
   print("\nStatus : "..status)
   print("Humidity : "..string.format("%.2f",hum).."%")
end

 

5. Measuring Temperature

   Each time a relative humidity measurement is made a temperature measurement is also made for the purposes of temperature  compensation  of  the  relative  humidity  measurement.  If  the  temperature  value  is  required,  it  can  be read  using  command  0xE0;  this  avoids  having  to  perform  a  second  temperature  measurement.  


   The  measure temperature  commands  0xE3  and  0xF3  will  perform  a  temperature  measurement  and  return  the  measurement value, command 0xE0 does not perform a measurement but returns the temperature value measured during the relative humidity measurement.
The checksum output is not available with the 0xE0 command.

The results of the temperature measurement may be converted to temperature in degrees Celsius (°C) using the following expression:


     temp = -46.85+175.72/65536.0*rval

  A temperature measurement will always return XXXXXX00 in the LSB field - Status bit = 0 -> marking a Temperature measurement data.


 function read_temp()
   write_Si_Reg(dev_addr, TempHoldCmd)
   read_Si_Reg(dev_addr)      
   temp = -46.85+175.72/65536.0*rval
   print("Status : "..status)
   print("Temperature : "..string.format("%.2f",temp).."C")
end


6. Main Program
init_I2C()

tmr.alarm( 0, 5000, 1, function()
    read_hum()
    read_temp()
end)




Wednesday, March 30, 2016

ESP8266 - ADC Input frontend



Autorange Analog frontend


For a deeper Hardware description please take a look at the ESP8266 Analog Extension Board





What we will need:
 


ADC Frontend description

 Selection of the input voltage divider is done using an analog switch driven by PCF8574 PORT P0 and P1 bits:

0 - 1:20 Divider
1 - 1:10 Divider
2 - 1:5 Divider
3 - Full Voltage Range


As the ADC Input is programmed to be used in the 0-2V range that will give us the followings available ranges:

0 -> 0 - 40V
1 -> 0 - 20V
2 -> 0 - 10V
3 -> 0 - 2V

In the case of using the MCP3421 ADC at 12 Bit resolution, we will have the following corresponding LSB values:

- 1:20 Divider  -  0.02V
- 1:10 Divider  -  0.01V
- 1:5 Divider    -  0.005V
- Full Range    -  0.001V




Software implementation

1. Init I2C bus/interface

 Standard I2C Bus Initialisation function:

function init_I2C()
    i2c.setup(bus, sda, scl, i2c.SLOW)
end

 2. Set PCF8574 PORT Register Function
function setPort( port, stat) 
    i2c.start(id)
    i2c.address(id, dev_addr ,i2c.TRANSMITTER)
    i2c.write(id,stat)
    i2c.stop(id)
end

 3. Set Port function 

 Just a nicer way to write data to PCF8574 Register. Remember that we need to write a "ZERO" to the corresponding bit. We are sinking not sourcing !!

function setPortdata(p)
    pp = 255-p
    setPort(0x20,pp)
end


4. Set Voltage Divider function

  Select the desired Voltage divider ratio based on the choosen ratio value and calibrated LSB data.

--calibration data
x20 = 0.020056
x10 = 0.01014
x5  = 0.005014
xfl = 0.0010075
xrt = x20 --by default start with highest VDIV!
function SetVDivider(rtio)
    if (rtio==0) then xrt=x20 end
    if (rtio==1) then xrt=x10 end
    if (rtio==2) then xrt=x5 end
    if (rtio==3) then xrt=xfl end
    print("XRT = "..xrt)
    setPortdata(rtio)    -- select desired voltage divider
    return xrt
end



       -- Set/Change Voltage Divider ratio:
SetVDivider(0)  -- 1:20 Divider
SetVDivider(1)  -- 1:10 Divider
SetVDivider(2)  -- 1:5  Divider
SetVDivider(3)  -- 1:1  Full Voltage in!!


5. MAIN Program


-- Main Program
id = 0
sda=2 --GPIO4
scl=1 --GPIO5
dev_addr = 0x20
--calibration data
x20 = 0.020056
x10 = 0.01014
x5  = 0.005014
xfl = 0.0010075
xrt = x20 --by default start with highest VDIV!
--init I2C Bus
init_I2C()

--Init Volatage divider
setPortdata(0) -- --by default start with highest VDIV!
SetVDivider(0)  -- 1:20 Divider

----MCP3421 ADC
require('mcp3421')
sda=2 --GPIO4
scl=1 --GPIO5
mcp3421:init(sda, scl)
mcp3421:write_ADC_config(0x68, 0x10)

tmr.alarm( 0, 1000, 1, function()
    adc_val = mcp3421:read_ADC_data(0x68)
    print("\nADC Value : "..adc_val.." \n Voltage  : " ..adc_val*xrt)
    return adc_val
end)