-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
raspi(pwm): add support for sysfs and fix pi-blaster (#1048)
- Loading branch information
1 parent
a2690d2
commit 915d0c8
Showing
25 changed files
with
1,054 additions
and
805 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
//go:build example | ||
// +build example | ||
|
||
// Do not build by default. | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"gobot.io/x/gobot/v2" | ||
"gobot.io/x/gobot/v2/drivers/gpio" | ||
"gobot.io/x/gobot/v2/platforms/adaptors" | ||
"gobot.io/x/gobot/v2/platforms/raspi" | ||
) | ||
|
||
// Wiring | ||
// PWM Raspi: header pin 12 (GPIO18-PWM0), please refer to the README.md, located in the folder of raspi platform, on | ||
// how to activate the pwm support. | ||
// Servo: orange (PWM), black (GND), red (VCC) 4-6V (please read the manual of your device) | ||
func main() { | ||
const ( | ||
pwmPin = "pwm0" | ||
wait = 3 * time.Second | ||
|
||
fiftyHzNanos = 20 * 1000 * 1000 // 50Hz = 0.02 sec = 20 ms | ||
) | ||
// usually a frequency of 50Hz is used for servos, most servos have 0.5 ms..2.5 ms for 0-180°, however the mapping | ||
// can be changed with options... | ||
// | ||
// for usage of pi-blaster driver just add the option "adaptors.WithPWMUsePiBlaster()" and use your pin number | ||
// instead of "pwm0" | ||
adaptor := raspi.NewAdaptor(adaptors.WithPWMDefaultPeriodForPin(pwmPin, fiftyHzNanos)) | ||
servo := gpio.NewServoDriver(adaptor, pwmPin) | ||
|
||
work := func() { | ||
fmt.Printf("first move to minimal position for %s...\n", wait) | ||
if err := servo.ToMin(); err != nil { | ||
log.Println(err) | ||
} | ||
|
||
time.Sleep(wait) | ||
|
||
fmt.Printf("second move to center position for %s...\n", wait) | ||
if err := servo.ToCenter(); err != nil { | ||
log.Println(err) | ||
} | ||
|
||
time.Sleep(wait) | ||
|
||
fmt.Printf("third move to maximal position for %s...\n", wait) | ||
if err := servo.ToMax(); err != nil { | ||
log.Println(err) | ||
} | ||
|
||
time.Sleep(wait) | ||
|
||
fmt.Println("finally move 0-180° (or what your servo do for the new mapping) and back forever...") | ||
angle := 0 | ||
fadeAmount := 45 | ||
|
||
gobot.Every(time.Second, func() { | ||
if err := servo.Move(byte(angle)); err != nil { | ||
log.Println(err) | ||
} | ||
angle = angle + fadeAmount | ||
if angle < 0 || angle > 180 { | ||
if angle < 0 { | ||
angle = 0 | ||
} | ||
if angle > 180 { | ||
angle = 180 | ||
} | ||
// change direction and recalculate | ||
fadeAmount = -fadeAmount | ||
angle = angle + fadeAmount | ||
} | ||
}) | ||
} | ||
|
||
robot := gobot.NewRobot("motorBot", | ||
[]gobot.Connection{adaptor}, | ||
[]gobot.Device{servo}, | ||
work, | ||
) | ||
|
||
if err := robot.Start(); err != nil { | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package adaptors | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"strconv" | ||
|
||
"gobot.io/x/gobot/v2/system" | ||
) | ||
|
||
const ( | ||
piBlasterPath = "/dev/pi-blaster" | ||
piBlasterMinDutyNano = 10000 // 10 us | ||
) | ||
|
||
// piBlasterPWMPin is the Raspberry Pi implementation of the PWMPinner interface. | ||
// It uses Pi Blaster. | ||
type piBlasterPWMPin struct { | ||
sys *system.Accesser | ||
pin string | ||
dc uint32 | ||
period uint32 | ||
} | ||
|
||
// newPiBlasterPWMPin returns a new PWM pin for pi-blaster access. | ||
func newPiBlasterPWMPin(sys *system.Accesser, pinNo int) *piBlasterPWMPin { | ||
return &piBlasterPWMPin{ | ||
sys: sys, | ||
pin: strconv.Itoa(pinNo), | ||
} | ||
} | ||
|
||
// Export exports the pin for use by the Raspberry Pi | ||
func (p *piBlasterPWMPin) Export() error { | ||
return nil | ||
} | ||
|
||
// Unexport releases the pin from the operating system | ||
func (p *piBlasterPWMPin) Unexport() error { | ||
return p.writeValue(fmt.Sprintf("release %v\n", p.pin)) | ||
} | ||
|
||
// Enabled returns always true for "enabled" | ||
func (p *piBlasterPWMPin) Enabled() (bool, error) { | ||
return true, nil | ||
} | ||
|
||
// SetEnabled do nothing for PiBlaster | ||
func (p *piBlasterPWMPin) SetEnabled(e bool) error { | ||
return nil | ||
} | ||
|
||
// Polarity returns always true for "normal" | ||
func (p *piBlasterPWMPin) Polarity() (bool, error) { | ||
return true, nil | ||
} | ||
|
||
// SetPolarity does not do anything when using PiBlaster | ||
func (p *piBlasterPWMPin) SetPolarity(bool) error { | ||
return nil | ||
} | ||
|
||
// Period returns the cached PWM period for pin | ||
func (p *piBlasterPWMPin) Period() (uint32, error) { | ||
return p.period, nil | ||
} | ||
|
||
// SetPeriod uses PiBlaster setting and cannot be changed. We allow setting once here to define a base period for | ||
// ServoWrite(). see https://github.com/sarfata/pi-blaster#how-to-adjust-the-frequency-and-the-resolution-of-the-pwm | ||
func (p *piBlasterPWMPin) SetPeriod(period uint32) error { | ||
if p.period != 0 { | ||
return fmt.Errorf("the period of PWM pins needs to be set to '%d' in pi-blaster source code", period) | ||
} | ||
p.period = period | ||
return nil | ||
} | ||
|
||
// DutyCycle returns the duty cycle for the pin | ||
func (p *piBlasterPWMPin) DutyCycle() (uint32, error) { | ||
return p.dc, nil | ||
} | ||
|
||
// SetDutyCycle writes the duty cycle to the pin | ||
func (p *piBlasterPWMPin) SetDutyCycle(dutyNanos uint32) error { | ||
if p.period == 0 { | ||
return fmt.Errorf("pi-blaster PWM pin period not set while try to set duty cycle to '%d'", dutyNanos) | ||
} | ||
|
||
if dutyNanos > p.period { | ||
return fmt.Errorf("the duty cycle (%d) exceeds period (%d) for pi-blaster", dutyNanos, p.period) | ||
} | ||
|
||
// never go below minimum allowed duty for pi blaster unless the duty equals to 0 | ||
if dutyNanos < piBlasterMinDutyNano && dutyNanos != 0 { | ||
dutyNanos = piBlasterMinDutyNano | ||
fmt.Printf("duty cycle value limited to '%d' ns for pi-blaster", dutyNanos) | ||
} | ||
|
||
duty := float64(dutyNanos) / float64(p.period) | ||
if err := p.writeValue(fmt.Sprintf("%v=%v\n", p.pin, duty)); err != nil { | ||
return err | ||
} | ||
|
||
p.dc = dutyNanos | ||
return nil | ||
} | ||
|
||
func (p *piBlasterPWMPin) writeValue(data string) error { | ||
fi, err := p.sys.OpenFile(piBlasterPath, os.O_WRONLY|os.O_APPEND, 0o644) | ||
defer fi.Close() //nolint:staticcheck // for historical reasons | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = fi.WriteString(data) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package adaptors | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"gobot.io/x/gobot/v2" | ||
"gobot.io/x/gobot/v2/system" | ||
) | ||
|
||
var _ gobot.PWMPinner = (*piBlasterPWMPin)(nil) | ||
|
||
func TestPiBlasterPWMPin(t *testing.T) { | ||
// arrange | ||
const path = "/dev/pi-blaster" | ||
a := system.NewAccesser() | ||
a.UseMockFilesystem([]string{path}) | ||
pin := newPiBlasterPWMPin(a, 1) | ||
// act & assert: activate pin for usage | ||
require.NoError(t, pin.Export()) | ||
require.NoError(t, pin.SetEnabled(true)) | ||
// act & assert: get and set polarity | ||
val, err := pin.Polarity() | ||
require.NoError(t, err) | ||
assert.True(t, val) | ||
require.NoError(t, pin.SetPolarity(false)) | ||
polarity, err := pin.Polarity() | ||
assert.True(t, polarity) | ||
require.NoError(t, err) | ||
// act & assert: get and set period | ||
period, err := pin.Period() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(0), period) | ||
require.NoError(t, pin.SetPeriod(20000000)) | ||
period, err = pin.Period() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(20000000), period) | ||
err = pin.SetPeriod(10000000) | ||
require.EqualError(t, err, "the period of PWM pins needs to be set to '10000000' in pi-blaster source code") | ||
// act & assert: cleanup | ||
require.NoError(t, pin.Unexport()) | ||
} | ||
|
||
func TestPiBlasterPWMPin_DutyCycle(t *testing.T) { | ||
// arrange | ||
const path = "/dev/pi-blaster" | ||
a := system.NewAccesser() | ||
a.UseMockFilesystem([]string{path}) | ||
pin := newPiBlasterPWMPin(a, 1) | ||
// act & assert: activate pin for usage | ||
require.NoError(t, pin.Export()) | ||
require.NoError(t, pin.SetEnabled(true)) | ||
// act & assert zero | ||
dc, err := pin.DutyCycle() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(0), dc) | ||
// act & assert error without period set, the value remains zero | ||
err = pin.SetDutyCycle(10000) | ||
require.EqualError(t, err, "pi-blaster PWM pin period not set while try to set duty cycle to '10000'") | ||
dc, err = pin.DutyCycle() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(0), dc) | ||
// arrange, act & assert a value | ||
pin.period = 20000000 | ||
require.NoError(t, pin.SetDutyCycle(10000)) | ||
dc, err = pin.DutyCycle() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(10000), dc) | ||
// act & assert error on over limit, the value remains | ||
err = pin.SetDutyCycle(20000001) | ||
require.EqualError(t, err, "the duty cycle (20000001) exceeds period (20000000) for pi-blaster") | ||
dc, err = pin.DutyCycle() | ||
require.NoError(t, err) | ||
assert.Equal(t, uint32(10000), dc) | ||
// act & assert: cleanup | ||
require.NoError(t, pin.Unexport()) | ||
} |
Oops, something went wrong.