You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/ds18b20/ds18b20.go

238 lines
7.3 KiB
Go

// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package ds18b20
import (
"errors"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/onewire"
"periph.io/x/conn/v3/physic"
)
// Family code of the specific device type
type Family byte
func (f Family) String() string {
switch f {
case DS18S20:
return "DS18S20"
case DS18B20:
return "DS18B20"
default:
return "unknown"
}
}
const DS18B20 Family = 0x28
const DS18S20 Family = 0x10
// ConvertAll performs a conversion on all DS18B20 devices on the bus.
//
// During the conversion it places the bus in strong pull-up mode to power
// parasitic devices and returns when the conversions have completed. This time
// period is determined by the maximum resolution of all devices on the bus and
// must be provided.
//
// ConvertAll uses time.Sleep to wait for the conversion to finish, which takes
// from 94ms to 752ms.
func ConvertAll(o onewire.Bus, maxResolutionBits int) error {
if maxResolutionBits < 9 || maxResolutionBits > 12 {
return errors.New("ds18b20: invalid maxResolutionBits")
}
if err := o.Tx([]byte{0xcc, 0x44}, nil, onewire.StrongPullup); err != nil {
return err
}
conversionSleep(maxResolutionBits)
return nil
}
// StartAll starts a conversion on all DS18B20 devices on the bus.
// Similar to ConvertAll but returns without waiting for conversion to finish.
// To be used in conjunction with LastTemp() function. Conversion timing must be
// handled by other means.
func StartAll(o onewire.Bus) error {
return o.Tx([]byte{0xcc, 0x44}, nil, onewire.StrongPullup)
}
// New returns an object that communicates over 1-wire to the DS18B20 sensor
// with the specified 64-bit address.
//
// resolutionBits must be in the range 9..12 and determines how many bits of
// precision the readings have. The resolution affects the conversion time:
// 9bits:94ms, 10bits:188ms, 11bits:375ms, 12bits:750ms.
//
// A resolution of 10 bits corresponds to 0.25C and tends to be a good
// compromise between conversion time and the device's inherent accuracy of
// +/-0.5C.
func New(o onewire.Bus, addr onewire.Address, resolutionBits int) (*Dev, error) {
if resolutionBits < 9 || resolutionBits > 12 {
return nil, errors.New("ds18b20: invalid resolutionBits")
}
d := &Dev{onewire: onewire.Dev{Bus: o, Addr: addr}, resolution: resolutionBits}
// Start by reading the scratchpad memory, this will tell us whether we can
// talk to the device correctly and also how it's configured.
spad, err := d.readScratchpad()
if err != nil {
return nil, err
}
// Change the resolution, if necessary (datasheet p.6).
if int(spad[4]>>5) != resolutionBits-9 {
// Set the value in the configuration register.
if err := d.onewire.Tx([]byte{0x4e, 0, 0, byte((resolutionBits-9)<<5) | 0x1f}, nil); err != nil {
return nil, err
}
// Copy the scratchpad to EEPROM to save the values.
if err := d.onewire.TxPower([]byte{0x48}, nil); err != nil {
return nil, err
}
// Wait for the write to complete.
sleep(10 * time.Millisecond)
}
return d, nil
}
// Dev is a handle to a Dallas Semi / Maxim DS18B20 temperature sensor on a
// 1-wire bus.
type Dev struct {
onewire onewire.Dev // device on 1-wire bus
resolution int // resolution in bits (9..12)
}
func (d *Dev) Family() Family {
return Family(d.onewire.Addr & 0xFF)
}
func (d *Dev) String() string {
return d.Family().String() + "{" + d.onewire.String() + "}"
}
// Halt implements conn.Resource.
func (d *Dev) Halt() error {
return nil
}
// Sense implements physic.SenseEnv.
func (d *Dev) Sense(e *physic.Env) error {
if err := d.onewire.TxPower([]byte{0x44}, nil); err != nil {
return err
}
conversionSleep(d.resolution)
t, err := d.LastTemp()
if err != nil {
return err
}
e.Temperature = t
return nil
}
// SenseContinuous implements physic.SenseEnv.
func (d *Dev) SenseContinuous(time.Duration) (<-chan physic.Env, error) {
// TODO(maruel): Manually poll in a loop via time.NewTicker.
return nil, errors.New("ds18b20: not implemented")
}
// Precision implements physic.SenseEnv.
func (d *Dev) Precision(e *physic.Env) {
e.Temperature = physic.Kelvin / 16
}
// LastTemp reads the temperature resulting from the last conversion from the
// device.
//
// It is useful in combination with ConvertAll.
func (d *Dev) LastTemp() (physic.Temperature, error) {
// Read the scratchpad memory.
spad, err := d.readScratchpad()
if err != nil {
return 0, err
}
c := d.parseTemperature(spad)
// The device powers up with a value of 85°C, so if we read that odds are
// very high that either no conversion was performed or that the conversion
// failed due to lack of power. This prevents reading a temp of exactly 85°C,
// but that seems like the right tradeoff.
if c == 85*physic.Celsius {
return 0, busError("ds18b20: has not performed a temperature conversion (insufficient pull-up?)")
}
return c, nil
}
// parseTemperature from scratchpad and handle special calculation for DS18S20
func (d *Dev) parseTemperature(spad []byte) physic.Temperature {
// spad[1] is MSB and spad[0] is LSB of the raw temperature value
rawTemp := int16(spad[1])<<8 | int16(spad[0])
if d.Family() == DS18S20 && spad[7] != 0 {
// for higher resolution some additional calculation is required
// TEMPERATURE = TEMP_READ - 0,25 + (COUNT_PER_C-COUNT_REMAIN)/COUNT_PER_C
// TEMP_READ = value from spad[1] (MSB) and spad[0] (LSB) with truncated last bit (0,5°C)
// COUNT_PER_C = spad[7]
// COUNT_REMAIN = spad[6]
// calculation from http://myarduinotoy.blogspot.com/2013/02/12bit-result-from-ds18s20.html
mask := 0xFFFE
rawTemp = ((rawTemp & int16(mask)) << 3) + 12 - int16(spad[6])
//rawTemp = rawTemp/2 // truncated last bit (0,5°C)
//rawTemp <<= 4 // convert to 12 bit precision (rawTemp is now in 1/16 °C)
//rawTemp = rawTemp-4 + (int16(spad[7])*16 - int16(spad[6])*16)/int16(spad[7])
//rawTemp += int16(16 - spad[6] - 4) // add compensation and remove 0.25 °C (4/16)
}
// rawTemp has 4 fractional bits. Need to do sign extension multiply by
// 1000 to get Millis, divide by 16 due to 4 fractional bits. Datasheet p.4.
v := physic.Temperature(rawTemp)
return v*physic.Kelvin/16 + physic.ZeroCelsius
}
// busError implements error and onewire.BusError.
type busError string
func (e busError) Error() string { return string(e) }
func (e busError) BusError() bool { return true }
// conversionSleep sleeps for the time a conversion takes, which depends
// on the resolution:
// 9bits:94ms, 10bits:188ms, 11bits:376ms, 12bits:752ms, datasheet p.6.
func conversionSleep(bits int) {
sleep((94 << uint(bits-9)) * time.Millisecond)
}
// readScratchpad reads the 9 bytes of scratchpad and checks the CRC.
// It returns the 8 bytes of scratchpad data (excluding the CRC byte).
func (d *Dev) readScratchpad() ([]byte, error) {
// Read the scratchpad memory.
var spad [9]byte
if err := d.onewire.Tx([]byte{0xbe}, spad[:]); err != nil {
return nil, err
}
// Check the scratchpad CRC.
if !onewire.CheckCRC(spad[:]) {
for _, s := range spad {
if s != 0xff {
return nil, busError("ds18b20: incorrect scratchpad CRC")
}
}
return nil, busError("ds18b20: device did not respond")
}
return spad[:8], nil
}
var sleep = time.Sleep
var _ conn.Resource = &Dev{}
var _ physic.SenseEnv = &Dev{}