├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── .gitignore └── gppiio │ ├── detect.go │ ├── get.go │ ├── gppiio.go │ ├── mode.go │ ├── mon.go │ ├── pull.go │ ├── set.go │ └── version.go ├── dio.go ├── dio_bmark_test.go ├── dio_test.go ├── example ├── blinker │ ├── .gitignore │ └── blinker.go ├── spi │ ├── .gitignore │ ├── adc0832 │ │ └── adc0832.go │ ├── mcp3008 │ │ └── mcp3008.go │ └── mcp3208 │ │ └── mcp3208.go └── watcher │ ├── .gitignore │ └── watcher.go ├── go.mod ├── go.sum ├── interrupt.go ├── interrupt_test.go ├── mem.go ├── mem_test.go └── spi ├── adc0832 └── adc0832.go ├── mcp3w0c └── mcp3w0c.go └── spi.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This workflow will build a golang project 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 7 | 8 | name: Go 9 | 10 | on: 11 | push: 12 | branches: [ "master" ] 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Set up Go 24 | uses: actions/setup-go@v3 25 | with: 26 | go-version: 1.19 27 | 28 | - name: Build 29 | run: go build -v ./... 30 | 31 | # - name: Test 32 | # run: go test -v ./... 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gpio.test 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Stian Eikeland 4 | Copyright (c) 2013 Karan Misra 5 | Copyright (c) 2017 Kent Gibson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOENV=GOOS=linux GOARCH=arm GOARM=6 2 | GOCMD=go 3 | GOBUILD=$(GOENV) $(GOCMD) build 4 | GOCLEAN=$(GOCMD) clean 5 | 6 | VERSION ?= $(shell git describe --tags --always --dirty 2> /dev/null ) 7 | LDFLAGS=-ldflags "-X=main.version=$(VERSION)" 8 | 9 | spis=$(patsubst %.go, %, $(wildcard example/spi/*/*.go)) 10 | examples=$(patsubst %.go, %, $(wildcard example/*/*.go)) 11 | bins= $(spis) $(examples) 12 | 13 | all: cmd/gppiio/gppiio $(bins) 14 | 15 | cmd/gppiio/gppiio : $(wildcard cmd/gppiio/*.go) 16 | cd $(@D); \ 17 | $(GOBUILD) $(LDFLAGS) 18 | 19 | $(bins) : % : %.go 20 | cd $(@D); \ 21 | $(GOBUILD) 22 | 23 | clean: 24 | $(GOCLEAN) ./... 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpio 2 | 3 | [![Build Status](https://img.shields.io/github/actions/workflow/status/warthog618/gpio/go.yml?logo=github&branch=master)](https://github.com/warthog618/gpio/actions/workflows/go.yml) 4 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/warthog618/gpio) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/warthog618/gpio)](https://goreportcard.com/report/github.com/warthog618/gpio) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/warthog618/gpio/blob/master/LICENSE) 7 | 8 | GPIO library for the Raspberry Pi. 9 | 10 | **gpio** is a Go library for accessing 11 | [GPIO](http://elinux.org/Rpi_Low-level_peripherals) pins on the [Raspberry 12 | Pi](https://en.wikipedia.org/wiki/Raspberry_Pi). 13 | 14 | The library was inspired by and borrows from 15 | [go-rpio](https://github.com/stianeikeland/go-rpio), which is fast but lacks 16 | interrupt support, and [embd](https://github.com/kidoman/embd), which supports 17 | interrupts, but uses sysfs for read/write and has a far broader scope than I 18 | require. 19 | 20 | This library does not support the Pi5, and never will. 21 | 22 | ## :warning: Deprecation Warning :warning: 23 | 24 | This library relies on the sysfs GPIO interface which is deprecated in the Linux 25 | kernel and is due for removal during 2020. Without sysfs, the watch/interrupt 26 | features of this library will no longer work. 27 | 28 | The sysfs GPIO interface has been superceded in the Linux kernel by the GPIO 29 | character device. The newer API is sufficiently different that reworking this 30 | library to use that API is not practical. Instead I have written a new library, 31 | [**gpiocdev**](https://github.com/warthog618/go-gpiocdev), that provides the same 32 | functionality as this library but using the GPIO character device. 33 | 34 | There are a couple of downsides to switching to **gpiocdev**: 35 | 36 | - The API is quite different, mostly due to the differences in the underlying 37 | APIs, so it is not a plugin replacement - you will need to do some code 38 | rework. 39 | - It is also slightly slower for both read and write as all hardware access is 40 | now via kernel calls rather than via hardware registers. However, if that is 41 | an issue for you then you probably should be writing a kernel device driver 42 | for your use case rather than trying to do something in userspace. 43 | - It requires a recent Linux kernel for full functionality. While the GPIO 44 | character device has been around since v4.8, the bias and reconfiguration 45 | capabilities required to provide functional equivalence to **gpio** were only 46 | added in v5.5. 47 | 48 | There are several benefits in the switch: 49 | 50 | - **gpiocdev** is not Raspberry Pi specific, but will work on any platform where 51 | the GPIO chip is supported by the Linux kernel, so code using **gpiocdev** is 52 | more portable. 53 | - **gpio** writes directly to hardware registers so it can conflict with other 54 | kernel drivers. The **gpiocdev** accesses the hardware internally using the same 55 | interfaces as other kernel drivers and so should play nice with them. 56 | - **gpiocdev** supports Linux GPIO line labels, so you can find your line by name 57 | (assuming it has been named by device-tree). 58 | - and of course, it will continue to work beyond 2020. 59 | 60 | I've already ported all my projects that were using **gpio** to **gpiocdev** and 61 | strongly suggest that you do the same. 62 | 63 | ## Features 64 | 65 | Supports the following functionality: 66 | 67 | - Pin Mode/Direction (Input / Output) 68 | - Write (High / Low) 69 | - Read (High / Low) 70 | - Pullups (Up / Down / None) 71 | - Watches/Interrupts (Rising/Falling/Both) 72 | 73 | ## Usage 74 | 75 | ```go 76 | import "github.com/warthog618/gpio" 77 | ``` 78 | 79 | ### Library Initialization 80 | 81 | Open memory range for GPIO access in /dev/gpiomem 82 | 83 | ```go 84 | err := gpio.Open() 85 | ``` 86 | 87 | Cleanup when done 88 | 89 | ```go 90 | gpio.Close() 91 | ``` 92 | 93 | ### Pin Initialization 94 | 95 | A Pin object is constructed using the *NewPin* function. The Pin object is then 96 | used for all operations on that pin. Note that the pin number refers to the BCM 97 | GPIO pin, not the physical pin on the Raspberry Pi header. Pin 4 here is exposed 98 | on the pin header as physical pin 7 (J8 7). Mappings are provided from Raspberry 99 | Pi J8 header pin names to BCM GPIO numbers, using the form J8pX. 100 | 101 | ```go 102 | pin := gpio.NewPin(4) 103 | pin := gpio.NewPin(gpio.J8p7) // Using Raspberry Pi J8 mapping. 104 | ``` 105 | 106 | There is no need to cleanup a pin if you no longer need to use it, unless it has 107 | Watches set in which case you should remove the *Watch*. 108 | 109 | ### Mode 110 | 111 | The pin mode controls whether the pin is an input or output. The existing mode 112 | can be read back. 113 | 114 | ```go 115 | mode := pin.Mode() 116 | pin.Output() // Set mode to Output 117 | pin.Input() // Set mode to Input 118 | pin.SetMode(gpio.Output) // Alternate syntax 119 | ``` 120 | 121 | To prevent output glitches, the pin level can be set using *High*/*Low*/*Write* 122 | before the pin is set to Output. 123 | 124 | ### Input 125 | 126 | ```go 127 | res := pin.Read() // Read state from pin (High / Low) 128 | ``` 129 | 130 | ### Output 131 | 132 | ```go 133 | pin.High() // Set pin High 134 | pin.Low() // Set pin Low 135 | pin.Toggle() // Toggle pin (Low -> High -> Low) 136 | 137 | pin.Write(gpio.High) // Alternate syntax 138 | ``` 139 | 140 | Also see example [example/blinker/blinker.go](example/blinker/blinker.go) 141 | 142 | ### Pullups 143 | 144 | Pull up state can be set using: 145 | 146 | ```go 147 | pin.PullUp() 148 | pin.PullDown() 149 | pin.PullNone() 150 | 151 | pin.SetPull(gpio.PullUp) // Alternate syntax 152 | ``` 153 | 154 | Unlike the Mode, the pull up state cannot be read back from hardware, so there is no *Pull* function. 155 | 156 | ### Watches 157 | 158 | The state of an input pin can be watched and trigger calls to handler functions. 159 | 160 | The watch can be on rising or falling edges, or both. 161 | 162 | The handler function is passed the triggering pin. 163 | 164 | ```go 165 | func handler(*Pin) { 166 | // handle change in pin value 167 | } 168 | pin.Watch(gpio.EdgeFalling,handler) // Call handler when pin changes from High to Low. 169 | 170 | pin.Watch(gpio.EdgeRising,handler) // Call handler when pin changes from Low to High. 171 | 172 | pin.Watch(gpio.EdgeBoth,handler) // Call handler when pin changes 173 | ``` 174 | 175 | A watch can be removed using the *Unwatch* function. 176 | 177 | ```go 178 | pin.Unwatch() 179 | ``` 180 | 181 | ## Tools 182 | 183 | A command line utility, **gppiio**, is provided to allow manual and scripted 184 | control of GPIO pins: 185 | 186 | ```sh 187 | $ ./gppiio 188 | gppiio is a utility to control Raspberry Pi GPIO pins 189 | 190 | Usage: 191 | gppiio [flags] 192 | gppiio [command] 193 | 194 | Available Commands: 195 | detect Identify the GPIO chip 196 | get Read the level of a pin or pins 197 | help Help about any command 198 | mode Read the functional mode of a pin or pins 199 | mon Monitor the level of a pin or pins 200 | pull Set the pull direction of a pin or pins 201 | set Set the level of a pin or pins 202 | version Display the version 203 | 204 | Flags: 205 | -h, --help help for gppiio 206 | 207 | Use "gppiio [command] --help" for more information about a command. 208 | ``` 209 | 210 | ## Examples 211 | 212 | Refer to the [examples](example) for more examples of usage. 213 | 214 | Examples can be cross-compiled from other platforms using 215 | 216 | ```sh 217 | GOOS=linux GOARCH=arm GOARM=6 go build 218 | ``` 219 | 220 | ## Tests 221 | 222 | The library is fully tested, other than some error cases that are difficult to test. 223 | 224 | The tests are intended to be run on a Raspberry Pi with J8 pin 7 floating and 225 | with pins 15 and 16 tied together, possibly using a jumper across the header. 226 | The tests set J8 pin 16 to an output so **DO NOT** run them on hardware where 227 | that pin is being externally driven. 228 | 229 | Tests have been run successfully on Raspberry Pi B (Rev 1 and Rev 2), B+, Pi2 B, 230 | Pi4 B, and Pi Zero W. The library should also work on other Raspberry Pi 231 | variants, I just don't have any available to test. 232 | 233 | The tests can be cross-compiled from other platforms using 234 | 235 | ```sh 236 | GOOS=linux GOARCH=arm GOARM=6 go test -c 237 | ``` 238 | 239 | Later Pis can also use ARM7 (GOARM=7). 240 | 241 | ### Benchmarks 242 | 243 | The tests include benchmarks on reads and writes. Reading pin levels through sysfs is provided for comparison. 244 | 245 | These are the results from a Raspberry Pi Zero W built with Go 1.13: 246 | 247 | ```sh 248 | $ ./gpio.test -test.bench=.* 249 | goos: linux 250 | goarch: arm 251 | pkg: github.com/warthog618/gpio 252 | BenchmarkRead 9485052 124 ns/op 253 | BenchmarkWrite 18478959 58.8 ns/op 254 | BenchmarkToggle 16695492 72.4 ns/op 255 | BenchmarkInterruptLatency 2348 453248 ns/op 256 | BenchmarkSysfsRead 32983 31004 ns/op 257 | BenchmarkSysfsWrite 17192 69840 ns/op 258 | BenchmarkSysfsToggle 17341 62962 ns/op 259 | 260 | PASS 261 | ``` 262 | 263 | ## Prerequisites 264 | 265 | The library assumes Linux, and has been tested on Raspbian Jessie, Stretch and Buster. 266 | 267 | The library targets all models of the Raspberry Pi, upt to and including the Pi 268 | 4B. Note that the Raspberry Pi Model B Rev 1.0 has different pinouts, so the J8 269 | mappings are incorrect for that particular revision. 270 | 271 | This library utilizes /dev/gpiomem, which must be available to the current user. 272 | This is generally available in recent Raspian releases. 273 | 274 | The library also utilizes the sysfs GPIO to support interrupts on changes to 275 | input pin values. The sysfs is not used to access the pin values, as the 276 | gpiomem approach is orders of magnitude faster (refer to the benchmarks). 277 | -------------------------------------------------------------------------------- /cmd/.gitignore: -------------------------------------------------------------------------------- 1 | gppiio/gppiio 2 | -------------------------------------------------------------------------------- /cmd/gppiio/detect.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/spf13/cobra" 13 | "github.com/warthog618/gpio" 14 | ) 15 | 16 | func init() { 17 | rootCmd.AddCommand(detectCmd) 18 | } 19 | 20 | var detectCmd = &cobra.Command{ 21 | Use: "detect", 22 | Short: "Identify the GPIO chip", 23 | Args: cobra.NoArgs, 24 | RunE: detect, 25 | } 26 | 27 | func detect(cmd *cobra.Command, args []string) error { 28 | err := gpio.Open() 29 | if err != nil { 30 | return err 31 | } 32 | defer gpio.Close() 33 | switch gpio.Chip() { 34 | case gpio.BCM2835: 35 | fmt.Println("bcm2835") 36 | case gpio.BCM2711: 37 | fmt.Println("bcm2711") 38 | default: 39 | fmt.Println("unknown") 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/gppiio/get.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/spf13/cobra" 13 | "github.com/warthog618/gpio" 14 | ) 15 | 16 | func init() { 17 | getCmd.Flags().BoolVarP(&getOpts.All, "all", "a", false, "get the levels of all lines") 18 | getCmd.Flags().BoolVarP(&getOpts.ActiveLow, "active-low", "l", false, "treat the line level as active low") 19 | getCmd.Flags().BoolVarP(&getOpts.Short, "short", "s", false, "single line output format") 20 | getCmd.SetHelpTemplate(getCmd.HelpTemplate() + extendedGetHelp) 21 | rootCmd.AddCommand(getCmd) 22 | } 23 | 24 | var ( 25 | getCmd = &cobra.Command{ 26 | Use: "get ...", 27 | Short: "Read the level of a pin or pins", 28 | Example: " gppio get 23 J8p15", 29 | PreRunE: preget, 30 | RunE: get, 31 | } 32 | getOpts = struct { 33 | ActiveLow bool 34 | Short bool 35 | All bool 36 | }{} 37 | ) 38 | 39 | var extendedGetHelp = ` 40 | Pins: 41 | Pins may be identified by name (J8pXX) or number (0-26). 42 | 43 | Note that reading a pin forces it into input mode. 44 | ` 45 | 46 | func preget(cmd *cobra.Command, args []string) error { 47 | if !getOpts.All { 48 | return cobra.MinimumNArgs(1)(cmd, args) 49 | } 50 | return nil 51 | } 52 | 53 | func get(cmd *cobra.Command, args []string) (err error) { 54 | var oo []int 55 | if getOpts.All { 56 | if len(oo) == 0 { 57 | oo = make([]int, gpio.MaxGPIOPin) 58 | for i := 0; i < gpio.MaxGPIOPin; i++ { 59 | oo[i] = i 60 | } 61 | } 62 | } else { 63 | oo, err = parseOffsets(args) 64 | if err != nil { 65 | return err 66 | } 67 | } 68 | err = gpio.Open() 69 | if err != nil { 70 | return err 71 | } 72 | defer gpio.Close() 73 | vv := make([]gpio.Level, len(oo)) 74 | for i, o := range oo { 75 | pin := gpio.NewPin(o) 76 | pin.Input() 77 | v := pin.Read() 78 | if getOpts.ActiveLow { 79 | v = !v 80 | } 81 | vv[i] = v 82 | } 83 | if getOpts.Short { 84 | printValuesShort(oo, vv) 85 | 86 | } else { 87 | printValues(oo, vv) 88 | } 89 | return nil 90 | } 91 | 92 | func printValues(oo []int, vv []gpio.Level) { 93 | for i, o := range oo { 94 | fmt.Printf("pin %2d: %t\n", o, vv[i]) 95 | } 96 | } 97 | 98 | func printValuesShort(oo []int, vv []gpio.Level) { 99 | fmt.Printf("%d", level2Int(vv[0])) 100 | for _, v := range vv[1:] { 101 | fmt.Printf(" %d", level2Int(v)) 102 | } 103 | fmt.Println() 104 | 105 | } 106 | 107 | func parseOffsets(args []string) ([]int, error) { 108 | oo := []int(nil) 109 | for _, arg := range args { 110 | o, err := parseOffset(arg) 111 | if err != nil { 112 | return nil, err 113 | } 114 | oo = append(oo, int(o)) 115 | } 116 | return oo, nil 117 | } 118 | 119 | func level2Int(l gpio.Level) int { 120 | if l == gpio.Low { 121 | return 0 122 | } 123 | return 1 124 | } 125 | -------------------------------------------------------------------------------- /cmd/gppiio/gppiio.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "os" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/spf13/cobra" 16 | "github.com/warthog618/gpio" 17 | ) 18 | 19 | var rootCmd = &cobra.Command{ 20 | Use: "gppiio", 21 | Short: "gppiio is a utility to control Raspberry Pi GPIO pins", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | cmd.Help() 24 | }, 25 | } 26 | 27 | func main() { 28 | if err := rootCmd.Execute(); err != nil { 29 | fmt.Println(err) 30 | os.Exit(1) 31 | } 32 | } 33 | 34 | func logErr(cmd *cobra.Command, err error) { 35 | fmt.Fprintf(os.Stderr, "gppiio %s: %s\n", cmd.Name(), err) 36 | } 37 | 38 | var pinNames = map[string]int{ 39 | "J8P3": gpio.J8p3, 40 | "J8P03": gpio.J8p3, 41 | "J8P5": gpio.J8p5, 42 | "J8P05": gpio.J8p5, 43 | "J8P7": gpio.J8p7, 44 | "J8P07": gpio.J8p7, 45 | "J8P8": gpio.J8p8, 46 | "J8P08": gpio.J8p8, 47 | "J8P10": gpio.J8p10, 48 | "J8P11": gpio.J8p11, 49 | "J8P12": gpio.J8p12, 50 | "J8P13": gpio.J8p12, 51 | "J8P15": gpio.J8p15, 52 | "J8P16": gpio.J8p16, 53 | "J8P18": gpio.J8p18, 54 | "J8P19": gpio.J8p19, 55 | "J8P21": gpio.J8p21, 56 | "J8P22": gpio.J8p22, 57 | "J8P23": gpio.J8p23, 58 | "J8P24": gpio.J8p24, 59 | "J8P26": gpio.J8p26, 60 | "J8P27": gpio.J8p27, 61 | "J8P28": gpio.J8p28, 62 | "J8P29": gpio.J8p29, 63 | "J8P31": gpio.J8p31, 64 | "J8P32": gpio.J8p32, 65 | "J8P33": gpio.J8p33, 66 | "J8P35": gpio.J8p35, 67 | "J8P36": gpio.J8p36, 68 | "J8P37": gpio.J8p37, 69 | "J8P38": gpio.J8p38, 70 | "J8P40": gpio.J8p40, 71 | } 72 | 73 | func parseOffset(arg string) (int, error) { 74 | if o, ok := pinNames[strings.ToUpper(arg)]; ok { 75 | return o, nil 76 | } 77 | o, err := strconv.ParseUint(arg, 10, 64) 78 | if err != nil { 79 | return 0, fmt.Errorf("can't parse pin '%s'", arg) 80 | } 81 | if o >= gpio.MaxGPIOPin { 82 | return 0, fmt.Errorf("unknown pin '%d'", o) 83 | } 84 | return int(o), nil 85 | } 86 | -------------------------------------------------------------------------------- /cmd/gppiio/mode.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/spf13/cobra" 13 | "github.com/warthog618/gpio" 14 | ) 15 | 16 | func init() { 17 | modeCmd.Flags().BoolVarP(&modeOpts.All, "all", "a", false, "get all line modes") 18 | modeCmd.Flags().BoolVarP(&modeOpts.Short, "short", "s", false, "single line output format") 19 | rootCmd.AddCommand(modeCmd) 20 | } 21 | 22 | var ( 23 | modeCmd = &cobra.Command{ 24 | Use: "mode ...", 25 | Short: "Read the functional mode of a pin or pins", 26 | PreRunE: premode, 27 | RunE: mode, 28 | } 29 | modeOpts = struct { 30 | ActiveLow bool 31 | Short bool 32 | All bool 33 | }{} 34 | ) 35 | 36 | func premode(cmd *cobra.Command, args []string) error { 37 | if !modeOpts.All { 38 | return cobra.MinimumNArgs(1)(cmd, args) 39 | } 40 | return nil 41 | } 42 | 43 | func mode(cmd *cobra.Command, args []string) (err error) { 44 | var oo []int 45 | if modeOpts.All { 46 | if len(oo) == 0 { 47 | oo = make([]int, gpio.MaxGPIOPin) 48 | for i := 0; i < gpio.MaxGPIOPin; i++ { 49 | oo[i] = i 50 | } 51 | } 52 | } else { 53 | oo, err = parseOffsets(args) 54 | if err != nil { 55 | return err 56 | } 57 | } 58 | err = gpio.Open() 59 | if err != nil { 60 | return err 61 | } 62 | defer gpio.Close() 63 | mm := make([]gpio.Mode, len(oo)) 64 | for i, o := range oo { 65 | pin := gpio.NewPin(o) 66 | m := pin.Mode() 67 | mm[i] = m 68 | } 69 | if modeOpts.Short { 70 | printModesShort(oo, mm) 71 | 72 | } else { 73 | printModes(oo, mm) 74 | } 75 | return nil 76 | } 77 | 78 | func printModes(oo []int, mm []gpio.Mode) { 79 | for i, o := range oo { 80 | fmt.Printf("pin %2d: %s\n", o, modeNames[mm[i]]) 81 | } 82 | } 83 | 84 | func printModesShort(oo []int, mm []gpio.Mode) { 85 | fmt.Printf("%d", mm[0]) 86 | for _, m := range mm[1:] { 87 | fmt.Printf(" %d", m) 88 | } 89 | fmt.Println() 90 | } 91 | 92 | var modeNames = map[gpio.Mode]string{ 93 | gpio.Input: "input", 94 | gpio.Output: "output", 95 | gpio.Alt0: "alt0", 96 | gpio.Alt1: "alt1", 97 | gpio.Alt2: "alt2", 98 | gpio.Alt3: "alt3", 99 | gpio.Alt4: "alt4", 100 | gpio.Alt5: "alt5", 101 | } 102 | -------------------------------------------------------------------------------- /cmd/gppiio/mon.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | "time" 15 | 16 | "github.com/spf13/cobra" 17 | "github.com/warthog618/gpio" 18 | ) 19 | 20 | func init() { 21 | monCmd.Flags().BoolVarP(&monOpts.ActiveLow, "active-low", "l", false, "treat the line state as active low") 22 | monCmd.Flags().BoolVarP(&monOpts.FallingEdge, "falling-edge", "f", false, "detect only falling edge events") 23 | monCmd.Flags().BoolVarP(&monOpts.RisingEdge, "rising-edge", "r", false, "detect only rising edge events") 24 | monCmd.Flags().UintVarP(&monOpts.NumEvents, "num-events", "n", 0, "exit after n edges") 25 | monCmd.Flags().BoolVarP(&monOpts.Quiet, "quiet", "q", false, "don't display event details") 26 | monCmd.Flags().BoolVarP(&monOpts.Sync, "sync", "s", false, "display and count the initial sync event") 27 | monCmd.SetHelpTemplate(monCmd.HelpTemplate() + extendedMonHelp) 28 | rootCmd.AddCommand(monCmd) 29 | } 30 | 31 | var extendedMonHelp = ` 32 | By default both rising and falling edge events are detected and reported. 33 | ` 34 | 35 | var ( 36 | monCmd = &cobra.Command{ 37 | Use: "mon ...", 38 | Short: "Monitor the level of a pin or pins", 39 | Long: `Wait for edge events on GPIO pins and print them to standard output.`, 40 | Args: cobra.MinimumNArgs(1), 41 | RunE: mon, 42 | } 43 | monOpts = struct { 44 | ActiveLow bool 45 | RisingEdge bool 46 | FallingEdge bool 47 | Quiet bool 48 | Sync bool 49 | NumEvents uint 50 | }{} 51 | ) 52 | 53 | type event struct { 54 | Time time.Time 55 | Pin int 56 | Level gpio.Level 57 | } 58 | 59 | func mon(cmd *cobra.Command, args []string) error { 60 | if monOpts.RisingEdge && monOpts.FallingEdge { 61 | return errors.New("can't filter both falling-edge and rising-edge events") 62 | } 63 | oo, err := parseOffsets(args) 64 | if err != nil { 65 | return err 66 | } 67 | err = gpio.Open() 68 | if err != nil { 69 | return err 70 | } 71 | if monOpts.ActiveLow { 72 | monOpts.RisingEdge = !monOpts.RisingEdge 73 | monOpts.FallingEdge = !monOpts.FallingEdge 74 | } 75 | var edge gpio.Edge 76 | switch { 77 | case monOpts.RisingEdge == monOpts.FallingEdge: 78 | edge = gpio.EdgeBoth 79 | case monOpts.RisingEdge: 80 | edge = gpio.EdgeRising 81 | case monOpts.FallingEdge: 82 | edge = gpio.EdgeFalling 83 | } 84 | evtchan := make(chan event) 85 | eh := func(p *gpio.Pin) { 86 | evt := event{ 87 | Time: time.Now(), 88 | Pin: p.Pin(), 89 | Level: p.Read(), 90 | } 91 | evtchan <- evt 92 | } 93 | defer gpio.Close() 94 | for _, o := range oo { 95 | pin := gpio.NewPin(o) 96 | pin.Input() 97 | pin.Watch(edge, eh) 98 | } 99 | monWait(evtchan) 100 | return nil 101 | } 102 | 103 | func monWait(evtchan <-chan event) { 104 | sigdone := make(chan os.Signal, 1) 105 | signal.Notify(sigdone, os.Interrupt, os.Kill) 106 | defer signal.Stop(sigdone) 107 | count := uint(0) 108 | pinSynced := make(map[int]bool) 109 | for { 110 | select { 111 | case evt := <-evtchan: 112 | level := evt.Level 113 | if monOpts.ActiveLow { 114 | level = !level 115 | } 116 | edge := "rising" 117 | if level == gpio.Low { 118 | edge = "falling" 119 | } 120 | if monOpts.Sync || pinSynced[evt.Pin] { 121 | if !monOpts.Quiet { 122 | fmt.Printf("event:%3d %-7s %s\n", evt.Pin, edge, evt.Time.Format(time.RFC3339Nano)) 123 | } 124 | count++ 125 | if monOpts.NumEvents > 0 && count >= monOpts.NumEvents { 126 | return 127 | } 128 | } 129 | pinSynced[evt.Pin] = true 130 | case <-sigdone: 131 | return 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /cmd/gppiio/pull.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "errors" 11 | 12 | "github.com/spf13/cobra" 13 | "github.com/warthog618/gpio" 14 | ) 15 | 16 | func init() { 17 | pullCmd.Flags().BoolVarP(&pullOpts.All, "all", "a", false, "set the pull of all lines") 18 | pullCmd.Flags().BoolVarP(&pullOpts.Up, "up", "u", false, "pull the line up") 19 | pullCmd.Flags().BoolVarP(&pullOpts.Down, "down", "d", false, "pull the line down") 20 | pullCmd.Flags().BoolVarP(&pullOpts.None, "none", "n", false, "disable pull on the line") 21 | pullCmd.SetHelpTemplate(pullCmd.HelpTemplate() + extendedSetHelp) 22 | rootCmd.AddCommand(pullCmd) 23 | } 24 | 25 | var ( 26 | pullCmd = &cobra.Command{ 27 | Use: "pull ...", 28 | Short: "Set the pull direction of a pin or pins", 29 | RunE: pull, 30 | Example: " gppio pull -u J8p15 J8P7", 31 | } 32 | pullOpts = struct { 33 | All bool 34 | Up bool 35 | Down bool 36 | None bool 37 | }{} 38 | ) 39 | 40 | var extendedPullHelp = ` 41 | Pins: 42 | Pins may be identified by name (J8pXX) or number (0-26). 43 | 44 | ` 45 | 46 | func prepull(cmd *cobra.Command, args []string) error { 47 | count := 0 48 | if pullOpts.Up { 49 | count++ 50 | } 51 | if pullOpts.Down { 52 | count++ 53 | } 54 | if pullOpts.None { 55 | count++ 56 | } 57 | if count == 0 { 58 | return errors.New("must specify pull level [-u|-d|-n]") 59 | } 60 | if count > 1 { 61 | return errors.New("must specify only one pull level") 62 | } 63 | if !getOpts.All { 64 | return cobra.MinimumNArgs(1)(cmd, args) 65 | } 66 | return nil 67 | } 68 | 69 | func pull(cmd *cobra.Command, args []string) (err error) { 70 | oo := []int(nil) 71 | if getOpts.All { 72 | if len(oo) == 0 { 73 | oo = make([]int, gpio.MaxGPIOPin) 74 | for i := 0; i < gpio.MaxGPIOPin; i++ { 75 | oo[i] = i 76 | } 77 | } 78 | } else { 79 | oo, err = parseOffsets(args) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | err = gpio.Open() 85 | if err != nil { 86 | return err 87 | } 88 | defer gpio.Close() 89 | var p gpio.Pull 90 | switch { 91 | case pullOpts.Up: 92 | p = gpio.PullUp 93 | case pullOpts.Down: 94 | p = gpio.PullDown 95 | case pullOpts.None: 96 | p = gpio.PullNone 97 | } 98 | for _, o := range oo { 99 | pin := gpio.NewPin(o) 100 | pin.SetPull(p) 101 | } 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /cmd/gppiio/set.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "strings" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/warthog618/gpio" 15 | ) 16 | 17 | func init() { 18 | setCmd.Flags().BoolVarP(&setOpts.ActiveLow, "active-low", "l", false, "treat the line level as active low") 19 | setCmd.SetHelpTemplate(setCmd.HelpTemplate() + extendedSetHelp) 20 | rootCmd.AddCommand(setCmd) 21 | } 22 | 23 | var ( 24 | setCmd = &cobra.Command{ 25 | Use: "set =...", 26 | Short: "Set the level of a pin or pins", 27 | Args: cobra.MinimumNArgs(1), 28 | RunE: set, 29 | Example: " gppio set J8p15=high J8P7=0", 30 | } 31 | setOpts = struct { 32 | ActiveLow bool 33 | }{} 34 | ) 35 | 36 | var extendedSetHelp = ` 37 | Pins: 38 | Pins may be identified by name (J8pXX) or number (0-26). 39 | 40 | Levels: 41 | Levels may be [high|hi|true|1|low|lo|false|0] and are case insensitive. 42 | 43 | Note that setting a pin forces it into output mode. 44 | ` 45 | 46 | func set(cmd *cobra.Command, args []string) error { 47 | ll := []int(nil) 48 | vv := []gpio.Level(nil) 49 | for _, arg := range args { 50 | o, v, err := parseLineLevel(arg) 51 | if err != nil { 52 | return err 53 | } 54 | ll = append(ll, o) 55 | vv = append(vv, v) 56 | } 57 | err := gpio.Open() 58 | if err != nil { 59 | return err 60 | } 61 | defer gpio.Close() 62 | for i, v := range vv { 63 | pin := gpio.NewPin(ll[i]) 64 | if getOpts.ActiveLow { 65 | v = !v 66 | } 67 | pin.Output() 68 | pin.Write(v) 69 | } 70 | return nil 71 | } 72 | 73 | func parseLineLevel(arg string) (int, gpio.Level, error) { 74 | aa := strings.Split(arg, "=") 75 | if len(aa) != 2 { 76 | return 0, gpio.Low, fmt.Errorf("invalid pin<->level mapping: %s", arg) 77 | } 78 | o, err := parseOffset(aa[0]) 79 | if err != nil { 80 | return 0, gpio.Low, err 81 | } 82 | v, err := parseLevel(aa[1]) 83 | if err != nil { 84 | return 0, gpio.Low, err 85 | } 86 | return int(o), v, nil 87 | } 88 | 89 | func parseLevel(arg string) (gpio.Level, error) { 90 | if l, ok := levelNames[strings.ToLower(arg)]; ok { 91 | return l, nil 92 | } 93 | return gpio.Low, fmt.Errorf("can't parse level '%s'", arg) 94 | } 95 | 96 | var levelNames = map[string]gpio.Level{ 97 | "high": gpio.High, 98 | "hi": gpio.High, 99 | "true": gpio.High, 100 | "1": gpio.High, 101 | "low": gpio.Low, 102 | "lo": gpio.Low, 103 | "false": gpio.Low, 104 | "0": gpio.Low, 105 | } 106 | -------------------------------------------------------------------------------- /cmd/gppiio/version.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2019 Kent Gibson . 4 | 5 | // +build linux 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var version = "undefined" 17 | 18 | func init() { 19 | rootCmd.AddCommand(versionCmd) 20 | } 21 | 22 | var versionCmd = &cobra.Command{ 23 | Use: "version", 24 | Short: "Display the version", 25 | Long: `All software has versions. This is gppiio's`, 26 | Args: cobra.NoArgs, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | fmt.Printf("%s (gppiio) %s\n", os.Args[0], version) 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /dio.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Package gpio provides GPIO access on the Raspberry Pi (rev 2 and later, up to but not including Pi5). 7 | // 8 | // Supports simple operations such as: 9 | // - Pin mode/direction (input/output) 10 | // - Pin write (high/low) 11 | // - Pin read (high/low) 12 | // - Pull up/down/off 13 | // 14 | // The package intentionally does not support: 15 | // - the obsoleted rev 1 PCB (no longer worth the effort) 16 | // - active low (to prevent confusion this package reflects only the actual hardware levels) 17 | // 18 | // Example of use: 19 | // 20 | // gpio.Open() 21 | // defer gpio.Close() 22 | // 23 | // pin := gpio.NewPin(gpio.J8p7) 24 | // pin.Low() 25 | // pin.Output() 26 | // 27 | // for { 28 | // pin.Toggle() 29 | // time.Sleep(time.Second) 30 | // } 31 | // 32 | // The library uses the raw BCM2835 pin numbers, not the ports as they are mapped 33 | // on the J8 output pins for the Raspberry Pi. 34 | // A mapping from J8 to BCM is provided for those wanting to use the J8 numbering. 35 | // 36 | // See the spec for full details of the BCM2835 controller: 37 | // http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf 38 | package gpio 39 | 40 | import ( 41 | "time" 42 | ) 43 | 44 | // Pin represents a single GPIO pin. 45 | type Pin struct { 46 | // Immutable fields 47 | pin int 48 | fsel int 49 | levelReg int 50 | clearReg int 51 | setReg int 52 | pullReg2711 int 53 | bank int 54 | mask uint32 55 | // Mutable fields 56 | shadow Level 57 | } 58 | 59 | // Level represents the high (true) or low (false) level of a Pin. 60 | type Level bool 61 | 62 | // Mode defines the IO mode of a Pin. 63 | type Mode int 64 | 65 | // Pull defines the pull up/down state of a Pin. 66 | type Pull int 67 | 68 | const ( 69 | memLength = 4096 70 | 71 | modeMask uint32 = 7 // pin mode is 3 bits wide 72 | pullMask uint32 = 3 // pull mode is 2 bits wide 73 | // BCM2835 pullReg is the same for all pins. 74 | pullReg2835 = 37 75 | ) 76 | 77 | // Pin Mode, a pin can be set in Input or Output mode 78 | const ( 79 | Input Mode = iota 80 | Output 81 | Alt5 82 | Alt4 83 | Alt0 84 | Alt1 85 | Alt2 86 | Alt3 87 | ) 88 | 89 | // Level of pin, High / Low 90 | const ( 91 | Low Level = false 92 | High Level = true 93 | ) 94 | 95 | // Pull Up / Down / Off 96 | const ( 97 | // Values match bcm pull field. 98 | PullNone Pull = iota 99 | PullDown 100 | PullUp 101 | ) 102 | 103 | // Convenience mapping from J8 pinouts to BCM pinouts. 104 | const ( 105 | J8p27 = iota 106 | J8p28 107 | J8p3 108 | J8p5 109 | J8p7 110 | J8p29 111 | J8p31 112 | J8p26 113 | J8p24 114 | J8p21 115 | J8p19 116 | J8p23 117 | J8p32 118 | J8p33 119 | J8p8 120 | J8p10 121 | J8p36 122 | J8p11 123 | J8p12 124 | J8p35 125 | J8p38 126 | J8p40 127 | J8p15 128 | J8p16 129 | J8p18 130 | J8p22 131 | J8p37 132 | J8p13 133 | MaxGPIOPin 134 | ) 135 | 136 | // GPIO aliases to J8 pins 137 | const ( 138 | GPIO2 = J8p3 139 | GPIO3 = J8p5 140 | GPIO4 = J8p7 141 | GPIO5 = J8p29 142 | GPIO6 = J8p31 143 | GPIO7 = J8p26 144 | GPIO8 = J8p24 145 | GPIO9 = J8p21 146 | GPIO10 = J8p19 147 | GPIO11 = J8p23 148 | GPIO12 = J8p32 149 | GPIO13 = J8p33 150 | GPIO14 = J8p8 151 | GPIO15 = J8p10 152 | GPIO16 = J8p36 153 | GPIO17 = J8p11 154 | GPIO18 = J8p12 155 | GPIO19 = J8p35 156 | GPIO20 = J8p38 157 | GPIO21 = J8p40 158 | GPIO22 = J8p15 159 | GPIO23 = J8p16 160 | GPIO24 = J8p18 161 | GPIO25 = J8p22 162 | GPIO26 = J8p37 163 | GPIO27 = J8p13 164 | ) 165 | 166 | // NewPin creates a new pin object. 167 | // The pin number provided is the BCM GPIO number. 168 | func NewPin(pin int) *Pin { 169 | if len(mem) == 0 { 170 | panic("GPIO not initialised.") 171 | } 172 | if pin < 0 || pin >= MaxGPIOPin { 173 | return nil 174 | } 175 | 176 | // Pre-calculate commonly used register addresses and bit masks. 177 | 178 | // Pin fsel register, 0 - 5 depending on pin 179 | fsel := pin / 10 180 | 181 | // This seems like overkill given the J8 pins are all on the first bank... 182 | bank := pin / 32 183 | mask := uint32(1 << uint(pin&0x1f)) 184 | 185 | // Input level register offset (13 / 14 depending on bank) 186 | levelReg := 13 + bank 187 | 188 | // Clear register, 10 / 11 depending on bank 189 | clearReg := 10 + bank 190 | 191 | // Set register, 7 / 8 depending on bank 192 | setReg := 7 + bank 193 | 194 | // Pull register, 57-60 depending on pin 195 | pullReg := 57 + pin/16 196 | 197 | shadow := Low 198 | if mem[levelReg]&mask != 0 { 199 | shadow = High 200 | } 201 | 202 | return &Pin{ 203 | pin: pin, 204 | fsel: fsel, 205 | bank: bank, 206 | mask: mask, 207 | levelReg: levelReg, 208 | clearReg: clearReg, 209 | pullReg2711: pullReg, 210 | setReg: setReg, 211 | shadow: shadow, 212 | } 213 | } 214 | 215 | // Input sets pin as Input. 216 | func (pin *Pin) Input() { 217 | pin.SetMode(Input) 218 | } 219 | 220 | // Output sets pin as Output. 221 | func (pin *Pin) Output() { 222 | pin.SetMode(Output) 223 | } 224 | 225 | // High sets pin High. 226 | func (pin *Pin) High() { 227 | pin.Write(High) 228 | } 229 | 230 | // Low sets pin Low. 231 | func (pin *Pin) Low() { 232 | pin.Write(Low) 233 | } 234 | 235 | // Mode returns the mode of the pin in the Function Select register. 236 | func (pin *Pin) Mode() Mode { 237 | // read Mode and current value 238 | modeShift := uint(pin.pin%10) * 3 239 | return Mode(mem[pin.fsel] >> modeShift & modeMask) 240 | } 241 | 242 | // Shadow returns the value of the last write to an output pin or the last read on an input pin. 243 | func (pin *Pin) Shadow() Level { 244 | return pin.shadow 245 | } 246 | 247 | // Pin returns the pin number that this Pin represents. 248 | func (pin *Pin) Pin() int { 249 | return pin.pin 250 | } 251 | 252 | // Toggle pin state 253 | func (pin *Pin) Toggle() { 254 | if pin.shadow { 255 | pin.Write(Low) 256 | } else { 257 | pin.Write(High) 258 | } 259 | } 260 | 261 | // SetMode sets the pin Mode. 262 | func (pin *Pin) SetMode(mode Mode) { 263 | // shift for pin mode field within fsel register. 264 | modeShift := uint(pin.pin%10) * 3 265 | 266 | memlock.Lock() 267 | defer memlock.Unlock() 268 | 269 | mem[pin.fsel] = mem[pin.fsel]&^(modeMask<. 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // 7 | // Test suite for dio module. 8 | // 9 | // Tests use J8 pins 7 (mostly) and 15 and 16 (for looped tests) 10 | // 11 | package gpio 12 | 13 | import ( 14 | "testing" 15 | 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func BenchmarkSysfsRead(b *testing.B) { 20 | assert.Nil(b, Open()) 21 | defer Close() 22 | pin := NewPin(J8p7) 23 | // setup sysfs 24 | err := export(pin) 25 | assert.Nil(b, err) 26 | defer unexport(pin) 27 | f, err := openValue(pin) 28 | assert.Nil(b, err) 29 | defer f.Close() 30 | r := make([]byte, 1) 31 | r[0] = 0 32 | for i := 0; i < b.N; i++ { 33 | f.Read(r) 34 | } 35 | } 36 | 37 | func BenchmarkSysfsWrite(b *testing.B) { 38 | assert.Nil(b, Open()) 39 | defer Close() 40 | pin := NewPin(J8p7) 41 | // setup sysfs 42 | err := export(pin) 43 | assert.Nil(b, err) 44 | defer unexport(pin) 45 | f, err := openValue(pin) 46 | assert.Nil(b, err) 47 | defer f.Close() 48 | r := "0" 49 | for i := 0; i < b.N; i++ { 50 | f.WriteString(r) 51 | } 52 | } 53 | 54 | func BenchmarkSysfsToggle(b *testing.B) { 55 | assert.Nil(b, Open()) 56 | defer Close() 57 | pin := NewPin(J8p7) 58 | // setup sysfs 59 | err := export(pin) 60 | assert.Nil(b, err) 61 | defer unexport(pin) 62 | f, err := openValue(pin) 63 | assert.Nil(b, err) 64 | defer f.Close() 65 | r := "0" 66 | for i := 0; i < b.N; i++ { 67 | if r == "0" { 68 | r = "1" 69 | } else { 70 | r = "0" 71 | } 72 | f.WriteString(r) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /dio_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // 7 | // Test suite for dio module. 8 | // 9 | // Tests use J8 pins 7 (mostly) and 15 and 16 (for looped tests) 10 | // 11 | package gpio_test 12 | 13 | import ( 14 | "testing" 15 | "time" 16 | 17 | "github.com/stretchr/testify/assert" 18 | "github.com/warthog618/gpio" 19 | ) 20 | 21 | func setupDIO(t *testing.T) { 22 | assert.Nil(t, gpio.Open()) 23 | } 24 | 25 | func teardownDIO() { 26 | gpio.Close() 27 | } 28 | 29 | func TestUninitialisedPanic(t *testing.T) { 30 | assert.Panics(t, func() { 31 | gpio.NewPin(gpio.J8p7) 32 | }) 33 | } 34 | 35 | func TestClosedPanic(t *testing.T) { 36 | setupDIO(t) 37 | teardownDIO() 38 | assert.Panics(t, func() { 39 | gpio.NewPin(gpio.J8p7) 40 | }) 41 | } 42 | 43 | func TestNew(t *testing.T) { 44 | setupDIO(t) 45 | defer teardownDIO() 46 | pin := gpio.NewPin(gpio.MaxGPIOPin) 47 | assert.Nil(t, pin) 48 | } 49 | 50 | func TestRead(t *testing.T) { 51 | setupDIO(t) 52 | defer teardownDIO() 53 | pin := gpio.NewPin(gpio.J8p7) 54 | // A basic read test - assuming the pin is input and pulled high 55 | // which is the default state for this pin on a Pi. 56 | assert.Equal(t, gpio.Input, pin.Mode()) 57 | // Assumes pin is initially pulled up and set as an input. 58 | assert.Equal(t, gpio.High, pin.Read()) 59 | } 60 | 61 | func TestMode(t *testing.T) { 62 | setupDIO(t) 63 | defer teardownDIO() 64 | pin := gpio.NewPin(gpio.J8p7) 65 | assert.Equal(t, gpio.Input, pin.Mode()) 66 | 67 | pin.SetMode(gpio.Output) 68 | assert.Equal(t, gpio.Output, pin.Mode()) 69 | 70 | pin.SetMode(gpio.Input) 71 | assert.Equal(t, gpio.Input, pin.Mode()) 72 | 73 | pin.Output() 74 | assert.Equal(t, gpio.Output, pin.Mode()) 75 | 76 | pin.Input() 77 | assert.Equal(t, gpio.Input, pin.Mode()) 78 | } 79 | 80 | func TestPull(t *testing.T) { 81 | setupDIO(t) 82 | defer teardownDIO() 83 | pin := gpio.NewPin(gpio.J8p7) 84 | defer pin.PullUp() 85 | // A basic read test - using the pull up/down to drive the pin. 86 | assert.Equal(t, gpio.Input, pin.Mode()) 87 | pin.PullUp() 88 | pullSettle := time.Microsecond 89 | time.Sleep(pullSettle) 90 | assert.Equal(t, gpio.High, pin.Read()) 91 | pin.PullDown() 92 | time.Sleep(pullSettle) 93 | assert.Equal(t, gpio.Low, pin.Read()) 94 | pin.SetPull(gpio.PullUp) 95 | time.Sleep(pullSettle) 96 | assert.Equal(t, gpio.High, pin.Read()) 97 | // no real way of testing this, but to trick coverage... 98 | pin.PullNone() 99 | } 100 | 101 | func TestPin(t *testing.T) { 102 | setupDIO(t) 103 | defer teardownDIO() 104 | pin := gpio.NewPin(gpio.J8p7) 105 | assert.Equal(t, gpio.J8p7, pin.Pin()) 106 | pin = gpio.NewPin(gpio.J8p16) 107 | assert.Equal(t, gpio.J8p16, pin.Pin()) 108 | } 109 | 110 | func TestWrite(t *testing.T) { 111 | setupDIO(t) 112 | defer teardownDIO() 113 | pin := gpio.NewPin(gpio.J8p7) 114 | mode := pin.Mode() 115 | assert.Equal(t, gpio.Input, mode) 116 | defer pin.SetMode(gpio.Input) 117 | 118 | pin.Write(gpio.Low) 119 | pin.SetMode(gpio.Output) 120 | assert.Equal(t, gpio.Output, pin.Mode()) 121 | assert.Equal(t, gpio.Low, pin.Read()) 122 | 123 | pin.Write(gpio.High) 124 | assert.Equal(t, gpio.High, pin.Shadow()) 125 | assert.Equal(t, gpio.High, pin.Read()) 126 | 127 | pin.Write(gpio.Low) 128 | assert.Equal(t, gpio.Low, pin.Shadow()) 129 | assert.Equal(t, gpio.Low, pin.Read()) 130 | 131 | pin.High() 132 | assert.Equal(t, gpio.High, pin.Shadow()) 133 | assert.Equal(t, gpio.High, pin.Read()) 134 | 135 | pin.Low() 136 | assert.Equal(t, gpio.Low, pin.Shadow()) 137 | assert.Equal(t, gpio.Low, pin.Read()) 138 | } 139 | 140 | // Looped tests require a jumper across Raspberry Pi J8 pins 15 and 16. 141 | func TestWriteLooped(t *testing.T) { 142 | setupDIO(t) 143 | defer teardownDIO() 144 | pinIn := gpio.NewPin(gpio.J8p15) 145 | pinOut := gpio.NewPin(gpio.J8p16) 146 | pinIn.SetMode(gpio.Input) 147 | defer pinOut.SetMode(gpio.Input) 148 | pinOut.Write(gpio.Low) 149 | pinOut.SetMode(gpio.Output) 150 | assert.Equal(t, gpio.Low, pinIn.Read()) 151 | 152 | pinOut.Write(gpio.High) 153 | assert.Equal(t, gpio.High, pinIn.Read()) 154 | 155 | pinOut.Write(gpio.Low) 156 | assert.Equal(t, gpio.Low, pinIn.Read()) 157 | } 158 | 159 | func TestToggle(t *testing.T) { 160 | setupDIO(t) 161 | defer teardownDIO() 162 | pin := gpio.NewPin(gpio.J8p7) 163 | defer pin.SetMode(gpio.Input) 164 | pin.Write(gpio.Low) 165 | pin.SetMode(gpio.Output) 166 | assert.Equal(t, gpio.Output, pin.Mode()) 167 | assert.Equal(t, gpio.Low, pin.Read()) 168 | 169 | pin.Toggle() 170 | assert.Equal(t, gpio.High, pin.Shadow()) 171 | assert.Equal(t, gpio.High, pin.Read()) 172 | 173 | pin.Toggle() 174 | assert.Equal(t, gpio.Low, pin.Shadow()) 175 | assert.Equal(t, gpio.Low, pin.Read()) 176 | } 177 | 178 | // Looped tests require a jumper across Raspberry Pi J8 pins 15 and 16. 179 | func TestToggleLooped(t *testing.T) { 180 | setupDIO(t) 181 | defer teardownDIO() 182 | pinIn := gpio.NewPin(gpio.J8p15) 183 | pinOut := gpio.NewPin(gpio.J8p16) 184 | pinIn.SetMode(gpio.Input) 185 | defer pinOut.SetMode(gpio.Input) 186 | pinOut.Write(gpio.Low) 187 | pinOut.SetMode(gpio.Output) 188 | assert.Equal(t, gpio.Output, pinOut.Mode()) 189 | assert.Equal(t, gpio.Low, pinOut.Read()) 190 | 191 | pinOut.Toggle() 192 | assert.Equal(t, gpio.High, pinIn.Read()) 193 | 194 | pinOut.Toggle() 195 | assert.Equal(t, gpio.Low, pinIn.Read()) 196 | } 197 | 198 | func BenchmarkRead(b *testing.B) { 199 | assert.Nil(b, gpio.Open()) 200 | defer gpio.Close() 201 | pin := gpio.NewPin(gpio.J8p7) 202 | for i := 0; i < b.N; i++ { 203 | _ = pin.Read() 204 | } 205 | } 206 | 207 | func BenchmarkWrite(b *testing.B) { 208 | err := gpio.Open() 209 | assert.Nil(b, err) 210 | defer gpio.Close() 211 | pin := gpio.NewPin(gpio.J8p7) 212 | for i := 0; i < b.N; i++ { 213 | pin.Write(gpio.High) 214 | } 215 | } 216 | 217 | func BenchmarkToggle(b *testing.B) { 218 | assert.Nil(b, gpio.Open()) 219 | defer gpio.Close() 220 | pin := gpio.NewPin(gpio.J8p7) 221 | defer pin.SetMode(gpio.Input) 222 | pin.Write(gpio.Low) 223 | pin.SetMode(gpio.Output) 224 | for i := 0; i < b.N; i++ { 225 | pin.Toggle() 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /example/blinker/.gitignore: -------------------------------------------------------------------------------- 1 | blinker 2 | -------------------------------------------------------------------------------- /example/blinker/blinker.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/warthog618/gpio" 16 | ) 17 | 18 | // This example drives GPIO 4, which is pin J8 7. 19 | // The pin is toggled high and low at 1Hz with a 50% duty cycle. 20 | // Do not run this on a Raspberry Pi which has this pin externally driven. 21 | func main() { 22 | err := gpio.Open() 23 | if err != nil { 24 | panic(err) 25 | } 26 | defer gpio.Close() 27 | pin := gpio.NewPin(gpio.GPIO4) 28 | defer pin.Input() 29 | pin.Output() 30 | // capture exit signals to ensure pin is reverted to input on exit. 31 | quit := make(chan os.Signal, 1) 32 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 33 | defer signal.Stop(quit) 34 | for { 35 | select { 36 | case <-time.After(500 * time.Millisecond): 37 | pin.Toggle() 38 | fmt.Println("Toggled", pin.Read()) 39 | case <-quit: 40 | return 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/spi/.gitignore: -------------------------------------------------------------------------------- 1 | adc0832/adc0832 2 | mcp3008/mcp3008 3 | mcp3208/mcp3208 4 | -------------------------------------------------------------------------------- /example/spi/adc0832/adc0832.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/warthog618/config" 12 | "github.com/warthog618/config/blob" 13 | "github.com/warthog618/config/blob/decoder/json" 14 | "github.com/warthog618/config/dict" 15 | "github.com/warthog618/config/env" 16 | "github.com/warthog618/config/pflag" 17 | "github.com/warthog618/gpio" 18 | "github.com/warthog618/gpio/spi/adc0832" 19 | ) 20 | 21 | // This example reads both channels from an ADC0832 connected to the RPI by four 22 | // data lines - CSZ, CLK, DI, and DO. The default pin assignments are defined in 23 | // loadConfig, but can be altered via configuration (env, flag or config file). 24 | // The DI and DO may be tied to reduce the pin count by one, though I prefer to 25 | // keep the two separate to remove the chance of accidental conflict. 26 | // All pins other than DO are outputs so do not run this example on a board 27 | // where those pins serve other purposes. 28 | func main() { 29 | cfg := loadConfig() 30 | err := gpio.Open() 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer gpio.Close() 35 | tclk := cfg.MustGet("tclk").Duration() 36 | tset := cfg.MustGet("tset").Duration() 37 | if tset < tclk { 38 | tset = tclk 39 | } 40 | a := adc0832.New( 41 | tclk, 42 | tset, 43 | cfg.MustGet("clk").Int(), 44 | cfg.MustGet("csz").Int(), 45 | cfg.MustGet("di").Int(), 46 | cfg.MustGet("do").Int()) 47 | defer a.Close() 48 | ch0 := a.Read(0) 49 | ch1 := a.Read(1) 50 | fmt.Printf("ch0=0x%02x, ch1=0x%02x\n", ch0, ch1) 51 | } 52 | 53 | func loadConfig() *config.Config { 54 | defaultConfig := map[string]interface{}{ 55 | "tclk": "2500ns", 56 | "tset": "2500ns", // should be at least tclk - enforced in main 57 | "clk": gpio.GPIO6, 58 | "csz": gpio.GPIO5, 59 | "di": gpio.GPIO19, 60 | "do": gpio.GPIO13, 61 | } 62 | def := dict.New(dict.WithMap(defaultConfig)) 63 | cfg := config.New( 64 | pflag.New(pflag.WithFlags( 65 | []pflag.Flag{{Short: 'c', Name: "config-file"}})), 66 | env.New(env.WithEnvPrefix("ADC0832_")), 67 | config.WithDefault(def)) 68 | cfg.Append( 69 | blob.NewConfigFile(cfg, "config.file", "adc0832.json", json.NewDecoder())) 70 | cfg = cfg.GetConfig("", config.WithMust) 71 | return cfg 72 | } 73 | -------------------------------------------------------------------------------- /example/spi/mcp3008/mcp3008.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/warthog618/config" 12 | "github.com/warthog618/config/blob" 13 | "github.com/warthog618/config/blob/decoder/json" 14 | "github.com/warthog618/config/dict" 15 | "github.com/warthog618/config/env" 16 | "github.com/warthog618/config/pflag" 17 | "github.com/warthog618/gpio" 18 | "github.com/warthog618/gpio/spi/mcp3w0c" 19 | ) 20 | 21 | // This example reads both channels from an MCP3008 connected to the RPI by four 22 | // data lines - CSZ, CLK, DI, and DO. The default pin assignments are defined in 23 | // loadConfig, but can be altered via configuration (env, flag or config file). 24 | // The DI and DO may be tied to reduce the pin count by one, though I prefer to 25 | // keep the two separate to remove the chance of accidental conflict. 26 | // All pins other than DO are outputs so do not run this example on a board 27 | // where those pins serve other purposes. 28 | func main() { 29 | cfg := loadConfig() 30 | err := gpio.Open() 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer gpio.Close() 35 | tclk := cfg.MustGet("tclk").Duration() 36 | adc := mcp3w0c.NewMCP3008( 37 | tclk, 38 | cfg.MustGet("clk").Int(), 39 | cfg.MustGet("csz").Int(), 40 | cfg.MustGet("di").Int(), 41 | cfg.MustGet("do").Int()) 42 | defer adc.Close() 43 | for ch := 0; ch < 8; ch++ { 44 | d := adc.Read(ch) 45 | fmt.Printf("ch%d=0x%04x\n", ch, d) 46 | } 47 | } 48 | 49 | func loadConfig() *config.Config { 50 | defaultConfig := map[string]interface{}{ 51 | "tclk": "500ns", 52 | "clk": gpio.GPIO21, 53 | "csz": gpio.GPIO6, 54 | "di": gpio.GPIO19, 55 | "do": gpio.GPIO26, 56 | } 57 | def := dict.New(dict.WithMap(defaultConfig)) 58 | cfg := config.New( 59 | pflag.New(pflag.WithFlags( 60 | []pflag.Flag{{Short: 'c', Name: "config-file"}})), 61 | env.New(env.WithEnvPrefix("MCP3008_")), 62 | config.WithDefault(def)) 63 | cfg.Append( 64 | blob.NewConfigFile(cfg, "config.file", "mcp3008.json", json.NewDecoder())) 65 | cfg = cfg.GetConfig("", config.WithMust) 66 | return cfg 67 | } 68 | -------------------------------------------------------------------------------- /example/spi/mcp3208/mcp3208.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/warthog618/config" 12 | "github.com/warthog618/config/blob" 13 | "github.com/warthog618/config/blob/decoder/json" 14 | "github.com/warthog618/config/dict" 15 | "github.com/warthog618/config/env" 16 | "github.com/warthog618/config/pflag" 17 | "github.com/warthog618/gpio" 18 | "github.com/warthog618/gpio/spi/mcp3w0c" 19 | ) 20 | 21 | // This example reads both channels from an MCP3208 connected to the RPI by four 22 | // data lines - SSZ, SCLK, MOSI, and MISO. The default pin assignments are defined in 23 | // loadConfig, but can be altered via configuration (env, flag or config file). 24 | // The DI and DO may be tied to reduce the pin count by one, though I prefer to 25 | // keep the two separate to remove the chance of accidental conflict. 26 | // All pins other than DO are outputs so do not run this example on a board 27 | // where those pins serve other purposes. 28 | func main() { 29 | cfg := loadConfig() 30 | err := gpio.Open() 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer gpio.Close() 35 | tclk := cfg.MustGet("tclk").Duration() 36 | adc := mcp3w0c.NewMCP3208( 37 | tclk, 38 | cfg.MustGet("sclk").Int(), 39 | cfg.MustGet("ssz").Int(), 40 | cfg.MustGet("mosi").Int(), 41 | cfg.MustGet("miso").Int()) 42 | defer adc.Close() 43 | for ch := 0; ch < 8; ch++ { 44 | d := adc.Read(ch) 45 | fmt.Printf("ch%d=0x%04x (%08b)\n", ch, d, d>>4) 46 | } 47 | } 48 | 49 | func loadConfig() *config.Config { 50 | defaultConfig := map[string]interface{}{ 51 | "tclk": "500ns", 52 | "sclk": gpio.GPIO24, 53 | "ssz": gpio.GPIO17, 54 | "mosi": gpio.GPIO27, 55 | "miso": gpio.GPIO22, 56 | } 57 | def := dict.New(dict.WithMap(defaultConfig)) 58 | cfg := config.New( 59 | pflag.New(pflag.WithFlags( 60 | []pflag.Flag{{Short: 'c', Name: "config-file"}})), 61 | env.New(env.WithEnvPrefix("MCP3208_")), 62 | config.WithDefault(def)) 63 | cfg.Append( 64 | blob.NewConfigFile(cfg, "config.file", "mcp3208.json", json.NewDecoder())) 65 | cfg = cfg.GetConfig("", config.WithMust) 66 | return cfg 67 | } 68 | -------------------------------------------------------------------------------- /example/watcher/.gitignore: -------------------------------------------------------------------------------- 1 | watcher 2 | -------------------------------------------------------------------------------- /example/watcher/watcher.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/warthog618/gpio" 16 | ) 17 | 18 | // Watches GPIO 4 (J8 7) and reports when it changes state. 19 | func main() { 20 | err := gpio.Open() 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer gpio.Close() 25 | pin := gpio.NewPin(gpio.J8p7) 26 | pin.Input() 27 | pin.PullUp() 28 | 29 | // capture exit signals to ensure resources are released on exit. 30 | quit := make(chan os.Signal, 1) 31 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 32 | defer signal.Stop(quit) 33 | 34 | err = pin.Watch(gpio.EdgeBoth, func(pin *gpio.Pin) { 35 | fmt.Printf("Pin 4 is %v", pin.Read()) 36 | }) 37 | if err != nil { 38 | panic(err) 39 | } 40 | defer pin.Unwatch() 41 | 42 | // In a real application the main thread would do something useful here. 43 | // But we'll just run for a minute then exit. 44 | fmt.Println("Watching Pin 4...") 45 | select { 46 | case <-time.After(time.Minute): 47 | case <-quit: 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/warthog618/gpio 2 | 3 | require ( 4 | github.com/spf13/cobra v0.0.5 5 | github.com/stretchr/testify v1.8.1 6 | github.com/warthog618/config v0.5.1 7 | golang.org/x/sys v0.3.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/fsnotify/fsnotify v1.6.0 // indirect 13 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 14 | github.com/kr/pretty v0.3.1 // indirect 15 | github.com/pkg/errors v0.9.1 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/spf13/pflag v1.0.5 // indirect 18 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | 22 | go 1.17 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 4 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 5 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 6 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 7 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 14 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 15 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 16 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 17 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 18 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 19 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 20 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 21 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 22 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 23 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 24 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 25 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 26 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 27 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 28 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 29 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 33 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 34 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 35 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 36 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 37 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 38 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 39 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 40 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 41 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 42 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 43 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 44 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 45 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 46 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 47 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 48 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 49 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 50 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 51 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 52 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 53 | github.com/warthog618/config v0.5.1 h1:H1nxU+xYxotdDsnUwaAI4iWoMkvHXUH8u84I+CkGSCI= 54 | github.com/warthog618/config v0.5.1/go.mod h1:6Fux1X42nlCKzdwP3iloUvHtBCZYa+lalHHO9V0arHE= 55 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 56 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 57 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 58 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 60 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 61 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 62 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 63 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 64 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 66 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 67 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 68 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 69 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 70 | -------------------------------------------------------------------------------- /interrupt.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Interrupt capabilities for DIO Pins. 7 | 8 | //go:build linux 9 | // +build linux 10 | 11 | package gpio 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "os" 17 | "strconv" 18 | "sync" 19 | "time" 20 | 21 | "golang.org/x/sys/unix" 22 | ) 23 | 24 | const ( 25 | // MaxGPIOInterrupt is the maximum pin number. 26 | MaxGPIOInterrupt = MaxGPIOPin 27 | ) 28 | 29 | // Edge represents the change in Pin level that triggers an interrupt. 30 | type Edge string 31 | 32 | const ( 33 | // EdgeNone indicates no level transitions will trigger an interrupt 34 | EdgeNone Edge = "none" 35 | 36 | // EdgeRising indicates an interrupt is triggered when the pin transitions from low to high. 37 | EdgeRising Edge = "rising" 38 | 39 | // EdgeFalling indicates an interrupt is triggered when the pin transitions from high to low. 40 | EdgeFalling Edge = "falling" 41 | 42 | // EdgeBoth indicates an interrupt is triggered when the pin changes level. 43 | EdgeBoth Edge = "both" 44 | ) 45 | 46 | type interrupt struct { 47 | pin *Pin 48 | handler func(*Pin) 49 | valueFile *os.File 50 | } 51 | 52 | // Watcher monitors the pins for level transitions that trigger interrupts. 53 | type Watcher struct { 54 | // Guards the following, and sysfs interactions. 55 | sync.Mutex 56 | 57 | epfd int 58 | 59 | // Map from pin to value Fd. 60 | interruptFds map[int]int 61 | 62 | // Map from pin Fd to interrupt 63 | interrupts map[int]*interrupt 64 | 65 | // closed when the watcher exits. 66 | doneCh chan struct{} 67 | 68 | // fds of the pipe for the shutdown handshake. 69 | donefds []int 70 | 71 | // true once the Watcher has been closed. 72 | closed bool 73 | } 74 | 75 | var defaultWatcher *Watcher 76 | 77 | func getDefaultWatcher() *Watcher { 78 | memlock.Lock() 79 | if defaultWatcher == nil { 80 | defaultWatcher = NewWatcher() 81 | } 82 | memlock.Unlock() 83 | return defaultWatcher 84 | } 85 | 86 | // NewWatcher creates a goroutine that watches Pins for transitions that trigger 87 | // interrupts. 88 | func NewWatcher() *Watcher { 89 | epfd, err := unix.EpollCreate1(0) 90 | if err != nil { 91 | panic(fmt.Sprintf("Unable to create epoll: %v", err)) 92 | } 93 | p := []int{0, 0} 94 | err = unix.Pipe2(p, unix.O_CLOEXEC) 95 | if err != nil { 96 | panic(fmt.Sprintf("Unable to create pipe: %v", err)) 97 | } 98 | epv := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(p[0])} 99 | unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(p[0]), &epv) 100 | w := &Watcher{ 101 | epfd: epfd, 102 | interruptFds: make(map[int]int), 103 | interrupts: make(map[int]*interrupt), 104 | doneCh: make(chan struct{}), 105 | donefds: p, 106 | } 107 | go w.watch() 108 | 109 | return w 110 | } 111 | 112 | func (w *Watcher) watch() { 113 | var epollEvents [MaxGPIOInterrupt]unix.EpollEvent 114 | defer close(w.doneCh) 115 | for { 116 | n, err := unix.EpollWait(w.epfd, epollEvents[:], -1) 117 | if err != nil { 118 | if err == unix.EBADF || err == unix.EINVAL { 119 | // fd closed so exit 120 | return 121 | } 122 | if err == unix.EINTR { 123 | continue 124 | } 125 | panic(fmt.Sprintf("EpollWait error: %v", err)) 126 | } 127 | for i := 0; i < n; i++ { 128 | event := epollEvents[i] 129 | if event.Fd == int32(w.donefds[0]) { 130 | unix.Close(w.epfd) 131 | unix.Close(w.donefds[0]) 132 | return 133 | } 134 | w.Lock() 135 | irq, ok := w.interrupts[int(event.Fd)] 136 | w.Unlock() 137 | if ok { 138 | go irq.handler(irq.pin) 139 | } 140 | } 141 | } 142 | } 143 | 144 | func closeInterrupts() { 145 | watcher := defaultWatcher 146 | if watcher == nil { 147 | return 148 | } 149 | defaultWatcher = nil 150 | watcher.Close() 151 | } 152 | 153 | // Close - His watch has ended. 154 | func (w *Watcher) Close() { 155 | w.Lock() 156 | if w.closed { 157 | w.Unlock() 158 | return 159 | } 160 | w.closed = true 161 | unix.Write(w.donefds[1], []byte("bye")) 162 | for fd := range w.interrupts { 163 | intr := w.interrupts[fd] 164 | intr.valueFile.Close() 165 | unexport(intr.pin) 166 | } 167 | w.interrupts = nil 168 | w.interruptFds = nil 169 | w.Unlock() 170 | <-w.doneCh 171 | unix.Close(w.donefds[1]) 172 | } 173 | 174 | // RegisterPin creates a watch on the given pin. 175 | // 176 | // The pin can only be registered once. Subsequent registers, 177 | // without an Unregister, will return an error. 178 | func (w *Watcher) RegisterPin(pin *Pin, edge Edge, handler func(*Pin)) (err error) { 179 | w.Lock() 180 | defer w.Unlock() 181 | 182 | _, ok := w.interruptFds[pin.pin] 183 | if ok { 184 | return ErrBusy 185 | } 186 | if err = export(pin); err != nil { 187 | return err 188 | } 189 | defer func() { 190 | if err != nil { 191 | unexport(pin) 192 | } 193 | }() 194 | if err = setEdge(pin, edge); err != nil { 195 | return err 196 | } 197 | valueFile, err := openValue(pin) 198 | if err != nil { 199 | return err 200 | } 201 | pinFd := int(valueFile.Fd()) 202 | 203 | event := unix.EpollEvent{Events: unix.EPOLLET & 0xffffffff} 204 | if err = unix.SetNonblock(pinFd, true); err != nil { 205 | return err 206 | } 207 | event.Fd = int32(pinFd) 208 | if err := unix.EpollCtl(w.epfd, unix.EPOLL_CTL_ADD, pinFd, &event); err != nil { 209 | return err 210 | } 211 | w.interruptFds[pin.pin] = pinFd 212 | w.interrupts[pinFd] = &interrupt{pin: pin, handler: handler, valueFile: valueFile} 213 | return nil 214 | } 215 | 216 | // UnregisterPin removes any watch on the Pin. 217 | func (w *Watcher) UnregisterPin(pin *Pin) { 218 | w.Lock() 219 | defer w.Unlock() 220 | 221 | pinFd, ok := w.interruptFds[pin.pin] 222 | if !ok { 223 | return 224 | } 225 | delete(w.interruptFds, pin.pin) 226 | unix.EpollCtl(w.epfd, unix.EPOLL_CTL_DEL, pinFd, nil) 227 | unix.SetNonblock(pinFd, false) 228 | intr, ok := w.interrupts[pinFd] 229 | if ok { 230 | delete(w.interrupts, pinFd) 231 | intr.valueFile.Close() 232 | } 233 | unexport(pin) 234 | } 235 | 236 | // Watch the pin for changes to level. 237 | // 238 | // The handler is called immediately, to allow the handler to initialise its state 239 | // with the current level, and then on the specified edges. 240 | // The edge determines which edge to watch. 241 | // There can only be one watcher on the pin at a time. 242 | func (p *Pin) Watch(edge Edge, handler func(*Pin)) error { 243 | watcher := getDefaultWatcher() 244 | return watcher.RegisterPin(p, edge, handler) 245 | } 246 | 247 | // Unwatch removes any watch from the pin. 248 | func (p *Pin) Unwatch() { 249 | watcher := getDefaultWatcher() 250 | watcher.UnregisterPin(p) 251 | } 252 | 253 | func waitWriteable(path string) error { 254 | try := 0 255 | for unix.Access(path, unix.W_OK) != nil { 256 | try++ 257 | if try > 10 { 258 | return ErrTimeout 259 | } 260 | time.Sleep(50 * time.Millisecond) 261 | } 262 | return nil 263 | } 264 | 265 | func export(p *Pin) error { 266 | file, err := os.OpenFile("/sys/class/gpio/export", os.O_WRONLY, os.ModeExclusive) 267 | if err != nil { 268 | return err 269 | } 270 | defer file.Close() 271 | _, err = file.WriteString(strconv.Itoa(int(p.pin))) 272 | if e, ok := err.(*os.PathError); ok && e.Err == unix.EBUSY { 273 | return ErrBusy 274 | } 275 | if err != nil { 276 | return err 277 | } 278 | // wait for pin to be exported on sysfs - can take > 100ms on older Pis 279 | return waitExported(p) 280 | } 281 | 282 | func openValue(p *Pin) (*os.File, error) { 283 | path := fmt.Sprintf("/sys/class/gpio/gpio%v/value", p.pin) 284 | return os.OpenFile(path, os.O_RDWR, os.ModeExclusive) 285 | } 286 | 287 | func setEdge(p *Pin, edge Edge) error { 288 | path := fmt.Sprintf("/sys/class/gpio/gpio%v/edge", p.pin) 289 | file, err := os.OpenFile(path, os.O_RDWR, os.ModeExclusive) 290 | if err != nil { 291 | return err 292 | } 293 | defer file.Close() 294 | _, err = file.Write([]byte(edge)) 295 | return err 296 | } 297 | 298 | func unexport(p *Pin) error { 299 | file, err := os.OpenFile("/sys/class/gpio/unexport", os.O_WRONLY, os.ModeExclusive) 300 | if err != nil { 301 | return err 302 | } 303 | defer file.Close() 304 | _, err = file.WriteString(strconv.Itoa(int(p.pin))) 305 | return err 306 | } 307 | 308 | // Wait for the sysfs GPIO files to become writable. 309 | func waitExported(p *Pin) error { 310 | path := fmt.Sprintf("/sys/class/gpio/gpio%v/value", p.pin) 311 | if err := waitWriteable(path); err != nil { 312 | return err 313 | } 314 | path = fmt.Sprintf("/sys/class/gpio/gpio%v/edge", p.pin) 315 | return waitWriteable(path) 316 | } 317 | 318 | var ( 319 | // ErrTimeout indicates the operation could not be performed within the 320 | // expected time. 321 | ErrTimeout = errors.New("timeout") 322 | 323 | // ErrBusy indicates the operation is already active on the pin. 324 | ErrBusy = errors.New("pin already in use") 325 | ) 326 | -------------------------------------------------------------------------------- /interrupt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // 7 | // Test suite for interrupt module. 8 | // 9 | // Tests use Raspberry Pi J8 pins 15 and 16 which must be jumpered together. 10 | // 11 | package gpio 12 | 13 | import ( 14 | "errors" 15 | "testing" 16 | "time" 17 | 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | func waitInterrupt(ch chan int, timeout time.Duration) (int, error) { 22 | select { 23 | case v := <-ch: 24 | return v, nil 25 | case <-time.After(timeout): 26 | return 0, errors.New("timeout") 27 | } 28 | } 29 | 30 | func setupIntr(t *testing.T) (pinIn *Pin, pinOut *Pin, watcher *Watcher) { 31 | assert.Nil(t, Open()) 32 | pinIn = NewPin(J8p15) 33 | pinOut = NewPin(J8p16) 34 | watcher = getDefaultWatcher() 35 | pinIn.SetMode(Input) 36 | pinOut.Write(Low) 37 | pinOut.SetMode(Output) 38 | return 39 | } 40 | 41 | func teardownIntr(pinIn *Pin, pinOut *Pin, watcher *Watcher) { 42 | pinOut.SetMode(Input) 43 | watcher.UnregisterPin(pinIn) 44 | Close() 45 | } 46 | 47 | func TestRegister(t *testing.T) { 48 | pinIn, pinOut, watcher := setupIntr(t) 49 | defer teardownIntr(pinIn, pinOut, watcher) 50 | ich := make(chan int) 51 | count := 0 52 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeRising, func(pin *Pin) { 53 | count++ 54 | ich <- count 55 | })) 56 | v, err := waitInterrupt(ich, 10*time.Millisecond) 57 | assert.Nil(t, err) 58 | assert.Equal(t, 1, v) 59 | _, err = waitInterrupt(ich, 10*time.Millisecond) 60 | assert.NotNil(t, err, "Spurious interrupt") 61 | } 62 | 63 | func TestReregister(t *testing.T) { 64 | pinIn, pinOut, watcher := setupIntr(t) 65 | defer teardownIntr(pinIn, pinOut, watcher) 66 | ich := make(chan int) 67 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeRising, func(pin *Pin) { 68 | ich <- 1 69 | })) 70 | v, err := waitInterrupt(ich, 10*time.Millisecond) 71 | assert.Nil(t, err) 72 | assert.Equal(t, 1, v) 73 | assert.NotNil(t, watcher.RegisterPin(pinIn, EdgeRising, func(pin *Pin) { 74 | ich <- 2 75 | }), "Reregistration didn't fail.") 76 | pinOut.High() 77 | v, err = waitInterrupt(ich, 10*time.Millisecond) 78 | assert.Nil(t, err) 79 | assert.Equal(t, 1, v) 80 | } 81 | 82 | func TestUnregister(t *testing.T) { 83 | pinIn, pinOut, watcher := setupIntr(t) 84 | defer teardownIntr(pinIn, pinOut, watcher) 85 | ich := make(chan int) 86 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeRising, func(pin *Pin) { 87 | ich <- 1 88 | }), "Registration failed") 89 | v, err := waitInterrupt(ich, 10*time.Millisecond) 90 | assert.Nil(t, err) 91 | assert.Equal(t, 1, v) 92 | watcher.UnregisterPin(pinIn) 93 | pinOut.High() 94 | _, err = waitInterrupt(ich, 10*time.Millisecond) 95 | assert.NotNil(t, err) 96 | // And again just for coverage. 97 | watcher.UnregisterPin(pinIn) 98 | } 99 | 100 | func TestEdgeRising(t *testing.T) { 101 | pinIn, pinOut, watcher := setupIntr(t) 102 | defer teardownIntr(pinIn, pinOut, watcher) 103 | ich := make(chan int) 104 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeRising, func(pin *Pin) { 105 | if pin.Read() == High { 106 | ich <- 1 107 | } else { 108 | ich <- 0 109 | } 110 | })) 111 | v, err := waitInterrupt(ich, 10*time.Millisecond) 112 | assert.Nil(t, err) 113 | assert.Equal(t, 0, v) 114 | // Can take a while for the init to be applied before it starts triggering 115 | // interrupts, so wait a bit... 116 | time.Sleep(time.Millisecond) 117 | for i := 0; i < 10; i++ { 118 | pinOut.High() 119 | v, err := waitInterrupt(ich, 10*time.Millisecond) 120 | if err != nil { 121 | t.Error("Missed high at", i) 122 | } else if v == 0 { 123 | t.Error("Triggered while low at", i) 124 | } 125 | pinOut.Low() 126 | _, err = waitInterrupt(ich, 10*time.Millisecond) 127 | if err == nil { 128 | t.Error("Spurious or delayed trigger at", i) 129 | } 130 | } 131 | } 132 | 133 | func TestEdgeFalling(t *testing.T) { 134 | pinIn, pinOut, watcher := setupIntr(t) 135 | defer teardownIntr(pinIn, pinOut, watcher) 136 | ich := make(chan int) 137 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeFalling, func(pin *Pin) { 138 | if pin.Read() == High { 139 | ich <- 1 140 | } else { 141 | ich <- 0 142 | } 143 | })) 144 | v, err := waitInterrupt(ich, 10*time.Millisecond) 145 | assert.Nil(t, err) 146 | assert.Equal(t, 0, v) 147 | for i := 0; i < 10; i++ { 148 | pinOut.High() 149 | _, err := waitInterrupt(ich, 10*time.Millisecond) 150 | if err == nil { 151 | t.Error("Spurious or delayed trigger at", i) 152 | } 153 | pinOut.Low() 154 | v, err = waitInterrupt(ich, 10*time.Millisecond) 155 | if err != nil { 156 | t.Error("Missed low at", i) 157 | } else if v == 1 { 158 | t.Error("Triggered while low at", i) 159 | } 160 | } 161 | } 162 | 163 | func TestEdgeBoth(t *testing.T) { 164 | pinIn, pinOut, watcher := setupIntr(t) 165 | defer teardownIntr(pinIn, pinOut, watcher) 166 | ich := make(chan int) 167 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeBoth, func(pin *Pin) { 168 | if pin.Read() == High { 169 | ich <- 1 170 | } else { 171 | ich <- 0 172 | } 173 | })) 174 | v, err := waitInterrupt(ich, 10*time.Millisecond) 175 | assert.Nil(t, err) 176 | assert.Equal(t, 0, v) 177 | for i := 0; i < 10; i++ { 178 | pinOut.High() 179 | v, err := waitInterrupt(ich, 10*time.Millisecond) 180 | if err != nil { 181 | t.Error("Missed high at", i) 182 | } else if v == 0 { 183 | t.Error("Triggered while low at", i) 184 | } 185 | pinOut.Low() 186 | v, err = waitInterrupt(ich, 10*time.Millisecond) 187 | if err != nil { 188 | t.Error("Missed low at", i) 189 | } else if v == 1 { 190 | t.Error("Triggered while high at", i) 191 | } 192 | } 193 | } 194 | 195 | func TestEdgeNone(t *testing.T) { 196 | pinIn, pinOut, watcher := setupIntr(t) 197 | defer teardownIntr(pinIn, pinOut, watcher) 198 | ich := make(chan int) 199 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeNone, func(pin *Pin) { 200 | if pin.Read() == High { 201 | ich <- 1 202 | } else { 203 | ich <- 0 204 | } 205 | })) 206 | v, err := waitInterrupt(ich, 10*time.Millisecond) 207 | assert.Nil(t, err) 208 | assert.Equal(t, 0, v) 209 | for i := 0; i < 10; i++ { 210 | pinOut.High() 211 | v, err := waitInterrupt(ich, 10*time.Millisecond) 212 | if err == nil { 213 | t.Error("Spurious or delayed trigger at", i, v) 214 | } 215 | pinOut.Low() 216 | v, err = waitInterrupt(ich, 10*time.Millisecond) 217 | if err == nil { 218 | t.Error("Spurious or delayed trigger at", i, v) 219 | } 220 | } 221 | } 222 | 223 | func TestUnexportedEdge(t *testing.T) { 224 | pinIn, pinOut, watcher := setupIntr(t) 225 | assert.NotNil(t, setEdge(pinIn, EdgeNone)) 226 | defer teardownIntr(pinIn, pinOut, watcher) 227 | } 228 | 229 | func TestCloseInterrupts(t *testing.T) { 230 | pinIn, pinOut, watcher := setupIntr(t) 231 | defer teardownIntr(pinIn, pinOut, watcher) 232 | ich := make(chan int) 233 | assert.Nil(t, watcher.RegisterPin(pinIn, EdgeNone, func(pin *Pin) { 234 | if pin.Read() == High { 235 | ich <- 1 236 | } else { 237 | ich <- 0 238 | } 239 | })) 240 | // absorb state sync interrupt 241 | v, err := waitInterrupt(ich, 10*time.Millisecond) 242 | assert.Nil(t, err, "Missing sync interrupt") 243 | assert.Equal(t, 0, v) 244 | closeInterrupts() 245 | // check no interrupts triggered by close 246 | _, err = waitInterrupt(ich, 10*time.Millisecond) 247 | assert.NotNil(t, err, "Interrupt triggered by close") 248 | // confirm that no further interrupts can be triggered. 249 | pinOut.High() 250 | _, err = waitInterrupt(ich, 10*time.Millisecond) 251 | assert.NotNil(t, err, "Interrupts still active after close") 252 | } 253 | 254 | func TestWatchExists(t *testing.T) { 255 | assert.Nil(t, Open()) 256 | defer Close() 257 | pinIn := NewPin(J8p15) 258 | pinIn.SetMode(Input) 259 | count := 0 260 | assert.Nil(t, pinIn.Watch(EdgeFalling, func(pin *Pin) { 261 | count++ 262 | })) 263 | assert.NotNil(t, pinIn.Watch(EdgeFalling, func(pin *Pin) { 264 | count++ 265 | })) 266 | time.Sleep(2 * time.Millisecond) 267 | if count != 1 { 268 | t.Error("Second handler called") 269 | } 270 | } 271 | 272 | // Looped tests require a jumper across Raspberry Pi J8 pins 15 and 16. 273 | // This is just a smoke test for the Watch and Unwatch methods. 274 | func TestWatchLooped(t *testing.T) { 275 | assert.Nil(t, Open()) 276 | defer Close() 277 | pinIn := NewPin(J8p15) 278 | pinOut := NewPin(J8p16) 279 | pinIn.SetMode(Input) 280 | defer pinOut.SetMode(Input) 281 | pinOut.Write(Low) 282 | pinOut.SetMode(Output) 283 | mode := pinOut.Mode() 284 | assert.Equal(t, Output, mode) 285 | called := false 286 | assert.Nil(t, pinIn.Watch(EdgeFalling, func(pin *Pin) { 287 | called = true 288 | })) 289 | time.Sleep(2 * time.Millisecond) 290 | assert.True(t, called) 291 | called = false 292 | pinOut.High() 293 | time.Sleep(2 * time.Millisecond) 294 | assert.False(t, called) 295 | pinOut.Low() 296 | time.Sleep(2 * time.Millisecond) 297 | assert.True(t, called) 298 | pinIn.Unwatch() 299 | called = false 300 | pinOut.High() 301 | pinOut.Low() 302 | time.Sleep(2 * time.Millisecond) 303 | assert.False(t, called) 304 | } 305 | 306 | // This provides a coarse estimate of the interrupt latency, 307 | // i.e. the time between an interrupt being triggered and handled. 308 | // There is some overhead in there due to the handshaking via a channel etc... 309 | // so this provides an upper bound. 310 | func BenchmarkInterruptLatency(b *testing.B) { 311 | assert.Nil(b, Open()) 312 | defer Close() 313 | pinIn := NewPin(J8p15) 314 | pinOut := NewPin(J8p16) 315 | pinIn.SetMode(Input) 316 | defer pinOut.SetMode(Input) 317 | pinOut.Write(Low) 318 | pinOut.SetMode(Output) 319 | mode := pinOut.Mode() 320 | assert.Equal(b, Output, mode) 321 | ich := make(chan int) 322 | assert.Nil(b, pinIn.Watch(EdgeBoth, func(pin *Pin) { 323 | ich <- 1 324 | })) 325 | defer pinIn.Unwatch() 326 | for i := 0; i < b.N; i++ { 327 | pinOut.Toggle() 328 | <-ich 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | //go:build linux 7 | // +build linux 8 | 9 | package gpio 10 | 11 | import ( 12 | "errors" 13 | "os" 14 | "reflect" 15 | "sync" 16 | "unsafe" 17 | 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | // Chipset identifies the GPIO chip. 22 | type Chipset int 23 | 24 | const ( 25 | // Unknown by default 26 | _ Chipset = iota 27 | 28 | // BCM2835 indicates the chipset is BCM2825 or compatible. 29 | BCM2835 30 | 31 | // BCM2711 indicates the chipset is BCM2711. 32 | BCM2711 33 | ) 34 | 35 | // Arrays for 8 / 32 bit access to memory and a semaphore for write locking 36 | var ( 37 | chipset Chipset 38 | 39 | // The memlock covers read/modify/write access to the mem block. 40 | // Individual reads and writes can skip the lock on the assumption that 41 | // concurrent register writes are atomic. e.g. Read, Write and Mode. 42 | memlock sync.Mutex 43 | mem []uint32 44 | mem8 []uint8 45 | ) 46 | 47 | // Open and memory map GPIO memory range from /dev/gpiomem . 48 | // Some reflection magic is used to convert it to a unsafe []uint32 pointer 49 | func Open() (err error) { 50 | if len(mem) != 0 { 51 | return ErrAlreadyOpen 52 | } 53 | file, err := os.OpenFile( 54 | "/dev/gpiomem", 55 | os.O_RDWR|os.O_SYNC, 56 | 0) 57 | 58 | if err != nil { 59 | return 60 | } 61 | defer file.Close() 62 | 63 | memlock.Lock() 64 | defer memlock.Unlock() 65 | 66 | // Memory map GPIO registers to byte array 67 | mem8, err = unix.Mmap( 68 | int(file.Fd()), 69 | 0, 70 | memLength, 71 | unix.PROT_READ|unix.PROT_WRITE, 72 | unix.MAP_SHARED) 73 | 74 | if err != nil { 75 | return 76 | } 77 | 78 | // Convert mapped byte memory to unsafe []uint32 pointer, adjust length as needed 79 | header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem8)) 80 | header.Len /= 4 // (32 bit = 4 bytes) 81 | header.Cap /= 4 82 | 83 | mem = *(*[]uint32)(unsafe.Pointer(&header)) 84 | 85 | if mem[60] == 0x6770696f { 86 | chipset = BCM2835 87 | } else { 88 | chipset = BCM2711 89 | } 90 | 91 | return nil 92 | } 93 | 94 | // Chip identifies the chipset on the system. 95 | // 96 | // This is not valid until Open has been called. 97 | func Chip() Chipset { 98 | return chipset 99 | } 100 | 101 | // Close removes the interrupt handlers and unmaps GPIO memory 102 | func Close() error { 103 | memlock.Lock() 104 | defer memlock.Unlock() 105 | closeInterrupts() 106 | mem = make([]uint32, 0) 107 | return unix.Munmap(mem8) 108 | } 109 | 110 | var ( 111 | // ErrAlreadyOpen indicates the mem is already open. 112 | ErrAlreadyOpen = errors.New("already open") 113 | ) 114 | -------------------------------------------------------------------------------- /mem_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Test suite for mem module. 7 | package gpio_test 8 | 9 | import ( 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | "github.com/warthog618/gpio" 14 | ) 15 | 16 | func TestOpen(t *testing.T) { 17 | assert.Nil(t, gpio.Open()) 18 | defer gpio.Close() 19 | } 20 | 21 | func TestOpenOpened(t *testing.T) { 22 | assert.Nil(t, gpio.Open()) 23 | defer gpio.Close() 24 | assert.NotNil(t, gpio.Open()) 25 | } 26 | 27 | func TestReOpen(t *testing.T) { 28 | assert.Nil(t, gpio.Open()) 29 | gpio.Close() 30 | assert.Nil(t, gpio.Open()) 31 | defer gpio.Close() 32 | } 33 | -------------------------------------------------------------------------------- /spi/adc0832/adc0832.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package adc0832 7 | 8 | import ( 9 | "time" 10 | 11 | "github.com/warthog618/gpio" 12 | "github.com/warthog618/gpio/spi" 13 | ) 14 | 15 | // ADC0832 reads ADC values from a connected ADC0832. 16 | // 17 | // The two data pins, di and do, may be tied and connected to a single GPIO pin. 18 | type ADC0832 struct { 19 | spi.SPI 20 | // time to allow mux to settle after clocking out ODD/SIGN 21 | tset time.Duration 22 | } 23 | 24 | // New creates a ADC0832. 25 | func New(tclk, tset time.Duration, clk, csz, di, do int) *ADC0832 { 26 | return &ADC0832{*spi.New(tclk, clk, csz, di, do), tset} 27 | } 28 | 29 | // Read returns the value of a single channel read from the ADC. 30 | func (adc *ADC0832) Read(ch int) uint8 { 31 | return adc.read(ch, gpio.High) 32 | } 33 | 34 | // ReadDifferential returns the value of a differential pair read from the ADC. 35 | func (adc *ADC0832) ReadDifferential(ch int) uint8 { 36 | return adc.read(ch, gpio.Low) 37 | } 38 | 39 | func (adc *ADC0832) read(ch int, sgl gpio.Level) uint8 { 40 | adc.Mu.Lock() 41 | adc.Ssz.High() 42 | adc.Sclk.Low() 43 | adc.Mosi.High() 44 | adc.Mosi.Output() 45 | time.Sleep(adc.Tclk) 46 | adc.Ssz.Low() 47 | 48 | odd := gpio.Low 49 | if ch != 0 { 50 | odd = gpio.High 51 | } 52 | adc.ClockOut(gpio.High) // Start 53 | adc.ClockOut(sgl) // SGL/DIFZ 54 | adc.ClockOut(odd) // ODD/Sign 55 | // mux settling 56 | adc.Mosi.Input() 57 | time.Sleep(adc.tset) 58 | adc.Sclk.High() 59 | // MSB first byte 60 | var d uint8 61 | for i := uint(0); i < 8; i++ { 62 | b := adc.ClockIn() 63 | d = d << 1 64 | if b { 65 | d = d | 0x01 66 | } 67 | } 68 | // ignore LSB bits - same as MSB just reversed order 69 | adc.Ssz.High() 70 | adc.Mu.Unlock() 71 | return d 72 | } 73 | -------------------------------------------------------------------------------- /spi/mcp3w0c/mcp3w0c.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | // Package mcp3w0c provides device drivers for MCP3004/3008/3204/3208 SPI ADCs. 7 | package mcp3w0c 8 | 9 | import ( 10 | "time" 11 | 12 | "github.com/warthog618/gpio" 13 | "github.com/warthog618/gpio/spi" 14 | ) 15 | 16 | // MCP3w0c reads ADC values from a connected Microchip MCP3xxx family device. 17 | // 18 | // Supported variants are MCP3004/3008/3204/3208. 19 | // The w indicates the width of the device (0 => 10, 2 => 12) 20 | // and the c the number of channels. 21 | // The two data pins, di and do, may be tied and connected to a single GPIO pin. 22 | type MCP3w0c struct { 23 | spi.SPI 24 | width uint 25 | } 26 | 27 | // New creates a MCP3w0c. 28 | func New(tclk time.Duration, clk, csz, di, do int, width uint) *MCP3w0c { 29 | return &MCP3w0c{*spi.New(tclk, clk, csz, di, do), width} 30 | } 31 | 32 | // NewMCP3008 creates a MCP3008. 33 | func NewMCP3008(tclk time.Duration, clk, csz, di, do int) *MCP3w0c { 34 | return &MCP3w0c{*spi.New(tclk, clk, csz, di, do), 10} 35 | } 36 | 37 | // NewMCP3208 creates a MCP3208. 38 | func NewMCP3208(tclk time.Duration, clk, csz, di, do int) *MCP3w0c { 39 | return &MCP3w0c{*spi.New(tclk, clk, csz, di, do), 12} 40 | } 41 | 42 | // Read returns the value of a single channel read from the ADC. 43 | func (adc *MCP3w0c) Read(ch int) uint16 { 44 | return adc.read(ch, gpio.High) 45 | } 46 | 47 | // ReadDifferential returns the value of a differential pair read from the ADC. 48 | func (adc *MCP3w0c) ReadDifferential(ch int) uint16 { 49 | return adc.read(ch, gpio.Low) 50 | } 51 | 52 | func (adc *MCP3w0c) read(ch int, sgl gpio.Level) uint16 { 53 | adc.Mu.Lock() 54 | adc.Ssz.High() 55 | adc.Sclk.Low() 56 | adc.Mosi.High() 57 | adc.Mosi.Output() 58 | time.Sleep(adc.Tclk) 59 | adc.Ssz.Low() 60 | 61 | adc.ClockOut(gpio.High) // Start 62 | adc.ClockOut(sgl) // SGL/DIFFZ 63 | for i := 2; i >= 0; i-- { 64 | d := gpio.Low 65 | if (ch >> uint(i) & 0x01) == 0x01 { 66 | d = gpio.High 67 | } 68 | adc.ClockOut(d) 69 | } 70 | // mux settling 71 | adc.Mosi.Input() 72 | time.Sleep(adc.Tclk) 73 | adc.Sclk.High() 74 | adc.ClockIn() // null bit 75 | var d uint16 76 | for i := uint(0); i < adc.width; i++ { 77 | d = d << 1 78 | if adc.ClockIn() { 79 | d = d | 0x01 80 | } 81 | } 82 | adc.Ssz.High() 83 | adc.Mu.Unlock() 84 | return d 85 | } 86 | -------------------------------------------------------------------------------- /spi/spi.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Kent Gibson . 2 | // 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package spi 7 | 8 | import ( 9 | "sync" 10 | "time" 11 | 12 | "github.com/warthog618/gpio" 13 | ) 14 | 15 | // SPI represents a device connected to the Raspberry Pi via an SPI bus using 3 16 | // or 4 GPIO lines. 17 | // 18 | // Depending on the device, the two data pins, Mosi and Miso, may be tied and 19 | // connected to a single GPIO pin. This is the basis for bit bashed SPI 20 | // interfaces using GPIO pins. It is not related to the SPI device drivers 21 | // provided by Linux. 22 | type SPI struct { 23 | Mu sync.Mutex 24 | // time between clock edges (i.e. half the cycle time) 25 | Tclk time.Duration 26 | Sclk *gpio.Pin 27 | Ssz *gpio.Pin 28 | Mosi *gpio.Pin 29 | Miso *gpio.Pin 30 | } 31 | 32 | // New creates a SPI. 33 | func New(tclk time.Duration, sclk, ssz, mosi, miso int) *SPI { 34 | spi := &SPI{ 35 | Tclk: tclk, 36 | Sclk: gpio.NewPin(sclk), 37 | Ssz: gpio.NewPin(ssz), 38 | Mosi: gpio.NewPin(mosi), 39 | Miso: gpio.NewPin(miso), 40 | } 41 | // hold SPI reset until needed... 42 | spi.Sclk.Low() 43 | spi.Sclk.Output() 44 | spi.Ssz.High() 45 | spi.Ssz.Output() 46 | return spi 47 | } 48 | 49 | // Close disables the output pins used to drive the SPI device. 50 | func (spi *SPI) Close() { 51 | spi.Mu.Lock() 52 | spi.Sclk.Input() 53 | spi.Ssz.Input() 54 | spi.Mosi.Input() 55 | spi.Mu.Unlock() 56 | } 57 | 58 | // ClockIn clocks in a data bit from the SPI device on Miso. 59 | // Assumes clock starts high and ends with the rising edge of the next clock. 60 | // Assumes caller already holds the Mu lock. 61 | func (spi *SPI) ClockIn() gpio.Level { 62 | time.Sleep(spi.Tclk) 63 | spi.Sclk.Low() // SPI device writes on the falling edge 64 | time.Sleep(spi.Tclk) 65 | b := spi.Miso.Read() 66 | spi.Sclk.High() 67 | return b 68 | } 69 | 70 | // ClockOut clocks out a data bit to the SPI device on Mosi. 71 | // Assumes clock starts low and ends with the falling edge of the next clock. 72 | // Assumes caller already holds the Mu lock. 73 | func (spi *SPI) ClockOut(l gpio.Level) { 74 | spi.Mosi.Write(l) 75 | time.Sleep(spi.Tclk) 76 | spi.Sclk.High() // SPI device reads on the rising edge 77 | time.Sleep(spi.Tclk) 78 | spi.Sclk.Low() 79 | } 80 | --------------------------------------------------------------------------------