mirror of https://github.com/periph/devices
Add support for DS248x i2c to onewire interface chip, and DS18B20 temp sensors (#59)
parent
495d261bae
commit
3bb6afb0ac
@ -0,0 +1,165 @@
|
|||||||
|
// 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 interfaces to Dallas Semi / Maxim DS18B20 and MAX31820
|
||||||
|
// 1-wire temperature sensors.
|
||||||
|
//
|
||||||
|
// Note that both DS18B20 and MAX31820 use family code 0x28.
|
||||||
|
//
|
||||||
|
// Both powered sensors and parasitically powered sensors are supported
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Datasheets
|
||||||
|
//
|
||||||
|
// https://datasheets.maximintegrated.com/en/ds/DS18B20-PAR.pdf
|
||||||
|
//
|
||||||
|
// http://datasheets.maximintegrated.com/en/ds/MAX31820.pdf
|
||||||
|
package ds18b20
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/periph/devices"
|
||||||
|
"github.com/google/periph/experimental/conn/onewire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
d.onewire.Tx([]byte{0x4e, 0, 0, byte((resolutionBits-9)<<5) | 0x1f}, nil)
|
||||||
|
// Copy the scratchpad to EEPROM to save the values.
|
||||||
|
d.onewire.TxPower([]byte{0x48}, nil)
|
||||||
|
// Wait for the write to complete
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
//===== Dev
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature performs a conversion and returns the temperature.
|
||||||
|
func (d *Dev) Temperature() (devices.Celsius, error) {
|
||||||
|
if err := d.onewire.TxPower([]byte{0x44}, nil); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
conversionSleep(d.resolution)
|
||||||
|
return d.LastTemp()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastTemp reads the temperature resulting from the last conversion from the device.
|
||||||
|
// It is useful in combination with ConvertAll.
|
||||||
|
func (d *Dev) LastTemp() (devices.Celsius, error) {
|
||||||
|
// Read the scratchpad memory.
|
||||||
|
spad, err := d.readScratchpad()
|
||||||
|
if err != nil {
|
||||||
|
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 devices.Millis, divide by 16 due to 4 fractional bits.
|
||||||
|
// Datasheet p.4.
|
||||||
|
c := (devices.Celsius(int8(spad[1]))<<8 + devices.Celsius(spad[0])) * 1000 / 16
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return 0, busError("ds18b20: has not performed a temperature conversion (insufficient pull-up?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
time.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
|
||||||
|
}
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/periph/devices"
|
||||||
|
"github.com/google/periph/experimental/conn/onewire"
|
||||||
|
"github.com/google/periph/experimental/conn/onewire/onewiretest"
|
||||||
|
"github.com/google/periph/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lets periph load all drivers and then runs the tests.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
host.Init()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTemperature tests a temperature conversion on a ds18b20 using
|
||||||
|
// recorded bus transactions.
|
||||||
|
func TestTemperature(t *testing.T) {
|
||||||
|
// set-up playback using the recording output.
|
||||||
|
ops := []onewiretest.IO{
|
||||||
|
// Match ROM + Read Scratchpad (init)
|
||||||
|
{Write: []uint8{0x55, 0x28, 0xac, 0x41, 0xe, 0x7, 0x0, 0x0, 0x74, 0xbe},
|
||||||
|
Read: []uint8{0xe0, 0x1, 0x0, 0x0, 0x3f, 0xff, 0x10, 0x10, 0x3f}, Pull: false},
|
||||||
|
// Match ROM + Convert
|
||||||
|
{Write: []uint8{0x55, 0x28, 0xac, 0x41, 0xe, 0x7, 0x0, 0x0, 0x74, 0x44},
|
||||||
|
Read: []uint8(nil), Pull: true},
|
||||||
|
// Match ROM + Read Scratchpad (read temp)
|
||||||
|
{Write: []uint8{0x55, 0x28, 0xac, 0x41, 0xe, 0x7, 0x0, 0x0, 0x74, 0xbe},
|
||||||
|
Read: []uint8{0xe0, 0x1, 0x0, 0x0, 0x3f, 0xff, 0x10, 0x10, 0x3f}, Pull: false},
|
||||||
|
}
|
||||||
|
var addr onewire.Address = 0x740000070e41ac28
|
||||||
|
var temp devices.Celsius = 30000 // 30.000°C
|
||||||
|
owBus := &onewiretest.Playback{Ops: ops}
|
||||||
|
// Init the ds18b20.
|
||||||
|
ds18b20, err := New(owBus, addr, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Read the temperature.
|
||||||
|
t0 := time.Now()
|
||||||
|
now, err := ds18b20.Temperature()
|
||||||
|
dt := time.Since(t0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Expect the correct value.
|
||||||
|
if now != temp {
|
||||||
|
t.Errorf("expected %s, got %s", temp.String(), now.String())
|
||||||
|
}
|
||||||
|
// Expect it to take >187ms
|
||||||
|
if dt < 188*time.Millisecond {
|
||||||
|
t.Errorf("expected conversion to take >187ms, took %s", dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConvertAll tests a temperature conversion on all ds18b20 using
|
||||||
|
// recorded bus transactions.
|
||||||
|
func TestConvertAll(t *testing.T) {
|
||||||
|
// set-up playback using the recording output.
|
||||||
|
ops := []onewiretest.IO{
|
||||||
|
// Skip ROM + Convert
|
||||||
|
{Write: []uint8{0xcc, 0x44}, Read: []uint8(nil), Pull: true},
|
||||||
|
}
|
||||||
|
owBus := &onewiretest.Playback{Ops: ops}
|
||||||
|
// Perform the conversion
|
||||||
|
t0 := time.Now()
|
||||||
|
if err := ConvertAll(owBus, 9); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Expect it to take >93ms
|
||||||
|
if dt := time.Since(t0); dt < 94*time.Millisecond {
|
||||||
|
t.Errorf("expected conversion to take >93ms, took %s", dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Commented out in order not to import periph/host, need to move to smoke test
|
||||||
|
// TestRecordTemp tests and records a temperature conversion. It outputs
|
||||||
|
// the recording if the tests are run with the verbose option.
|
||||||
|
//
|
||||||
|
// This test is skipped unless the -record flag is passed to the test executable.
|
||||||
|
// Use either `go test -args -record` or `ds18b20.test -test.v -record`.
|
||||||
|
func TestRecordTemp(t *testing.T) {
|
||||||
|
// Only proceed to init hardware and test if -record flag is passed
|
||||||
|
if !*record {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
host.Init()
|
||||||
|
|
||||||
|
i2cBus, err := i2c.New(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
owBus, err := ds248x.New(i2cBus, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
devices, err := owBus.Search(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
addrs := "1-wire devices found:"
|
||||||
|
for _, a := range devices {
|
||||||
|
addrs += fmt.Sprintf(" %#016x", a)
|
||||||
|
}
|
||||||
|
t.Log(addrs)
|
||||||
|
// See whether there's a ds18b20 on the bus.
|
||||||
|
var addr onewire.Address
|
||||||
|
for _, a := range devices {
|
||||||
|
if a&0xff == 0x28 {
|
||||||
|
addr = a
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if addr == 0 {
|
||||||
|
t.Fatal("no DS18B20 found")
|
||||||
|
}
|
||||||
|
t.Logf("var addr onewire.Address = %#016x", addr)
|
||||||
|
// Start recording and perform a temperature conversion.
|
||||||
|
rec := &onewiretest.Record{Bus: owBus}
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
ds18b20, err := New(rec, addr, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ds18b20 init: %s", err)
|
||||||
|
}
|
||||||
|
temp, err := ds18b20.Temperature()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Output what got recorded.
|
||||||
|
t.Log("var ops = []onewiretest.IO{")
|
||||||
|
for _, op := range rec.Ops {
|
||||||
|
t.Logf(" %#v,", op)
|
||||||
|
}
|
||||||
|
t.Log("}")
|
||||||
|
t.Logf("var temp devices.Celsius = %d // %s", temp, temp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
var record *bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
record = flag.Bool("record", false, "record real hardware accesses")
|
||||||
|
}
|
||||||
|
*/
|
||||||
@ -0,0 +1,186 @@
|
|||||||
|
// 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 ds248x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/periph/conn"
|
||||||
|
"github.com/google/periph/experimental/conn/onewire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dev is a handle to a ds248x device and it implements the onewire.Bus interface.
|
||||||
|
//
|
||||||
|
// Dev implements a persistent error model: if a fatal error is encountered it places
|
||||||
|
// itself into an error state and immediately returns the last error on all subsequent
|
||||||
|
// calls. A fresh Dev, which reinitializes the hardware, must be created to proceed.
|
||||||
|
//
|
||||||
|
// A persistent error is only set when there is a problem with the ds248x device itself
|
||||||
|
// (or the I²C bus used to access it). Errors on the 1-wire bus do not cause persistent
|
||||||
|
// errors and implement the onewire.BusError interface to indicate this fact.
|
||||||
|
type Dev struct {
|
||||||
|
sync.Mutex // lock for the bus while a transaction is in progress
|
||||||
|
i2c conn.Conn // i2c device handle for the ds248x
|
||||||
|
isDS2483 bool // true: ds2483, false: ds2482-100
|
||||||
|
confReg byte // value written to configuration register
|
||||||
|
tReset time.Duration // time to perform a 1-wire reset
|
||||||
|
tSlot time.Duration // time to perform a 1-bit 1-wire read/write
|
||||||
|
err error // persistent error, device will no longer operate
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
func (d *Dev) String() string {
|
||||||
|
return fmt.Sprintf("ds248x")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tx performs a bus transaction, sending and receiving bytes, and
|
||||||
|
// ending by pulling the bus high either weakly or strongly depending
|
||||||
|
// on the value of power.
|
||||||
|
//
|
||||||
|
// A strong pull-up is typically required to power temperature conversion
|
||||||
|
// or EEPROM writes.
|
||||||
|
func (d *Dev) Tx(w, r []byte, power onewire.Pullup) error {
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
|
||||||
|
// Issue 1-wire bus reset.
|
||||||
|
if present, err := d.reset(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !present {
|
||||||
|
return busError("ds248x: no device present")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send bytes onto 1-wire bus.
|
||||||
|
for i, b := range w {
|
||||||
|
if power == onewire.StrongPullup && i == len(w)-1 && len(r) == 0 {
|
||||||
|
// This is the last byte, need to activate strong pull-up.
|
||||||
|
d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil)
|
||||||
|
}
|
||||||
|
d.i2cTx([]byte{cmd1WWrite, b}, nil)
|
||||||
|
d.waitIdle(7 * d.tSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes from one-wire bus.
|
||||||
|
for i, _ := range r {
|
||||||
|
if power == onewire.StrongPullup && i == len(r)-1 {
|
||||||
|
// This is the last byte, need to activate strong-pull-up
|
||||||
|
d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil)
|
||||||
|
}
|
||||||
|
d.i2cTx([]byte{cmd1WRead}, r[i:i+1])
|
||||||
|
d.waitIdle(7 * d.tSlot)
|
||||||
|
d.i2cTx([]byte{cmdSetReadPtr, regRDR}, r[i:i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search performs a "search" cycle on the 1-wire bus and returns the
|
||||||
|
// addresses of all devices on the bus if alarmOnly is false and of all
|
||||||
|
// devices in alarm state if alarmOnly is true.
|
||||||
|
//
|
||||||
|
// If an error occurs during the search the already-discovered devices are
|
||||||
|
// returned with the error.
|
||||||
|
func (d *Dev) Search(alarmOnly bool) ([]onewire.Address, error) {
|
||||||
|
return onewire.Search(d, alarmOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchTriplet performs a single bit search triplet command on the bus,
|
||||||
|
// waits for it to complete and returs the outcome.
|
||||||
|
//
|
||||||
|
// SearchTriplet should not be used directly, use Search instead.
|
||||||
|
func (d *Dev) SearchTriplet(direction byte) (onewire.TripletResult, error) {
|
||||||
|
// Send one-wire triplet command.
|
||||||
|
var dir byte
|
||||||
|
if direction != 0 {
|
||||||
|
dir = 0x80
|
||||||
|
}
|
||||||
|
d.i2cTx([]byte{cmd1WTriplet, dir}, nil)
|
||||||
|
// Wait and read status register, concoct result from there.
|
||||||
|
status := d.waitIdle(0 * d.tSlot) // in theory 3*tSlot but it's actually overlapped
|
||||||
|
tr := onewire.TripletResult{
|
||||||
|
GotZero: status&0x20 == 0,
|
||||||
|
GotOne: status&0x40 == 0,
|
||||||
|
Taken: status >> 7,
|
||||||
|
}
|
||||||
|
return tr, d.err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// reset issues a reset signal on the 1-wire bus and returns true if any device
|
||||||
|
// responded with a presence pulse.
|
||||||
|
func (d *Dev) reset() (bool, error) {
|
||||||
|
// Issue reset.
|
||||||
|
d.i2cTx([]byte{cmd1WReset}, nil)
|
||||||
|
|
||||||
|
// Wait for reset to complete.
|
||||||
|
status := d.waitIdle(d.tReset)
|
||||||
|
if d.err != nil {
|
||||||
|
return false, d.err
|
||||||
|
}
|
||||||
|
// Detect bus short and turn into 1-wire error
|
||||||
|
if (status & 4) != 0 {
|
||||||
|
return false, shortedBusError("onewire/ds248x: bus has a short")
|
||||||
|
}
|
||||||
|
return (status & 2) != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// i2cTx is a helper function to call i2c.Tx and handle the error by persisting it.
|
||||||
|
func (d *Dev) i2cTx(w, r []byte) {
|
||||||
|
if d.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.err = d.i2c.Tx(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitIdle waits for the one wire bus to be idle.
|
||||||
|
//
|
||||||
|
// It initially sleeps for the delay and then polls the status register and
|
||||||
|
// sleeps for a tenth of the delay each time the status register indicates
|
||||||
|
// that the bus is still busy. The last read status byte is returned.
|
||||||
|
//
|
||||||
|
// An overall timeout of 3ms is applied to the whole procedure. waitIdle
|
||||||
|
// uses the persistent error model and returns 0 if there is an error.
|
||||||
|
func (d *Dev) waitIdle(delay time.Duration) byte {
|
||||||
|
if d.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// Overall timeout.
|
||||||
|
tOut := time.Now().Add(3 * time.Millisecond)
|
||||||
|
time.Sleep(delay)
|
||||||
|
for {
|
||||||
|
// Read status register.
|
||||||
|
var status [1]byte
|
||||||
|
d.i2cTx(nil, status[:])
|
||||||
|
// If bus idle complete, return status. This also returns if d.err!=nil
|
||||||
|
// because in that case status[0]==0.
|
||||||
|
if (status[0] & 1) == 0 {
|
||||||
|
return status[0]
|
||||||
|
}
|
||||||
|
// If we're timing out return error. This is an error with the ds248x, not with
|
||||||
|
// devices on the 1-wire bus, hence it is persistent.
|
||||||
|
if time.Now().After(tOut) {
|
||||||
|
d.err = fmt.Errorf("ds248x: timeout waiting for bus cycle to finish")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// Try not to hog the kernel thread.
|
||||||
|
time.Sleep(delay / 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortedBusError implements error and onewire.ShortedBusError.
|
||||||
|
type shortedBusError string
|
||||||
|
|
||||||
|
func (e shortedBusError) Error() string { return string(e) }
|
||||||
|
func (e shortedBusError) IsShorted() bool { return true }
|
||||||
|
func (e shortedBusError) BusError() bool { return true }
|
||||||
|
|
||||||
|
// busError implements error and onewire.BusError.
|
||||||
|
type busError string
|
||||||
|
|
||||||
|
func (e busError) Error() string { return string(e) }
|
||||||
|
func (e busError) BusError() bool { return true }
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
// 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 ds248x controls a Maxim DS2483 or DS2482-100 1-wire interface chip over I²C.
|
||||||
|
//
|
||||||
|
// Datasheets
|
||||||
|
//
|
||||||
|
// https://www.maximintegrated.com/en/products/digital/one-wire/DS2483.html
|
||||||
|
//
|
||||||
|
// https://www.maximintegrated.com/en/products/interface/controllers-expanders/DS2482-100.html
|
||||||
|
package ds248x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/periph/conn/i2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PupOhm controls the strength of the passive pull-up resistor
|
||||||
|
// on the 1-wire data line. The default value is 1000Ω.
|
||||||
|
type PupOhm uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
R500Ω = 4 // 500Ω passive pull-up resistor
|
||||||
|
R1000Ω = 6 // 1000Ω passive pull-up resistor
|
||||||
|
)
|
||||||
|
|
||||||
|
// Opts contains options to pass to the constructor.
|
||||||
|
type Opts struct {
|
||||||
|
Addr uint16 // I²C address, default 0x18
|
||||||
|
PassivePullup bool // false:use active pull-up, true: disable active pullup
|
||||||
|
|
||||||
|
// The following options are only available on the ds2483 (not ds2482-100).
|
||||||
|
// The actual value used is the closest possible value (rounded up or down).
|
||||||
|
ResetLow time.Duration // reset low time, range 440μs..740μs
|
||||||
|
PresenceDetect time.Duration // presence detect sample time, range 58μs..76μs
|
||||||
|
Write0Low time.Duration // write zero low time, range 52μs..70μs
|
||||||
|
Write0Recovery time.Duration // write zero recovery time, range 2750ns..25250ns
|
||||||
|
PullupRes PupOhm // passive pull-up resistance, true: 500Ω, false: 1kΩ
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an device object that communicates over I²C to the DS2482/DS2483 controller.
|
||||||
|
func New(i i2c.Bus, opts *Opts) (*Dev, error) {
|
||||||
|
addr := uint16(0x18)
|
||||||
|
if opts != nil {
|
||||||
|
switch opts.Addr {
|
||||||
|
case 0x18, 0x19, 0x20, 0x21:
|
||||||
|
addr = opts.Addr
|
||||||
|
case 0x00:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("ds248x: given address not supported by device")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d := &Dev{i2c: &i2c.Dev{Bus: i, Addr: addr}}
|
||||||
|
if err := d.makeDev(opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// defaults holds default values for optional parameters.
|
||||||
|
var defaults = Opts{
|
||||||
|
PassivePullup: false,
|
||||||
|
ResetLow: 560 * time.Microsecond,
|
||||||
|
PresenceDetect: 68 * time.Microsecond,
|
||||||
|
Write0Low: 64 * time.Microsecond,
|
||||||
|
Write0Recovery: 5250 * time.Nanosecond,
|
||||||
|
PullupRes: R1000Ω,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dev) makeDev(opts *Opts) error {
|
||||||
|
// Doctor the opts to apply default values.
|
||||||
|
if opts == nil {
|
||||||
|
opts = &defaults
|
||||||
|
}
|
||||||
|
if opts.ResetLow == 0 {
|
||||||
|
opts.ResetLow = defaults.ResetLow
|
||||||
|
}
|
||||||
|
if opts.PresenceDetect == 0 {
|
||||||
|
opts.PresenceDetect = defaults.PresenceDetect
|
||||||
|
}
|
||||||
|
if opts.Write0Low == 0 {
|
||||||
|
opts.Write0Low = defaults.Write0Low
|
||||||
|
}
|
||||||
|
if opts.Write0Recovery == 0 {
|
||||||
|
opts.Write0Recovery = defaults.Write0Recovery
|
||||||
|
}
|
||||||
|
if opts.PullupRes == 0 {
|
||||||
|
opts.PullupRes = defaults.PullupRes
|
||||||
|
}
|
||||||
|
d.tReset = 2 * opts.ResetLow
|
||||||
|
d.tSlot = opts.Write0Low + opts.Write0Recovery
|
||||||
|
|
||||||
|
// Issue a reset command.
|
||||||
|
if err := d.i2c.Tx([]byte{cmdReset}, nil); err != nil {
|
||||||
|
return fmt.Errorf("ds248x: error while resetting: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the status register to confirm that we have a responding ds248x
|
||||||
|
var stat [1]byte
|
||||||
|
if err := d.i2c.Tx([]byte{cmdSetReadPtr, regStatus}, stat[:]); err != nil {
|
||||||
|
return fmt.Errorf("ds248x: error while reading status register: %s", err)
|
||||||
|
}
|
||||||
|
if stat[0] != 0x18 {
|
||||||
|
return fmt.Errorf("ds248x: invalid status register value: %#x, expected 0x18\n", stat[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the device configuration register to get the chip out of reset state, immediately
|
||||||
|
// read it back to get confirmation.
|
||||||
|
d.confReg = 0xe1 // standard-speed, no strong pullup, no powerdown, active pull-up
|
||||||
|
if opts.PassivePullup {
|
||||||
|
d.confReg ^= 0x11
|
||||||
|
}
|
||||||
|
var dcr [1]byte
|
||||||
|
if err := d.i2c.Tx([]byte{cmdWriteConfig, d.confReg}, dcr[:]); err != nil {
|
||||||
|
return fmt.Errorf("ds248x: error while writing device config register: %s", err)
|
||||||
|
}
|
||||||
|
// When reading back we only get the bottom nibble
|
||||||
|
if dcr[0] != d.confReg&0x0f {
|
||||||
|
return fmt.Errorf("ds248x: failure to write device config register, wrote %#x got %#x back",
|
||||||
|
d.confReg, dcr[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the read ptr to the port configuration register to determine whether we have a
|
||||||
|
// ds2483 vs ds2482-100. This will fail on devices that do not have a port config
|
||||||
|
// register, such as the ds2482-100.
|
||||||
|
d.isDS2483 = d.i2c.Tx([]byte{cmdSetReadPtr, regPCR}, nil) == nil
|
||||||
|
|
||||||
|
// Set the options for the ds2483.
|
||||||
|
if d.isDS2483 {
|
||||||
|
buf := []byte{cmdAdjPort,
|
||||||
|
byte(0x00 + ((opts.ResetLow/time.Microsecond - 430) / 20 & 0x0f)),
|
||||||
|
byte(0x20 + ((opts.PresenceDetect/time.Microsecond - 55) / 2 & 0x0f)),
|
||||||
|
byte(0x40 + ((opts.Write0Low/time.Microsecond - 51) / 2 & 0x0f)),
|
||||||
|
byte(0x60 + (((opts.Write0Recovery-1250)/2500 + 5) & 0x0f)),
|
||||||
|
byte(0x80 + (opts.PullupRes & 0x0f)),
|
||||||
|
}
|
||||||
|
if err := d.i2c.Tx(buf, nil); err != nil {
|
||||||
|
return fmt.Errorf("ds248x: error while setting port config values: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdReset = 0xf0 // reset ds248x
|
||||||
|
cmdSetReadPtr = 0xe1 // set the read pointer
|
||||||
|
cmdWriteConfig = 0xd2 // write the device configuration
|
||||||
|
cmdAdjPort = 0xc3 // adjust 1-wire port
|
||||||
|
cmd1WReset = 0xb4 // reset the 1-wire bus
|
||||||
|
cmd1WBit = 0x87 // perform a single-bit transaction on the 1-wire bus
|
||||||
|
cmd1WWrite = 0xa5 // perform a byte write on the 1-wire bus
|
||||||
|
cmd1WRead = 0x96 // perform a byte read on the 1-wire bus
|
||||||
|
cmd1WTriplet = 0x78 // perform a triplet operation (2 bit reads, a bit write)
|
||||||
|
|
||||||
|
regDCR = 0xc3 // read ptr for device configuration register
|
||||||
|
regStatus = 0xf0 // read ptr for status register
|
||||||
|
regRDR = 0xe1 // read ptr for read-data register
|
||||||
|
regPCR = 0xb4 // read ptr for port configuration register
|
||||||
|
)
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
// 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 ds248x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/periph/conn/i2c"
|
||||||
|
"github.com/google/periph/conn/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInit tests the initialization of a ds2483 using a recording.
|
||||||
|
func TestInit(t *testing.T) {
|
||||||
|
var ops = []i2ctest.IO{
|
||||||
|
{Addr: 0x18, Write: []byte{0xf0}, Read: []byte(nil)},
|
||||||
|
{Addr: 0x18, Write: []byte{0xe1, 0xf0}, Read: []byte{0x18}},
|
||||||
|
{Addr: 0x18, Write: []byte{0xd2, 0xe1}, Read: []byte{0x1}},
|
||||||
|
{Addr: 0x18, Write: []byte{0xe1, 0xb4}, Read: []byte(nil)},
|
||||||
|
{Addr: 0x18, Write: []byte{0xc3, 0x6, 0x26, 0x46, 0x66, 0x86}, Read: []byte(nil)},
|
||||||
|
{Addr: 0x18, Write: []byte{0x78, 0x0}, Read: []byte(nil)},
|
||||||
|
{Addr: 0x18, Write: []byte{}, Read: []byte{0xe8}},
|
||||||
|
}
|
||||||
|
|
||||||
|
bus := &i2ctest.Playback{Ops: ops}
|
||||||
|
if _, err := New(bus, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Open the I²C bus to which the DS248x is connected.
|
||||||
|
i2cBus, err := i2c.New(-1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer i2cBus.Close()
|
||||||
|
|
||||||
|
// Open the DS248x to get a 1-wire bus.
|
||||||
|
owBus, err := New(i2cBus, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Search devices on the bus
|
||||||
|
devices, err := owBus.Search(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Found %d 1-wire devices: ", len(devices))
|
||||||
|
for _, d := range devices {
|
||||||
|
fmt.Printf(" %#16x", uint64(d))
|
||||||
|
}
|
||||||
|
fmt.Print('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Commented out in order not to import periph/host, need to move to smoke test
|
||||||
|
// TestRecordInit tests and records the initialization of a ds248x by accessing
|
||||||
|
// real hardware and outputs the recording ready to use for playback in
|
||||||
|
// TestInit.
|
||||||
|
//
|
||||||
|
// This test is skipped unless the -record flag is passed to the test executable.
|
||||||
|
// Use either `go test -args -record` or `ds2483.test -test.v -record`.
|
||||||
|
func TestRecordInit(t *testing.T) {
|
||||||
|
// Only proceed to init hardware and test if -record flag is passed
|
||||||
|
if !*record {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
host.Init()
|
||||||
|
|
||||||
|
i2cReal, err := i2c.New(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
i2cBus := &i2ctest.Record{Bus: i2cReal}
|
||||||
|
// Now init the ds248x.
|
||||||
|
owBus, err := New(i2cBus, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Perform a search triplet operation to see whether anyone is on the bus
|
||||||
|
// (we could do a full search but that would produce a very long recording).
|
||||||
|
_, err = owBus.SearchTriplet(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Output the recording.
|
||||||
|
t.Logf("var ops = i2ctest.IO{\n")
|
||||||
|
for _, op := range i2cBus.Ops {
|
||||||
|
t.Logf(" {Addr: %#x, Write: %#v, Read: %#v},\n", op.Addr, op.Write, op.Read)
|
||||||
|
}
|
||||||
|
t.Logf("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
var record *bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
record = flag.Bool("record", false, "record real hardware accesses")
|
||||||
|
}
|
||||||
|
*/
|
||||||
Loading…
Reference in New Issue