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 }