mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
9ed648f4a5
I have a SCD41, and would like to use it for single shot measurements instead of "low power periodic" measurements to achieve much lower average current consumption.
218 lines
5.8 KiB
Go
218 lines
5.8 KiB
Go
// Package scd4x provides a driver for the scd4x I2C envrironment sensor.
|
|
//
|
|
// Datasheet: https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf
|
|
//
|
|
// This driver is heavily influenced by the scd4x code from Adafruit for CircuitPython:
|
|
// https://github.com/adafruit/Adafruit_CircuitPython_SCD4X
|
|
// Thank you!
|
|
package scd4x // import "tinygo.org/x/drivers/scd4x"
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
)
|
|
|
|
type Device struct {
|
|
bus drivers.I2C
|
|
tx []byte
|
|
rx []byte
|
|
Address uint8
|
|
|
|
// used to cache the most recent readings
|
|
co2 uint16
|
|
temperature uint16
|
|
humidity uint16
|
|
}
|
|
|
|
// New returns SCD4x device for the provided I2C bus using default address of 0x62.
|
|
func New(i2c drivers.I2C) *Device {
|
|
return &Device{
|
|
bus: i2c,
|
|
tx: make([]byte, 5),
|
|
rx: make([]byte, 18),
|
|
Address: Address,
|
|
}
|
|
}
|
|
|
|
// Configure the device.
|
|
func (d *Device) Configure() (err error) {
|
|
if err := d.StopPeriodicMeasurement(); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// reset the chip
|
|
if err := d.sendCommand(CmdReinit); err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
return
|
|
}
|
|
|
|
// Connected returns whether sensor has been found.
|
|
func (d *Device) Connected() bool {
|
|
// TODO: something here to check if the sensor is connected
|
|
return true
|
|
}
|
|
|
|
// DataReady checks the sensor to see if new data is available.
|
|
func (d *Device) DataReady() (bool, error) {
|
|
if err := d.sendCommandWithResult(CmdDataReady, d.rx[0:3]); err != nil {
|
|
return false, err
|
|
}
|
|
return !(d.rx[0]&0x07 == 0 && d.rx[1] == 0), nil
|
|
}
|
|
|
|
// StartPeriodicMeasurement puts the sensor into working mode, about 5s per measurement.
|
|
func (d *Device) StartPeriodicMeasurement() error {
|
|
return d.sendCommand(CmdStartPeriodicMeasurement)
|
|
}
|
|
|
|
// StopPeriodicMeasurement stops the sensor reading data.
|
|
func (d *Device) StopPeriodicMeasurement() error {
|
|
return d.sendCommand(CmdStopPeriodicMeasurement)
|
|
}
|
|
|
|
// StartLowPowerPeriodicMeasurement puts the sensor into low power working mode,
|
|
// about 30s per measurement.
|
|
func (d *Device) StartLowPowerPeriodicMeasurement() error {
|
|
return d.sendCommand(CmdStartLowPowerPeriodicMeasurement)
|
|
}
|
|
|
|
// MeasureSingleShot starts a single measurement cycle (SCD41 only). After this
|
|
// command is complete, the caller should wait for 5000ms before trying to read
|
|
// the result.
|
|
func (d *Device) MeasureSingleShot() error {
|
|
return d.sendCommand(CmdMeasureSingleShot)
|
|
}
|
|
|
|
// ReadData reads the data from the sensor and caches it.
|
|
func (d *Device) ReadData() error {
|
|
if err := d.sendCommandWithResult(CmdReadMeasurement, d.rx[0:9]); err != nil {
|
|
return err
|
|
}
|
|
d.co2 = binary.BigEndian.Uint16(d.rx[0:2])
|
|
d.temperature = binary.BigEndian.Uint16(d.rx[3:5])
|
|
d.humidity = binary.BigEndian.Uint16(d.rx[6:8])
|
|
return nil
|
|
}
|
|
|
|
// Update reads new data from the sensor (if new data is available) and caches
|
|
// it for reading in the CO2, Temperature, and Humidity methods.
|
|
func (d *Device) Update(measurements drivers.Measurement) error {
|
|
if measurements&(drivers.Temperature|drivers.Humidity|drivers.Concentration) != 0 {
|
|
return d.ReadData()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadCO2 returns the CO2 concentration in PPM (parts per million).
|
|
//
|
|
// Deprecated: use Update() and CO2() instead.
|
|
func (d *Device) ReadCO2() (co2 int32, err error) {
|
|
ok, err := d.DataReady()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ok {
|
|
err = d.ReadData()
|
|
}
|
|
return int32(d.co2), err
|
|
}
|
|
|
|
// CO2 returns last read the CO2 concentration in PPM (parts per million).
|
|
func (d *Device) CO2() int32 {
|
|
return int32(d.co2)
|
|
}
|
|
|
|
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
|
//
|
|
// Deprecated: use Update() and Temperature() instead.
|
|
func (d *Device) ReadTemperature() (temperature int32, err error) {
|
|
ok, err := d.DataReady()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ok {
|
|
err = d.ReadData()
|
|
}
|
|
return d.Temperature(), err
|
|
}
|
|
|
|
// Temperature returns the last read temperature in celsius milli degrees
|
|
// (°C/1000).
|
|
func (d *Device) Temperature() int32 {
|
|
// temp = -45 + 175 * value / 2¹⁶
|
|
return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192)
|
|
}
|
|
|
|
// ReadTempC returns the value in the temperature value in Celsius.
|
|
func (d *Device) ReadTempC() float32 {
|
|
t, _ := d.ReadTemperature()
|
|
return float32(t) / 1000
|
|
}
|
|
|
|
// ReadTempF returns the value in the temperature value in Fahrenheit.
|
|
func (d *Device) ReadTempF() float32 {
|
|
return d.ReadTempC()*1.8 + 32.0
|
|
}
|
|
|
|
// ReadHumidity returns the current relative humidity in %rH.
|
|
//
|
|
// Warning: the value returned here is less precise than the humidity returned
|
|
// from Humidity()!
|
|
//
|
|
// Deprecated: use Update() and Temperature() instead.
|
|
func (d *Device) ReadHumidity() (humidity int32, err error) {
|
|
ok, err := d.DataReady()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ok {
|
|
err = d.ReadData()
|
|
}
|
|
// humidity = 100 * value / 2¹⁶
|
|
return (25 * int32(d.humidity)) / 16384, err
|
|
}
|
|
|
|
// Humidity returns the relative humidity in hundredths of a percent (in other
|
|
// words, with a range 0..10_000).
|
|
//
|
|
// Warning: the value returned here is of a different scale (more precise) than
|
|
// ReadHumidity()!
|
|
func (d *Device) Humidity() int32 {
|
|
return (2500 * int32(d.humidity)) / 16384
|
|
}
|
|
|
|
func (d *Device) sendCommand(command uint16) error {
|
|
binary.BigEndian.PutUint16(d.tx[0:], command)
|
|
return d.bus.Tx(uint16(d.Address), d.tx[0:2], nil)
|
|
}
|
|
|
|
func (d *Device) sendCommandWithResult(command uint16, result []byte) error {
|
|
binary.BigEndian.PutUint16(d.tx[0:], command)
|
|
if err := d.bus.Tx(uint16(d.Address), d.tx[0:2], nil); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
return d.bus.Tx(uint16(d.Address), nil, result)
|
|
}
|
|
|
|
func crc8(buf []byte) uint8 {
|
|
var crc uint8 = 0xff
|
|
for _, b := range buf {
|
|
crc ^= b
|
|
for i := 0; i < 8; i++ {
|
|
if crc&0x80 != 0 {
|
|
crc = (crc << 1) ^ 0x31
|
|
} else {
|
|
crc <<= 1
|
|
}
|
|
}
|
|
}
|
|
return crc & 0xff
|
|
}
|