kidoman / embd

Embedded Programming Framework in Go
http://embd.kidoman.io
MIT License
1.28k stars 156 forks source link

write: /sys/class/gpio/export: device or resource busy #30

Open captncraig opened 9 years ago

captncraig commented 9 years ago

I am trying to get simple gpio working on my rpi model B.

From the gopath/src/github.com/kidoman/embd/samples directory I run go build gpio.go and sudo ./gpio.

I get quick panic with panic: write: /sys/class/gpio/export: device or resource busy

Running latest raspbian image. I have built go from source on the device.

kidoman commented 9 years ago

Can you try with sudo ?

gmonnerat commented 9 years ago

// +build ignore
package main
import (
        "flag"
        "github.com/kidoman/embd"
        "time"
        _ "github.com/kidoman/embd/host/all"
)
func main() {
        flag.Parse()
        if err := embd.InitGPIO(); err != nil {
                panic(err)
        }
        defer embd.CloseGPIO()
        led, err := embd.NewDigitalPin(17)
        if err != nil {
                panic(err)
        }
        defer led.Close()
        if err := led.SetDirection(embd.Out); err != nil {
                panic(err)
        }
        for i := 0; i < 10; i++ {
                if err := led.Write(embd.High); err != nil {
                        panic(err)
                }
                time.Sleep(1 * time.Second)
                if err := led.Write(embd.Low); err != nil {
                        panic(err)
                }
        }
        if err := led.SetDirection(embd.In); err != nil {
                panic(err)
        }
}

I can reproduce that with this code and before finish, I do Ctrl + c.

When I run again:


pi@raspberrypi ~/rpi/blink_led $ sudo ./main
panic: write /sys/class/gpio/export: device or resource busy
goroutine 1 [running]:
main.main()
        /home/pi/rpi/blink_led/main.go:27 +0x1a8
goroutine 17 [syscall, locked to thread]:
runtime.goexit()
        /home/pi/go/src/runtime/asm_arm.s:1014 +0x4
goroutine 5 [chan receive]:
github.com/golang/glog.(*loggingT).flushDaemon(0x1ec590)
        /home/pi/gocode/src/github.com/golang/glog/glog.go:879 +0x60
created by github.com/golang/glog.init.1
        /home/pi/gocode/src/github.com/golang/glog/glog.go:410 +0x2cc
goroutine 8 [runnable]:
github.com/kidoman/embd/host/generic.initEpollListener.func1(0x1040e5f0)
        /home/pi/gocode/src/github.com/kidoman/embd/host/generic/interrupt.go:56
created by github.com/kidoman/embd/host/generic.initEpollListener
        /home/pi/gocode/src/github.com/kidoman/embd/host/generic/interrupt.go:70 +0x1cc
captncraig commented 9 years ago

somehow restarting fixed my problem. I was also mucking about in raspi-config too, so not sure if anything there helped.

I think if I exit without closing down gpio it can leave stuff around that is hard to clean up. I can catch interrupts and execute a close before exiting, but I am not sure it is a good idea for anything outside of main to do this.

akofoed commented 9 years ago

Two options: 1) root@raspberrypi:~# echo 10 > /sys/class/gpio/unexport

2) You have to handle the signal in a goroutine and then switch on it in a loop, making the program exit cleanly and run it's defer's.

package main

import (
    "os"
    "os/signal"
    "syscall"
    "fmt"
    //"time"

    "github.com/kidoman/embd"
    _ "github.com/kidoman/embd/host/rpi" // This loads the RPi driver
)

// For cleanup...
func cleanup() <- chan bool {
    exitProg := make(chan bool)
    //exitProg <- false
    signalChannel := make(chan os.Signal, 2)
    signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
    fmt.Println("Setting up SIG")
    go func() {
        sig := <-signalChannel
        switch sig {
            case os.Interrupt:
                //handle SIGINT
                fmt.Println("\nSIGINT caught")
            case syscall.SIGTERM:
                //handle SIGTERM
                fmt.Println("\nSIGTERM caught")
        }
        exitProg <- true
        fmt.Println("Received an interrupt...\n")

    }()
    return exitProg
}

