From 0fa1d1b61c8e4d9d4bfb9934fe1a9d7dccd16ec2 Mon Sep 17 00:00:00 2001 From: SjB Date: Sun, 31 Aug 2014 00:39:41 -0400 Subject: [PATCH] gpio: adding interrupt this is inspired by Dave Cheney's gpio library and his work on EPOLL --- gpio.go | 22 +++++++ host/generic/digitalpin.go | 26 ++++++++ host/generic/interrupt.go | 126 +++++++++++++++++++++++++++++++++++++ samples/gpiointerrupt.go | 42 +++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 host/generic/interrupt.go create mode 100644 samples/gpiointerrupt.go diff --git a/gpio.go b/gpio.go index 7aae699..17badd2 100644 --- a/gpio.go +++ b/gpio.go @@ -7,6 +7,9 @@ import "time" // The Direction type indicates the direction of a GPIO pin. type Direction int +// The Edge trigger for the GPIO Interrupt +type Edge string + const ( // In represents read mode. In Direction = iota @@ -23,8 +26,27 @@ const ( High ) +const ( + EdgeNone Edge = "none" + EdgeRising Edge = "rising" + EdgeFalling Edge = "falling" + EdgeBoth Edge = "both" +) + +// InterruptPin implements access to a Interruptable capable GPIO pin. +type InterruptPin interface { + + // Start watching this pin for interrupt + Watch(edge Edge, handler func(DigitalPin)) error + + // Stop watching this pin for interrupt + StopWatching() error +} + // DigitalPin implements access to a digital IO capable GPIO pin. type DigitalPin interface { + InterruptPin + // N returns the logical GPIO number. N() int diff --git a/host/generic/digitalpin.go b/host/generic/digitalpin.go index fabdeb5..80a81ea 100644 --- a/host/generic/digitalpin.go +++ b/host/generic/digitalpin.go @@ -228,6 +228,10 @@ func (p *digitalPin) PullDown() error { } func (p *digitalPin) Close() error { + if err := p.StopWatching(); err != nil { + return err + } + if err := p.drv.Unregister(p.id); err != nil { return err } @@ -253,3 +257,25 @@ func (p *digitalPin) Close() error { return nil } + +func (p *digitalPin) setEdge(edge embd.Edge) error { + file, err := p.openFile(path.Join(p.basePath(), "edge")) + if err != nil { + return err + } + defer file.Close() + + _, err = file.Write([]byte(edge)) + return err +} + +func (p *digitalPin) Watch(edge embd.Edge, handler func(embd.DigitalPin)) error { + if err := p.setEdge(edge); err != nil { + return err + } + return registerInterrupt(p, handler) +} + +func (p *digitalPin) StopWatching() error { + return unregisterInterrupt(p) +} diff --git a/host/generic/interrupt.go b/host/generic/interrupt.go new file mode 100644 index 0000000..e2ebfa8 --- /dev/null +++ b/host/generic/interrupt.go @@ -0,0 +1,126 @@ +// Generic Interrupt Pins. + +package generic + +import ( + "errors" + "fmt" + "sync" + "syscall" + + "github.com/kidoman/embd" +) + +const ( + MaxGPIOInterrupt = 64 +) + +var ErrorPinAlreadyRegistered = errors.New("pin interrupt already registered") + +type interrupt struct { + pin embd.DigitalPin + initialTrigger bool + handler func(embd.DigitalPin) +} + +func (i *interrupt) Signal() { + if !i.initialTrigger { + i.initialTrigger = true + return + } + i.handler(i.pin) +} + +type ePollListener struct { + epollFd int + interruptablePins map[int]*interrupt + mu sync.Mutex +} + +var ePollListenerInstance *ePollListener + +func getEPollListenerInstance() *ePollListener { + if ePollListenerInstance == nil { + ePollListenerInstance = initEPollListener() + } + return ePollListenerInstance +} + +func initEPollListener() *ePollListener { + epollFd, err := syscall.EpollCreate1(0) + if err != nil { + panic(fmt.Sprintf("Unable to create epoll: %v", err)) + } + listener := &ePollListener{epollFd: epollFd, interruptablePins: make(map[int]*interrupt)} + + go func() { + var epollEvents [MaxGPIOInterrupt]syscall.EpollEvent + + for { + n, err := syscall.EpollWait(listener.epollFd, epollEvents[:], -1) + if err != nil { + panic(fmt.Sprintf("EpollWait error: %v", err)) + } + for i := 0; i < n; i++ { + if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok { + irq.Signal() + } + } + } + }() + return listener +} + +func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) error { + l := getEPollListenerInstance() + + pinFd := int(pin.val.Fd()) + + l.mu.Lock() + defer l.mu.Unlock() + + if _, ok := l.interruptablePins[pinFd]; ok { + return ErrorPinAlreadyRegistered + } + + var event syscall.EpollEvent + event.Events = syscall.EPOLLIN | (syscall.EPOLLET & 0xffffffff) | syscall.EPOLLPRI + + if err := syscall.SetNonblock(pinFd, true); err != nil { + return err + } + + event.Fd = int32(pinFd) + + if err := syscall.EpollCtl(l.epollFd, syscall.EPOLL_CTL_ADD, pinFd, &event); err != nil { + return err + } + + l.interruptablePins[pinFd] = &interrupt{pin: pin, handler: handler} + + return nil +} + +func unregisterInterrupt(pin *digitalPin) error { + l := getEPollListenerInstance() + + pinFd := int(pin.val.Fd()) + + l.mu.Lock() + defer l.mu.Unlock() + + if _, ok := l.interruptablePins[pinFd]; !ok { + return nil + } + + if err := syscall.EpollCtl(l.epollFd, syscall.EPOLL_CTL_DEL, pinFd, nil); err != nil { + return err + } + + if err := syscall.SetNonblock(pinFd, false); err != nil { + return err + } + + delete(l.interruptablePins, pinFd) + return nil +} diff --git a/samples/gpiointerrupt.go b/samples/gpiointerrupt.go new file mode 100644 index 0000000..9b6ccc9 --- /dev/null +++ b/samples/gpiointerrupt.go @@ -0,0 +1,42 @@ +// +build ignore + +package main + +import ( + "flag" + "fmt" + + "github.com/kidoman/embd" + + _ "github.com/kidoman/embd/host/all" +) + +func main() { + flag.Parse() + + if err := embd.InitGPIO(); err != nil { + panic(err) + } + defer embd.CloseGPIO() + + btn, err := embd.NewDigitalPin(10) + if err != nil { + panic(err) + } + defer btn.Close() + + if err := btn.SetDirection(embd.In); err != nil { + panic(err) + } + btn.ActiveLow(false) + + quit := make(chan interface{}) + err = btn.Watch(embd.EdgeFalling, func(btn embd.DigitalPin) { + quit <- btn + }) + if err != nil { + panic(err) + } + + fmt.Printf("Button %v was pressed.\n", <-quit) +}