Skip to content

Commit

Permalink
Implement ioctl access to Linux GPIO chips/lines.
Browse files Browse the repository at this point in the history
  • Loading branch information
gsexton committed Aug 28, 2024
1 parent 50d4ed0 commit f2576ad
Show file tree
Hide file tree
Showing 9 changed files with 1,964 additions and 768 deletions.
73 changes: 73 additions & 0 deletions gpioioctl/Testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Testing

The tests implemented perform *functional* tests of the library. This means that
the tests performed interact with a GPIO chipset, and perform actual read/write
operations. Using this test set, it's possible to quickly and accurately check
if the library is working as expected on a specific hardware/kernel combination.

## Requirements

Although the library is not Raspberry Pi specific, the GPIO pin names used for
tests are.

As written, the tests must be executed on a Raspberry Pi SBC running Linux. Tested
models are:

* Raspberry Pi 3B
* Raspberry Pi Zero W
* Raspberry Pi 4
* Raspberry Pi 5

You must also have the golang SDK installed.

## Setting Up

In order to execute the functional tests, you must jumper the sets of pins shown
below together.

For example, the single line tests require GPIO5 and GPIO13 to be connected to
each other, so a jumper is required between pins 29 and 33. For the multi-line
tests to work, you must connect the following GPIO pins together with jumpers.

| GPIO Output | Output Pin # | GPIO Input | Input Pin # |
| ----------- | ------------ | ---------- | ----------- |
| GPIO2 | 3 | GPIO10 | 19 |
| GPIO3 | 5 | GPIO11 | 23 |
| GPIO4 | 7 | GPIO12 | 32 |
| GPIO5 | 29 | GPIO13 | 33 |
| GPIO6 | 31 | GPIO14 | 8 |
| GPIO7 | 26 | GPIO15 | 10 |
| GPIO8 | 24 | GPIO16 | 36 |
| GPIO9 | 21 | GPIO17 | 11 |

## Cross-Compiling
If you don't have a working go installation on the target machine, you can cross
compile from one machine and then copy the test binary to the target machine.

To cross compile for Raspberry Pi, execute the command:

```bash
$periph.io/x/host/gpioctl> GOOS=linux GOARCH=arm64 go test -c
$periph.io/x/host/gpioctl> scp gpioioctl.test [email protected]:~
$periph.io/x/host/gpioctl> ssh [email protected]
$user> ./gpioioctl.test -test.v
```
for Pi Zero W, use:

```bash
$periph.io/x/host/gpioctl> GOOS=linux GOARCH=arm GOARM=6 go test -c
$periph.io/x/host/gpioctl> scp gpioioctl.test [email protected]:~
$periph.io/x/host/gpioctl> ssh [email protected]
$user> ./gpioioctl.test -test.v

```

## Executing the Tests

After connecting the jumper wires as shown above, and you have golang installed
and the go/bin directory in the path, change to this directory and execute the
command:

```bash
$> go test -v -cover
```
103 changes: 103 additions & 0 deletions gpioioctl/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package gpioioctl_test
// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.


import (
"fmt"
"log"
"time"

"periph.io/x/conn/v3/driver/driverreg"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/gpio/gpioreg"
"periph.io/x/host/v3"
"periph.io/x/host/v3/gpioioctl"
)

func ExampleChips() {
_, _ = host.Init()
_, _ = driverreg.Init()

fmt.Println("GPIO Test Program")
chip := gpioioctl.Chips[0]
defer chip.Close()
fmt.Println(chip.String())
// Test by flashing an LED.
led := gpioreg.ByName("GPIO5")
fmt.Println("Flashing LED ", led.Name())
for i := range 20 {
_ = led.Out((i % 2) == 0)
time.Sleep(500 * time.Millisecond)
}
_ = led.Out(true)

testRotary(chip, "GPIO20", "GPIO21", "GPIO19")
}

// Test the LineSet functionality by using it to read a Rotary Encoder w/ Button.
func testRotary(chip *gpioioctl.GPIOChip, stateLine, dataLine, buttonLine string) {
config := gpioioctl.LineSetConfig{DefaultDirection: gpioioctl.LineInput, DefaultEdge: gpio.RisingEdge, DefaultPull: gpio.PullUp}
config.Lines = []string{stateLine, dataLine, buttonLine}
// The Data Pin of the Rotary Encoder should NOT have an edge.
_ = config.AddOverrides(gpioioctl.LineInput, gpio.NoEdge, gpio.PullUp, dataLine)
ls, err := chip.LineSetFromConfig(&config)
if err != nil {
log.Fatal(err)
}
defer ls.Close()
statePinNumber := uint32(ls.ByOffset(0).Number())
buttonPinNumber := uint32(ls.ByOffset(2).Number())

var tLast = time.Now().Add(-1 * time.Second)
var halting bool
go func() {
time.Sleep(60 * time.Second)
halting = true
fmt.Println("Sending halt!")
_ = ls.Halt()
}()
fmt.Println("Test Rotary Switch - Turn dial to test rotary encoder, press button to test it.")
for {
lineNumber, _, err := ls.WaitForEdge(0)
if err == nil {
tNow := time.Now()
if (tNow.UnixMilli() - tLast.UnixMilli()) < 100 {
continue
}
tLast = tNow
if lineNumber == statePinNumber {
var bits uint64
tDeadline := tNow.UnixNano() + 20_000_000
var consecutive uint64
for time.Now().UnixNano() < tDeadline {
// Spin on reading the pins until we get some number
// of consecutive readings that are the same.
bits, _ = ls.Read(0x03)
if bits&0x01 == 0x00 {
// We're bouncing.
consecutive = 0
} else {
consecutive += 1
if consecutive > 25 {
if bits == 0x01 {
fmt.Printf("Clockwise bits=%d\n", bits)
} else if bits == 0x03 {
fmt.Printf("CounterClockwise bits=%d\n", bits)
}
break
}
}
}
} else if lineNumber == buttonPinNumber {
fmt.Println("Button Pressed!")
}
} else {
fmt.Println("Timeout detected")
if halting {
break
}
}
}
}
Loading

0 comments on commit f2576ad

Please sign in to comment.