ESP32_CCS811-CO2-sensor

Place your projects here
Post Reply
User avatar
PeterN
Posts: 366
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 171 times
Been thanked: 203 times
Contact:

ESP32_CCS811-CO2-sensor

Post by PeterN »

After I received a CCS811 CO2-sensor, I tried to build a minimal I2C driver for it in ANNEX32.
With an ESP32, this works quite well now.

eCO2 and TVOC shown at wlog every 2 seconds

This is the wlog-output in "not silent" mode:


---start-I2C-scan---
found a device at I2C-Adr dec 90 , hex 5A
---end-I2C-scan---

-------------CCS811-Sensor_Status:-------------
STATUSBYTE : 17 = &b10001
Boot Mode
valid firmware loaded
DATA available to read
no Error
----------------------------------------

Change from BOOT-Mode to App Mode

-------------CCS811-Sensor_Status:-------------
STATUSBYTE : 152 = &b10011000
Application Mode
valid firmware loaded
DATA available to read
no Error
----------------------------------------

Set MEAS_MODE_REG to &b:10000 = read new Data once per 1 second

-------------CCS811-Sensor_Status:-------------
STATUSBYTE : 152 = &b10011000
Application Mode
valid firmware loaded
DATA available to read
no Error
----------------------------------------

eCO2 = 0 TVOC = 0
eCO2 = 0 TVOC = 0
eCO2 = 400 TVOC = 0
eCO2 = 403 TVOC = 0
eCO2 = 400 TVOC = 0
eCO2 = 463 TVOC = 9
eCO2 = 441 TVOC = 6
eCO2 = 435 TVOC = 5
eCO2 = 435 TVOC = 5


And this is my code for it:

Code: [Local Link Removed for Guests]

'#######################################################################
'Show AIR QUALITY with a CCS811 CO2-TVOC-Sensor at I2C-pins of an ESP32
'#######################################################################
' DB9JG 2021/02/13 V1.0
Version$           = "V1.0"

SILENT             = 0    '1 => suppress  all messages   0 => show some status-messages via  wlog

SDA_PIN            = 21   'SDA=21 at ESP32
SCL_PIN            = 22   'SCL=22 at ESP32
CCS811temp         = 0
CCS811_schreiben   = &hb4 '  Die I2C-Adresse von CCS811 zum schreiben hB4 =1011 0100    7 Bit Plus das  Bit 0 für lesen (0)
CCS811_lesen       = &hb5 '  Die I2C-Adresse von CCS811 zum lesen     hB5 = 10110101    7 Bit Plus das  Bit 0 für schreiben (1)
STATUS_REG         = &h00
MEAS_MODE_REG      = &h01
ALG_RESULT_DATA    = &h02
ENV_DATA           = &h05
NTC_REG            = &h06
THRESHOLDS         = &h10
BASELINE           = &h11
HW_ID_REG          = &h20
ERROR_ID_REG       = &hE0
APP_START_REG      = &hF4
SW_RESET           = &hFF
CCS811_I2C_ADR     = &h5A
GPIO_WAKE          = &h5
DRIVE_MODE_IDLE    = &h0
DRIVE_MODE_1SEC    = &h10
DRIVE_MODE_10SEC   = &h20
DRIVE_MODE_60SEC   = &h30
INTERRUPT_DRIVEN   = &h8
THRESHOLDS_ENABLED = &h4
Dim BUFFER(10)  'I2C-Sende- / Empfangspuffer.

if not silent GOSUB I2C_SCANNER  


I2C.SETUP SDA_PIN, SCL_PIN    ' set I2C ports
GOSUB SHOW_CCS811_STATUS

GOSUB CCS811_GO_APP_MODE   'leave BOOT-Mode
GOSUB SHOW_CCS811_STATUS   

GOSUB CCS811_SET_MODE      'CCS811 generates eCO2 and TVOC once per second
GOSUB SHOW_CCS811_STATUS

TIMER0 2000, CCS811_SHOW   'fetch eCO2 and TVOC from CCS811 sensor

WAIT

end '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!



'######################################################################
CCS811_SHOW:
GOSUB CCS811_READ
wlog "eCO2 = ";eCO2,"TVOC = ";TVOC
return

'######################################################################
CCS811_READ:
' Bit 3 = Data Ready 0:no new Data 1:new Data
' Bit 0 Error dedection 0:no Error 1: Error on i2c or Sensor
i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read

