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