tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/scd4x/scd4x.go (about) 1 // Package scd4x provides a driver for the scd4x I2C envrironment sensor. 2 // 3 // Datasheet: https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf 4 // 5 // This driver is heavily influenced by the scd4x code from Adafruit for CircuitPython: 6 // https://github.com/adafruit/Adafruit_CircuitPython_SCD4X 7 // Thank you! 8 package scd4x // import "tinygo.org/x/drivers/scd4x" 9 10 import ( 11 "encoding/binary" 12 "time" 13 14 "tinygo.org/x/drivers" 15 ) 16 17 type Device struct { 18 bus drivers.I2C 19 tx []byte 20 rx []byte 21 Address uint8 22 23 // used to cache the most recent readings 24 co2 uint16 25 temperature uint16 26 humidity uint16 27 } 28 29 // New returns SCD4x device for the provided I2C bus using default address of 0x62. 30 func New(i2c drivers.I2C) *Device { 31 return &Device{ 32 bus: i2c, 33 tx: make([]byte, 5), 34 rx: make([]byte, 18), 35 Address: Address, 36 } 37 } 38 39 // Configure the device. 40 func (d *Device) Configure() (err error) { 41 if err := d.StopPeriodicMeasurement(); err != nil { 42 return err 43 } 44 time.Sleep(500 * time.Millisecond) 45 46 // reset the chip 47 if err := d.sendCommand(CmdReinit); err != nil { 48 return err 49 } 50 51 time.Sleep(20 * time.Millisecond) 52 return 53 } 54 55 // Connected returns whether sensor has been found. 56 func (d *Device) Connected() bool { 57 // TODO: something here to check if the sensor is connected 58 return true 59 } 60 61 // DataReady checks the sensor to see if new data is available. 62 func (d *Device) DataReady() (bool, error) { 63 if err := d.sendCommandWithResult(CmdDataReady, d.rx[0:3]); err != nil { 64 return false, err 65 } 66 return !(d.rx[0]&0x07 == 0 && d.rx[1] == 0), nil 67 } 68 69 // StartPeriodicMeasurement puts the sensor into working mode, about 5s per measurement. 70 func (d *Device) StartPeriodicMeasurement() error { 71 return d.sendCommand(CmdStartPeriodicMeasurement) 72 } 73 74 // StopPeriodicMeasurement stops the sensor reading data. 75 func (d *Device) StopPeriodicMeasurement() error { 76 return d.sendCommand(CmdStopPeriodicMeasurement) 77 } 78 79 // StartLowPowerPeriodicMeasurement puts the sensor into low power working mode, 80 // about 30s per measurement. 81 func (d *Device) StartLowPowerPeriodicMeasurement() error { 82 return d.sendCommand(CmdStartLowPowerPeriodicMeasurement) 83 } 84 85 // ReadData reads the data from the sensor and caches it. 86 func (d *Device) ReadData() error { 87 if err := d.sendCommandWithResult(CmdReadMeasurement, d.rx[0:9]); err != nil { 88 return err 89 } 90 d.co2 = binary.BigEndian.Uint16(d.rx[0:2]) 91 d.temperature = binary.BigEndian.Uint16(d.rx[3:5]) 92 d.humidity = binary.BigEndian.Uint16(d.rx[6:8]) 93 return nil 94 } 95 96 // ReadCO2 returns the CO2 concentration in PPM (parts per million). 97 func (d *Device) ReadCO2() (co2 int32, err error) { 98 ok, err := d.DataReady() 99 if err != nil { 100 return 0, err 101 } 102 if ok { 103 err = d.ReadData() 104 } 105 return int32(d.co2), err 106 } 107 108 // ReadTemperature returns the temperature in celsius milli degrees (°C/1000) 109 func (d *Device) ReadTemperature() (temperature int32, err error) { 110 ok, err := d.DataReady() 111 if err != nil { 112 return 0, err 113 } 114 if ok { 115 err = d.ReadData() 116 } 117 // temp = -45 + 175 * value / 2¹⁶ 118 return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192), err 119 } 120 121 // ReadTempC returns the value in the temperature value in Celsius. 122 func (d *Device) ReadTempC() float32 { 123 t, _ := d.ReadTemperature() 124 return float32(t) / 1000 125 } 126 127 // ReadTempF returns the value in the temperature value in Fahrenheit. 128 func (d *Device) ReadTempF() float32 { 129 return d.ReadTempC()*1.8 + 32.0 130 } 131 132 // ReadHumidity returns the current relative humidity in %rH. 133 func (d *Device) ReadHumidity() (humidity int32, err error) { 134 ok, err := d.DataReady() 135 if err != nil { 136 return 0, err 137 } 138 if ok { 139 err = d.ReadData() 140 } 141 // humidity = 100 * value / 2¹⁶ 142 return (25 * int32(d.humidity)) / 16384, err 143 } 144 145 func (d *Device) sendCommand(command uint16) error { 146 binary.BigEndian.PutUint16(d.tx[0:], command) 147 return d.bus.Tx(uint16(d.Address), d.tx[0:2], nil) 148 } 149 150 func (d *Device) sendCommandWithValue(command, value uint16) error { 151 binary.BigEndian.PutUint16(d.tx[0:], command) 152 binary.BigEndian.PutUint16(d.tx[2:], value) 153 d.tx[4] = crc8(d.tx[2:4]) 154 return d.bus.Tx(uint16(d.Address), d.tx[0:5], nil) 155 } 156 157 func (d *Device) sendCommandWithResult(command uint16, result []byte) error { 158 binary.BigEndian.PutUint16(d.tx[0:], command) 159 if err := d.bus.Tx(uint16(d.Address), d.tx[0:2], nil); err != nil { 160 return err 161 } 162 time.Sleep(time.Millisecond) 163 return d.bus.Tx(uint16(d.Address), nil, result) 164 } 165 166 func crc8(buf []byte) uint8 { 167 var crc uint8 = 0xff 168 for _, b := range buf { 169 crc ^= b 170 for i := 0; i < 8; i++ { 171 if crc&0x80 != 0 { 172 crc = (crc << 1) ^ 0x31 173 } else { 174 crc <<= 1 175 } 176 } 177 } 178 return crc & 0xff 179 }