Skip to content

Peregrine2

Markus Grönholm edited this page Jun 16, 2026 · 5 revisions

Peregrine2 motion controller

Install required packages

python -mpip install pyserial alshain

Quick start

import alshain
import serial
import sys

# Serial port as first command line parameter
com = serial.Serial( sys.argv[1], alshain.BAUDRATE, timeout = 0.25 )

dev = alshain.Peregrine2( com, address = 1 )

# Axis A is using a stepper motor
dev.write( alshain.Peregrine2.Parameters.MODULE_A, alshain.Peregrine2.Options.MODULE_STEPPER )

# Enable axis A so motor is powered and it can move
dev.enable_a( True )

# Move axis
dev.move_a( pos = 10000, speed = 1000, accel = 2000 )

# Query current position etc
print( dev.status_a() )

Usage

# Axis must be enabled before servo loop starts to control it
# Disabling an axis powers down motor, encoder readings are still tracked
dev.enable_a( True )
dev.enable_b( True )


# Query current status
dev.status_a()
dev.status_b()

# Status tuple
# status[0] = position
# status[1] = velocity
# status[2] = flags
#  status[2]["RUNNING"] # Is commanded path is executing (ie. motor running)
#  status[2]["LIMIT+"] # State of limit+ switch
#  status[2]["LIMIT-"] # State of limit+ switch
#  status[2]["INPOS"] # Is position within servo loop tolerance
#  status[2]["EMG_STOP"] # State of emergency stop input


# Issue a movement command:
# pos = absolute target position in encoder steps
# speed = max speed in encoder steps / s
# accel = max acceleration in encode steps / s²
dev.move_a(pos = 1000, speed = 10000, accel = 10000)
dev.move_b(pos = 1000, speed = 10000, accel = 10000)

# Overwrite axis current position (when homed, etc.)
dev.set_position_a( position = 0 )
dev.set_position_b( position = 0 )

Parameters

dev.read( alshain.Peregrine2.Parameters.ENCODER_A ) # Current encode value, axis A
dev.read( alshain.Peregrine2.Parameters.ENCODER_B ) # Current encode value, axis B
dev.read( alshain.Peregrine2.Parameters.POSITION_A ) # Current position in encoder steps, axis A
dev.read( alshain.Peregrine2.Parameters.POSITION_B ) # Current position in encoder steps, axis B

dev.read( alshain.Peregrine2.Parameters.TARGET_A ) # Target position, axis A [encoder steps]
dev.read( alshain.Peregrine2.Parameters.TARGET_B ) # Target position, axis B [encoder steps]

dev.read( alshain.Peregrine2.Parameters.STEPRATE_A ) # Current steprate, axis A [steps/s]
dev.read( alshain.Peregrine2.Parameters.STEPRATE_B ) # Current steprate, axis A [steps/s]

dev.read( alshain.Peregrine2.Parameters.ENABLED_A ) # Is axis A enabled [0 -> no, 1 -> yes]
dev.read( alshain.Peregrine2.Parameters.ENABLED_B ) # Is axis B enabled [0 -> no, 1 -> yes]

# Path parameters
dev.read( alshain.Peregrine2.Parameters.PATH_A_T ) # Current path time, axis A [s]
dev.read( alshain.Peregrine2.Parameters.PATH_A_T0 ) # End of acceleration phase, axis A [s]
dev.read( alshain.Peregrine2.Parameters.PATH_A_T1 ) # End of constant velocity phase, axis A [s]
dev.read( alshain.Peregrine2.Parameters.PATH_A_T2 ) # End of deacceleration phase, axis A [s]

dev.read( alshain.Peregrine2.Parameters.PATH_B_T ) # Current path time, axis B [s]
dev.read( alshain.Peregrine2.Parameters.PATH_B_T0 ) # End of acceleration phase, axis B [s]
dev.read( alshain.Peregrine2.Parameters.PATH_B_T1 ) # End of constant velocity phase, axis B [s]
dev.read( alshain.Peregrine2.Parameters.PATH_B_T2 ) # End of deacceleration phase, axis B [s]

dev.read( alshain.Peregrine2.Parameters.PATH_A_X0 ) # Path starting position, axis A [steps]
dev.read( alshain.Peregrine2.Parameters.PATH_A_X1 ) # Path end position, axis A [steps]
dev.read( alshain.Peregrine2.Parameters.PATH_B_X0 ) # Path starting position, axis B [steps]
dev.read( alshain.Peregrine2.Parameters.PATH_B_X1 ) # Path end position, axis B [steps]

# Limit switches
dev.read( alshain.Peregrine2.Parameters.LIMIT_A_MINUS ) # Limit- state, axis A [0/1]
dev.read( alshain.Peregrine2.Parameters.LIMIT_A_PLUS ) # Limit+ state, axis A [0/1]
dev.read( alshain.Peregrine2.Parameters.LIMIT_B_MINUS ) # Limit- state, axis B [0/1]
dev.read( alshain.Peregrine2.Parameters.LIMIT_B_PLUS ) # Limit+ state, axis B [0/1]

# Limit switch mode, options:
# LIMIT_SWITCH_DISABLED -> limit switch disabled
# LIMIT_SWITCH_ENABLED -> limit switch enabled (state change will stop movement)
# LIMIT_SWITCH_INVERTED -> limit switch enabled, polarity inverted

dev.write( alshain.Peregrine2.Parameters.LIMIT_A_MINUS_MODE, alshain.Peregrine.Options.LIMIT_SWITCH_ENABLED )
dev.write( alshain.Peregrine2.Parameters.LIMIT_A_PLUS_MODE, alshain.Peregrine.Options.LIMIT_SWITCH_ENABLED  )
dev.write( alshain.Peregrine2.Parameters.LIMIT_B_MINUS_MODE, alshain.Peregrine.Options.LIMIT_SWITCH_ENABLED  )
dev.write( alshain.Peregrine2.Parameters.LIMIT_B_PLUS_MODE, alshain.Peregrine.Options.LIMIT_SWITCH_ENABLED  )

# Per-axis driver module configuration
# MODULE_UNKNOWN -> no driver module present, movement halted
# MODULE_STEPPER -> stepper driver present, using step/dir to control movement
# MODULE_HBRIDGE -> H-bridge driver (for DC motors) present, using pwm/dir to control movement

dev.write( alshain.Peregrine2.Parameters.MODULE_A, alshain.Peregrine2.Options.MODULE_STEPPER )
dev.write( alshain.Peregrine2.Parameters.MODULE_B, alshain.Peregrine2.Options.MODULE_STEPPER )

# Set driver config pins
# Value = 0..7, 3bit config
# For stepper motor modules with TMC2209:
# 0 (000) -> 1/8  microstepping, stealthChop
# 1 (001) -> 1/32 microstepping, stealthChop
# 2 (010) -> 1/64 microstepping, stealthChop
# 3 (011) -> 1/16 microstepping, stealthChop
# 4 (100) -> 1/8  microstepping, spreadCycle
# 5 (101) -> 1/32 microstepping, spreadCycle
# 6 (110) -> 1/64 microstepping, spreadCycle
# 7 (111) -> 1/8  microstepping, spreadCycle

dev.write( alshain.Peregrine2.Parameters.DRIVE_CFG_A, 0x02 )
dev.write( alshain.Peregrine2.Parameters.DRIVE_CFG_B, 0x02 )

# Motor rotation direction compared to encoder, -1 or 1
dev.write( alshain.Peregrine2.Parameters.MOTOR_DIR_A, 1 )
dev.write( alshain.Peregrine2.Parameters.MOTOR_DIR_B, -1 )

# Servo loop parameters, Peregrine2 uses a LQR for control (normalised so that B1 = 1)
# err = (target - current_position) * error_gain
# vel_est = velocity estimate from tracking loop
# f_out = err * K0 + vel_est * K1

dev.write( alshain.Peregrine2.Parameters.ERROR_GAIN_A, 20.0) # Default = 20.0
dev.write( alshain.Peregrine2.Parameters.LQR_K0_A, 0.57917087 ) # Default = 0.57917087
dev.write( alshain.Peregrine2.Parameters.LQR_K1_A, 1.54562698 ) # Default = 1.54562698
dev.write( alshain.Peregrine2.Parameters.DEADZONE_A, 100 ) # Tolerance of servo loop (default = 100 [steps])
dev.write( alshain.Peregrine2.Parameters.SLEWRATE_A, 3000.0 ) # Maximum step rate change per servo loop

# Same default values for axis B
dev.write( alshain.Peregrine2.Parameters.ERROR_GAIN_B, 20.0 )
dev.write( alshain.Peregrine2.Parameters.LQR_K0_B, 0.57917087 )
dev.write( alshain.Peregrine2.Parameters.LQR_K1_B, 1.54562698 )
dev.write( alshain.Peregrine2.Parameters.DEADZONE_B, 100 )
dev.write( alshain.Peregrine2.Parameters.SLEWRATE_B, 3000.0 )

