mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-29 13:49:14 +08:00
271 lines
6.8 KiB
Go
271 lines
6.8 KiB
Go
package gpio
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gobot.io/x/gobot/v2"
|
|
)
|
|
|
|
const easyDriverDebug = false
|
|
|
|
// easyOptionApplier needs to be implemented by each configurable option type
|
|
type easyOptionApplier interface {
|
|
apply(cfg *easyConfiguration)
|
|
}
|
|
|
|
// easyConfiguration contains all changeable attributes of the driver.
|
|
type easyConfiguration struct {
|
|
dirPin string
|
|
enPin string
|
|
sleepPin string
|
|
}
|
|
|
|
// easyDirPinOption is the type for applying a pin for change direction
|
|
type easyDirPinOption string
|
|
|
|
// easyEnPinOption is the type for applying a pin for device disabling/enabling
|
|
type easyEnPinOption string
|
|
|
|
// easySleepPinOption is the type for applying a pin for setting device to sleep/wake
|
|
type easySleepPinOption string
|
|
|
|
// EasyDriver is an driver for stepper hardware board from SparkFun (https://www.sparkfun.com/products/12779)
|
|
// This should also work for the BigEasyDriver (untested). It is basically a wrapper for the common StepperDriver{}
|
|
// with the specific additions for the board, e.g. direction, enable and sleep outputs.
|
|
type EasyDriver struct {
|
|
*StepperDriver
|
|
easyCfg *easyConfiguration
|
|
stepPin string
|
|
anglePerStep float32
|
|
sleeping bool
|
|
}
|
|
|
|
// NewEasyDriver returns a new driver
|
|
// TODO: Support selecting phase input instead of hard-wiring MS1 and MS2 to board truth table
|
|
// A - DigitalWriter
|
|
// anglePerStep - Step angle of motor
|
|
// stepPin - Pin corresponding to step input on EasyDriver
|
|
//
|
|
// Supported options:
|
|
//
|
|
// "WithName"
|
|
// "WithEasyDirectionPin"
|
|
// "WithEasyEnablePin"
|
|
// "WithEasySleepPin"
|
|
func NewEasyDriver(a DigitalWriter, anglePerStep float32, stepPin string, opts ...interface{}) *EasyDriver {
|
|
if anglePerStep <= 0 {
|
|
panic("angle per step needs to be greater than zero")
|
|
}
|
|
|
|
if stepPin == "" {
|
|
panic("step pin is mandatory for easy driver")
|
|
}
|
|
|
|
stepper := NewStepperDriver(a, [4]string{}, nil, 1)
|
|
stepper.driverCfg.name = gobot.DefaultName("EasyDriver")
|
|
stepper.stepperDebug = easyDriverDebug
|
|
stepper.haltIfRunning = false
|
|
stepper.stepsPerRev = 360.0 / anglePerStep
|
|
d := &EasyDriver{
|
|
StepperDriver: stepper,
|
|
easyCfg: &easyConfiguration{},
|
|
stepPin: stepPin,
|
|
anglePerStep: anglePerStep,
|
|
}
|
|
d.stepFunc = d.onePinStepping
|
|
d.sleepFunc = d.sleepWithSleepPin
|
|
d.beforeHalt = d.shutdown
|
|
|
|
// 1/4 of max speed. Not too fast, not too slow
|
|
d.speedRpm = d.MaxSpeed() / 4
|
|
|
|
for _, opt := range opts {
|
|
switch o := opt.(type) {
|
|
case optionApplier:
|
|
o.apply(d.driverCfg)
|
|
case easyOptionApplier:
|
|
o.apply(d.easyCfg)
|
|
default:
|
|
oNames := []string{"WithEasyDirectionPin", "WithEasyEnablePin", "WithEasySleepPin"}
|
|
msg := fmt.Sprintf("'%s' can not be applied on '%s', consider to use one of the options instead: %s",
|
|
opt, d.driverCfg.name, strings.Join(oNames, ", "))
|
|
panic(msg)
|
|
}
|
|
}
|
|
|
|
return d
|
|
}
|
|
|
|
// WithEasyDirectionPin configure a pin for change the moving direction.
|
|
func WithEasyDirectionPin(pin string) easyOptionApplier {
|
|
return easyDirPinOption(pin)
|
|
}
|
|
|
|
// WithEasyEnablePin configure a pin for disabling/enabling the driver.
|
|
func WithEasyEnablePin(pin string) easyOptionApplier {
|
|
return easyEnPinOption(pin)
|
|
}
|
|
|
|
// WithEasySleepPin configure a pin for sleep/wake the driver.
|
|
func WithEasySleepPin(pin string) easyOptionApplier {
|
|
return easySleepPinOption(pin)
|
|
}
|
|
|
|
// SetDirection sets the direction to be moving.
|
|
func (d *EasyDriver) SetDirection(direction string) error {
|
|
if d.easyCfg.dirPin == "" {
|
|
return fmt.Errorf("dirPin is not set for '%s'", d.driverCfg.name)
|
|
}
|
|
|
|
direction = strings.ToLower(direction)
|
|
if direction != StepperDriverForward && direction != StepperDriverBackward {
|
|
return fmt.Errorf("Invalid direction '%s'. Value should be '%s' or '%s'",
|
|
direction, StepperDriverForward, StepperDriverBackward)
|
|
}
|
|
|
|
writeVal := byte(0) // low is forward
|
|
if direction == StepperDriverBackward {
|
|
writeVal = 1 // high is backward
|
|
}
|
|
|
|
if err := d.digitalWrite(d.easyCfg.dirPin, writeVal); err != nil {
|
|
return err
|
|
}
|
|
|
|
// ensure that write of variable can not interfere with read in step()
|
|
d.valueMutex.Lock()
|
|
defer d.valueMutex.Unlock()
|
|
d.direction = direction
|
|
|
|
return nil
|
|
}
|
|
|
|
// Enable enables all motor output
|
|
func (d *EasyDriver) Enable() error {
|
|
if d.easyCfg.enPin == "" {
|
|
d.disabled = false
|
|
return fmt.Errorf("enPin is not set - board '%s' is enabled by default", d.driverCfg.name)
|
|
}
|
|
|
|
// enPin is active low
|
|
if err := d.digitalWrite(d.easyCfg.enPin, 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.disabled = false
|
|
return nil
|
|
}
|
|
|
|
// Disable disables all motor output
|
|
func (d *EasyDriver) Disable() error {
|
|
if d.easyCfg.enPin == "" {
|
|
return fmt.Errorf("enPin is not set for '%s'", d.driverCfg.name)
|
|
}
|
|
|
|
_ = d.stopIfRunning() // drop step errors
|
|
|
|
// enPin is active low
|
|
if err := d.digitalWrite(d.easyCfg.enPin, 1); err != nil {
|
|
return err
|
|
}
|
|
d.disabled = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsEnabled returns a bool stating whether motor is enabled
|
|
func (d *EasyDriver) IsEnabled() bool {
|
|
return !d.disabled
|
|
}
|
|
|
|
// Wake wakes up the driver
|
|
func (d *EasyDriver) Wake() error {
|
|
if d.easyCfg.sleepPin == "" {
|
|
return fmt.Errorf("sleepPin is not set for '%s'", d.driverCfg.name)
|
|
}
|
|
|
|
// sleepPin is active low
|
|
if err := d.digitalWrite(d.easyCfg.sleepPin, 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.sleeping = false
|
|
|
|
// we need to wait 1ms after sleeping before doing a step to charge the step pump (according to data sheet)
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsSleeping returns a bool stating whether motor is sleeping
|
|
func (d *EasyDriver) IsSleeping() bool {
|
|
return d.sleeping
|
|
}
|
|
|
|
func (d *EasyDriver) onePinStepping() error {
|
|
// ensure that read and write of variables (direction, stepNum) can not interfere
|
|
d.valueMutex.Lock()
|
|
defer d.valueMutex.Unlock()
|
|
|
|
// a valid steps occurs for a low to high transition
|
|
if err := d.digitalWrite(d.stepPin, 0); err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(d.getDelayPerStep())
|
|
if err := d.digitalWrite(d.stepPin, 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.direction == StepperDriverForward {
|
|
d.stepNum++
|
|
} else {
|
|
d.stepNum--
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// sleepWithSleepPin puts the driver to sleep and disables all motor output. Low power mode.
|
|
func (d *EasyDriver) sleepWithSleepPin() error {
|
|
if d.easyCfg.sleepPin == "" {
|
|
return fmt.Errorf("sleepPin is not set for '%s'", d.driverCfg.name)
|
|
}
|
|
|
|
_ = d.stopIfRunning() // drop step errors
|
|
|
|
// sleepPin is active low
|
|
if err := d.digitalWrite(d.easyCfg.sleepPin, 0); err != nil {
|
|
return err
|
|
}
|
|
d.sleeping = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o easyDirPinOption) String() string {
|
|
return "direction pin option easy driver"
|
|
}
|
|
|
|
func (o easyEnPinOption) String() string {
|
|
return "enable pin option easy driver"
|
|
}
|
|
|
|
func (o easySleepPinOption) String() string {
|
|
return "sleep pin option easy driver"
|
|
}
|
|
|
|
func (o easyDirPinOption) apply(cfg *easyConfiguration) {
|
|
cfg.dirPin = string(o)
|
|
}
|
|
|
|
func (o easyEnPinOption) apply(cfg *easyConfiguration) {
|
|
cfg.enPin = string(o)
|
|
}
|
|
|
|
func (o easySleepPinOption) apply(cfg *easyConfiguration) {
|
|
cfg.sleepPin = string(o)
|
|
}
|