mirror of https://github.com/periph/devices
gpioutil: add PollEdge for polling based edge detection.
This function returns a gpio.PinIO implementing edge detection via polling.pull/1/head
parent
6853591d2a
commit
717ef921fa
@ -0,0 +1,117 @@
|
|||||||
|
// 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 gpioutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/gpio"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pollEdge is a gpio.PinIO where edge detection is done manually.
|
||||||
|
type pollEdge struct {
|
||||||
|
// Immutable.
|
||||||
|
gpio.PinIO
|
||||||
|
// period is the delay between each poll.
|
||||||
|
period time.Duration
|
||||||
|
die chan struct{}
|
||||||
|
|
||||||
|
// Mutable.
|
||||||
|
// edge is the current edge detection.
|
||||||
|
edge gpio.Edge
|
||||||
|
}
|
||||||
|
|
||||||
|
// PollEdge returns a gpio.PinIO which implements edge detection via polling.
|
||||||
|
//
|
||||||
|
// Example of GPIOs without edge detection are GPIOs accessible over an I²C
|
||||||
|
// chip or over USB.
|
||||||
|
//
|
||||||
|
// freq must be above 0. A reasonable value is 20Hz reading. High rate
|
||||||
|
// essentially means a busy loop.
|
||||||
|
func PollEdge(p gpio.PinIO, freq physic.Frequency) gpio.PinIO {
|
||||||
|
return &pollEdge{PinIO: p, period: freq.Duration(), die: make(chan struct{}, 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In implements gpio.PinIO.
|
||||||
|
func (p *pollEdge) In(pull gpio.Pull, edge gpio.Edge) error {
|
||||||
|
p.edge = gpio.NoEdge
|
||||||
|
err := p.PinIO.In(pull, gpio.NoEdge)
|
||||||
|
if err == nil {
|
||||||
|
p.edge = edge
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForEdge implements gpio.PinIO.
|
||||||
|
func (p *pollEdge) WaitForEdge(timeout time.Duration) bool {
|
||||||
|
select {
|
||||||
|
case <-p.die:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case <-p.die:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
curr := p.PinIO.Read()
|
||||||
|
// -1 means to wait indefinitely.
|
||||||
|
if timeout >= 0 {
|
||||||
|
defer time.AfterFunc(timeout, func() {
|
||||||
|
p.die <- struct{}{}
|
||||||
|
}).Stop()
|
||||||
|
}
|
||||||
|
// Sadly it's not possible to stop then restart a ticker, so we can't cache
|
||||||
|
// it in the object.
|
||||||
|
t := time.NewTicker(p.period)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
n := p.PinIO.Read()
|
||||||
|
if n != curr {
|
||||||
|
switch p.edge {
|
||||||
|
case gpio.RisingEdge:
|
||||||
|
if n == gpio.High {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
curr = n
|
||||||
|
case gpio.FallingEdge:
|
||||||
|
if n == gpio.Low {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
curr = n
|
||||||
|
case gpio.BothEdges:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-p.die:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halt implements gpio.PinIO.
|
||||||
|
//
|
||||||
|
// It unblocks any WaitForEdge loop.
|
||||||
|
func (p *pollEdge) Halt() error {
|
||||||
|
select {
|
||||||
|
// If a WaitForEdge was pending, it will be unblocked.
|
||||||
|
case p.die <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real implements gpio.RealPin.
|
||||||
|
func (p *pollEdge) Real() gpio.PinIO {
|
||||||
|
if r, ok := p.PinIO.(gpio.RealPin); ok {
|
||||||
|
return r.Real()
|
||||||
|
}
|
||||||
|
return p.PinIO
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ gpio.PinIO = &pollEdge{}
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
// 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 gpioutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/gpio"
|
||||||
|
"periph.io/x/periph/conn/gpio/gpiotest"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAssumption(t *testing.T) {
|
||||||
|
f := gpiotest.Pin{}
|
||||||
|
if f.In(gpio.PullNoChange, gpio.BothEdges) == nil {
|
||||||
|
t.Fatal("Using gpiotest.Pin in no edge support mode")
|
||||||
|
}
|
||||||
|
if PollEdge(&f, 20*physic.Hertz) == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_Short(t *testing.T) {
|
||||||
|
p := PollEdge(&gpiotest.Pin{}, physic.Hertz)
|
||||||
|
if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := p.Halt(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// timeout triggers.
|
||||||
|
if p.WaitForEdge(time.Nanosecond) {
|
||||||
|
t.Fatal("unexpected edge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_Halt(t *testing.T) {
|
||||||
|
f := pinWait{wait: make(chan struct{})}
|
||||||
|
p := PollEdge(&f, physic.Hertz)
|
||||||
|
go func() {
|
||||||
|
// Make sure the pin was read at least once, which means the code below is
|
||||||
|
// inside WaitForEdge().
|
||||||
|
<-f.wait
|
||||||
|
if err := p.Halt(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// p.die triggers.
|
||||||
|
if p.WaitForEdge(-1) {
|
||||||
|
t.Fatal("unexpected edge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_RisingEdge(t *testing.T) {
|
||||||
|
f := pinLevels{levels: []gpio.Level{gpio.High, gpio.Low, gpio.High}}
|
||||||
|
p := PollEdge(&f, physic.KiloHertz)
|
||||||
|
if err := p.In(gpio.PullNoChange, gpio.RisingEdge); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !p.WaitForEdge(-1) {
|
||||||
|
t.Fatal("expected edge")
|
||||||
|
}
|
||||||
|
if len(f.levels) != 0 {
|
||||||
|
t.Fatalf("unconsumed levels: %v", f.levels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_FallingEdge(t *testing.T) {
|
||||||
|
f := pinLevels{levels: []gpio.Level{gpio.Low, gpio.High, gpio.Low}}
|
||||||
|
p := PollEdge(&f, physic.KiloHertz)
|
||||||
|
if err := p.In(gpio.PullNoChange, gpio.FallingEdge); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !p.WaitForEdge(-1) {
|
||||||
|
t.Fatal("expected edge")
|
||||||
|
}
|
||||||
|
if len(f.levels) != 0 {
|
||||||
|
t.Fatalf("unconsumed levels: %v", f.levels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_BothEdges(t *testing.T) {
|
||||||
|
f := pinLevels{levels: []gpio.Level{gpio.High, gpio.Low}}
|
||||||
|
p := PollEdge(&f, physic.KiloHertz)
|
||||||
|
if err := p.In(gpio.PullNoChange, gpio.BothEdges); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !p.WaitForEdge(-1) {
|
||||||
|
t.Fatal("expected edge")
|
||||||
|
}
|
||||||
|
if len(f.levels) != 0 {
|
||||||
|
t.Fatal("unconsumed level")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_RealPin(t *testing.T) {
|
||||||
|
f := gpiotest.Pin{}
|
||||||
|
p := PollEdge(&f, physic.Hertz)
|
||||||
|
r, ok := p.(gpio.RealPin)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected gpio.RealPin")
|
||||||
|
}
|
||||||
|
a, ok := r.Real().(*gpiotest.Pin)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected gpiotest.Pin")
|
||||||
|
}
|
||||||
|
if a != &f {
|
||||||
|
t.Fatal("expected actual pin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollEdge_RealPin_Deep(t *testing.T) {
|
||||||
|
f := gpiotest.Pin{}
|
||||||
|
p := PollEdge(PollEdge(&f, physic.Hertz), physic.Hertz)
|
||||||
|
r, ok := p.(gpio.RealPin)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected gpio.RealPin")
|
||||||
|
}
|
||||||
|
a, ok := r.Real().(*gpiotest.Pin)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected gpiotest.Pin")
|
||||||
|
}
|
||||||
|
if a != &f {
|
||||||
|
t.Fatal("expected actual pin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
type pinLevels struct {
|
||||||
|
gpiotest.Pin
|
||||||
|
mu sync.Mutex
|
||||||
|
levels []gpio.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pinLevels) Read() gpio.Level {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
l := p.levels[0]
|
||||||
|
p.levels = p.levels[1:]
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type pinWait struct {
|
||||||
|
gpiotest.Pin
|
||||||
|
wait chan struct{}
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pinWait) Read() gpio.Level {
|
||||||
|
p.once.Do(func() {
|
||||||
|
p.wait <- struct{}{}
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue