// 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 // Tic’s 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< 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{}