Skip to content

Commit

Permalink
gpio: extract PWM() from PinOut to PinPWM; add PinDefaultPull.
Browse files Browse the repository at this point in the history
Implement gpio.Duty and related interfaces and remove all stub PWM()
implementations. Working PWM() implementation will follow up soon.
  • Loading branch information
maruel committed Apr 14, 2017
1 parent 00e1c32 commit 2ce56f3
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 64 deletions.
102 changes: 84 additions & 18 deletions conn/gpio/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
// that can be found in the LICENSE file.

// Package gpio defines digital pins.
//
// While all GPIO implementations are expected to implement PinIO, they may
// expose more specific functionality like PinPWM, PinDefaultPull, etc.
package gpio

import (
"errors"
"fmt"
"strconv"
"strings"
"time"

"periph.io/x/periph/conn/pin"
Expand Down Expand Up @@ -78,6 +83,72 @@ func (i Edge) String() string {
return edgeName[edgeIndex[i]:edgeIndex[i+1]]
}

const (
// DutyMax is a duty cycle of 100%.
DutyMax Duty = 65535
// DutyHalf is a 50% duty PWM, which boils down to a normal clock.
DutyHalf Duty = DutyMax / 2
)

// Duty is the duty cycle for a PWM.
//
// Valid values are between 0 and DutyMax.
type Duty int32

func (d Duty) String() string {
// TODO(maruel): Implement one fractional number.
return fmt.Sprintf("%d%%", (d+50)/(DutyMax/100))
}

// Valid returns true if the Duty cycle value is valid.
func (d Duty) Valid() bool {
return d >= 0 && d <= DutyMax
}

// ParseDuty parses a string and converts it to a Duty value.
func ParseDuty(s string) (Duty, error) {
percent := strings.HasSuffix(s, "%")
if percent {
s = s[:len(s)-1]
}
i64, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, err
}
i := Duty(i64)
if percent {
// TODO(maruel): Add support for fractional number.
if i < 0 {
return 0, errors.New("duty must be >= 0%")
}
if i > 100 {
return 0, errors.New("duty must be <= 100%")
}
return ((i * DutyMax) + 49) / 100, nil
}
if i < 0 {
return 0, errors.New("duty must be >= 0")
}
if i > DutyMax {
return 0, fmt.Errorf("duty must be <= %d", DutyMax)
}
return i, nil
}

// PinPWM exposes hardware PWM.
//
// The driver may uses DMA controller underneath for zero CPU implementation.
type PinPWM interface {
// PWM sets the PWM output on supported pins.
//
// To use as a general purpose clock, set duty to DutyHalf. Some pins may
// only support DutyHalf and no other value.
//
// Using 0 as period will use the optimal value as supported/preferred by the
// pin.
PWM(duty Duty, period time.Duration) error
}

// PinIn is an input GPIO pin.
//
// It may optionally support internal pull resistor and edge based triggering.
Expand Down Expand Up @@ -126,13 +197,6 @@ type PinIn interface {
Pull() Pull
}

const (
// Max is the PWM fully at high. One should use Out(High) instead.
Max = 65536
// Half is a 50% PWM duty cycle.
Half = Max / 2
)

// PinOut is an output GPIO pin.
type PinOut interface {
pin.Pin
Expand All @@ -144,26 +208,32 @@ type PinOut interface {
// Out() tries to empty the accumulated edges detected if the gpio was
// previously set as input but this is not 100% guaranteed due to the OS.
Out(l Level) error
// PWM sets a pin as output with a specified duty cycle between 0 and Max.
//
// The pin should use the highest frequency it can use.
//
// Use Half for a 50% duty cycle.
PWM(duty int) error
}

// PinIO is a GPIO pin that supports both input and output. It matches both
// interfaces PinIn and PinOut.
//
// A GPIO pin implementing PinIO may fail at either input or output or both.
//
// The GPIO pin may optionally support more interfaces, like PinPWM,
// PinDefaultPull.
type PinIO interface {
pin.Pin
// PinIn
In(pull Pull, edge Edge) error
Read() Level
WaitForEdge(timeout time.Duration) bool
Pull() Pull
// PinOut
Out(l Level) error
PWM(duty int) error
}

// PinDefaultPull is optionally implemented to return the default pull at boot
// time. This is useful to determine if the pin is acceptable for operation
// with certain devices.
type PinDefaultPull interface {
// DefaultPull returns the pull that is initialized on CPU reset.
DefaultPull() Pull
}

// INVALID implements PinIO and fails on all access.
Expand Down Expand Up @@ -227,10 +297,6 @@ func (invalidPin) Out(Level) error {
return errInvalidPin
}

func (invalidPin) PWM(duty int) error {
return errInvalidPin
}

var _ PinIn = INVALID
var _ PinOut = INVALID
var _ PinIO = INVALID
70 changes: 69 additions & 1 deletion conn/gpio/gpio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,82 @@ func TestStrings(t *testing.T) {
}
}

func TestDuty_String(t *testing.T) {
data := []struct {
d Duty
expected string
}{
{0, "0%"},
{1, "0%"},
{DutyMax / 200, "0%"},
{DutyMax/100 - 1, "1%"},
{DutyMax / 100, "1%"},
{DutyMax, "100%"},
{DutyMax - 1, "100%"},
{DutyHalf, "50%"},
{DutyHalf + 1, "50%"},
{DutyHalf - 1, "50%"},
{DutyHalf + DutyMax/100, "51%"},
{DutyHalf - DutyMax/100, "49%"},
}
for i, line := range data {
if actual := line.d.String(); actual != line.expected {
t.Fatalf("line %d: Duty(%d).String() == %q, expected %q", i, line.d, actual, line.expected)
}
}
}

