forked from hybridgroup/gobot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request hybridgroup#630 from kml183/add-ccs8110-driver
Add CCS811 I2C Driver
- Loading branch information
Showing
3 changed files
with
681 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,349 @@ | ||
package i2c | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"time" | ||
|
||
"gobot.io/x/gobot" | ||
) | ||
|
||
// CCS811DriveMode type | ||
type CCS811DriveMode uint8 | ||
|
||
// Operating modes which dictate how often measurements are being made. If 0x00 is used as an operating mode, measurements will be disabled | ||
const ( | ||
CCS811DriveModeIdle CCS811DriveMode = 0x00 | ||
CCS811DriveMode1Sec = 0x01 | ||
CCS811DriveMode10Sec = 0x02 | ||
CCS811DriveMode60Sec = 0x03 | ||
CCS811DriveMode250MS = 0x04 | ||
) | ||
|
||
const ( | ||
|
||
//DefaultAddress is the default I2C address for the ccs811 | ||
ccs811DefaultAddress = 0x5A | ||
|
||
//Registers, all definitions have been taken from the datasheet | ||
//Single byte read only register which indicates if a device is active, if new data is available or if an error occurred. | ||
ccs811RegStatus = 0x00 | ||
//This is Single byte register, which is used to enable sensor drive mode and interrupts. | ||
ccs811RegMeasMode = 0x01 | ||
//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. | ||
ccs811RegAlgResultData = 0x02 | ||
//Two byte read only register which contains the latest readings from the sensor. | ||
ccs811RegRawData = 0x03 | ||
//A multi-byte register that can be written with the current Humidity and Temperature values if known. | ||
ccs811RegEnvData = 0x05 | ||
//Register that holds the NTC value used for temperature calcualtions | ||
ccs811RegNtc = 0x06 | ||
//Asserting the SW_RESET will restart the CCS811 in Boot mode to enable new application firmware to be downloaded. | ||
ccs811RegSwReset = 0xFF | ||
//Single byte read only register which holds the HW ID which is 0x81 for this family of CCS81x devices. | ||
ccs811RegHwID = 0x20 | ||
//Single byte read only register that contains the hardware version. The value is 0x1X | ||
ccs811RegHwVersion = 0x21 | ||
//Two byte read only register which contain the version of the firmware bootloader stored in the CCS811 in the format Major.Minor.Trivial | ||
ccs811RegFwBootVersion = 0x23 | ||
//Two byte read only register which contain the version of the firmware application stored in the CCS811 in the format Major.Minor.Trivial | ||
ccs811RegFwAppVersion = 0x24 | ||
//To change the mode of the CCS811 from Boot mode to running the application, a single byte write of 0xF4 is required. | ||
ccs811RegAppStart = 0xF4 | ||
|
||
// Constants | ||
// The hardware ID code | ||
ccs811HwIDCode = 0x81 | ||
) | ||
|
||
var ( | ||
// The sequence of bytes needed to do a software reset | ||
ccs811SwResetSequence = []byte{0x11, 0xE5, 0x72, 0x8A} | ||
) | ||
|
||
// CCS811Status represents the current status of the device defined by the ccs811RegStatus. | ||
// The following definitions were taken from https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=15 | ||
type CCS811Status struct { | ||
//There is some sort of error on the i2c bus or there is an error with the internal sensor | ||
HasError byte | ||
//A new data sample is ready in ccs811RegAlgResultData | ||
DataReady byte | ||
//Valid application firmware loaded | ||
AppValid byte | ||
//Firmware is in application mode. CCS811 is ready to take sensor measurements | ||
FwMode byte | ||
} | ||
|
||
//NewCCS811Status returns a new instance of the package ccs811 status definiton | ||
func NewCCS811Status(data uint8) *CCS811Status { | ||
return &CCS811Status{ | ||
HasError: data & 0x01, | ||
DataReady: (data >> 3) & 0x01, | ||
AppValid: (data >> 4) & 0x01, | ||
FwMode: (data >> 7) & 0x01, | ||
} | ||
} | ||
|
||
//CCS811MeasMode represents the current measurement configuration. | ||
//The following definitions were taken from the bit fields of the ccs811RegMeasMode defined in | ||
//https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=16 | ||
type CCS811MeasMode struct { | ||
//If intThresh is 1 a data measurement will only be taken when the sensor value mets the threshold constraint. | ||
//The threshold value is set in the threshold register (0x10) | ||
intThresh uint8 | ||
//If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is avaliable. | ||
intDataRdy uint8 | ||
//driveMode represents the sampling rate of the sensor. If the value is 0, the measurement process is idle. | ||
driveMode CCS811DriveMode | ||
} | ||
|
||
//NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired intial | ||
//state of the measurement mode register. | ||
func NewCCS811MeasMode() *CCS811MeasMode { | ||
return &CCS811MeasMode{ | ||
// Disable this by default as this library does not contain the functionality to use the internal interrupt feature. | ||
intThresh: 0x00, | ||
intDataRdy: 0x00, | ||
driveMode: CCS811DriveMode1Sec, | ||
} | ||
} | ||
|
||
// GetMeasMode returns the measurement mode | ||
func (mm *CCS811MeasMode) GetMeasMode() byte { | ||
return (mm.intThresh << 2) | (mm.intDataRdy << 3) | uint8((mm.driveMode << 4)) | ||
} | ||
|
||
//CCS811Driver is the Gobot driver for the CCS811 (air quality sensor) Adafruit breakout board | ||
type CCS811Driver struct { | ||
name string | ||
connector Connector | ||
connection Connection | ||
measMode *CCS811MeasMode | ||
ntcResistanceValue uint32 | ||
Config | ||
} | ||
|
||
//NewCCS811Driver creates a new driver for the CCS811 (air quality sensor) | ||
func NewCCS811Driver(a Connector, options ...func(Config)) *CCS811Driver { | ||
l := &CCS811Driver{ | ||
name: gobot.DefaultName("CCS811"), | ||
connector: a, | ||
measMode: NewCCS811MeasMode(), | ||
//Recommended resistance value is 100,000 | ||
ntcResistanceValue: 100000, | ||
Config: NewConfig(), | ||
} | ||
|
||
for _, option := range options { | ||
option(l) | ||
} | ||
|
||
return l | ||
} | ||
|
||
//WithCCS811MeasMode sets the sampling rate of the device | ||
func WithCCS811MeasMode(mode CCS811DriveMode) func(Config) { | ||
return func(c Config) { | ||
d, _ := c.(*CCS811Driver) | ||
d.measMode.driveMode = mode | ||
} | ||
} | ||
|
||
//WithCCS811NTCResistance sets reistor value used in the temperature calculations. | ||
//This resistor must be placed between pin 4 and pin 8 of the chip | ||
func WithCCS811NTCResistance(val uint32) func(Config) { | ||
return func(c Config) { | ||
d, _ := c.(*CCS811Driver) | ||
d.ntcResistanceValue = val | ||
} | ||
} | ||
|
||
//Start initializes the sensor | ||
func (d *CCS811Driver) Start() (err error) { | ||
bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) | ||
address := d.GetAddressOrDefault(ccs811DefaultAddress) | ||
|
||
if d.connection, err = d.connector.GetConnection(address, bus); err != nil { | ||
return err | ||
} | ||
|
||
return d.initialize() | ||
} | ||
|
||
//Name returns the Name for the Driver | ||
func (d *CCS811Driver) Name() string { return d.name } | ||
|
||
//SetName sets the Name for the Driver | ||
func (d *CCS811Driver) SetName(n string) { d.name = n } | ||
|
||
//Connection returns the connection for the Driver | ||
func (d *CCS811Driver) Connection() gobot.Connection { return d.connector.(gobot.Connection) } | ||
|
||
//Halt returns true if devices is halted successfully | ||
func (d *CCS811Driver) Halt() (err error) { return } | ||
|
||
//GetHardwareVersion returns the hardware version of the device in the form of 0x1X | ||
func (d *CCS811Driver) GetHardwareVersion() (uint8, error) { | ||
v, err := d.connection.ReadByteData(ccs811RegHwVersion) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return v, nil | ||
} | ||
|
||
//GetFirmwareBootVersion returns the bootloader version | ||
func (d *CCS811Driver) GetFirmwareBootVersion() (uint16, error) { | ||
v, err := d.connection.ReadWordData(ccs811RegFwBootVersion) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return v, nil | ||
} | ||
|
||
//GetFirmwareAppVersion returns the app code version | ||
func (d *CCS811Driver) GetFirmwareAppVersion() (uint16, error) { | ||
v, err := d.connection.ReadWordData(ccs811RegFwAppVersion) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return v, nil | ||
} | ||
|
||
//GetStatus returns the current status of the device | ||
func (d *CCS811Driver) GetStatus() (*CCS811Status, error) { | ||
s, err := d.connection.ReadByteData(ccs811RegStatus) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cs := NewCCS811Status(s) | ||
return cs, nil | ||
} | ||
|
||
//GetTemperature returns the device temperature in celcius. | ||
//If you do not have an NTC resistor installed, this function should not be called | ||
func (d *CCS811Driver) GetTemperature() (float32, error) { | ||
|
||
buf, err := d.read(ccs811RegNtc, 4) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
vref := ((uint16(buf[0]) << 8) | uint16(buf[1])) | ||
vrntc := ((uint16(buf[2]) << 8) | uint16(buf[3])) | ||
rntc := (float32(vrntc) * float32(d.ntcResistanceValue) / float32(vref)) | ||
|
||
ntcTemp := float32(math.Log(float64(rntc / 10000.0))) | ||
ntcTemp /= 3380.0 | ||
ntcTemp += 1.0 / (25 + 273.15) | ||
ntcTemp = 1.0 / ntcTemp | ||
ntcTemp -= 273.15 | ||
|
||
return ntcTemp, nil | ||
} | ||
|
||
//GetGasData returns the data for the gas sensor. | ||
//eco2 is returned in ppm and tvoc is returned in ppb | ||
func (d *CCS811Driver) GetGasData() (uint16, uint16, error) { | ||
|
||
data, err := d.read(ccs811RegAlgResultData, 4) | ||
if err != nil { | ||
return 0, 0, err | ||
} | ||
|
||
// Bit masks defined by https://ams.com/documents/20143/36005/CCS811_AN000369_2-00.pdf/25d0db9a-92b9-fa7f-362c-a7a4d1e292be#page=14 | ||
eco2 := (uint16(data[0]) << 8) | uint16(data[1]) | ||
tvoC := (uint16(data[2]) << 8) | uint16(data[3]) | ||
|
||
return eco2, tvoC, nil | ||
} | ||
|
||
//HasData returns true if the device has not errored and temperature/gas data is avaliable | ||
func (d *CCS811Driver) HasData() (bool, error) { | ||
s, err := d.GetStatus() | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
if !(s.DataReady == 0x01) || (s.HasError == 0x01) { | ||
return false, nil | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
//EnableExternalInterrupt enables the external output hardware interrupt pin 3. | ||
func (d *CCS811Driver) EnableExternalInterrupt() error { | ||
d.measMode.intDataRdy = 1 | ||
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) | ||
} | ||
|
||
//DisableExternalInterrupt disables the external output hardware interrupt pin 3. | ||
func (d *CCS811Driver) DisableExternalInterrupt() error { | ||
d.measMode.intDataRdy = 0 | ||
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) | ||
} | ||
|
||
//updateMeasMode writes the current value of measMode to the measurement mode register. | ||
func (d *CCS811Driver) updateMeasMode() error { | ||
return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) | ||
} | ||
|
||
//ResetDevice does a software reset of the device. After this operation is done, | ||
//the user must start the app code before the sensor can take any measurements | ||
func (d *CCS811Driver) resetDevice() error { | ||
return d.connection.WriteBlockData(ccs811RegSwReset, ccs811SwResetSequence) | ||
} | ||
|
||
//startApp starts the app code in the device. This operation has to be done after a | ||
//software reset to start taking sensor measurements. | ||
func (d *CCS811Driver) startApp() error { | ||
//Write without data is needed to start the app code | ||
_, err := d.connection.Write([]byte{ccs811RegAppStart}) | ||
return err | ||
} | ||
|
||
func (d *CCS811Driver) initialize() error { | ||
deviceID, err := d.connection.ReadByteData(ccs811RegHwID) | ||
if err != nil { | ||
return fmt.Errorf("Failed to get the device id from ccs811RegHwID with error: %s", err.Error()) | ||
} | ||
|
||
// Verify that the connected device is the CCS811 sensor | ||
if deviceID != ccs811HwIDCode { | ||
return fmt.Errorf("The fetched device id %d is not the known id %d with error", deviceID, ccs811HwIDCode) | ||
} | ||
|
||
if err := d.resetDevice(); err != nil { | ||
return fmt.Errorf("Was not able to reset the device with error: %s", err.Error()) | ||
} | ||
|
||
// Required sleep to allow device to switch states | ||
time.Sleep(100 * time.Millisecond) | ||
|
||
if err := d.startApp(); err != nil { | ||
return fmt.Errorf("Failed to start app code with error: %s", err.Error()) | ||
} | ||
|
||
if err := d.updateMeasMode(); err != nil { | ||
return fmt.Errorf("Failed to update the measMode register with error: %s", err.Error()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// An implementation of the ReadBlockData i2c operation. This code was copied from the BMP280Driver code | ||
func (d *CCS811Driver) read(reg byte, n int) ([]byte, error) { | ||
if _, err := d.connection.Write([]byte{reg}); err != nil { | ||
return nil, err | ||
} | ||
buf := make([]byte, n) | ||
bytesRead, err := d.connection.Read(buf) | ||
if bytesRead != n || err != nil { | ||
return nil, err | ||
} | ||
return buf, nil | ||
} |
Oops, something went wrong.