If CCS811temp and 8 then                               'if Bit3 of status_register =1 then DATA_available
  i2c.writeRegByte CCS811_I2C_ADR, ALG_RESULT_DATA ,0  'selects the Result mailbox
  i2c.ReqFrom CCS811_I2C_ADR, 8
  for i = 1 to 8                                       'The Result mailbox contains  8 Bytes
    BUFFER(i)  = i2c.read
  next
  eCO2 = 256*BUFFER(1) + BUFFER(2)    'eCO2 ppm
  TVOC = 256*BUFFER(3) + BUFFER(4)    'eTVOC
else
  eCO2 = 0
  TVOC = 0
end if

return
'#########################################################################################################################################################




'######################################################################
SHOW_CCS811_STATUS:
'----------------------Status request-------------------------------------
' Bit 7 FW_Mode = 0:Boot Mode 1:Application Mode
' Bit 4 =  Application Firmware loaded 0: no 1:Valid application loaded
' Bit 3 = Data Ready 0:no new Data 1:new Data
' Bit 0 Error dedection 0:no Error 1: Error on i2c or Sensor
if silent return

i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read
pause  25
wlog ""
wlog "-------------CCS811-Sensor_Status:-------------"
wlog "STATUSBYTE  :",CCS811temp,"= &b"; bin$(CCS811temp)
If CCS811temp and 128 then
  wlog " Application Mode"
else
  wlog " Boot Mode"
end if
If CCS811temp and 16 then
  wlog " valid firmware loaded"
else
  wlog "  NO valid firmware loaded!"
end if
If CCS811temp and 8 then
  wlog " DATA available to read"
else
  wlog " no DATA available to read"
end if
If CCS811temp and 1 then
  wlog " Error on i2c or sensor!!"
  'Read the ERROR-ID
  i2c.writeRegByte CCS811_I2C_ADR, ERROR_ID_REG , 0
  i2c.ReqFrom CCS811_I2C_ADR,1
  CCS811temp = i2c.read
  wlog " ERROR_ID_REG : dec",CCS811temp,"= &b";bin$(CCS811temp)
else
  wlog " no Error"
end if
wlog "----------------------------------------"
wlog " "
RETURN



'######################################################################
CCS811_GO_APP_MODE:
'----------------------- Change to Application Mode -------------------
if not silent wlog "Change from BOOT-Mode to  App Mode"
'i2c.writeRegByte CCS811_I2C_ADR, APP_START_REG,0
' geht nicht da value geschreiben wird, Register soll aber nur angewählt werden
'Aber DAS funktioniert:
i2c.begin CCS811_I2C_ADR
I2c.write APP_START_REG
' value hier NICHT schreiben!!!
'  i2c.write value
i2c.end
pause 10
return

'######################################################################
CCS811_SET_MODE:
'------------------------Modus einstellen---------------------------
MODUS = &b00010000      'Bit7=0reserved, 6-4=001Mode 1 jede sekunde neue daten, 3=0 Interrupt für neue Daten aus, 2=0 Interrupt Mode Normal,  1-0 =00 Reserved
if not silent wlog "Set MEAS_MODE_REG to &b:"; bin$(MODUS); " = read new Data once per 1 second"
i2c.writeRegByte CCS811_I2C_ADR, MEAS_MODE_REG, MODUS
pause  10
return


'######################################################################
I2C_SCANNER:
'I2C Address Scanner
'print and wlog  the address of the I2C-devices found

I2C.SETUP 21, 22  ' set I2C port on pins 21 and 22
print "---start-I2C-scan---"
wlog  "---start-I2C-scan---"
for i = 0 to 120
  i2c.begin i
  if i2c.end = 0 then
    print "found a device at I2C-Adr dec "; i ,", hex "; hex$(i)
    wlog  "found a device at I2C-Adr dec "; i ,", hex "; hex$(i)
    pause 10
  end if
next i
print "---end-I2C-scan---"
wlog  "---end-I2C-scan---"
return
Last edited by PeterN on Tue Feb 23, 2021 5:48 pm, edited 1 time in total.
User avatar
PeterN
Posts: 366
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 171 times
Been thanked: 203 times
Contact:

Re: ESP32_CCS811-CO2-sensor

Post by PeterN »

I have carried out further tests with the CCS811 and have arrived at a CO2 traffic light.

