hx711: implement analog.PinADC (#335)

Add locking.
Add minimal testing.
pull/1/head
M-A 8 years ago committed by GitHub
parent 9bd83d1b9b
commit 077c5a6558

@ -2,7 +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 hx711 implements an interface to the HX711 analog to digital converter. // Package hx711 implements an interface to the 24-bits HX711 analog to digital
// converter.
// //
// Datasheet // Datasheet
// //
@ -11,9 +12,11 @@ package hx711
import ( import (
"errors" "errors"
"sync"
"time" "time"
"periph.io/x/periph/conn/gpio" "periph.io/x/periph/conn/gpio"
"periph.io/x/periph/experimental/conn/analog"
) )
var ( var (
@ -33,27 +36,32 @@ const (
CHANNEL_B_GAIN_32 InputMode = 2 CHANNEL_B_GAIN_32 InputMode = 2
) )
// Dev is a handle to a hx711.
type Dev struct { type Dev struct {
// Immutable.
name string
clk gpio.PinOut
data gpio.PinIn
// Mutable.
mu sync.Mutex
inputMode InputMode inputMode InputMode
clk gpio.PinOut
data gpio.PinIn
done chan struct{} done chan struct{}
} }
// New creates a new HX711 device. // New creates a new HX711 device.
// The data pin must support edge detection. If your pin doesn't natively //
// support edge detection you can use PollEdge from // The data pin must support edge detection. If your pin doesn't natively
// periph.io/x/periph/experimental/conn/gpio/gpioutil // support edge detection you can use PollEdge from gpioutil.
func New(clk gpio.PinOut, data gpio.PinIn) (*Dev, error) { func New(clk gpio.PinOut, data gpio.PinIn) (*Dev, error) {
if err := data.In(gpio.PullDown, gpio.FallingEdge); err != nil { if err := data.In(gpio.PullDown, gpio.FallingEdge); err != nil {
return nil, err return nil, err
} }
if err := clk.Out(gpio.Low); err != nil { if err := clk.Out(gpio.Low); err != nil {
return nil, err return nil, err
} }
return &Dev{ return &Dev{
name: "hx711{" + clk.Name() + ", " + data.Name() + "}",
inputMode: CHANNEL_A_GAIN_128, inputMode: CHANNEL_A_GAIN_128,
clk: clk, clk: clk,
data: data, data: data,
@ -61,68 +69,63 @@ func New(clk gpio.PinOut, data gpio.PinIn) (*Dev, error) {
}, nil }, nil
} }
// SetInputMode changes the voltage gain and channel multiplexer mode. // String implements analog.PinADC.
func (d *Dev) SetInputMode(inputMode InputMode) { func (d *Dev) String() string {
d.inputMode = inputMode return d.name
d.readImmediately()
} }
// IsReady returns true if there is data ready to be read from the ADC. // Name implements analog.PinADC.
func (d *Dev) IsReady() bool { func (d *Dev) Name() string {
return d.data.Read() == gpio.Low return d.String()
} }
// Read reads a single value from the ADC. It blocks until the ADC indicates // Number implements analog.PinADC.
// there is data ready for retrieval. If the ADC doesn't pull its Data pin low func (d *Dev) Number() int {
// to indicate there is data ready before the timeout is reached, TimeoutError return -1
// is returned. }
func (d *Dev) Read(timeout time.Duration) (int32, error) {
// Wait for the falling edge that indicates the ADC has data.
if !d.IsReady() {
if !d.data.WaitForEdge(timeout) {
return 0, TimeoutError
}
}
return d.readImmediately(), nil // Function implements analog.PinADC.
func (d *Dev) Function() string {
return "ADC"
} }
func (d *Dev) readImmediately() int32 { // SetInputMode changes the voltage gain and channel multiplexer mode.
// Shift the 24-bit 2's compliment value. func (d *Dev) SetInputMode(inputMode InputMode) error {
var value uint32 d.mu.Lock()
for i := 0; i < 24; i++ { defer d.mu.Unlock()
d.clk.Out(gpio.High) d.inputMode = inputMode
level := d.data.Read() _, err := d.readRaw()
d.clk.Out(gpio.Low) return err
}
value <<= 1 // Range implements analog.PinADC.
if level { func (d *Dev) Range() (analog.Sample, analog.Sample) {
value |= 1 return analog.Sample{Raw: -(1 << 23)}, analog.Sample{Raw: 1 << 23}
} }
}
// Pulse the clock 1-3 more times to set the new ADC mode. // Read implements analog.PinADC.
for i := 0; i < int(d.inputMode); i++ { func (d *Dev) Read() (analog.Sample, error) {
d.clk.Out(gpio.High) d.mu.Lock()
d.clk.Out(gpio.Low) defer d.mu.Unlock()
} raw, err := d.readRaw()
// Convert the 24-bit 2's compliment value to a 32-bit signed value. return analog.Sample{Raw: raw}, err
return int32(value<<8) >> 8
} }
// StartContinuousRead starts reading values continuously from the ADC. It // ReadContinuous starts reading values continuously from the ADC. It
// returns a channel that you can use to receive these values. // returns a channel that you can use to receive these values.
// //
// You must call Halt to stop reading. // You must call Halt to stop reading.
// //
// Calling StartContinuousRead again before Halt is an error, // Calling ReadContinuous again before Halt is an error,
// and nil will be returned. // and nil will be returned.
func (d *Dev) StartContinuousRead() <-chan int32 { func (d *Dev) ReadContinuous() <-chan analog.Sample {
d.mu.Lock()
defer d.mu.Unlock()
if d.done != nil { if d.done != nil {
return nil return nil
} }
done := make(chan struct{}) done := make(chan struct{})
ret := make(chan int32) ret := make(chan analog.Sample)
go func() { go func() {
for { for {
@ -131,9 +134,9 @@ func (d *Dev) StartContinuousRead() <-chan int32 {
close(ret) close(ret)
return return
default: default:
value, err := d.Read(time.Second) value, err := d.ReadTimeout(time.Second)
if err == nil { if err == nil {
ret <- value ret <- analog.Sample{Raw: value}
} }
} }
} }
@ -143,11 +146,70 @@ func (d *Dev) StartContinuousRead() <-chan int32 {
return ret return ret
} }
// Halt stops a continuous read that was started with StartContinuousRead. // Halt stops a continuous read that was started with ReadContinuous.
// This will close the channel that was returned by StartContinuousRead. //
func (d *Dev) Halt() { // This will close the channel that was returned by ReadContinuous.
func (d *Dev) Halt() error {
d.mu.Lock()
defer d.mu.Unlock()
if d.done != nil { if d.done != nil {
close(d.done) close(d.done)
d.done = nil d.done = nil
} }
return nil
}
// IsReady returns true if there is data ready to be read from the ADC.
func (d *Dev) IsReady() bool {
return d.data.Read() == gpio.Low
}
// ReadTimeout reads a single value from the ADC.
//
// It blocks until the ADC indicates there is data ready for retrieval. If the
// ADC doesn't pull its Data pin low to indicate there is data ready before the
// timeout is reached, TimeoutError is returned.
func (d *Dev) ReadTimeout(timeout time.Duration) (int32, error) {
// Wait for the falling edge that indicates the ADC has data.
d.mu.Lock()
defer d.mu.Unlock()
if !d.IsReady() {
if !d.data.WaitForEdge(timeout) {
return 0, TimeoutError
}
}
return d.readRaw()
} }
func (d *Dev) readRaw() (int32, error) {
// Shift the 24-bit 2's compliment value.
var value uint32
for i := 0; i < 24; i++ {
if err := d.clk.Out(gpio.High); err != nil {
return 0, err
}
level := d.data.Read()
if err := d.clk.Out(gpio.Low); err != nil {
return 0, err
}
value <<= 1
if level {
value |= 1
}
}
// Pulse the clock 1-3 more times to set the new ADC mode.
for i := 0; i < int(d.inputMode); i++ {
if err := d.clk.Out(gpio.High); err != nil {
return 0, err
}
if err := d.clk.Out(gpio.Low); err != nil {
return 0, err
}
}
// Convert the 24-bit 2's compliment value to a 32-bit signed value.
return int32(value<<8) >> 8, nil
}
var _ analog.PinADC = &Dev{}

@ -0,0 +1,128 @@
// Copyright 2018 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 hx711
import (
"errors"
"testing"
"time"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpiotest"
)
func TestNew(t *testing.T) {
clk := gpiotest.Pin{N: "clk"}
data := gpiotest.Pin{N: "data", EdgesChan: make(chan gpio.Level)}
d, err := New(&clk, &data)
if err != nil {
t.Fatal(err)
}
if s := d.String(); s != "hx711{clk, data}" {
t.Fatal(s)
}
if s := d.Name(); s != "hx711{clk, data}" {
t.Fatal(s)
}
if n := d.Number(); n != -1 {
t.Fatal(n)
}
if f := d.Function(); f != "ADC" {
t.Fatal(f)
}
min, max := d.Range()
// TODO(davidsansome): Is that the right values?
if min.Raw != -8388608 {
t.Fatal(min.Raw)
}
if max.Raw != 8388608 {
t.Fatal(max.Raw)
}
if !d.IsReady() {
t.Fatal("data is low")
}
if err := d.SetInputMode(CHANNEL_A_GAIN_128); err != nil {
t.Fatal(err)
}
if err := d.Halt(); err != nil {
t.Fatal(err)
}
}
func TestNew_Fail(t *testing.T) {
ok := gpiotest.Pin{N: "ok", EdgesChan: make(chan gpio.Level)}
fail := failPin{gpiotest.Pin{N: "fail"}}
if _, err := New(&fail, &ok); err == nil {
t.Fatal("expected failure")
}
if _, err := New(&ok, &fail); err == nil {
t.Fatal("expected failure")
}
}
func TestRead(t *testing.T) {
clk := gpiotest.Pin{N: "clk"}
data := gpiotest.Pin{N: "data", EdgesChan: make(chan gpio.Level)}
d, err := New(&clk, &data)
if err != nil {
t.Fatal(err)
}
// TODO(davidsansome): Real testing.
r, err := d.Read()
if err != nil {
t.Fatal(err)
}
if r.Raw != 0 {
t.Fatal("we should implement something")
}
}
func TestReadTimeout(t *testing.T) {
clk := gpiotest.Pin{N: "clk"}
data := gpiotest.Pin{N: "data", EdgesChan: make(chan gpio.Level)}
d, err := New(&clk, &data)
if err != nil {
t.Fatal(err)
}
// TODO(davidsansome): Real testing.
r, err := d.ReadTimeout(time.Second)
if err != nil {
t.Fatal(err)
}
if r != 0 {
t.Fatal("we should implement something")
}
}
func TestReadContinuous(t *testing.T) {
clk := gpiotest.Pin{N: "clk"}
data := gpiotest.Pin{N: "data", EdgesChan: make(chan gpio.Level)}
d, err := New(&clk, &data)
if err != nil {
t.Fatal(err)
}
// TODO(davidsansome): Real testing.
c := d.ReadContinuous()
if c == nil {
t.Fatal("expected chan")
}
if err := d.Halt(); err != nil {
t.Fatal(err)
}
}
//
type failPin struct {
gpiotest.Pin
}
func (f *failPin) In(pull gpio.Pull, edge gpio.Edge) error {
return errors.New("fail")
}
func (f *failPin) Out(l gpio.Level) error {
return errors.New("fail")
}
Loading…
Cancel
Save