-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,73 @@ | ||
# 'gpio' smoke test | ||
# Testing | ||
|
||
Verifies that the library physically work. It requires the user to connect two | ||
GPIO pins together and provide their pin number at the command line. | ||
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. | ||
|
||
Example output running on a Raspberry Pi: | ||
## 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 | ||
|
||
``` | ||
$ gpio-test 12 6 | ||
Using drivers: | ||
- bcm283x | ||
- rpi | ||
- sysfs-gpio | ||
- sysfs-spi | ||
- sysfs-i2c | ||
Using pins and their current state: | ||
- GPIO12: In/High | ||
- GPIO6: In/High | ||
Testing GPIO6 -> GPIO12 | ||
Testing base functionality | ||
GPIO12.In(Float) | ||
GPIO6.Out(Low) | ||
-> GPIO12: In/Low | ||
-> GPIO6: Out/Low | ||
GPIO6.Out(High) | ||
-> GPIO12: In/High | ||
-> GPIO6: Out/High | ||
Testing edges | ||
GPIO12.Edges() | ||
GPIO6.Out(Low) | ||
Low <- GPIO12 | ||
GPIO6.Out(High) | ||
High <- GPIO12 | ||
GPIO6.Out(Low) | ||
Low <- GPIO12 | ||
GPIO12.DisableEdges() | ||
Testing pull resistor | ||
GPIO6.In(Down) | ||
-> GPIO12: In/Low | ||
-> GPIO6: In/Low | ||
GPIO6.In(Up) | ||
-> GPIO12: In/High | ||
-> GPIO6: In/High | ||
Testing GPIO12 -> GPIO6 | ||
Testing base functionality | ||
GPIO6.In(Float) | ||
GPIO12.Out(Low) | ||
-> GPIO6: In/Low | ||
-> GPIO12: Out/Low | ||
GPIO12.Out(High) | ||
-> GPIO6: In/High | ||
-> GPIO12: Out/High | ||
Testing edges | ||
GPIO6.Edges() | ||
GPIO12.Out(Low) | ||
Low <- GPIO6 | ||
GPIO12.Out(High) | ||
High <- GPIO6 | ||
GPIO12.Out(Low) | ||
Low <- GPIO6 | ||
GPIO6.DisableEdges() | ||
Testing pull resistor | ||
GPIO12.In(Down) | ||
-> GPIO6: In/Low | ||
-> GPIO12: In/Low | ||
GPIO12.In(Up) | ||
-> GPIO6: In/High | ||
-> GPIO12: In/High | ||
|
||
## 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 | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package gpiosmoketest | ||
|
||
// 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. | ||
// | ||
// The In/Out tests depend upon having a jumper wire connecting _OUT_LINE and | ||
// _IN_LINE | ||
|
||
import ( | ||
"time" | ||
|
||
"periph.io/x/conn/v3/driver/driverreg" | ||
"periph.io/x/conn/v3/gpio" | ||
"periph.io/x/host/v3/gpioioctl" | ||
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: ubuntu-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: ubuntu-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: ubuntu-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / test: ubuntu-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: macos-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: macos-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: macos-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / test: macos-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / test: windows-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: windows-latest
Check failure on line 15 in periph-smoketest/gpiosmoketest/gpio.go GitHub Actions / lint: windows-latest
|
||
) | ||
|
||
const ( | ||
_OUT_LINE = "GPIO5" | ||
_IN_LINE = "GPIO13" | ||
) | ||
|
||
func init() { | ||
_, _ = driverreg.Init() | ||
} | ||
|
||
func TestWriteReadSinglePin(t *SmokeTestT) { | ||
var err error | ||
chip := gpioioctl.Chips[0] | ||
inLine := chip.ByName(_IN_LINE) | ||
outLine := chip.ByName(_OUT_LINE) | ||
defer inLine.Close() | ||
defer outLine.Close() | ||
err = outLine.Out(true) | ||
if err != nil { | ||
t.Errorf("outLine.Out() %s", err) | ||
} | ||
if val := inLine.Read(); !val { | ||
t.Error("Error reading/writing GPIO Pin. Expected true, received false!") | ||
} | ||
if inLine.Pull() != gpio.PullUp { | ||
t.Errorf("Pull() returned %s expected %s", gpioioctl.PullLabels[inLine.Pull()], gpioioctl.PullLabels[gpio.PullUp]) | ||
} | ||
err = outLine.Out(false) | ||
if err != nil { | ||
t.Errorf("outLine.Out() %s", err) | ||
} | ||
if val := inLine.Read(); val { | ||
t.Error("Error reading/writing GPIO Pin. Expected false, received true!") | ||
} | ||
/* | ||
By Design, lines should auto change directions if Read()/Out() are called | ||
and they don't match. | ||
*/ | ||
err = inLine.Out(false) | ||
if err != nil { | ||
t.Errorf("inLine.Out() %s", err) | ||
} | ||
time.Sleep(500 * time.Millisecond) | ||
err = inLine.Out(true) | ||
if err != nil { | ||
t.Errorf("inLine.Out() %s", err) | ||
} | ||
if val := outLine.Read(); !val { | ||
t.Error("Error read/writing with auto-reverse of line functions.") | ||
} | ||
err = inLine.Out(false) | ||
if err != nil { | ||
t.Errorf("TestWriteReadSinglePin() %s", err) | ||
} | ||
if val := outLine.Read(); val { | ||
t.Error("Error read/writing with auto-reverse of line functions.") | ||
} | ||
|
||
} | ||
|
||
func clearEdges(line gpio.PinIn) bool { | ||
result := false | ||
for line.WaitForEdge(10 * time.Millisecond) { | ||
result = true | ||
} | ||
return result | ||
} | ||
|
||
func TestWaitForEdgeTimeout(t *SmokeTestT) { | ||
line := gpioioctl.Chips[0].ByName(_IN_LINE) | ||
defer line.Close() | ||
err := line.In(gpio.PullUp, gpio.BothEdges) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
clearEdges(line) | ||
tStart := time.Now().UnixMilli() | ||
line.WaitForEdge(5 * time.Second) | ||
tEnd := time.Now().UnixMilli() | ||
tDiff := tEnd - tStart | ||
if tDiff < 4500 || tDiff > 5500 { | ||
t.Errorf("timeout duration failure. Expected duration: 5000, Actual duration: %d", tDiff) | ||
} | ||
} | ||
|
||
// Test detection of rising, falling, and both. | ||
func TestWaitForEdgeSinglePin(t *SmokeTestT) { | ||
tests := []struct { | ||
startVal gpio.Level | ||
edge gpio.Edge | ||
writeVal gpio.Level | ||
}{ | ||
{startVal: false, edge: gpio.RisingEdge, writeVal: true}, | ||
{startVal: true, edge: gpio.FallingEdge, writeVal: false}, | ||
{startVal: false, edge: gpio.BothEdges, writeVal: true}, | ||
{startVal: true, edge: gpio.BothEdges, writeVal: false}, | ||
} | ||
var err error | ||
line := gpioioctl.Chips[0].ByName(_IN_LINE) | ||
outLine := gpioioctl.Chips[0].ByName(_OUT_LINE) | ||
defer line.Close() | ||
defer outLine.Close() | ||
|
||
for _, test := range tests { | ||
err = outLine.Out(test.startVal) | ||
if err != nil { | ||
t.Errorf("set initial value. %s", err) | ||
} | ||
err = line.In(gpio.PullUp, test.edge) | ||
if err != nil { | ||
t.Errorf("line.In() %s", err) | ||
} | ||
clearEdges(line) | ||
err = outLine.Out(test.writeVal) | ||
if err != nil { | ||
t.Errorf("outLine.Out() %s", err) | ||
} | ||
if edgeReceived := line.WaitForEdge(time.Second); !edgeReceived { | ||
t.Errorf("Expected Edge %s was not received on transition from %t to %t", gpioioctl.EdgeLabels[test.edge], test.startVal, test.writeVal) | ||
} | ||
} | ||
} | ||
|
||
func TestHalt(t *SmokeTestT) { | ||
line := gpioioctl.Chips[0].ByName(_IN_LINE) | ||
defer line.Close() | ||
err := line.In(gpio.PullUp, gpio.BothEdges) | ||
if err != nil { | ||
t.Fatalf("TestHalt() %s", err) | ||
} | ||
clearEdges(line) | ||
// So what we'll do here is setup a goroutine to wait three seconds and then send a halt. | ||
go func() { | ||
time.Sleep(time.Second * 3) | ||
err = line.Halt() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
}() | ||
tStart := time.Now().UnixMilli() | ||
line.WaitForEdge(time.Second * 30) | ||
tEnd := time.Now().UnixMilli() | ||
tDiff := tEnd - tStart | ||
if tDiff > 3500 { | ||
t.Errorf("error calling halt to interrupt WaitForEdge() Duration %d exceeded expected value.", tDiff) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright 2025 The Periph Authors. All rights reserved. | ||
// Use of this source code is governed under the Apache License, Version 2.0 | ||
// that can be found in the LICENSE file. | ||
|
||
// Package gpioioctlsmoketest is leveraged by periph-smoketest to verify that basic | ||
// GPIO pin functionality works. | ||
package gpiosmoketest | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"log" | ||
) | ||
|
||
type SmokeTestT struct { | ||
error bool | ||
fatal bool | ||
} | ||
|
||
func (st *SmokeTestT) Error(args ...any) { | ||
st.error = true | ||
log.Println(args...) | ||
} | ||
func (st *SmokeTestT) Errorf(format string, args ...any) { | ||
st.error = true | ||
log.Printf(format, args...) | ||
} | ||
func (st *SmokeTestT) Fatal(args ...any) { | ||
st.fatal = true | ||
log.Fatal(args...) | ||
} | ||
func (st *SmokeTestT) Fatalf(format string, args ...any) { | ||
st.fatal = true | ||
log.Fatalf(format, args...) | ||
} | ||
func (st *SmokeTestT) Log(args ...any) { | ||
log.Println(args...) | ||
} | ||
func (st *SmokeTestT) Logf(format string, args ...any) { | ||
log.Printf(format, args...) | ||
} | ||
func (st *SmokeTestT) ErrorsOrFatals() bool { | ||
return st.error || st.fatal | ||
} | ||
|
||
type SmokeTest struct { | ||
} | ||
|
||
// Name implements periph-smoketest.SmokeTest. | ||
func (s *SmokeTest) Name() string { | ||
return "gpio" | ||
} | ||
|
||
// Description implements periph-smoketest.SmokeTest. | ||
func (s *SmokeTest) Description() string { | ||
return "Tests basic functionality, edge detection and input pull resistors" | ||
} | ||
|
||
// Run implements periph-smoketest.SmokeTest. | ||
func (s *SmokeTest) Run(f *flag.FlagSet, args []string) error { | ||
st := &SmokeTestT{} | ||
TestWriteReadSinglePin(st) | ||
TestWaitForEdgeTimeout(st) | ||
TestWaitForEdgeSinglePin(st) | ||
TestHalt(st) | ||
TestLineSetCreation(st) | ||
TestLineSetReadWrite(st) | ||
TestLineSetWaitForEdgeTimeout(st) | ||
TestLineSetHalt(st) | ||
TestLineSetWaitForEdge(st) | ||
TestLineSetConfigWithOverride(st) | ||
if st.ErrorsOrFatals() { | ||
return errors.New("Smoketest failure.") | ||
} | ||
return nil | ||
} | ||
|