You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/tic/tic.go

1340 lines
42 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package tic
import (
"encoding/binary"
"errors"
"fmt"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/i2c"
"periph.io/x/conn/v3/physic"
)
// I2CAddr is the default I²C address for the Tic.
const I2CAddr uint16 = 0x0E
// InputNull represents a null or missing value for some of the Tic's 16-bit
// input variables.
const InputNull uint16 = 0xFFFF
var (
// ErrConnectionFailed is returned when the driver fails to connect.
ErrConnectionFailed = errors.New("failed to connect to Tic")
// ErrInvalidSetting is returned when you provide an invalid value.
ErrInvalidSetting = errors.New("invalid setting")
// ErrUnsupportedVariant is returned when a method or setting isn't
// supported by the Tic variant.
ErrUnsupportedVariant = errors.New("invalid command for Tic variant")
// ErrIncorrectPlanningMode is returned when you call a method that isn't
// compatible with the Tic's current planning mode.
ErrIncorrectPlanningMode = errors.New("incorrect planning mode")
)
// Variant represents the specific Tic controller variant.
type Variant string
const (
TicT825 Variant = "Tic T825"
TicT834 Variant = "Tic T834"
TicT500 Variant = "Tic T500"
TicT249 Variant = "Tic T249"
Tic36v4 Variant = "Tic 36v4"
)
// Dev is a handle to a Tic motor controller device.
type Dev struct {
c conn.Conn
variant Variant
}
// NewI2C returns an object that communicates with a Tic motor controller over
// I²C.
//
// The default address is tic.I2CAddr.
func NewI2C(b i2c.Bus, variant Variant, addr uint16) (*Dev, error) {
// Check the variant is valid.
switch variant {
case TicT825, TicT834, TicT500, TicT249, Tic36v4:
default:
return nil, errors.New("device variant is invalid")
}
d := Dev{
c: &i2c.Dev{Bus: b, Addr: addr},
variant: variant,
}
// Test the connection by doing an I²C transaction. Throw away the result.
_, err := d.GetStepMode()
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrConnectionFailed, err)
}
return &d, nil
}
// String returns the device name in a readable format.
//
// String implements conn.Resource.
func (d *Dev) String() string {
return string(d.variant)
}
// Halt stops the motor abruptly without respecting the deceleration limit.
//
// Halt implements conn.Resource.
func (d *Dev) Halt() error {
return d.HaltAndHold()
}
// GetTargetPosition gets the target position, in microsteps.
//
// This is only possible if the planning mode from GetPlanningMode() is
// tic.PlanningModeTargetPosition.
func (d *Dev) GetTargetPosition() (int32, error) {
mode, err := d.GetPlanningMode()
if err != nil {
return 0, err
}
if mode != PlanningModeTargetPosition {
return 0, ErrIncorrectPlanningMode
}
v, err := d.getVar32(OffsetTargetPosition)
return int32(v), err
}
// SetTargetPosition sets the target position of the Tic, in microsteps.
//
// This function sends a "Set target position" to the Tic. The Tic will enter
// target position planning mode and start moving the motor to reach the target
// position.
func (d *Dev) SetTargetPosition(position int32) error {
return d.commandW32(cmdSetTargetPosition, uint32(position))
}
// GetTargetVelocity gets the target velocity, in microsteps per 10000 seconds.
//
// The default step mode is 1 microstep = 1 full step.
//
// This is only possible if the planning mode from GetPlanningMode() is
// tic.PlanningModeTargetVelocity.
func (d *Dev) GetTargetVelocity() (int32, error) {
mode, err := d.GetPlanningMode()
if err != nil {
return 0, err
}
if mode != PlanningModeTargetVelocity {
return 0, ErrIncorrectPlanningMode
}
v, err := d.getVar32(OffsetTargetVelocity)
return int32(v), err
}
// SetTargetVelocity sets the target velocity of the Tic, in microsteps per
// 10000 seconds.
//
// The default step mode is 1 microstep = 1 full step.
//
// This function sends a "Set target velocity" command to the Tic. The Tic will
// enter target velocity planning mode and start accelerating or decelerating to
// reach the target velocity.
func (d *Dev) SetTargetVelocity(velocity int32) error {
return d.commandW32(cmdSetTargetVelocity, uint32(velocity))
}
// HaltAndSetPosition stops the motor abruptly without respecting the
// deceleration limit and sets the "Current position" variable, which represents
// where the Tic currently thinks the motor's output is.
//
// This function sends a "Halt and set position" command to the Tic. Besides
// stopping the motor and setting the current position, this command also
// clears the "Position uncertain" flag, sets the "Input state" to "halt", and
// clears the "Input after scaling" variable.
func (d *Dev) HaltAndSetPosition(position int32) error {
return d.commandW32(cmdHaltAndSetPosition, uint32(position))
}
// HaltAndHold stops the motor abruptly without respecting the deceleration
// limit.
//
// This function sends a "Halt and hold" command to the Tic. Besides stopping
// the motor, this command also sets the "Position uncertain" flag (because
// the abrupt stop might cause steps to be missed), sets the "Input state" to
// "halt", and clears the "Input after scaling" variable.
func (d *Dev) HaltAndHold() error {
return d.commandQuick(cmdHaltAndHold)
}
// GoHomeReverse tells the Tic to start its homing procedure in the reverse
// direction.
//
// See the "Homing" section of the Tic user's guide for details.
func (d *Dev) GoHomeReverse() error {
return d.commandW7(cmdGoHome, 0)
}
// GoHomeForward tells the Tic to start its homing procedure in the forward
// direction.
//
// See the "Homing" section of the Tic user's guide for details.
func (d *Dev) GoHomeForward() error {
return d.commandW7(cmdGoHome, 1)
}
// ResetCommandTimeout prevents the "Command timeout" error from happening for
// some time.
//
// The Tic's default command timeout period is 1000 ms, but it can be changed or
// disabled in the Tic Control Center.
//
// This function sends a "Reset command timeout" command to the Tic.
func (d *Dev) ResetCommandTimeout() error {
return d.commandQuick(cmdResetCommandTimeout)
}
// Deenergize de-energizes the stepper motor coils.
//
// This function sends a De-energize command to the Tic, causing it to disable
// its stepper motor driver. The motor will stop moving and consuming power. The
// Tic will set the "Intentionally de-energized" error bit, turn on its red LED,
// and drive its ERR line high. This command also sets the "Position uncertain"
// flag (because the Tic is no longer in control of the motor's position).
//
// Note that the Energize command, which can be sent with Energize(), will undo
// the effect of this command (except it will leave the "Position uncertain"
// flag set) and could make the system start up again.
func (d *Dev) Deenergize() error {
return d.commandQuick(cmdDeenergize)
}
// Energize sends the Energize command.
//
// This function sends an Energize command to the Tic, clearing the
// "Intentionally de-energized" error bit. If there are no other errors,
// this allows the system to start up.
func (d *Dev) Energize() error {
return d.commandQuick(cmdEnergize)
}
// ExitSafeStart sends the "Exit safe start" command.
//
// This command causes the safe start violation error to be cleared for 200 ms.
// If there are no other errors, this allows the system to start up.
func (d *Dev) ExitSafeStart() error {
return d.commandQuick(cmdExitSafeStart)
}
// EnterSafeStart sends the "Enter safe start" command.
//
// This command has no effect if safe-start is disabled in the Tic's settings.
//
// This command causes the Tic to stop the motor and set its safe start
// violation error bit. An "Exit safe start" command is required before the Tic
// will move the motor again.
//
// See the Tic user's guide for information about what this command does in
// the other control modes.
func (d *Dev) EnterSafeStart() error {
return d.commandQuick(cmdEnterSafeStart)
}
// Reset sends the Reset command.
//
// This command makes the Tic forget most parts of its current state. For
// more information, see the Tic user's guide.
func (d *Dev) Reset() error {
err := d.commandQuick(cmdReset)
// The Tic's I²C interface will be unreliable for a brief period after the
// Tic receives the Reset command, so delay 10 ms here.
time.Sleep(10 * time.Millisecond)
return err
}
// ClearDriverError attempts to clear a motor driver error.
//
// This function sends a "Clear driver error" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) ClearDriverError() error {
return d.commandQuick(cmdClearDriverError)
}
// GetMaxSpeed gets the current maximum speed, in microsteps per 10000 seconds.
//
// This is the current value, which could differ from the value in the Tic's
// settings.
func (d *Dev) GetMaxSpeed() (uint32, error) {
return d.getVar32(OffsetSpeedMax)
}
// SetMaxSpeed sets the maximum speed, in units of steps per 10000 seconds.
//
// Example:
//
// err := dev.SetMaxSpeed(5550000) // 555 steps per second
//
// This function sends a "Set max speed" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) SetMaxSpeed(speed uint32) error {
return d.commandW32(cmdSetSpeedMax, speed)
}
// GetStartingSpeed gets the starting speed in microsteps per 10000 seconds.
//
// This is the current value, which could differ from the value in the Tic's
// settings.
func (d *Dev) GetStartingSpeed() (uint32, error) {
return d.getVar32(OffsetStartingSpeed)
}
// SetStartingSpeed sets the starting speed, in units of steps per 10000
// seconds.
//
// Example:
//
// err := dev.SetStartingSpeed(500000) // 50 steps per second
//
// This function sends a "Set starting speed" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) SetStartingSpeed(speed uint32) error {
return d.commandW32(cmdSetStartingSpeed, speed)
}
// GetMaxAccel gets the maximum acceleration, in microsteps per second per 100
// seconds.
//
// This is the current value, which could differ from the value in the Tic's
// settings.
func (d *Dev) GetMaxAccel() (uint32, error) {
return d.getVar32(OffsetAccelMax)
}
// SetMaxAccel sets the maximum acceleration, in units of steps per second per
// 100 seconds.
//
// Example:
//
// err := dev.SetMaxAccel(10000) // 100 steps per second per second
//
// This function sends a "Set max acceleration" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) SetMaxAccel(accel uint32) error {
return d.commandW32(cmdSetAccelMax, accel)
}
// GetMaxDecel gets the maximum deceleration, in microsteps per second per 100
// seconds.
//
// This is the current value, which could differ from the value in the Tic's
// settings.
func (d *Dev) GetMaxDecel() (uint32, error) {
return d.getVar32(OffsetDecelMax)
}
// SetMaxDecel sets the maximum deceleration, in units of steps per second per
// 100 seconds.
//
// Example:
//
// err := dev.SetMaxDecel(10000) // 100 steps per second per second
//
// This function sends a "Set max deceleration" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) SetMaxDecel(decel uint32) error {
return d.commandW32(cmdSetDecelMax, decel)
}
// StepMode describes how many microsteps add up to one fulls step.
type StepMode uint8
const (
// StepModeFull is 1 microstep per step.
StepModeFull StepMode = 0
// StepModeHalf is 2 microsteps per step.
StepModeHalf StepMode = 1
// StepModeMicrostep4 is 4 microsteps per step.
StepModeMicrostep4 StepMode = 2
// StepModeMicrostep8 is 8 microsteps per step.
StepModeMicrostep8 StepMode = 3
// StepModeMicrostep16 is 16 microsteps per step. Valid for Tic T834, Tic
// T825 and Tic 36v4 only.
StepModeMicrostep16 StepMode = 4
// StepModeMicrostep32 is 32 microsteps per step. Valid for Tic T834, Tic
// T825 and Tic 36v4 only.
StepModeMicrostep32 StepMode = 5
// StepModeMicrostep2_100p is 2 microsteps per step at 100% coil current.
// Valid for Tic T249 only.
StepModeMicrostep2_100p StepMode = 6
// StepModeMicrostep64 is 64 microsteps per step. Valid for Tic 36v4 only.
StepModeMicrostep64 StepMode = 7
// StepModeMicrostep128 is 128 microsteps per step. Valid for Tic 36v4 only.
StepModeMicrostep128 StepMode = 8
// StepModeMicrostep256 is 256 microsteps per step. Valid for Tic 36v4 only.
StepModeMicrostep256 StepMode = 9
)
// GetStepMode gets the current step mode of the stepper motor.
//
// Example:
//
// mode, err := dev.GetStepMode()
// if mode == tic.StepModeMicrostep8 {
// // The Tic is currently using 1/8 microsteps.
// }
func (d *Dev) GetStepMode() (StepMode, error) {
v, err := d.getVar8(OffsetStepMode)
return StepMode(v), err
}
// SetStepMode sets the stepper motor's step mode, which defines how many
// microsteps correspond to one full step.
//
// Example:
//
// err := dev.SetStepMode(tic.StepModeMicrostep8)
//
// This function sends a "Set step mode" command to the Tic. For more
// information, see the Tic user's guide.
func (d *Dev) SetStepMode(mode StepMode) error {
if mode > StepModeMicrostep256 {
return ErrInvalidSetting
}
// Check that the variant supports the step mode.
switch d.variant {
case TicT825, TicT834:
if mode > StepModeMicrostep32 {
return ErrUnsupportedVariant
}
case TicT500:
if mode > StepModeMicrostep8 {
return ErrUnsupportedVariant
}
case TicT249:
if mode > StepModeMicrostep2_100p {
return ErrUnsupportedVariant
}
case Tic36v4:
if mode == StepModeMicrostep2_100p {
return ErrUnsupportedVariant
}
}
return d.commandW7(cmdSetStepMode, uint8(mode))
}
// ticT500CurrentTable is used to convert TicT500 current codes to current.
var ticT500CurrentTable = [33]physic.ElectricCurrent{
0 * physic.MilliAmpere,
1 * physic.MilliAmpere,
174 * physic.MilliAmpere,
343 * physic.MilliAmpere,
495 * physic.MilliAmpere,
634 * physic.MilliAmpere,
762 * physic.MilliAmpere,
880 * physic.MilliAmpere,
990 * physic.MilliAmpere,
1092 * physic.MilliAmpere,
1189 * physic.MilliAmpere,
1281 * physic.MilliAmpere,
1368 * physic.MilliAmpere,
1452 * physic.MilliAmpere,
1532 * physic.MilliAmpere,
1611 * physic.MilliAmpere,
1687 * physic.MilliAmpere,
1762 * physic.MilliAmpere,
1835 * physic.MilliAmpere,
1909 * physic.MilliAmpere,
1982 * physic.MilliAmpere,
2056 * physic.MilliAmpere,
2131 * physic.MilliAmpere,
2207 * physic.MilliAmpere,
2285 * physic.MilliAmpere,
2366 * physic.MilliAmpere,
2451 * physic.MilliAmpere,
2540 * physic.MilliAmpere,
2634 * physic.MilliAmpere,
2734 * physic.MilliAmpere,
2843 * physic.MilliAmpere,
2962 * physic.MilliAmpere,
3093 * physic.MilliAmpere,
}
// ticT249CurrentUnits is used by the library to convert between milliamps and
// the native current unit of the Tic T249, which is 40 mA.
const ticT249CurrentUnits uint16 = 40
// ticCurrentUnits is used by the library to convert between milliamps and the
// native current unit of the T825 and Tic T834, which is 32 mA.
const ticCurrentUnits uint16 = 32
// GetCurrentLimit gets the stepper motor coil current limit.
//
// This is the value being used now, which could differ from the value in the
// Tic's settings.
func (d *Dev) GetCurrentLimit() (physic.ElectricCurrent, error) {
code, err := d.getVar8(OffsetCurrentLimit)
if err != nil {
return 0, err
}
switch d.variant {
case TicT500:
const maxCode = uint8(len(ticT500CurrentTable) - 1)
if code > maxCode {
code = maxCode
}
return ticT500CurrentTable[code], nil
case TicT249:
milliamps := uint16(code) * ticT249CurrentUnits
return physic.ElectricCurrent(milliamps) * physic.MilliAmpere, nil
case Tic36v4:
milliamps := (uint32(55000)*uint32(code) + 384) / 768
return physic.ElectricCurrent(milliamps) * physic.MilliAmpere, nil
default:
// Tic T825 or Tic T834.
milliamps := uint16(code) * ticCurrentUnits
return physic.ElectricCurrent(milliamps) * physic.MilliAmpere, nil
}
}
// SetCurrentLimit sets the stepper motor coil current limit. If the desired
// current limit is not available, this function uses the closest current limit
// option that is lower than the desired one.
//
// Example:
//
// err := dev.SetCurrentLimit(500 * physic.MilliAmpere)
//
// This command temporarily sets the stepper motor coil current limit of the
// driver. The provided value will override the corresponding setting from the
// Tics non-volatile memory until the next Reset command or power cycle.
//
// This function sends a "Set current limit" command to the Tic. For more
// information about this command and how to choose a good current limit, see
// the Tic user's guide.
func (d *Dev) SetCurrentLimit(limit physic.ElectricCurrent) error {
milliamps := uint16(limit / physic.MilliAmpere)
var code uint8
switch d.variant {
case TicT500:
for i := range ticT500CurrentTable {
if ticT500CurrentTable[i] <= limit {
code = uint8(i)
} else {
break
}
}
case TicT249:
code = uint8(milliamps / ticT249CurrentUnits)
case Tic36v4:
// The Tic 36v4 represents current limits using numbers between 0
// and 127 that are linearly proportional to the current limit. All
// numbers within this range are valid current limits.
const (
tic36v4MinCurrentLimit = 72 * physic.MilliAmpere
tic36v4MaxCurrentLimit = 9095 * physic.MilliAmpere
)
switch {
case limit < tic36v4MinCurrentLimit:
code = 0
case limit >= tic36v4MaxCurrentLimit:
code = 127
default:
code = uint8((uint32(milliamps)*768 - 55000/2) / 55000)
if (code < 127) &&
((55000*(uint32(code)+1)+384)/768) <= uint32(milliamps) {
code++
}
}
default:
code = uint8(milliamps / ticCurrentUnits)
}
return d.commandW7(cmdSetCurrentLimit, code)
}
// DecayMode describes the possible decay modes. These are valid for the Tic
// T825, T834 and 36v4 only.
type DecayMode uint8
const (
// DecayModeMixed specifies "Mixed" decay mode on the Tic T825 and
// "Mixed 50%" on the Tic T834.
DecayModeMixed DecayMode = 0
// DecayModeSlow specifies "Slow" decay mode.
DecayModeSlow DecayMode = 1
// DecayModeFast specifies "Fast" decay mode.
DecayModeFast DecayMode = 2
// DecayModeMixed50 is the same as Mixed, but better expresses your
// intent if you want to use "Mixed 50%" mode on a Tic T834.
DecayModeMixed50 DecayMode = 0
// DecayModeMixed25 specifies "Mixed 25%" decay mode on the Tic T834 and
// is the same as Mixed on the Tic T825.
DecayModeMixed25 DecayMode = 3
// This specifies "Mixed 75%" decay mode on the Tic T834 and is the same as
// Mixed on the Tic T825.
DecayModeMixed75 DecayMode = 4
)
// GetDecayMode gets the current decay mode of the stepper motor driver.
//
// Example:
//
// mode, err := dev.GetDecayMode()
// if mode == tic.DecayModeSlow {
// // The Tic is in slow decay mode.
// }
func (d *Dev) GetDecayMode() (DecayMode, error) {
v, err := d.getVar8(OffsetDecayMode)
return DecayMode(v), err
}
// SetDecayMode sets the stepper motor driver's decay mode.
//
// Example:
//
// err := dev.SetDecayMode(DecayModeSlow)
//
// The decay modes are documented in the Tic user's guide.
func (d *Dev) SetDecayMode(mode DecayMode) error {
if mode > DecayModeMixed75 {
return ErrInvalidSetting
}
// Check that the variant supports the decay mode.
switch d.variant {
case TicT825:
if mode > DecayModeFast {
return ErrUnsupportedVariant
}
case TicT834:
case Tic36v4:
default:
return ErrUnsupportedVariant
}
return d.commandW7(cmdSetDecayMode, uint8(mode))
}
// AGCMode describes possible Active Gain Control modes.
type AGCMode uint8
const (
AGCModeOff AGCMode = 0
AGCModeOn AGCMode = 1
AGCModeActiveOff AGCMode = 2
)
// GetAGCMode gets the Active Gain Control mode.
//
// This is only valid for the Tic T249.
func (d *Dev) GetAGCMode() (AGCMode, error) {
if d.variant != TicT249 {
return 0, ErrUnsupportedVariant
}
v, err := d.getVar8(OffsetAGCMode)
return AGCMode(v & 0xF), err
}
// SetAGCMode sets the Active Gain Control mode.
//
// This is only valid for the Tic T249.
func (d *Dev) SetAGCMode(mode AGCMode) error {
if d.variant != TicT249 {
return ErrUnsupportedVariant
}
if mode > AGCModeActiveOff {
return ErrInvalidSetting
}
return d.commandW7(cmdSetAGCOption, uint8(mode)&0xF)
}
// AGCBottomCurrentLimit describes the possible Active Gain Control bottom
// current limit percentages.
type AGCBottomCurrentLimit uint8
const (
AGCBottomCurrentLimitP45 AGCBottomCurrentLimit = 0
AGCBottomCurrentLimitP50 AGCBottomCurrentLimit = 1
AGCBottomCurrentLimitP55 AGCBottomCurrentLimit = 2
AGCBottomCurrentLimitP60 AGCBottomCurrentLimit = 3
AGCBottomCurrentLimitP65 AGCBottomCurrentLimit = 4
AGCBottomCurrentLimitP70 AGCBottomCurrentLimit = 5
AGCBottomCurrentLimitP75 AGCBottomCurrentLimit = 6
AGCBottomCurrentLimitP80 AGCBottomCurrentLimit = 7
)
// GetAGCBottomCurrentLimit gets the Active Gain Control bottom current limit.
//
// This is only valid for the Tic T249.
func (d *Dev) GetAGCBottomCurrentLimit() (AGCBottomCurrentLimit, error) {
if d.variant != TicT249 {
return 0, ErrUnsupportedVariant
}
v, err := d.getVar8(OffsetAGCBottomCurrentLimit)
return AGCBottomCurrentLimit(v & 0xF), err
}
// SetAGCBottomCurrentLimit sets the Active Gain Control bottom current limit.
//
// This is only valid for the Tic T249.
func (d *Dev) SetAGCBottomCurrentLimit(limit AGCBottomCurrentLimit) error {
if d.variant != TicT249 {
return ErrUnsupportedVariant
}
if limit > AGCBottomCurrentLimitP80 {
return ErrInvalidSetting
}
return d.commandW7(cmdSetAGCOption, 0x10|(uint8(limit)&0xF))
}
// AGCCurrentBoostSteps describes the possible Active Gain Control current boost
// steps values.
type AGCCurrentBoostSteps uint8
const (
AGCCurrentBoostStepsS5 AGCCurrentBoostSteps = 0
AGCCurrentBoostStepsS7 AGCCurrentBoostSteps = 1
AGCCurrentBoostStepsS9 AGCCurrentBoostSteps = 2
AGCCurrentBoostStepsS11 AGCCurrentBoostSteps = 3
)
// GetAGCCurrentBoostSteps gets the Active Gain Control current boost steps.
//
// This is only valid for the Tic T249.
func (d *Dev) GetAGCCurrentBoostSteps() (AGCCurrentBoostSteps, error) {
if d.variant != TicT249 {
return 0, ErrUnsupportedVariant
}
v, err := d.getVar8(OffsetAGCCurrentBoostSteps)
return AGCCurrentBoostSteps(v & 0xF), err
}
// SetAGCCurrentBoostSteps sets the Active Gain Control current boost steps.
//
// This is only valid for the Tic T249.
func (d *Dev) SetAGCCurrentBoostSteps(steps AGCCurrentBoostSteps) error {
if d.variant != TicT249 {
return ErrUnsupportedVariant
}
if steps > AGCCurrentBoostStepsS11 {
return ErrInvalidSetting
}
return d.commandW7(cmdSetAGCOption, 0x20|(uint8(steps)&0xF))
}
// AGCFrequencyLimit describes the possible Active Gain Control frequency limit
// values.
type AGCFrequencyLimit uint8
const (
AGCFrequencyLimitOff AGCFrequencyLimit = 0
AGCFrequencyLimitF225Hz AGCFrequencyLimit = 1
AGCFrequencyLimitF450Hz AGCFrequencyLimit = 2
AGCFrequencyLimitF675Hz AGCFrequencyLimit = 3
)
// GetAGCFrequencyLimit gets the Active Gain Control frequency limit.
//
// This is only valid for the Tic T249.
func (d *Dev) GetAGCFrequencyLimit() (AGCFrequencyLimit, error) {
if d.variant != TicT249 {
return 0, ErrUnsupportedVariant
}
v, err := d.getVar8(OffsetAGCFrequencyLimit)
return AGCFrequencyLimit(v & 0xF), err
}
// SetAGCFrequencyLimit sets the Active Gain Control frequency limit.
//
// This is only valid for the Tic T249.
func (d *Dev) SetAGCFrequencyLimit(limit AGCFrequencyLimit) error {
if d.variant != TicT249 {
return ErrUnsupportedVariant
}
if limit > AGCFrequencyLimitF675Hz {
return ErrInvalidSetting
}
return d.commandW7(cmdSetAGCOption, 0x30|(uint8(limit)&0xF))
}
// OperationState describes the possible operation states for the Tic.
type OperationState uint8
const (
OperationStateReset OperationState = 0
OperationStateDeenergized OperationState = 2
OperationStateSoftError OperationState = 4
OperationStateWaitingForErrLine OperationState = 6
OperationStateStartingUp OperationState = 8
OperationStateNormal OperationState = 10
)
// GetOperationState gets the Tic's current operation state, which indicates
// whether it is operating normally or in an error state.
//
// Example:
//
// state, err := dev.GetOperationState()
// if state != tic.OperationStateNormal {
// // There is an error, or the Tic is starting up.
// }
//
// For more information, see the "Error handling" section of the Tic user's
// guide.
func (d *Dev) GetOperationState() (OperationState, error) {
v, err := d.getVar8(OffsetOperationState)
return OperationState(v), err
}
// ticMiscFlags1 describes the bits in the Tic's Misc Flags 1 register.
type ticMiscFlags1 uint8
const (
ticMiscFlags1Energized ticMiscFlags1 = 0
ticMiscFlags1PositionUncertain ticMiscFlags1 = 1
ticMiscFlags1ForwardLimitActive ticMiscFlags1 = 2
ticMiscFlags1ReverseLimitActive ticMiscFlags1 = 3
ticMiscFlags1HomingActive ticMiscFlags1 = 4
)
// IsEnergized returns true if the motor driver is energized (trying to send
// current to its outputs).
func (d *Dev) IsEnergized() (bool, error) {
v, err := d.getVar8(OffsetMiscFlags1)
return ((v >> uint8(ticMiscFlags1Energized)) & 1) != 0, err
}
// IsPositionUncertain gets a flag that indicates whether there has been
// external confirmation that the value of the Tic's "Current position" variable
// is correct.
//
// For more information, see the "Error handling" section of the Tic user's
// guide.
func (d *Dev) IsPositionUncertain() (bool, error) {
v, err := d.getVar8(OffsetMiscFlags1)
return ((v >> uint8(ticMiscFlags1PositionUncertain)) & 1) != 0, err
}
// IsForwardLimitActive returns true if one of the forward limit switches is
// active.
func (d *Dev) IsForwardLimitActive() (bool, error) {
v, err := d.getVar8(OffsetMiscFlags1)
return ((v >> uint8(ticMiscFlags1ForwardLimitActive)) & 1) != 0, err
}
// IsReverseLimitActive returns true if one of the reverse limit switches is
// active.
func (d *Dev) IsReverseLimitActive() (bool, error) {
v, err := d.getVar8(OffsetMiscFlags1)
return ((v >> uint8(ticMiscFlags1ReverseLimitActive)) & 1) != 0, err
}
// IsHomingActive returns true if the Tic's homing procedure is running.
func (d *Dev) IsHomingActive() (bool, error) {
v, err := d.getVar8(OffsetMiscFlags1)
return ((v >> uint8(ticMiscFlags1HomingActive)) & 1) != 0, err
}
// ErrorBit describes the Tic's error bits. See the "Error handling" section of
// the Tic user's guide for more information about what these errors mean.
type ErrorBit uint32
const (
ErrorBitIntentionallyDeenergized ErrorBit = 0
ErrorBitMotorDriverError ErrorBit = 1
ErrorBitLowVin ErrorBit = 2
ErrorBitKillSwitch ErrorBit = 3
ErrorBitRequiredInputInvalid ErrorBit = 4
ErrorBitSerialError ErrorBit = 5
ErrorBitCommandTimeout ErrorBit = 6
ErrorBitSafeStartViolation ErrorBit = 7
ErrorBitErrLineHigh ErrorBit = 8
ErrorBitSerialFraming ErrorBit = 16
ErrorBitRxOverrun ErrorBit = 17
ErrorBitFormat ErrorBit = 18
ErrorBitCRC ErrorBit = 19
ErrorBitEncoderSkip ErrorBit = 20
)
// GetErrorStatus gets the errors that are currently stopping the motor.
//
// Each bit in the returned register represents a different error. The bits are
// defined by the tic.ErrorBit constants.
//
// Example:
//
// status, err := dev.GetErrorStatus()
// if status&(1<<tic.ErrorBitLowVin) != 0 {
// // Handle loss of power.
// }
//
// HasError may be used instead to check for specific errors.
func (d *Dev) GetErrorStatus() (uint16, error) {
return d.getVar16(OffsetErrorStatus)
}
// HasError returns true if the Tic is in the error state described by the given
// error bit.
//
// Example:
//
// isLowVin, err := dev.HasError(tic.ErrorBitLowVin)
// if isLowVin {
// // Handle loss of power.
// }
func (d *Dev) HasError(bit ErrorBit) (bool, error) {
status, err := d.GetErrorStatus()
return status&(1<<bit) != 0, err
}
// GetErrorsOccurred gets the errors that have occurred since the last time this
// function was called.
//
// Note that the Tic Control Center constantly clears the bits in this
// register, so if you are running the Tic Control Center then you will not
// be able to reliably detect errors with this function.
//
// Each bit in the returned register represents a different error. The bits
// are defined by the tic.Errorbit constants.
//
// Example:
//
// errors, err := dev.GetErrorsOccurred()
// if errors&(1<<tic.ErrorBitMotorDriverError) != 0 {
// // Handle the motor driver error.
// }
func (d *Dev) GetErrorsOccurred() (uint32, error) {
const length = 4
buffer, err := d.getSegment(
cmdGetVariableAndClearErrors, OffsetErrorsOccurred, length,
)
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(buffer), nil
}
// PlanningMode describes the possible planning modes for the Tic's step
// generation code.
type PlanningMode uint8
const (
PlanningModeOff PlanningMode = 0
PlanningModeTargetPosition PlanningMode = 1
PlanningModeTargetVelocity PlanningMode = 2
)
// GetPlanningMode returns the current planning mode for the Tic's step
// generation code.
//
// This tells us whether the Tic is sending steps, and if it is sending steps,
// tells us whether it is in Target Position or Target Velocity mode.
//
// Example:
//
// mode, err := dev.GetPlanningMode()
// if mode == tic.PlanningModeTargetPosition {
// // The Tic is moving the stepper motor to a target position, or has
// // already reached it and is at rest.
// }
func (d *Dev) GetPlanningMode() (PlanningMode, error) {
v, err := d.getVar8(OffsetPlanningMode)
return PlanningMode(v), err
}
// GetCurrentPosition gets the current position of the stepper motor, in
// microsteps.
//
// Note that this just tracks steps that the Tic has commanded the stepper
// driver to take, which could be different from the actual position of the
// motor.
func (d *Dev) GetCurrentPosition() (int32, error) {
v, err := d.getVar32(OffsetCurrentPosition)
return int32(v), err
}
// GetCurrentVelocity gets the current velocity of the stepper motor, in
// microsteps per 10000 seconds.
//
// Note that this is just the velocity used in the Tic's step planning
// algorithms, and it might not correspond to the actual velocity of the motor.
func (d *Dev) GetCurrentVelocity() (int32, error) {
v, err := d.getVar32(OffsetCurrentVelocity)
return int32(v), err
}
// GetActingTargetPosition gets the acting target position, in microsteps.
//
// This is a variable used in the Tic's target position step planning algorithm,
// and it could be invalid while the motor is stopped.
//
// This is mainly intended for getting insight into how the Tic's algorithms
// work or troubleshooting issues, and most people should not use this.
func (d *Dev) GetActingTargetPosition() (uint32, error) {
return d.getVar32(OffsetActingTargetPosition)
}
// GetTimeSinceLastStep gets the time since the last step, in timer ticks.
//
// Each timer tick represents one third of a microsecond. The Tic only updates
// this variable every 5 milliseconds or so, and it could be invalid while the
// motor is stopped.
//
// This is mainly intended for getting insight into how the Tic's algorithms
// work or troubleshooting issues, and most people should not use this.
func (d *Dev) GetTimeSinceLastStep() (uint32, error) {
return d.getVar32(OffsetTimeSinceLastStep)
}
// ResetCause describes the possible causes of a full microcontroller reset for
// the Tic.
type ResetCause uint8
const (
ResetCausePowerUp = 0
ResetCauseBrownout = 1
ResetCauseResetLine = 2
ResetCauseWatchdog = 4
ResetCauseSoftware = 8
ResetCauseStackOverflow = 16
ResetCauseStackUnderflow = 32
)
// GetDeviceReset gets the cause of the controller's last full microcontroller
// reset.
//
// Example:
//
// reset, err := dev.GetDeviceReset()
// if reset == tic.ResetCauseBrownout {
// // There was a brownout reset - the power supply could not keep up.
// }
//
// The Reset command (Reset()) does not affect this variable.
func (d *Dev) GetDeviceReset() (ResetCause, error) {
v, err := d.getVar8(OffsetDeviceReset)
return ResetCause(v), err
}
// GetVoltageIn gets the current measurement of the VIN voltage, in millivolts.
func (d *Dev) GetVoltageIn() (physic.ElectricPotential, error) {
mv, err := d.getVar16(OffsetVoltageIn)
return physic.ElectricPotential(mv) * physic.MilliVolt, err
}
// GetUpTime gets the time since the last full reset of the Tic's
// microcontroller, in milliseconds.
//
// A Reset command (Reset()) does not count.
func (d *Dev) GetUpTime() (time.Duration, error) {
ms, err := d.getVar32(OffsetUpTime)
return time.Duration(ms) * time.Millisecond, err
}
// GetEncoderPosition gets the raw encoder count measured from the Tic's RX and
// TX lines.
func (d *Dev) GetEncoderPosition() (int32, error) {
v, err := d.getVar32(OffsetEncoderPosition)
return int32(v), err
}
// GetRCPulseWidth gets the raw pulse width measured on the Tic's RC input, in
// units of twelfths of a microsecond.
//
// Returns tic.InputNull if the RC input is missing or invalid.
//
// Example:
//
// width, err := dev.GetRCPulseWidth()
// if width != tic.InputNull && width > 1500*12 {
// // Pulse width is greater than 1500 microseconds.
// }
func (d *Dev) GetRCPulseWidth() (uint16, error) {
return d.getVar16(OffsetRCPulseWidth)
}
// Pin describes a Tic control pin.
type Pin uint8
const (
PinSCL Pin = 0
PinSDA Pin = 1
PinTX Pin = 2
PinRX Pin = 3
PinRC Pin = 4
)
// GetAnalogReading gets the analog reading from the specified pin.
//
// The reading is left-justified, so 0xFFFF represents a voltage equal to the
// Tic's 5V pin (approximately 4.8 V).
//
// Returns tic.InputNull if the analog reading is disabled or not ready.
//
// Example:
//
// reading, err := dev.GetAnalogReading(tic.PinSDA)
// if reading != tic.InputNull && reading < 32768 {
// // The reading is less than about 2.4 V.
// }
func (d *Dev) GetAnalogReading(pin Pin) (uint16, error) {
return d.getVar16(OffsetAnalogReadingSCL + 2*offset(pin))
}
// IsDigitalReading gets a digital reading from the specified pin.
//
// Returns true for high and false for low.
func (d *Dev) IsDigitalReading(pin Pin) (bool, error) {
readings, err := d.getVar8(OffsetDigitalReadings)
return ((readings >> pin) & 1) != 0, err
}
// PinState describes a Tic's pin state.
type PinState uint8
const (
PinStateHighImpedance PinState = 0
PinStateInputPullUp PinState = 1
PinStateOutputLow PinState = 2
PinStateOutputHigh PinState = 3
)
// GetPinState gets the current state of a pin, i.e. what kind of input or
// output it is.
//
// Note that the state might be misleading if the pin is being used as an I²C
// or serial pin.
//
// Example:
//
// state, err := dev.GetPinState(PinSCL)
// if state == tic.PinStateOutputHigh {
// // SCL is driving high.
// }
func (d *Dev) GetPinState(pin Pin) (PinState, error) {
if pin > PinRX {
// State not available for PinRC.
return 0, ErrInvalidSetting
}
states, err := d.getVar8(OffsetPinStates)
return PinState((states >> (2 * uint8(pin))) & 0b11), err
}
// InputState describes the possible states of the Tic's main input.
type InputState uint8
const (
// The input is not ready yet. More samples are needed, or a command has not
// been received yet.
InputStateNotReady InputState = 0
// The input is invalid.
InputStateInvalid InputState = 1
// The input is valid and is telling the Tic to halt the motor.
InputStateHalt InputState = 2
// The input is valid and is telling the Tic to go to a target position,
// which you can get with GetInputAfterScaling().
InputStatePosition InputState = 3
// The input is valid and is telling the Tic to go to a target velocity,
// which you can get with GetInputAfterScaling().
InputStateVelocity InputState = 4
)
// GetInputState gets the current state of the Tic's main input.
//
// Example:
//
// state, err := dev.GetInputState()
// if state == tic.InputStatePosition {
// // The Tic's input is specifying a target position.
// }
func (d *Dev) GetInputState() (InputState, error) {
v, err := d.getVar8(OffsetInputState)
return InputState(v), err
}
// GetInputAfterAveraging gets a variable used in the process that converts raw
// RC and analog values into a motor position or speed. This is mainly for
// debugging your input scaling settings in RC or analog mode.
//
// A value of tic.InputNull means the input value is not available.
func (d *Dev) GetInputAfterAveraging() (uint16, error) {
return d.getVar16(OffsetInputAfterAveraging)
}
// GetInputAfterHysteresis gets a variable used in the process that converts raw
// RC and analog values into a motor position or speed. This is mainly for
// debugging your input scaling settings in RC or analog mode.
//
// A value of tic.InputNull means the input value is not available.
func (d *Dev) GetInputAfterHysteresis() (uint16, error) {
return d.getVar16(OffsetInputAfterHysteresis)
}
// GetInputAfterScaling gets the value of the Tic's main input after scaling has
// been applied.
//
// If the input is valid, this number is the target position or target velocity
// specified by the input.
func (d *Dev) GetInputAfterScaling() (int32, error) {
v, err := d.getVar32(OffsetInputAfterScaling)
return int32(v), err
}
// MotorDriverError describes the possible motor driver errors for the Tic T249.
type MotorDriverError uint8
const (
MotorDriverErrorNone MotorDriverError = 0
MotorDriverErrorOverCurrent MotorDriverError = 1
MotorDriverErrorOverTemperature MotorDriverError = 2
)
// GetLastMotorDriverError gets the cause of the last motor driver error.
//
// This is only valid for the Tic T249.
func (d *Dev) GetLastMotorDriverError() (MotorDriverError, error) {
if d.variant != TicT249 {
return 0, ErrUnsupportedVariant
}
v, err := d.getVar8(OffsetLastMotorDriverError)
return MotorDriverError(v), err
}
// GetLastHPDriverErrors gets the "Last HP driver errors" variable.
//
// Each bit in this register represents an error. If the bit is 1, the error was
// one of the causes of the Tic's last motor driver error.
//
// This is only valid for the Tic 36v4.
func (d *Dev) GetLastHPDriverErrors() (uint8, error) {
if d.variant != Tic36v4 {
return 0, ErrUnsupportedVariant
}
return d.getVar8(OffsetLastHPDriverErrors)
}
// GetSetting gets a contiguous block of settings from the Tic's EEPROM.
//
// The maximum length that can be fetched is 15 bytes.
//
// This library does not attempt to interpret the settings and say what they
// mean. If you are interested in how the settings are encoded in the Tic's
// EEPROM, see the "Settings reference" section of the Tic user's guide.
func (d *Dev) GetSetting(offset offset, length uint) ([]uint8, error) {
if length > 15 {
return nil, errors.New("maximum length exceeded")
}
return d.getSegment(cmdGetSetting, offset, length)
}
// offset represents where settings are stored in the Tic's EEPROM memory. See
// the "Variable reference" section of the Tic user's guide for details.
type offset uint8
const (
OffsetOperationState offset = 0x00 // uint8 return type
OffsetMiscFlags1 offset = 0x01 // uint8 return type
OffsetErrorStatus offset = 0x02 // uint16 return type
OffsetErrorsOccurred offset = 0x04 // uint32 return type
OffsetPlanningMode offset = 0x09 // uint8 return type
OffsetTargetPosition offset = 0x0A // int32 return type
OffsetTargetVelocity offset = 0x0E // int32 return type
OffsetStartingSpeed offset = 0x12 // uint32 return type
OffsetSpeedMax offset = 0x16 // uint32 return type
OffsetDecelMax offset = 0x1A // uint32 return type
OffsetAccelMax offset = 0x1E // uint32 return type
OffsetCurrentPosition offset = 0x22 // int32 return type
OffsetCurrentVelocity offset = 0x26 // int32 return type
OffsetActingTargetPosition offset = 0x2A // int32 return type
OffsetTimeSinceLastStep offset = 0x2E // uint32 return type
OffsetDeviceReset offset = 0x32 // uint8 return type
OffsetVoltageIn offset = 0x33 // uint16 return type
OffsetUpTime offset = 0x35 // uint32 return type
OffsetEncoderPosition offset = 0x39 // int32 return type
OffsetRCPulseWidth offset = 0x3D // uint16 return type
OffsetAnalogReadingSCL offset = 0x3F // uint16 return type
OffsetAnalogReadingSDA offset = 0x41 // uint16 return type
OffsetAnalogReadingTX offset = 0x43 // uint16 return type
OffsetAnalogReadingRX offset = 0x45 // uint16 return type
OffsetDigitalReadings offset = 0x47 // uint8 return type
OffsetPinStates offset = 0x48 // uint8 return type
OffsetStepMode offset = 0x49 // uint8 return type
OffsetCurrentLimit offset = 0x4A // uint8 return type
OffsetDecayMode offset = 0x4B // uint8 return type
OffsetInputState offset = 0x4C // uint8 return type
OffsetInputAfterAveraging offset = 0x4D // uint16 return type
OffsetInputAfterHysteresis offset = 0x4F // uint16 return type
OffsetInputAfterScaling offset = 0x51 // uint16 return type
OffsetLastMotorDriverError offset = 0x55 // uint8 return type
OffsetAGCMode offset = 0x56 // uint8 return type
OffsetAGCBottomCurrentLimit offset = 0x57 // uint8 return type
OffsetAGCCurrentBoostSteps offset = 0x58 // uint8 return type
OffsetAGCFrequencyLimit offset = 0x59 // uint8 return type
OffsetLastHPDriverErrors offset = 0xFF // uint8 return type
)
// command represents Tic command codes which are used for its I²C interface.
// See the "Command reference" section of the Tic user's guide for details.
type command uint8
const (
cmdSetTargetPosition command = 0xE0
cmdSetTargetVelocity command = 0xE3
cmdHaltAndSetPosition command = 0xEC
cmdHaltAndHold command = 0x89
cmdGoHome command = 0x97
cmdResetCommandTimeout command = 0x8C
cmdDeenergize command = 0x86
cmdEnergize command = 0x85
cmdExitSafeStart command = 0x83
cmdEnterSafeStart command = 0x8F
cmdReset command = 0xB0
cmdClearDriverError command = 0x8A
cmdSetSpeedMax command = 0xE6
cmdSetStartingSpeed command = 0xE5
cmdSetAccelMax command = 0xEA
cmdSetDecelMax command = 0xE9
cmdSetStepMode command = 0x94
cmdSetCurrentLimit command = 0x91
cmdSetDecayMode command = 0x92
cmdSetAGCOption command = 0x98
cmdGetVariable command = 0xA1
cmdGetVariableAndClearErrors command = 0xA2
cmdGetSetting command = 0xA8
)
var _ conn.Resource = &Dev{}
var _ fmt.Stringer = &Dev{}