gobot.io/x/gobot/v2@v2.1.0/platforms/adaptors/pwmpinsadaptor.go (about)

     1  package adaptors
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	multierror "github.com/hashicorp/go-multierror"
     8  	"gobot.io/x/gobot/v2"
     9  	"gobot.io/x/gobot/v2/system"
    10  )
    11  
    12  // note for period in nano seconds:
    13  // 100000000ns = 100ms = 10Hz, 10000000ns = 10ms = 100Hz,  1000000ns = 1ms = 1kHz,
    14  // 100000ns = 100us = 10kHz, 10000ns = 10us = 100kHz, 1000ns = 1us = 1MHz,
    15  // 100ns = 10MHz, 10ns = 100MHz, 1ns = 1GHz
    16  const pwmPeriodDefault = 10000000 // 10 ms = 100 Hz
    17  
    18  type pwmPinTranslator func(pin string) (path string, channel int, err error)
    19  type pwmPinInitializer func(gobot.PWMPinner) error
    20  
    21  type pwmPinsOption interface {
    22  	setInitializer(pwmPinInitializer)
    23  	setDefaultPeriod(uint32)
    24  	setPolarityInvertedIdentifier(string)
    25  }
    26  
    27  // PWMPinsAdaptor is a adaptor for PWM pins, normally used for composition in platforms.
    28  type PWMPinsAdaptor struct {
    29  	sys                        *system.Accesser
    30  	translate                  pwmPinTranslator
    31  	initialize                 pwmPinInitializer
    32  	periodDefault              uint32
    33  	polarityNormalIdentifier   string
    34  	polarityInvertedIdentifier string
    35  	adjustDutyOnSetPeriod      bool
    36  	pins                       map[string]gobot.PWMPinner
    37  	mutex                      sync.Mutex
    38  }
    39  
    40  // NewPWMPinsAdaptor provides the access to PWM pins of the board. It uses sysfs system drivers. The translator is used
    41  // to adapt the pin header naming, which is given by user, to the internal file name nomenclature. This varies by each
    42  // platform. If for some reasons the default initializer is not suitable, it can be given by the option
    43  // "WithPWMPinInitializer()".
    44  func NewPWMPinsAdaptor(sys *system.Accesser, t pwmPinTranslator, options ...func(pwmPinsOption)) *PWMPinsAdaptor {
    45  	a := &PWMPinsAdaptor{
    46  		sys:                        sys,
    47  		translate:                  t,
    48  		periodDefault:              pwmPeriodDefault,
    49  		polarityNormalIdentifier:   "normal",
    50  		polarityInvertedIdentifier: "inverted",
    51  		adjustDutyOnSetPeriod:      true,
    52  	}
    53  	a.initialize = a.getDefaultInitializer()
    54  	for _, option := range options {
    55  		option(a)
    56  	}
    57  	return a
    58  }
    59  
    60  // WithPWMPinInitializer substitute the default initializer.
    61  func WithPWMPinInitializer(pc pwmPinInitializer) func(pwmPinsOption) {
    62  	return func(a pwmPinsOption) {
    63  		a.setInitializer(pc)
    64  	}
    65  }
    66  
    67  // WithPWMPinDefaultPeriod substitute the default period of 10 ms (100 Hz) for all created pins.
    68  func WithPWMPinDefaultPeriod(periodNanoSec uint32) func(pwmPinsOption) {
    69  	return func(a pwmPinsOption) {
    70  		a.setDefaultPeriod(periodNanoSec)
    71  	}
    72  }
    73  
    74  // WithPolarityInvertedIdentifier use the given identifier, which will replace the default "inverted".
    75  func WithPolarityInvertedIdentifier(identifier string) func(pwmPinsOption) {
    76  	return func(a pwmPinsOption) {
    77  		a.setPolarityInvertedIdentifier(identifier)
    78  	}
    79  }
    80  
    81  // Connect prepare new connection to PWM pins.
    82  func (a *PWMPinsAdaptor) Connect() error {
    83  	a.mutex.Lock()
    84  	defer a.mutex.Unlock()
    85  
    86  	a.pins = make(map[string]gobot.PWMPinner)
    87  	return nil
    88  }
    89  
    90  // Finalize closes connection to PWM pins.
    91  func (a *PWMPinsAdaptor) Finalize() (err error) {
    92  	a.mutex.Lock()
    93  	defer a.mutex.Unlock()
    94  
    95  	for _, pin := range a.pins {
    96  		if pin != nil {
    97  			if errs := pin.SetEnabled(false); errs != nil {
    98  				err = multierror.Append(err, errs)
    99  			}
   100  			if errs := pin.Unexport(); errs != nil {
   101  				err = multierror.Append(err, errs)
   102  			}
   103  		}
   104  	}
   105  	a.pins = nil
   106  	return err
   107  }
   108  
   109  // PwmWrite writes a PWM signal to the specified pin.
   110  func (a *PWMPinsAdaptor) PwmWrite(id string, val byte) (err error) {
   111  	a.mutex.Lock()
   112  	defer a.mutex.Unlock()
   113  
   114  	pin, err := a.pwmPin(id)
   115  	if err != nil {
   116  		return
   117  	}
   118  	period, err := pin.Period()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	duty := gobot.FromScale(float64(val), 0, 255.0)
   123  	return pin.SetDutyCycle(uint32(float64(period) * duty))
   124  }
   125  
   126  // ServoWrite writes a servo signal to the specified pin.
   127  func (a *PWMPinsAdaptor) ServoWrite(id string, angle byte) (err error) {
   128  	a.mutex.Lock()
   129  	defer a.mutex.Unlock()
   130  
   131  	pin, err := a.pwmPin(id)
   132  	if err != nil {
   133  		return
   134  	}
   135  	period, err := pin.Period()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	// 0.5 ms => -90
   141  	// 1.5 ms =>   0
   142  	// 2.0 ms =>  90
   143  	minDuty := 100 * 0.0005 * float64(period)
   144  	maxDuty := 100 * 0.0020 * float64(period)
   145  	duty := uint32(gobot.ToScale(gobot.FromScale(float64(angle), 0, 180), minDuty, maxDuty))
   146  	return pin.SetDutyCycle(duty)
   147  }
   148  
   149  // SetPeriod adjusts the period of the specified PWM pin immediately.
   150  // If duty cycle is already set, also this value will be adjusted in the same ratio.
   151  func (a *PWMPinsAdaptor) SetPeriod(id string, period uint32) error {
   152  	a.mutex.Lock()
   153  	defer a.mutex.Unlock()
   154  
   155  	pin, err := a.pwmPin(id)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	return setPeriod(pin, period, a.adjustDutyOnSetPeriod)
   160  }
   161  
   162  // PWMPin initializes the pin for PWM and returns matched pwmPin for specified pin number.
   163  // It implements the PWMPinnerProvider interface.
   164  func (a *PWMPinsAdaptor) PWMPin(id string) (gobot.PWMPinner, error) {
   165  	a.mutex.Lock()
   166  	defer a.mutex.Unlock()
   167  
   168  	return a.pwmPin(id)
   169  }
   170  
   171  func (a *PWMPinsAdaptor) setInitializer(pinInit pwmPinInitializer) {
   172  	a.initialize = pinInit
   173  }
   174  
   175  func (a *PWMPinsAdaptor) setDefaultPeriod(periodNanoSec uint32) {
   176  	a.periodDefault = periodNanoSec
   177  }
   178  
   179  func (a *PWMPinsAdaptor) setPolarityInvertedIdentifier(identifier string) {
   180  	a.polarityInvertedIdentifier = identifier
   181  }
   182  
   183  func (a *PWMPinsAdaptor) getDefaultInitializer() func(gobot.PWMPinner) error {
   184  	return func(pin gobot.PWMPinner) error {
   185  		if err := pin.Export(); err != nil {
   186  			return err
   187  		}
   188  		// Make sure PWM is disabled before change anything (period needs to be >0 for this check)
   189  		if period, _ := pin.Period(); period > 0 {
   190  			if err := pin.SetEnabled(false); err != nil {
   191  				return err
   192  			}
   193  		}
   194  		if err := setPeriod(pin, a.periodDefault, false); err != nil {
   195  			return err
   196  		}
   197  		// period needs to be set >1 before all next statements
   198  		if err := pin.SetPolarity(true); err != nil {
   199  			return err
   200  		}
   201  		return pin.SetEnabled(true)
   202  	}
   203  }
   204  
   205  func (a *PWMPinsAdaptor) pwmPin(id string) (gobot.PWMPinner, error) {
   206  	if a.pins == nil {
   207  		return nil, fmt.Errorf("not connected")
   208  	}
   209  
   210  	pin := a.pins[id]
   211  
   212  	if pin == nil {
   213  		path, channel, err := a.translate(id)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		pin = a.sys.NewPWMPin(path, channel, a.polarityNormalIdentifier, a.polarityInvertedIdentifier)
   218  		if err := a.initialize(pin); err != nil {
   219  			return nil, err
   220  		}
   221  		a.pins[id] = pin
   222  	}
   223  
   224  	return pin, nil
   225  }
   226  
   227  // setPeriod adjusts the PWM period of the given pin. If duty cycle is already set and this feature is not suppressed,
   228  // also this value will be adjusted in the same ratio. The order of writing the values must be observed, otherwise an
   229  // error occur "write error: Invalid argument".
   230  func setPeriod(pin gobot.PWMPinner, period uint32, adjustDuty bool) error {
   231  	var errorBase = fmt.Sprintf("setPeriod(%v, %d) failed", pin, period)
   232  
   233  	var oldDuty uint32
   234  	var err error
   235  	if adjustDuty {
   236  		if oldDuty, err = pin.DutyCycle(); err != nil {
   237  			return fmt.Errorf("%s with '%v'", errorBase, err)
   238  		}
   239  	}
   240  
   241  	if oldDuty == 0 {
   242  		if err := pin.SetPeriod(period); err != nil {
   243  			return fmt.Errorf("%s with '%v'", errorBase, err)
   244  		}
   245  	} else {
   246  		// adjust duty cycle in the same ratio
   247  		oldPeriod, err := pin.Period()
   248  		if err != nil {
   249  			return fmt.Errorf("%s with '%v'", errorBase, err)
   250  		}
   251  		duty := uint32(uint64(oldDuty) * uint64(period) / uint64(oldPeriod))
   252  
   253  		// the order depends on value (duty must not be bigger than period in any situation)
   254  		if duty > oldPeriod {
   255  			if err := pin.SetPeriod(period); err != nil {
   256  				return fmt.Errorf("%s with '%v'", errorBase, err)
   257  			}
   258  			if err := pin.SetDutyCycle(uint32(duty)); err != nil {
   259  				return fmt.Errorf("%s with '%v'", errorBase, err)
   260  			}
   261  		} else {
   262  			if err := pin.SetDutyCycle(uint32(duty)); err != nil {
   263  				return fmt.Errorf("%s with '%v'", errorBase, err)
   264  			}
   265  			if err := pin.SetPeriod(period); err != nil {
   266  				return fmt.Errorf("%s with '%v'", errorBase, err)
   267  			}
   268  		}
   269  	}
   270  	return nil
   271  }