(UPDATE 2021/02/21: I inserted here my newest code Version1.4 which includes a baseline-save and -restore
The ranges for the LEDs are still experimental, to show an early reaction.

My current CCS811-firmware version is still the out-of-the box Version1.


Image
Image
Image
Image

My intermediate status code for the CO2-Traffic-Light (with experimental CO2 range values for the LEDs:
(UPDATE 2021/02/21: I inserted here my newest code Version1.4 which includes a baseline-save and -restore

Code: [Local Link Removed for Guests]


'#######################################################################
' CO2-TRAFFIC-LIGHT
' ##################
'Shows AIR QUALITY with
' - CCS811 eCO2-TVOC-Sensor at I2C-pins of an ESP32
' - 3 LEDs (green, yellow, red to show the range of CO2 ppm
' - saves the 2 BASELINE-bytes in /BASELINE1.txt and /BASELINE2.txt

' - SAVE OF BASELINE after 20 Minutes in an unpolluted environment
' as a kind of calibration if variable BASELINE_SAVE = 1
' - AUTOMATED RESTORE OF SAVED BASELINE to avoid taking a baseline
' in polluted evir if variable RESTORE_BASELINE = 1

'to do:
' - Webinterface with CO2 ppm and TVOV ppb and settings
' - BEEP if poor quality for more than X seconds
' - eMail if poor quality for more than X seconds

'#######################################################################
' DB9JG@me.com
Version$ = "V1.4"

'------SETTINGS-------------
SAVE_BASELINE = 0 '1 => saves the two baseline-bytes to data file after 20 Minutes of run-in regularly
RESTORE_BASELINE = 1 '1 => restores the two baseline-bytes from data file after 20 Minutes of run-in regularly

SILENT = 1 '1 => suppress all messages 0 => show some status-messages via wlog

LL_GREEN = 400 'lower limit of the range for the green LED
LL_Yellow = 500 'lower limit of the range for the yellow LED
LL_RED = 600 'lower limit of the range for the red LED
'---------------------------

SDA_PIN = 21 'SDA=21 at ESP32
SCL_PIN = 22 'SCL=22 at ESP32
CCS811temp = 0
STATUS_REG = &h00
MEAS_MODE_REG = &h01
ALG_RESULT_DATA = &h02
ENV_DATA = &h05
NTC_REG = &h06
THRESHOLDS = &h10
BASELINE = &h11
HW_ID_REG = &h20
ERROR_ID_REG = &hE0
APP_START_REG = &hF4
SW_RESET = &hFF
CCS811_I2C_ADR = &h5A
GPIO_WAKE = &h5
DRIVE_MODE_IDLE = &h0
DRIVE_MODE_1SEC = &h10
DRIVE_MODE_10SEC = &h20
DRIVE_MODE_60SEC = &h30
INTERRUPT_DRIVEN = &h8
THRESHOLDS_ENABLED = &h4
BASELINE1$ = "??"
BASELINE2$ = "??"
count = 0
Dim BUFFER(10) 'I2C-Sende- / Empfangspuffer.

LED_RED = 14 'GPIOs for LEDs
LED_YELLOW = 13
LED_GREEN = 12

pin.mode LED_RED, OUTPUT
pin.mode LED_YELLOW, OUTPUT
pin.mode LED_GREEN, OUTPUT

PIN(LED_RED) = 1 'show that the device is working
PIN(LED_YELLOW) = 1 'show that the device is working
PIN(LED_GREEN) = 1 'show that the device is working

I2C.SETUP SDA_PIN, SCL_PIN ' set I2C ports
if not silent GOSUB I2C_SCANNER ' show all I2C-bus-devices

GOSUB SHOW_CCS811_STATUS ' show Status-byte

GOSUB CCS811_GO_APP_MODE 'leave BOOT-Mode, start APP-Mode
GOSUB SHOW_CCS811_STATUS

GOSUB CCS811_SET_DRIVE_MODE 'CCS811 generates eCO2 and TVOC once per second
GOSUB SHOW_CCS811_STATUS

'save baseline to files regularly after min 20Min of run-in !! IN CLEAN AIR CONDITION !!
if SAVE_BASELINE then TIMER0 (20*60000), CCS811_SAVE_BASELINE_TO_FILE

'restore baseline from files regularly after min 20Min of run-in
if RESTORE_BASELINE then TIMER0 (5*60000), CCS811_RESTORE_BL_FROM_FILE

TIMER1 1050, CCS811_SHOW 'read and show eCO2 and TVOC from CCS811 sensor

WAIT

end '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

'######################################################################
CCS811_SHOW:

GOSUB CCS811_READ_eCO2
gosub SET_CO2_TRAFFIC_LIGHT
'wlog "eCO2 = ";eCO2,"TVOC = ";TVOC

if count = 0 then
' gosub CCS811_SAVE_BASELINE_TO_FILE 'TEST ONLY---------------------
GOSUB CSS811_READ_BASELINE
wlog time$, "eCO2 = ";eCO2,"TVOC = ";TVOC ,"BASELINE: ";BASELINE1$, BASELINE2$
end if
count = (count +1) mod 30
'count = (count +1) mod 60 ' 60 * 1 Sekunden
'count = (count +1) mod 300 ' x * 1 Sekunden !!!!!TEST!!!!!!!!!!!!!!!!!
return

'######################################################################
CCS811_READ_eCO2:

' Bit 3 = Data Ready 0:no new Data 1:new Data
' Bit 0 Error dedection 0:no Error 1: Error on i2c or Sensor
'''''''''''''''''''''''''''''i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read

If CCS811temp and 8 then 'DATA_available
i2c.writeRegByte CCS811_I2C_ADR, ALG_RESULT_DATA ,0 'Select the Result mailbox
i2c.ReqFrom CCS811_I2C_ADR, 8
for i = 1 to 8 'The Result mailbox contains 8 Bytes
BUFFER(i) = i2c.read
next
eCO2 = 256*BUFFER(1) + BUFFER(2) 'eCO2 ppm
TVOC = 256*BUFFER(3) + BUFFER(4) 'eTVOC
else
eCO2 = 0
TVOC = 0
end if
return

'######################################################################
CSS811_READ_BASELINE:

'read 2 bytes from baseline-mailbox
if not silent wlog "Read the two baseline bytes:"
i2c.begin CCS811_I2C_ADR
I2c.write BASELINE
i2c.end
i2c.ReqFrom CCS811_I2C_ADR, 2 'Two bytes from baseline mailbox
BASELINE1$ = hex$(i2c.read)
BASELINE2$ = hex$(i2c.read)
if not silent wlog " ",BASELINE1$,BASELINE2$
return

'######################################################################
CCS811_SAVE_BASELINE_TO_FILE:

GOSUB CSS811_READ_BASELINE
File.save "/BASELINE1.txt", BASELINE1$
File.save "/BASELINE2.txt", BASELINE2$
if not silent wlog "Saved baseline-byte1 ";BASELINE1$ ; " to file /BASELINE1.txt"
if not silent wlog "Saved baseline-byte2 ";BASELINE2$ ; " to file /BASELINE2.txt"
return

'######################################################################
CCS811_RESTORE_BL_FROM_FILE:

'restore 2 bytes from Datafile to baseline-mailbox
if not silent wlog "Restore the two baseline bytes:"
if file.exists("/BASELINE1.txt") then BASELINE1$ = File.read$("/BASELINE1.txt")
if file.exists("/BASELINE2.txt") then BASELINE2$ = File.read$("/BASELINE2.txt")

'BASELINE1$ = "09" 'TEST
'BASELINE2$ = "81" 'TEST

i2c.begin CCS811_I2C_ADR
I2c.write BASELINE ' select baseline-mailbox
I2c.write val("&h" + BASELINE1$) ' write byte #1 to baseline-mailbox
I2c.write val("&h" + BASELINE2$) ' write byte #2 to baseline-mailbox
i2c.end

if not silent wlog "Restored baseline-byte1 ";BASELINE1$ ; " from file /BASELINE1.txt"
if not silent wlog "Restored baseline-byte2 ";BASELINE2$ ; " from file /BASELINE2.txt"
GOSUB CSS811_READ_BASELINE
return

'######################################################################
SET_CO2_TRAFFIC_LIGHT:

SELECT CASE eCO2

CASE LL_GREEN to (LL_YELLOW -1) : 'GREEN range
PIN(LED_GREEN) = 1 - PIN(LED_GREEN)
PIN(LED_RED) = 0
PIN(LED_YELLOW) = 0

CASE LL_YELLOW to (LL_RED -1) : 'YELLOW range
PIN(LED_YELLOW) = 1 - PIN(LED_YELLOW)
PIN(LED_RED) = 0
PIN(LED_GREEN) = 0

CASE LL_RED to 9999 : 'RED range
PIN(LED_RED) = 0
pause 200
PIN(LED_RED) = 1 - PIN(LED_RED)
PIN(LED_GREEN) = 0
PIN(LED_YELLOW) = 0

CASE ELSE:
PIN(LED_RED) = 1
PIN(LED_YELLOW) = 1
PIN(LED_GREEN) = 1
END SELECT
return

'######################################################################
SHOW_CCS811_STATUS:

if silent return
wlog ""
wlog "-------------CCS811-Sensor_Status:-------------"
'Read the status-byte
i2c.writeRegByte CCS811_I2C_ADR, STATUS_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read

wlog "STATUSBYTE :",CCS811temp,"= &b"; bin$(CCS811temp)

If CCS811temp and 128 then
wlog " CCS811 is in Application Mode"
else
wlog " CCS811 is in Boot Mode"
end if
If CCS811temp and 16 then
wlog " A valid firmware is loaded"
else
wlog " NO valid firmware loaded!"
end if
If CCS811temp and 8 then
wlog " DATA is available to read"
else
wlog " NO DATA available to read"
end if
If CCS811temp and 1 then
wlog " Error on i2c or sensor!!"
'Read the ERROR-ID only if error bit is set
i2c.writeRegByte CCS811_I2C_ADR, ERROR_ID_REG , 0
i2c.ReqFrom CCS811_I2C_ADR,1
CCS811temp = i2c.read
wlog " ERROR_ID_REG : dec",CCS811temp,"= &b";bin$(CCS811temp)
else
wlog " NO Error detected"
end if
wlog "----------------------------------------"
wlog " "
RETURN

'######################################################################
CCS811_GO_APP_MODE:

if not silent wlog "Change from BOOT-Mode to App Mode"
i2c.begin CCS811_I2C_ADR
i2c.write APP_START_REG
i2c.end
return

'######################################################################
CCS811_SET_DRIVE_MODE:

'Bit7=0reserved, 'Bit 6-4=001 read data 1 per sec
'3=0 Interrupt off, 2=0 Interrupt Mode Normal, 1-0 =00 Reserved
if not silent then
wlog "Set MEAS_MODE_REG to &b:"; bin$(DRIVE_MODE_1SEC); " = read new data once per 1 second"
end if
i2c.writeRegByte CCS811_I2C_ADR, MEAS_MODE_REG, DRIVE_MODE_1SEC
return

'######################################################################
I2C_SCANNER:

'I2C Address Scanner
'wlog all addresses of the I2C-devices found

'I2C.SETUP SDA_PIN, SCL_PIN ' set I2C ports
wlog "---start-I2C-scan---"
for i = 0 to 120
i2c.begin i
if i2c.end = 0 then
wlog "Found a device at I2C-Adr dec "; i ,", hex "; hex$(i)
pause 10
end if
next i
wlog "---end-I2C-scan---"
return
[/code
[/height]]
Last edited by PeterN on Tue Mar 02, 2021 3:41 pm, edited 2 times in total.
User avatar
PeterN
Posts: 366
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 171 times
Been thanked: 203 times
Contact:

Re: ESP32_CCS811-CO2-sensor

Post by PeterN »

I have put some more work into my CO2-traffic-light.
The intention is to have a simple and cheap device that gives an indication of when to open the windows in a (class)room because of exhaled and possibly polluted air.

I have used a cheap CCS811 sensor. This sensor does not directly measure the CO2 content, but in particular the amount of volatile organic compounds exhaled by the human lungs. From this, the sensor indirectly calculates an eTVOC and eCO2 value.

The CCS811 requires a one-time BURN-IN time of at least 48 hours and a regular RUN-IN time of 20 minutes after start before reliable values can be output.

The sensor assumes that the lowest eCO2 value measured in a longer interval (at the same temperature and humidity) corresponds to "clean" air in an in-between well-ventilated room with about 400ppm CO2.
This highly scattering baseline value is also dependent on various environmental factors. It is determined dynamically in each case, as the sensor exhibits principle-related short-term and long-term changes in sensitivity. However, the simple and cheap sensor itself does not store this baseline value.

I have tried to take this behaviour into account in my code.

To ensure that the code shown here is up to date, but not multiple and old, I have updated the lines of code in the post above, and will try to keep it up to date further.




Helpful links for me with some data and fact sheets :
https://www.sciosense.com/products/envi ... -solution/
https://revspace.nl/CJMCU-811
Post Reply