func TestDuty_Valid(t *testing.T) {
if !Duty(0).Valid() {
t.Fatal("0 is valid")
}
if !DutyHalf.Valid() {
t.Fatal("half is valid")
}
if !DutyMax.Valid() {
t.Fatal("half is valid")
}
if Duty(-1).Valid() {
t.Fatal("-1 is not valid")
}
if (DutyMax + 1).Valid() {
t.Fatal("-1 is not valid")
}
}

func TestParseDuty(t *testing.T) {
data := []struct {
input string
d Duty
hasErr bool
}{
{"", 0, true},
{"0", 0, false},
{"0%", 0, false},
{"1", 1, false},
{"1%", 655, false},
{"100%", DutyMax, false},
{"65535", DutyMax, false},
{"65536", 0, true},
{"101%", 0, true},
{"-1", 0, true},
{"-1%", 0, true},
}
for i, line := range data {
if d, err := ParseDuty(line.input); d != line.d || (err != nil) != line.hasErr {
t.Fatalf("line %d: Parse(%q) == %d, %q, expected %d, %t", i, line.input, d, err, line.d, line.hasErr)
}
}
}

func TestInvalid(t *testing.T) {
if INVALID.String() != "INVALID" || INVALID.Name() != "INVALID" || INVALID.Number() != -1 || INVALID.Function() != "" {
t.Fail()
}
if INVALID.In(Float, NoEdge) != errInvalidPin || INVALID.Read() != Low || INVALID.WaitForEdge(time.Minute) || INVALID.Pull() != PullNoChange {
t.Fail()
}
if INVALID.Out(Low) != errInvalidPin || INVALID.PWM(0) != errInvalidPin {
if INVALID.Out(Low) != errInvalidPin {
t.Fail()
}
}
5 changes: 0 additions & 5 deletions conn/gpio/gpiotest/gpiotest.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,4 @@ func (p *Pin) Out(l gpio.Level) error {
return nil
}

// PWM implements gpio.PinOut.
func (p *Pin) PWM(duty int) error {
return errors.New("gpiotest: pwm is not implemented")
}

var _ gpio.PinIO = &Pin{}
3 changes: 0 additions & 3 deletions conn/gpio/gpiotest/gpiotest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ func TestPin_edge(t *testing.T) {

func TestPin_fail(t *testing.T) {
p := &Pin{N: "GPIO1", Num: 1, Fn: "I2C1_SDA"}
if err := p.PWM(5); err == nil {
t.Fatal()
}
if err := p.In(gpio.Float, gpio.BothEdges); err == nil {
t.Fatal()
}
Expand Down
5 changes: 0 additions & 5 deletions host/allwinner/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,6 @@ func (p *Pin) Out(l gpio.Level) error {
return nil
}

// PWM is not supported.
func (p *Pin) PWM(duty int) error {
return p.wrap(errors.New("pwm is not supported"))
}

//

// function returns the current GPIO pin function.
Expand Down
5 changes: 0 additions & 5 deletions host/allwinner/gpio_pl.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,6 @@ func (p *PinPL) Out(l gpio.Level) error {
return nil
}

// PWM implements gpio.PinOut.
func (p *PinPL) PWM(duty int) error {
return p.wrap(errors.New("pwm is not supported"))
}

//

// function returns the current GPIO pin function.
Expand Down
7 changes: 0 additions & 7 deletions host/bcm283x/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,6 @@ func (p *Pin) Out(l gpio.Level) error {
return nil
}

// PWM implements gpio.PinOut.
func (p *Pin) PWM(duty int) error {
return p.wrap(errors.New("pwm is not supported"))
}

// Special functionality.

// DefaultPull returns the default pull for the function.
//
// The CPU doesn't return the current pull.
Expand Down
5 changes: 0 additions & 5 deletions host/sysfs/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,6 @@ func (p *Pin) Out(l gpio.Level) error {
return nil
}

// PWM implements gpio.PinOut.
func (p *Pin) PWM(duty int) error {
return errors.New("sysfs-gpio: pwm is not supported")
}

//

// open opens the gpio sysfs handle to /value and /direction.
Expand Down
7 changes: 0 additions & 7 deletions host/sysfs/gpio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,6 @@ func TestPin_Out(t *testing.T) {
}
}

func TestPin_PWM(t *testing.T) {
p := Pin{number: 42, name: "foo", root: "/tmp/gpio/priv/"}
if p.PWM(0) == nil {
t.Fatal("sysfs-gpio doesn't support PWM")
}
}

func TestPin_readInt(t *testing.T) {
if _, err := readInt("/tmp/gpio/priv/invalid_file"); err == nil {
t.Fatal("file is not expected to exist")
Expand Down
5 changes: 0 additions & 5 deletions host/sysfs/led.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,6 @@ func (l *LED) Out(level gpio.Level) error {
return err
}

// PWM implements gpio.PinOut.
func (l *LED) PWM(duty int) error {
return errors.New("sysfs-led: pwm is not supported")
}

//

func (l *LED) open() error {
Expand Down
3 changes: 0 additions & 3 deletions host/sysfs/led_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,4 @@ func TestLED_not_supported(t *testing.T) {
if pull := l.Pull(); pull != gpio.PullNoChange {
t.Fatal(pull)
}
if err := l.PWM(0); err == nil {
t.Fatal("not supported")
}
}

0 comments on commit 2ce56f3

Please sign in to comment.