# Precomputed estimates for K0 and K1 for stepper motors
# Microstepping  Encoder steps per rev
#     1/8               8192             -> K0 = 0.12037854, K1 = 0.31540801
#     1/16              8192             -> K0 = 0.23881568, K1 = 0.62721283
#     1/32              8192             -> K0 = 0.46333292, K1 = 1.22781125
#     1/64              8192             -> K0 = 0.83961247, K1 = 2.29196417
#     1/8               4096             -> K0 = 0.23881568, K1 = 0.62721283
#     1/16              4096             -> K0 = 0.46333292, K1 = 1.22781125
#     1/32              4096             -> K0 = 0.83961247, K1 = 2.29196417
#     1/64              4096             -> K0 = 1.32623841, K1 = 3.90157956

dev.read( alshain.Peregrine2.Parameters.EMG_STOP ) # State of the emg stop input
# Peregrine sets EMG_STOP_LATCH to 1 when emergency stop activated. User must clear it manually.
# Clears also in power loss
dev.read( alshain.Peregrine2.Parameters.EMG_STOP_LATCH )
dev.write( alshain.Peregrine2.Parameters.EMG_STOP_LATCH, 0 ) # Clear latch value


# Current and voltage monitoring
dev.read( alshain.Peregrine2.Parameters.IMON0 ) # Axis A, driver current [A]
dev.read( alshain.Peregrine2.Parameters.IMON1 ) # Axis B, driver current [A]
dev.read( alshain.Peregrine2.Parameters.A0 ) # Voltage at analog input 0
dev.read( alshain.Peregrine2.Parameters.A1 ) # Voltage at analog input 0
dev.read( alshain.Peregrine2.Parameters.A2 ) # Voltage at analog input 0
dev.read( alshain.Peregrine2.Parameters.VMON ) # Power supply voltage [V]

# Status of GPIO inputs
dev.read( alshain.Peregrine2.Parameters.GPIO0 )
dev.read( alshain.Peregrine2.Parameters.GPIO1 )

# Analog outputs are current DACs, range 0..50mA (0 .. 50e-3 amps)
dev.write( alshain.Peregrine2.Parameters.IDAC1, 0.0 ) # IDAC1 at 0mA
dev.write( alshain.Peregrine2.Parameters.IDAC2, 10e-3 ) # IDAC2 set to 10mA

# If driver module is H-bridge, set PWM override (0..100% -> 0..1000)
dev.write( alshain.Peregrine2.Parameters.HBRIDGE_PWM_A, 500 ) # set to 50% duty cycle
dev.write( alshain.Peregrine2.Parameters.HBRIDGE_PWM_B, 0 ) # 0% duty cycle

# Fault detection, isolation and recovery (FDIR) subsystem parameters
# FDIR system tries to detect stalls and similar situations by monitoring rotational speed from encoder
# and comparing it to target step rate
dev.write( alshain.Peregrine2.Parameters.FDIR_MAX_ERROR_A )
dev.read( alshain.Peregrine2.Parameters.FDIR_STATE_A )

dev.write( alshain.Peregrine2.Parameters.FDIR_MAX_ERROR_B )
dev.read( alshain.Peregrine2.Parameters.FDIR_STATE_B )

# Peregrine2 features an analog triggering feature, stores positions on both rising and falling edge
# Works for analog input channels A0 and A1

# Analog input channel A0
dev.write( alshain.Peregrine2.Parameters.ANALOG_THRESHOLD_0, 0.5 ) # Set trigger threshold for channel A0 to 0.5V
dev.write( alshain.Peregrine2.Parameters.ANALOG_TH_0_HYST, 0.01 ) # Set hysteresis for trigger threshold to 0.01V
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_0_POS_A ) # Last position for axis A stored when triggered
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_0_POS_B ) # Last position for axis B stored when triggered
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_0_STATE ) # State of the trigger comparator, channel A0

# Analog input channel A1
dev.write( alshain.Peregrine2.Parameters.ANALOG_THRESHOLD_1, 1.0 ) # Set trigger threshold for channel A1 to 1.0V
dev.write( alshain.Peregrine2.Parameters.ANALOG_TH_1_HYST, 0.2 ) # Set hysteresis for trigger threshold to 0.2V
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_1_POS_A )
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_1_POS_B )
dev.read( alshain.Peregrine2.Parameters.ANALOG_TH_1_STATE )

# Is servo loop enabled (default: yes)
dev.write( alshain.Peregrine2.Parameters.SERVO_LOOP_ENABLED_A, 1 ) # 0 -> disabled, 1 -> enabled
dev.write( alshain.Peregrine2.Parameters.SERVO_LOOP_ENABLED_B, 1 )

Clone this wiki locally