Support for DS18S20 (#18)

pull/19/head
Benjamin Böhmke 5 years ago committed by GitHub
parent b5024d6816
commit ddb2231182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,8 +2,8 @@
// Use of this source code is governed under the Apache License, Version 2.0 // Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
// Package ds18b20 interfaces to Dallas Semi / Maxim DS18B20 and MAX31820 // Package ds18b20 interfaces to Dallas Semi / Maxim DS18S20, DS18B20 and
// 1-wire temperature sensors. // MAX31820 1-wire temperature sensors.
// //
// Note that both DS18B20 and MAX31820 use family code 0x28. // Note that both DS18B20 and MAX31820 use family code 0x28.
// //
@ -11,8 +11,8 @@
// as long as the bus driver can provide sufficient power using an active // as long as the bus driver can provide sufficient power using an active
// pull-up. // pull-up.
// //
// The DS18B20 alarm functionality and reading/writing the 2 alarm bytes in // The DS18B20/DS18S20 alarm functionality and reading/writing the 2 alarm
// the EEPROM are not supported. The DS18S20 is also not supported. // bytes in the EEPROM are not supported.
// //
// More details // More details
// //
@ -21,6 +21,7 @@
// Datasheets // Datasheets
// //
// https://datasheets.maximintegrated.com/en/ds/DS18B20-PAR.pdf // https://datasheets.maximintegrated.com/en/ds/DS18B20-PAR.pdf
// https://datasheets.maximintegrated.com/en/ds/DS18S20.pdf
// //
// http://datasheets.maximintegrated.com/en/ds/MAX31820.pdf // http://datasheets.maximintegrated.com/en/ds/MAX31820.pdf
package ds18b20 package ds18b20

@ -13,6 +13,23 @@ import (
"periph.io/x/conn/v3/physic" "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. // 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 // During the conversion it places the bus in strong pull-up mode to power
@ -89,8 +106,12 @@ type Dev struct {
resolution int // resolution in bits (9..12) resolution int // resolution in bits (9..12)
} }
func (d *Dev) Family() Family {
return Family(d.onewire.Addr & 0xFF)
}
func (d *Dev) String() string { func (d *Dev) String() string {
return "DS18B20{" + d.onewire.String() + "}" return d.Family().String() + "{" + d.onewire.String() + "}"
} }
// Halt implements conn.Resource. // Halt implements conn.Resource.
@ -134,24 +155,46 @@ func (d *Dev) LastTemp() (physic.Temperature, error) {
return 0, err return 0, err
} }
// spad[1] is MSB, spad[0] is LSB and has 4 fractional bits. Need to do sign c := d.parseTemperature(spad)
// extension multiply by 1000 to get Millis, divide by 16 due to 4 fractional
// bits. Datasheet p.4.
v := physic.Temperature((int(int8(spad[1]))<<8 + int(spad[0])))
c := v*physic.Kelvin/16 + physic.ZeroCelsius
// The device powers up with a value of 85°C, so if we read that odds are // 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 // 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, // failed due to lack of power. This prevents reading a temp of exactly 85°C,
// but that seems like the right tradeoff. // but that seems like the right tradeoff.
if c == 85000 { if c == 85*physic.Celsius {
return 0, busError("ds18b20: has not performed a temperature conversion (insufficient pull-up?)") return 0, busError("ds18b20: has not performed a temperature conversion (insufficient pull-up?)")
} }
return c, nil 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. // busError implements error and onewire.BusError.
type busError string type busError string

@ -5,6 +5,7 @@
package ds18b20 package ds18b20
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -84,6 +85,50 @@ func TestSense(t *testing.T) {
} }
} }
// TestParseTemperature tests a temperature parsing from scratchpad for DS18S20
// and DS18B20
func TestParseTemperature(t *testing.T) {
var testData = []struct {
family Family
scratchpad []byte
expectedTemp float64
}{
{DS18B20, []byte{0xD0, 0x07, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 125},
{DS18B20, []byte{0x50, 0x05, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 85},
{DS18B20, []byte{0x91, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 25.0625},
{DS18B20, []byte{0xA2, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 10.125},
{DS18B20, []byte{0x08, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 0.5},
{DS18B20, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, 0},
{DS18B20, []byte{0xF8, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, -0.5},
{DS18B20, []byte{0x5E, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, -10.125},
{DS18B20, []byte{0x6F, 0xFE, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, -25.0625},
{DS18B20, []byte{0x90, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10}, -55},
{DS18S20, []byte{0xFA, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, 125},
{DS18S20, []byte{0xAA, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, 85},
{DS18S20, []byte{0x32, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0B, 0x10}, 25.0625},
{DS18S20, []byte{0x32, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, 25},
{DS18S20, []byte{0x14, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0A, 0x10}, 10.125},
{DS18S20, []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x04, 0x10}, 0.5},
{DS18S20, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, 0},
{DS18S20, []byte{0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x04, 0x10}, -0.5},
{DS18S20, []byte{0xEC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0E, 0x10}, -10.125},
{DS18S20, []byte{0xCE, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, -25},
{DS18S20, []byte{0xCE, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0D, 0x10}, -25.0625},
{DS18S20, []byte{0x92, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0x10}, -55},
}
for _, entry := range testData {
t.Run(fmt.Sprintf("%s>%f", entry.family, entry.expectedTemp), func(st *testing.T) {
d := &Dev{onewire: onewire.Dev{Addr: onewire.Address(0x740000070e41ac00 + int64(entry.family))}}
c := d.parseTemperature(entry.scratchpad)
if c.Celsius() != entry.expectedTemp {
st.Errorf("expected %f, got %f", entry.expectedTemp, c.Celsius())
}
})
}
}
// TestConvertAll tests a temperature conversion on all ds18b20 using // TestConvertAll tests a temperature conversion on all ds18b20 using
// recorded bus transactions. // recorded bus transactions.
func TestConvertAll(t *testing.T) { func TestConvertAll(t *testing.T) {

Loading…
Cancel
Save