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 }