func main() {
    // Main program
    if err := embd.InitGPIO(); err != nil {
        fmt.Println("Error during GPIO init!")
        panic(err)
    }
    defer embd.CloseGPIO()

    // Setup LED
    pinNo := 10
    led, err := embd.NewDigitalPin(pinNo)
    if err != nil {
        fmt.Println("Error setting NewDigitalPin (10)!")
        panic(err)
    }
    defer led.Close()

    if err := led.SetDirection(embd.Out); err != nil {
        fmt.Println("Error in SetDirection (10)!")
        panic(err)
    }

    // Setup BTN
    btn, err := embd.NewDigitalPin(22)
    if err != nil {
        fmt.Println("Error in NewDigitalPin (22)!")
        panic(err)
    }
    defer btn.Close()

    if err := btn.SetDirection(embd.In); err != nil {
        fmt.Println("Error in SetDirection (22)!")
        panic(err)
    }
    ///btn.ActiveLow(false)
    // Listen for ntn press
    btnEvent := make(chan int)
    err = btn.Watch(embd.EdgeBoth, func(btn embd.DigitalPin) {
        btnVal, _ := btn.Read()
        //fmt.Println(btnVal)
        btnEvent <- btnVal
    })
    if err != nil {
        panic(err)
    }

    exitProg := cleanup()

    // Run forever
    fmt.Println("BTNLED demo...")

    doExit := false
    btnVal := 0

    for !doExit {
        select {
        case doExit = <-exitProg:

        case btnVal = <-btnEvent:
            //fmt.Println(e)
            if btnVal == embd.High {
                if err := led.Write(embd.High); err != nil {
                    fmt.Println("Error writing High to GPIO", pinNo, "!")
                    panic(err)
                }
            } else {
                if err := led.Write(embd.Low); err != nil {
                    fmt.Println("Error writing Low to GPIO", pinNo, "!")
                    //panic(err)
                }
            }
        }
    }

    // Close and cleanup
    if err := led.SetDirection(embd.In); err != nil {
        panic(err)
    }
}
bgentry commented 9 years ago

I ran into this same issue on a Raspberry Pi 2 Model B. Following @akofoed's instructions, I was able to get the program to run multiple times in a row without any more errors:

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/kidoman/embd"
    _ "github.com/kidoman/embd/host/rpi" // This loads the RPi driver
    "golang.org/x/net/context"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    sigch := make(chan os.Signal, 2)
    signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)
    go handleSignals(sigch, ctx, cancel)

    log.Println("hello world!")
    embd.InitGPIO()
    defer embd.CloseGPIO()

    pin, err := embd.NewDigitalPin("GPIO_5")
    if err != nil {
        log.Fatal("opening pin:", err)
    }
    defer resetPin(pin)

    if err = pin.SetDirection(embd.Out); err != nil {
        log.Fatal("setting pin direction:", err)
    }

    nextValHigh := true
    for {
        select {
        case <-ctx.Done():
            return
        case <-time.After(500 * time.Millisecond):
            if nextValHigh {
                pin.Write(embd.High)
            } else {
                pin.Write(embd.Low)
            }
            nextValHigh = !nextValHigh
        }
    }
}

func handleSignals(sigch <-chan os.Signal, ctx context.Context, cancel context.CancelFunc) {
    select {
    case <-ctx.Done():
    case sig := <-sigch:
        switch sig {
        case os.Interrupt:
            log.Println("SIGINT")
        case syscall.SIGTERM:
            log.Println("SIGTERM")
        }
        cancel()
    }
}

func resetPin(pin embd.DigitalPin) {
    if err := pin.SetDirection(embd.In); err != nil {
        log.Fatal("resetting pin:", err)
    }
    pin.Close()
}

What do you think about a PR to add this cleanup to the Raspberry Pi examples (including the readme)? That, or this should be handled automatically by the cleanup/Close() functions for either the GPIO or the pin. Are there cases where people don't want those pins to be cleaned up after the program exits?