246 lines
5.3 KiB
Go
246 lines
5.3 KiB
Go
package gpio
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gobot.io/x/gobot"
|
|
)
|
|
|
|
type phase [][4]byte
|
|
|
|
// StepperModes to decide on Phase and Stepping
|
|
var StepperModes = struct {
|
|
SinglePhaseStepping [][4]byte
|
|
DualPhaseStepping [][4]byte
|
|
HalfStepping [][4]byte
|
|
}{
|
|
//1 cycle = 4 steps with lesser torque
|
|
SinglePhaseStepping: [][4]byte{
|
|
{1, 0, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 0, 1, 0},
|
|
{0, 0, 0, 1},
|
|
},
|
|
//1 cycle = 4 steps with higher torque and current
|
|
DualPhaseStepping: [][4]byte{
|
|
{1, 0, 0, 1},
|
|
{1, 1, 0, 0},
|
|
{0, 1, 1, 0},
|
|
{0, 0, 1, 1},
|
|
},
|
|
//1 cycle = 8 steps with lesser torque than full stepping
|
|
HalfStepping: [][4]byte{
|
|
{1, 0, 0, 1},
|
|
{1, 0, 0, 0},
|
|
{1, 1, 0, 0},
|
|
{0, 1, 0, 0},
|
|
{0, 1, 1, 0},
|
|
{0, 0, 1, 0},
|
|
{0, 0, 1, 1},
|
|
{0, 0, 0, 1},
|
|
},
|
|
}
|
|
|
|
// StepperDriver object
|
|
type StepperDriver struct {
|
|
name string
|
|
pins [4]string
|
|
connection DigitalWriter
|
|
phase phase
|
|
stepsPerRev uint
|
|
moving bool
|
|
direction string
|
|
stepNum int
|
|
speed uint
|
|
mutex *sync.Mutex
|
|
gobot.Commander
|
|
}
|
|
|
|
// NewStepperDriver returns a new StepperDriver given a
|
|
// DigitalWriter
|
|
// Pins - To which the stepper is connected
|
|
// Phase - Defined by StepperModes {SinglePhaseStepping, DualPhaseStepping, HalfStepping}
|
|
// Steps - No of steps per revolution of Stepper motor
|
|
func NewStepperDriver(a DigitalWriter, pins [4]string, phase phase, stepsPerRev uint) *StepperDriver {
|
|
s := &StepperDriver{
|
|
name: gobot.DefaultName("Stepper"),
|
|
connection: a,
|
|
pins: pins,
|
|
phase: phase,
|
|
stepsPerRev: stepsPerRev,
|
|
moving: false,
|
|
direction: "forward",
|
|
stepNum: 0,
|
|
speed: 1,
|
|
mutex: &sync.Mutex{},
|
|
Commander: gobot.NewCommander(),
|
|
}
|
|
s.speed = s.GetMaxSpeed()
|
|
|
|
s.AddCommand("Move", func(params map[string]interface{}) interface{} {
|
|
steps, _ := strconv.Atoi(params["steps"].(string))
|
|
return s.Move(steps)
|
|
})
|
|
s.AddCommand("Run", func(params map[string]interface{}) interface{} {
|
|
return s.Run()
|
|
})
|
|
s.AddCommand("Halt", func(params map[string]interface{}) interface{} {
|
|
return s.Halt()
|
|
})
|
|
|
|
return s
|
|
}
|
|
|
|
// Name of StepperDriver
|
|
func (s *StepperDriver) Name() string { return s.name }
|
|
|
|
// SetName sets name for StepperDriver
|
|
func (s *StepperDriver) SetName(n string) { s.name = n }
|
|
|
|
// Connection returns StepperDriver's connection
|
|
func (s *StepperDriver) Connection() gobot.Connection { return s.connection.(gobot.Connection) }
|
|
|
|
// Start implements the Driver interface and keeps running the stepper till halt is called
|
|
func (s *StepperDriver) Start() (err error) { return }
|
|
|
|
// Run continuously runs the stepper
|
|
func (s *StepperDriver) Run() (err error) {
|
|
//halt if already moving
|
|
if s.moving == true {
|
|
s.Halt()
|
|
}
|
|
|
|
s.mutex.Lock()
|
|
s.moving = true
|
|
s.mutex.Unlock()
|
|
|
|
go func() {
|
|
for {
|
|
if s.moving == false {
|
|
break
|
|
}
|
|
s.step()
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
// Halt implements the Driver interface and halts the motion of the Stepper
|
|
func (s *StepperDriver) Halt() (err error) {
|
|
s.mutex.Lock()
|
|
s.moving = false
|
|
s.mutex.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// SetDirection sets the direction in which motor should be moving, Default is forward
|
|
func (s *StepperDriver) SetDirection(direction string) error {
|
|
direction = strings.ToLower(direction)
|
|
if direction != "forward" && direction != "backward" {
|
|
return errors.New("Invalid direction. Value should be forward or backward")
|
|
}
|
|
|
|
s.mutex.Lock()
|
|
s.direction = direction
|
|
s.mutex.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// IsMoving returns a bool stating whether motor is currently in motion
|
|
func (s *StepperDriver) IsMoving() bool {
|
|
return s.moving
|
|
}
|
|
|
|
// Step moves motor one step in giving direction
|
|
func (s *StepperDriver) step() error {
|
|
if s.direction == "forward" {
|
|
s.stepNum++
|
|
} else {
|
|
s.stepNum--
|
|
}
|
|
|
|
if s.stepNum >= int(s.stepsPerRev) {
|
|
s.stepNum = 0
|
|
} else if s.stepNum < 0 {
|
|
s.stepNum = int(s.stepsPerRev) - 1
|
|
}
|
|
|
|
r := int(math.Abs(float64(s.stepNum))) % len(s.phase)
|
|
|
|
for i, v := range s.phase[r] {
|
|
if err := s.connection.DigitalWrite(s.pins[i], v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Move moves the motor for given number of steps
|
|
func (s *StepperDriver) Move(stepsToMove int) error {
|
|
if stepsToMove == 0 {
|
|
return s.Halt()
|
|
}
|
|
|
|
if s.moving == true {
|
|
//stop previous motion
|
|
s.Halt()
|
|
}
|
|
|
|
s.mutex.Lock()
|
|
s.moving = true
|
|
s.direction = "forward"
|
|
|
|
if stepsToMove < 0 {
|
|
s.direction = "backward"
|
|
}
|
|
s.mutex.Unlock()
|
|
|
|
stepsLeft := int64(math.Abs(float64(stepsToMove)))
|
|
//Do not remove *1000 and change duration to time.Millisecond. It has been done for a reason
|
|
delay := time.Duration(60000*1000/(s.stepsPerRev*s.speed)) * time.Microsecond
|
|
|
|
for stepsLeft > 0 {
|
|
if err := s.step(); err != nil {
|
|
return err
|
|
}
|
|
stepsLeft--
|
|
time.Sleep(delay)
|
|
}
|
|
|
|
s.moving = false
|
|
return nil
|
|
}
|
|
|
|
// GetCurrentStep gives the current step of motor
|
|
func (s *StepperDriver) GetCurrentStep() int {
|
|
return s.stepNum
|
|
}
|
|
|
|
// GetMaxSpeed gives the max RPM of motor
|
|
func (s *StepperDriver) GetMaxSpeed() uint {
|
|
//considering time for 1 rev as no of steps per rev * 1.5 (min time req between each step)
|
|
return uint(60000 / (float64(s.stepsPerRev) * 1.5))
|
|
}
|
|
|
|
// SetSpeed sets the rpm
|
|
func (s *StepperDriver) SetSpeed(rpm uint) error {
|
|
if rpm <= 0 {
|
|
return errors.New("RPM cannot be a zero or negative value")
|
|
}
|
|
|
|
m := s.GetMaxSpeed()
|
|
if rpm > m {
|
|
rpm = m
|
|
}
|
|
|
|
s.speed = rpm
|
|
return nil
|
|
}
|