gobot.io/x/gobot@v1.16.0/drivers/i2c/ccs811_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"time"
     7  
     8  	"gobot.io/x/gobot"
     9  )
    10  
    11  // CCS811DriveMode type
    12  type CCS811DriveMode uint8
    13  
    14  // Operating modes which dictate how often measurements are being made. If 0x00 is used as an operating mode, measurements will be disabled
    15  const (
    16  	CCS811DriveModeIdle  CCS811DriveMode = 0x00
    17  	CCS811DriveMode1Sec                  = 0x01
    18  	CCS811DriveMode10Sec                 = 0x02
    19  	CCS811DriveMode60Sec                 = 0x03
    20  	CCS811DriveMode250MS                 = 0x04
    21  )
    22  
    23  const (
    24  
    25  	//DefaultAddress is the default I2C address for the ccs811
    26  	ccs811DefaultAddress = 0x5A
    27  
    28  	//Registers, all definitions have been taken from the datasheet
    29  	//Single byte read only register which indicates if a device is active, if new data is available or if an error occurred.
    30  	ccs811RegStatus = 0x00
    31  	//This is Single byte register, which is used to enable sensor drive mode and interrupts.
    32  	ccs811RegMeasMode = 0x01
    33  	//This multi-byte read only register contains the calculated eCO2 (ppm) and eTVOC (ppb) values followed by the STATUS register, ERROR_ID register and the RAW_DATA register.
    34  	ccs811RegAlgResultData = 0x02
    35  	//Two byte read only register which contains the latest readings from the sensor.
    36  	ccs811RegRawData = 0x03
    37  	//A multi-byte register that can be written with the current Humidity and Temperature values if known.
    38  	ccs811RegEnvData = 0x05
    39  	//Register that holds the NTC value used for temperature calcualtions
    40  	ccs811RegNtc = 0x06
    41  	//Asserting the SW_RESET will restart the CCS811 in Boot mode to enable new application firmware to be downloaded.
    42  	ccs811RegSwReset = 0xFF
    43  	//Single byte read only register which holds the HW ID which is 0x81 for this family of CCS81x devices.
    44  	ccs811RegHwID = 0x20
    45  	//Single byte read only register that contains the hardware version. The value is 0x1X
    46  	ccs811RegHwVersion = 0x21
    47  	//Two byte read only register which contain the version of the firmware bootloader stored in the CCS811 in the format Major.Minor.Trivial
    48  	ccs811RegFwBootVersion = 0x23
    49  	//Two byte read only register which contain the version of the firmware application stored in the CCS811 in the format Major.Minor.Trivial
    50  	ccs811RegFwAppVersion = 0x24
    51  	//To change the mode of the CCS811 from Boot mode to running the application, a single byte write of 0xF4 is required.
    52  	ccs811RegAppStart = 0xF4
    53  
    54  	// Constants
    55  	// The hardware ID code
    56  	ccs811HwIDCode = 0x81
    57  )
    58  
    59  var (
    60  	// The sequence of bytes needed to do a software reset
    61  	ccs811SwResetSequence = []byte{0x11, 0xE5, 0x72, 0x8A}
    62  )
    63  
    64  // CCS811Status represents the current status of the device defined by the ccs811RegStatus.
    65  // The following definitions were taken from https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=15
    66  type CCS811Status struct {
    67  	//There is some sort of error on the i2c bus or there is an error with the internal sensor
    68  	HasError byte
    69  	//A new data sample is ready in ccs811RegAlgResultData
    70  	DataReady byte
    71  	//Valid application firmware loaded
    72  	AppValid byte
    73  	//Firmware is in application mode. CCS811 is ready to take sensor measurements
    74  	FwMode byte
    75  }
    76  
    77  //NewCCS811Status returns a new instance of the package ccs811 status definiton
    78  func NewCCS811Status(data uint8) *CCS811Status {
    79  	return &CCS811Status{
    80  		HasError:  data & 0x01,
    81  		DataReady: (data >> 3) & 0x01,
    82  		AppValid:  (data >> 4) & 0x01,
    83  		FwMode:    (data >> 7) & 0x01,
    84  	}
    85  }
    86  
    87  //CCS811MeasMode represents the current measurement configuration.
    88  //The following definitions were taken from the bit fields of the ccs811RegMeasMode defined in
    89  //https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=16
    90  type CCS811MeasMode struct {
    91  	//If intThresh is 1 a data measurement will only be taken when the sensor value mets the threshold constraint.
    92  	//The threshold value is set in the threshold register (0x10)
    93  	intThresh uint8
    94  	//If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is avaliable.
    95  	intDataRdy uint8
    96  	//driveMode represents the sampling rate of the sensor. If the value is 0, the measurement process is idle.
    97  	driveMode CCS811DriveMode
    98  }
    99  
   100  //NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired intial
   101  //state of the measurement mode register.
   102  func NewCCS811MeasMode() *CCS811MeasMode {
   103  	return &CCS811MeasMode{
   104  		// Disable this by default as this library does not contain the functionality to use the internal interrupt feature.
   105  		intThresh:  0x00,
   106  		intDataRdy: 0x00,
   107  		driveMode:  CCS811DriveMode1Sec,
   108  	}
   109  }
   110  
   111  // GetMeasMode returns the measurement mode
   112  func (mm *CCS811MeasMode) GetMeasMode() byte {
   113  	return (mm.intThresh << 2) | (mm.intDataRdy << 3) | uint8((mm.driveMode << 4))
   114  }
   115  
   116  //CCS811Driver is the Gobot driver for the CCS811 (air quality sensor) Adafruit breakout board
   117  type CCS811Driver struct {
   118  	name               string
   119  	connector          Connector
   120  	connection         Connection
   121  	measMode           *CCS811MeasMode
   122  	ntcResistanceValue uint32
   123  	Config
   124  }
   125  
   126  //NewCCS811Driver creates a new driver for the CCS811 (air quality sensor)
   127  func NewCCS811Driver(a Connector, options ...func(Config)) *CCS811Driver {
   128  	l := &CCS811Driver{
   129  		name:      gobot.DefaultName("CCS811"),
   130  		connector: a,
   131  		measMode:  NewCCS811MeasMode(),
   132  		//Recommended resistance value is 100,000
   133  		ntcResistanceValue: 100000,
   134  		Config:             NewConfig(),
   135  	}
   136  
   137  	for _, option := range options {
   138  		option(l)
   139  	}
   140  
   141  	return l
   142  }
   143  
   144  //WithCCS811MeasMode sets the sampling rate of the device
   145  func WithCCS811MeasMode(mode CCS811DriveMode) func(Config) {
   146  	return func(c Config) {
   147  		d, _ := c.(*CCS811Driver)
   148  		d.measMode.driveMode = mode
   149  	}
   150  }
   151  
   152  //WithCCS811NTCResistance sets reistor value used in the temperature calculations.
   153  //This resistor must be placed between pin 4 and pin 8 of the chip
   154  func WithCCS811NTCResistance(val uint32) func(Config) {
   155  	return func(c Config) {
   156  		d, _ := c.(*CCS811Driver)
   157  		d.ntcResistanceValue = val
   158  	}
   159  }
   160  
   161  //Start initializes the sensor
   162  func (d *CCS811Driver) Start() (err error) {
   163  	bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
   164  	address := d.GetAddressOrDefault(ccs811DefaultAddress)
   165  
   166  	if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
   167  		return err
   168  	}
   169  
   170  	return d.initialize()
   171  }
   172  
   173  //Name returns the Name for the Driver
   174  func (d *CCS811Driver) Name() string { return d.name }
   175  
   176  //SetName sets the Name for the Driver
   177  func (d *CCS811Driver) SetName(n string) { d.name = n }
   178  
   179  //Connection returns the connection for the Driver
   180  func (d *CCS811Driver) Connection() gobot.Connection { return d.connector.(gobot.Connection) }
   181  
   182  //Halt returns true if devices is halted successfully
   183  func (d *CCS811Driver) Halt() (err error) { return }
   184  
   185  //GetHardwareVersion returns the hardware version of the device in the form of 0x1X
   186  func (d *CCS811Driver) GetHardwareVersion() (uint8, error) {
   187  	v, err := d.connection.ReadByteData(ccs811RegHwVersion)
   188  	if err != nil {
   189  		return 0, err
   190  	}
   191  
   192  	return v, nil
   193  }
   194  
   195  //GetFirmwareBootVersion returns the bootloader version
   196  func (d *CCS811Driver) GetFirmwareBootVersion() (uint16, error) {
   197  	v, err := d.connection.ReadWordData(ccs811RegFwBootVersion)
   198  	if err != nil {
   199  		return 0, err
   200  	}
   201  
   202  	return v, nil
   203  }
   204  
   205  //GetFirmwareAppVersion returns the app code version
   206  func (d *CCS811Driver) GetFirmwareAppVersion() (uint16, error) {
   207  	v, err := d.connection.ReadWordData(ccs811RegFwAppVersion)
   208  	if err != nil {
   209  		return 0, err
   210  	}
   211  
   212  	return v, nil
   213  }
   214  
   215  //GetStatus returns the current status of the device
   216  func (d *CCS811Driver) GetStatus() (*CCS811Status, error) {
   217  	s, err := d.connection.ReadByteData(ccs811RegStatus)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	cs := NewCCS811Status(s)
   223  	return cs, nil
   224  }
   225  
   226  //GetTemperature returns the device temperature in celcius.
   227  //If you do not have an NTC resistor installed, this function should not be called
   228  func (d *CCS811Driver) GetTemperature() (float32, error) {
   229  
   230  	buf, err := d.read(ccs811RegNtc, 4)
   231  	if err != nil {
   232  		return 0, err
   233  	}
   234  
   235  	vref := ((uint16(buf[0]) << 8) | uint16(buf[1]))
   236  	vrntc := ((uint16(buf[2]) << 8) | uint16(buf[3]))
   237  	rntc := (float32(vrntc) * float32(d.ntcResistanceValue) / float32(vref))
   238  
   239  	ntcTemp := float32(math.Log(float64(rntc / 10000.0)))
   240  	ntcTemp /= 3380.0
   241  	ntcTemp += 1.0 / (25 + 273.15)
   242  	ntcTemp = 1.0 / ntcTemp
   243  	ntcTemp -= 273.15
   244  
   245  	return ntcTemp, nil
   246  }
   247  
   248  //GetGasData returns the data for the gas sensor.
   249  //eco2 is returned in ppm and tvoc is returned in ppb
   250  func (d *CCS811Driver) GetGasData() (uint16, uint16, error) {
   251  
   252  	data, err := d.read(ccs811RegAlgResultData, 4)
   253  	if err != nil {
   254  		return 0, 0, err
   255  	}
   256  
   257  	// Bit masks defined by https://ams.com/documents/20143/36005/CCS811_AN000369_2-00.pdf/25d0db9a-92b9-fa7f-362c-a7a4d1e292be#page=14
   258  	eco2 := (uint16(data[0]) << 8) | uint16(data[1])
   259  	tvoC := (uint16(data[2]) << 8) | uint16(data[3])
   260  
   261  	return eco2, tvoC, nil
   262  }
   263  
   264  //HasData returns true if the device has not errored and temperature/gas data is avaliable
   265  func (d *CCS811Driver) HasData() (bool, error) {
   266  	s, err := d.GetStatus()
   267  	if err != nil {
   268  		return false, err
   269  	}
   270  
   271  	if !(s.DataReady == 0x01) || (s.HasError == 0x01) {
   272  		return false, nil
   273  	}
   274  
   275  	return true, nil
   276  }
   277  
   278  //EnableExternalInterrupt enables the external output hardware interrupt pin 3.
   279  func (d *CCS811Driver) EnableExternalInterrupt() error {
   280  	d.measMode.intDataRdy = 1
   281  	return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
   282  }
   283  
   284  //DisableExternalInterrupt disables the external output hardware interrupt pin 3.
   285  func (d *CCS811Driver) DisableExternalInterrupt() error {
   286  	d.measMode.intDataRdy = 0
   287  	return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
   288  }
   289  
   290  //updateMeasMode writes the current value of measMode to the measurement mode register.
   291  func (d *CCS811Driver) updateMeasMode() error {
   292  	return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode())
   293  }
   294  
   295  //ResetDevice does a software reset of the device. After this operation is done,
   296  //the user must start the app code before the sensor can take any measurements
   297  func (d *CCS811Driver) resetDevice() error {
   298  	return d.connection.WriteBlockData(ccs811RegSwReset, ccs811SwResetSequence)
   299  }
   300  
   301  //startApp starts the app code in the device. This operation has to be done after a
   302  //software reset to start taking sensor measurements.
   303  func (d *CCS811Driver) startApp() error {
   304  	//Write without data is needed to start the app code
   305  	_, err := d.connection.Write([]byte{ccs811RegAppStart})
   306  	return err
   307  }
   308  
   309  func (d *CCS811Driver) initialize() error {
   310  	deviceID, err := d.connection.ReadByteData(ccs811RegHwID)
   311  	if err != nil {
   312  		return fmt.Errorf("Failed to get the device id from ccs811RegHwID with error: %s", err.Error())
   313  	}
   314  
   315  	// Verify that the connected device is the CCS811 sensor
   316  	if deviceID != ccs811HwIDCode {
   317  		return fmt.Errorf("The fetched device id %d is not the known id %d with error", deviceID, ccs811HwIDCode)
   318  	}
   319  
   320  	if err := d.resetDevice(); err != nil {
   321  		return fmt.Errorf("Was not able to reset the device with error: %s", err.Error())
   322  	}
   323  
   324  	// Required sleep to allow device to switch states
   325  	time.Sleep(100 * time.Millisecond)
   326  
   327  	if err := d.startApp(); err != nil {
   328  		return fmt.Errorf("Failed to start app code with error: %s", err.Error())
   329  	}
   330  
   331  	if err := d.updateMeasMode(); err != nil {
   332  		return fmt.Errorf("Failed to update the measMode register with error: %s", err.Error())
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  // An implementation of the ReadBlockData i2c operation. This code was copied from the BMP280Driver code
   339  func (d *CCS811Driver) read(reg byte, n int) ([]byte, error) {
   340  	if _, err := d.connection.Write([]byte{reg}); err != nil {
   341  		return nil, err
   342  	}
   343  	buf := make([]byte, n)
   344  	bytesRead, err := d.connection.Read(buf)
   345  	if bytesRead != n || err != nil {
   346  		return nil, err
   347  	}
   348  	return buf, nil
   349  }