diff --git a/conn/gpio/gpio.go b/conn/gpio/gpio.go index 3739df8b7..fce9d0de1 100644 --- a/conn/gpio/gpio.go +++ b/conn/gpio/gpio.go @@ -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" @@ -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. @@ -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 @@ -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. @@ -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 diff --git a/conn/gpio/gpio_test.go b/conn/gpio/gpio_test.go index 4ea0fd561..2dfee1476 100644 --- a/conn/gpio/gpio_test.go +++ b/conn/gpio/gpio_test.go @@ -43,6 +43,74 @@ 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() @@ -50,7 +118,7 @@ func TestInvalid(t *testing.T) { 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() } } diff --git a/conn/gpio/gpiotest/gpiotest.go b/conn/gpio/gpiotest/gpiotest.go index 78605f8c6..dae5a915d 100644 --- a/conn/gpio/gpiotest/gpiotest.go +++ b/conn/gpio/gpiotest/gpiotest.go @@ -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{} diff --git a/conn/gpio/gpiotest/gpiotest_test.go b/conn/gpio/gpiotest/gpiotest_test.go index d090412da..c7353ddaa 100644 --- a/conn/gpio/gpiotest/gpiotest_test.go +++ b/conn/gpio/gpiotest/gpiotest_test.go @@ -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() } diff --git a/host/allwinner/gpio.go b/host/allwinner/gpio.go index 65ea357ea..2cc9051ea 100644 --- a/host/allwinner/gpio.go +++ b/host/allwinner/gpio.go @@ -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. diff --git a/host/allwinner/gpio_pl.go b/host/allwinner/gpio_pl.go index 314976c8f..3a3dfa4ef 100644 --- a/host/allwinner/gpio_pl.go +++ b/host/allwinner/gpio_pl.go @@ -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. diff --git a/host/bcm283x/gpio.go b/host/bcm283x/gpio.go index 987a35000..1a3ba49c3 100644 --- a/host/bcm283x/gpio.go +++ b/host/bcm283x/gpio.go @@ -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. diff --git a/host/sysfs/gpio.go b/host/sysfs/gpio.go index 6f42f619b..87b8b71a6 100644 --- a/host/sysfs/gpio.go +++ b/host/sysfs/gpio.go @@ -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. diff --git a/host/sysfs/gpio_test.go b/host/sysfs/gpio_test.go index 5c41d49f8..7cecbe612 100644 --- a/host/sysfs/gpio_test.go +++ b/host/sysfs/gpio_test.go @@ -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") diff --git a/host/sysfs/led.go b/host/sysfs/led.go index 7f1c6b021..49d31315b 100644 --- a/host/sysfs/led.go +++ b/host/sysfs/led.go @@ -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 { diff --git a/host/sysfs/led_test.go b/host/sysfs/led_test.go index 0a6e7bf3b..2d053a42b 100644 --- a/host/sysfs/led_test.go +++ b/host/sysfs/led_test.go @@ -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") - } }