diff --git a/platforms/raspi/pwm_pin.go b/platforms/raspi/pwm_pin.go new file mode 100644 index 00000000..226aadaf --- /dev/null +++ b/platforms/raspi/pwm_pin.go @@ -0,0 +1,93 @@ +package raspi + +import ( + "errors" + "fmt" + "os" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/sysfs" +) + +const piBlasterPeriod = 10000000 + +// PWMPin is the Raspberry Pi implementation of the PWMPinner interface. +// It uses Pi Blaster. +type PWMPin struct { + pin string + dc uint32 +} + +// NewPwmPin returns a new PWMPin +func NewPWMPin(pin string) *PWMPin { + return &PWMPin{ + pin: pin} +} + +// Export exports the pin for use by the Raspberry Pi +func (p *PWMPin) Export() error { + return nil +} + +// Unexport unexports the pin and releases the pin from the operating system +func (p *PWMPin) Unexport() error { + return p.piBlaster(fmt.Sprintf("release %v\n", p.pin)) +} + +// Enable enables/disables the PWM pin +func (p *PWMPin) Enable(e bool) (err error) { + return nil +} + +// Polarity returns the polarity either normal or inverted +func (p *PWMPin) Polarity() (polarity string, err error) { + return "normal", nil +} + +// InvertPolarity does not do anything when using PiBlaster +func (p *PWMPin) InvertPolarity(invert bool) (err error) { + return nil +} + +// Period returns the current PWM period for pin +func (p *PWMPin) Period() (period uint32, err error) { + return piBlasterPeriod, nil +} + +// SetPeriod does not do anything when using PiBlaster +func (p *PWMPin) SetPeriod(period uint32) (err error) { + return nil +} + +// DutyCycle returns the duty cycle for the pin +func (p *PWMPin) DutyCycle() (duty uint32, err error) { + return p.dc, nil +} + +// SetDutyCycle writes the duty cycle to the pin +func (p *PWMPin) SetDutyCycle(duty uint32) (err error) { + if duty > piBlasterPeriod { + return errors.New("Duty cycle exceeds period.") + } + p.dc = duty + + val := gobot.FromScale(float64(p.dc), 0, piBlasterPeriod) + + // never go below minimum allowed duty for pi blaster + if val < 0.05 { + val = 0.05 + } + return p.piBlaster(fmt.Sprintf("%v=%v\n", p.pin, val)) +} + +func (p *PWMPin) piBlaster(data string) (err error) { + fi, err := sysfs.OpenFile("/dev/pi-blaster", os.O_WRONLY|os.O_APPEND, 0644) + defer fi.Close() + + if err != nil { + return err + } + + _, err = fi.WriteString(data) + return +} diff --git a/platforms/raspi/pwm_pin_test.go b/platforms/raspi/pwm_pin_test.go new file mode 100644 index 00000000..8a96770b --- /dev/null +++ b/platforms/raspi/pwm_pin_test.go @@ -0,0 +1,38 @@ +package raspi + +import ( + "testing" + + "gobot.io/x/gobot/gobottest" + "gobot.io/x/gobot/sysfs" +) + +var _ sysfs.PWMPinner = (*PWMPin)(nil) + +func TestPwmPin(t *testing.T) { + pin := NewPWMPin("1") + gobottest.Assert(t, pin.Export(), nil) + gobottest.Assert(t, pin.Enable(true), nil) + val, _ := pin.Polarity() + gobottest.Assert(t, val, "normal") + gobottest.Assert(t, pin.InvertPolarity(true), nil) + val, _ = pin.Polarity() + gobottest.Assert(t, val, "normal") + + period, _ := pin.Period() + gobottest.Assert(t, period, uint32(10000000)) + gobottest.Assert(t, pin.SetPeriod(1000), nil) + period, _ = pin.Period() + gobottest.Assert(t, period, uint32(10000000)) + + dc, _ := pin.DutyCycle() + gobottest.Assert(t, dc, uint32(0)) + + // call currently fails in test + gobottest.Refute(t, pin.SetDutyCycle(10000), nil) + dc, _ = pin.DutyCycle() + gobottest.Assert(t, dc, uint32(10000)) + + // call currently fails in test + gobottest.Refute(t, pin.Unexport(), nil) +} diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index b2696dc2..f21d3246 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -23,7 +23,7 @@ type Adaptor struct { name string revision string digitalPins map[int]*sysfs.DigitalPin - pwmPins []int + pwmPins map[int]*PWMPin i2cDefaultBus int i2cBuses [2]sysfs.I2cDevice } @@ -33,7 +33,7 @@ func NewAdaptor() *Adaptor { r := &Adaptor{ name: gobot.DefaultName("RaspberryPi"), digitalPins: make(map[int]*sysfs.DigitalPin), - pwmPins: []int{}, + pwmPins: make(map[int]*PWMPin), } content, _ := readFile() for _, v := range strings.Split(string(content), "\n") { @@ -77,8 +77,10 @@ func (r *Adaptor) Finalize() (err error) { } } for _, pin := range r.pwmPins { - if perr := r.piBlaster(fmt.Sprintf("release %v\n", pin)); err != nil { - err = multierror.Append(err, perr) + if pin != nil { + if perr := pin.Unexport(); err != nil { + err = multierror.Append(err, perr) + } } } for _, bus := range r.i2cBuses { @@ -150,23 +152,24 @@ func (r *Adaptor) GetDefaultBus() int { // PwmWrite writes a PWM signal to the specified pin func (r *Adaptor) PwmWrite(pin string, val byte) (err error) { - sysfsPin, err := r.pwmPin(pin) + sysfsPin, err := r.PWMPin(pin) if err != nil { return err } - return r.piBlaster(fmt.Sprintf("%v=%v\n", sysfsPin, gobot.FromScale(float64(val), 0, 255))) + + duty := uint32(gobot.FromScale(float64(val), 0, 255) * piBlasterPeriod) + return sysfsPin.SetDutyCycle(duty) } // ServoWrite writes a servo signal to the specified pin func (r *Adaptor) ServoWrite(pin string, angle byte) (err error) { - sysfsPin, err := r.pwmPin(pin) + sysfsPin, err := r.PWMPin(pin) if err != nil { return err } - val := (gobot.ToScale(gobot.FromScale(float64(angle), 0, 180), 0, 200) / 1000.0) + 0.05 - - return r.piBlaster(fmt.Sprintf("%v=%v\n", sysfsPin, val)) + duty := uint32(gobot.FromScale(float64(angle), 0, 180) * piBlasterPeriod) + return sysfsPin.SetDutyCycle(duty) } func (r *Adaptor) translatePin(pin string) (i int, err error) { @@ -181,25 +184,20 @@ func (r *Adaptor) translatePin(pin string) (i int, err error) { return } -func (r *Adaptor) pwmPin(pin string) (i int, err error) { - i, err = r.translatePin(pin) +func (r *Adaptor) PWMPin(pin string) (raspiPWMPin *PWMPin, err error) { + i, err := r.translatePin(pin) if err != nil { return } - newPin := true - for _, pin := range r.pwmPins { - if i == pin { - newPin = false + if r.pwmPins[i] == nil { + r.pwmPins[i] = NewPWMPin(strconv.Itoa(i)) + if err = r.pwmPins[i].Export(); err != nil { return } } - if newPin { - r.pwmPins = append(r.pwmPins, i) - } - - return + return r.pwmPins[i], nil } func (r *Adaptor) piBlaster(data string) (err error) { diff --git a/platforms/raspi/raspi_adaptor_test.go b/platforms/raspi/raspi_adaptor_test.go index acfeb661..a0c1ad11 100644 --- a/platforms/raspi/raspi_adaptor_test.go +++ b/platforms/raspi/raspi_adaptor_test.go @@ -110,9 +110,9 @@ func TestAdaptorDigitalPWM(t *testing.T) { gobottest.Assert(t, strings.Split(fs.Files["/dev/pi-blaster"].Contents, "\n")[0], "4=1") - gobottest.Assert(t, a.ServoWrite("11", 255), nil) + gobottest.Assert(t, a.ServoWrite("11", 90), nil) - gobottest.Assert(t, strings.Split(fs.Files["/dev/pi-blaster"].Contents, "\n")[0], "17=0.25") + gobottest.Assert(t, strings.Split(fs.Files["/dev/pi-blaster"].Contents, "\n")[0], "17=0.5") gobottest.Assert(t, a.PwmWrite("notexist", 1), errors.New("Not a valid pin")) gobottest.Assert(t, a.ServoWrite("notexist", 1), errors.New("Not a valid pin"))