From ddb2231182a52c52b503d3a58e39217d4c6609d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Sun, 3 Oct 2021 21:46:27 +0200 Subject: [PATCH] Support for DS18S20 (#18) --- ds18b20/doc.go | 9 ++++--- ds18b20/ds18b20.go | 59 +++++++++++++++++++++++++++++++++++------ ds18b20/ds18b20_test.go | 45 +++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/ds18b20/doc.go b/ds18b20/doc.go index d520509..6feeb7f 100644 --- a/ds18b20/doc.go +++ b/ds18b20/doc.go @@ -2,8 +2,8 @@ // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. -// Package ds18b20 interfaces to Dallas Semi / Maxim DS18B20 and MAX31820 -// 1-wire temperature sensors. +// Package ds18b20 interfaces to Dallas Semi / Maxim DS18S20, DS18B20 and +// MAX31820 1-wire temperature sensors. // // 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 // pull-up. // -// The DS18B20 alarm functionality and reading/writing the 2 alarm bytes in -// the EEPROM are not supported. The DS18S20 is also not supported. +// The DS18B20/DS18S20 alarm functionality and reading/writing the 2 alarm +// bytes in the EEPROM are not supported. // // More details // @@ -21,6 +21,7 @@ // Datasheets // // 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 package ds18b20 diff --git a/ds18b20/ds18b20.go b/ds18b20/ds18b20.go index 6d83573..7a8b86c 100644 --- a/ds18b20/ds18b20.go +++ b/ds18b20/ds18b20.go @@ -13,6 +13,23 @@ import ( "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 @@ -89,8 +106,12 @@ type Dev struct { resolution int // resolution in bits (9..12) } +func (d *Dev) Family() Family { + return Family(d.onewire.Addr & 0xFF) +} + func (d *Dev) String() string { - return "DS18B20{" + d.onewire.String() + "}" + return d.Family().String() + "{" + d.onewire.String() + "}" } // Halt implements conn.Resource. @@ -134,24 +155,46 @@ func (d *Dev) LastTemp() (physic.Temperature, error) { return 0, err } - // spad[1] is MSB, spad[0] is LSB and 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((int(int8(spad[1]))<<8 + int(spad[0]))) - c := v*physic.Kelvin/16 + physic.ZeroCelsius + 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 == 85000 { + 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 diff --git a/ds18b20/ds18b20_test.go b/ds18b20/ds18b20_test.go index 1407c42..274d411 100644 --- a/ds18b20/ds18b20_test.go +++ b/ds18b20/ds18b20_test.go @@ -5,6 +5,7 @@ package ds18b20 import ( + "fmt" "reflect" "testing" "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 // recorded bus transactions. func TestConvertAll(t *testing.T) {