diff --git a/README.md b/README.md
index 287cbf9c..668304b5 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ pip install pytrinamic
## Getting Started
-Please have a look at the [code examples on GitHub](https://github.com/trinamic/PyTrinamic/tree/feature_feature_hierarchy_v2/examples).
+Please have a look at the [code examples on GitHub](https://github.com/trinamic/PyTrinamic/tree/master/examples).
## Migration Guide
diff --git a/examples/evalboards/MAX22216/plunger_move.py b/examples/evalboards/MAX22216/plunger_move.py
index ed576d23..768d4b88 100644
--- a/examples/evalboards/MAX22216/plunger_move.py
+++ b/examples/evalboards/MAX22216/plunger_move.py
@@ -1,12 +1,18 @@
+
+import logging
+import time
+
import pytrinamic
from pytrinamic.connections import ConnectionManager
from pytrinamic.ic import MAX22216
from pytrinamic.evalboards import MAX22216_eval
-import time
+
+
+logging.basicConfig(level=logging.DEBUG)
pytrinamic.show_info()
-with ConnectionManager(debug=True).connect() as my_interface:
+with ConnectionManager().connect() as my_interface:
print(my_interface)
eval = MAX22216_eval(my_interface)
@@ -14,9 +20,10 @@
solenoid = ic.motors[0]
solenoid.u_supply = 24.0 # V
- solenoid.u_dc_h = 24.0 # V
+
+ solenoid.u_dc_h = 10.0 # V
solenoid.u_dc_l = 0.0 # V
- solenoid.u_dc_l2h = 24.0 # V
+ solenoid.u_dc_l2h = 10.0 # V
solenoid.u_dc_h2l = 0.0 # V
solenoid.u_ac = 1.0 # V ampl
solenoid.f_ac = 50.0 # Hz
diff --git a/examples/evalboards/MAX22216/ramdebug.py b/examples/evalboards/MAX22216/ramdebug.py
index 9511ffe5..27c6b290 100644
--- a/examples/evalboards/MAX22216/ramdebug.py
+++ b/examples/evalboards/MAX22216/ramdebug.py
@@ -1,11 +1,14 @@
+import logging
+
import pytrinamic
from pytrinamic.connections import ConnectionManager
from pytrinamic.ic import MAX22216
from pytrinamic.RAMDebug import Channel, RAMDebug, RAMDebug_Trigger
+logging.basicConfig(level=logging.DEBUG)
pytrinamic.show_info()
-with ConnectionManager(debug=True).connect() as my_interface:
+with ConnectionManager().connect() as my_interface:
print(my_interface)
ch = Channel.field(0, MAX22216.FIELD.ADC_VM_RAW, signed=True, eval_channel=1)
diff --git a/examples/evalboards/TMC2240/register_dump.py b/examples/evalboards/TMC2240/register_dump.py
new file mode 100644
index 00000000..45161331
--- /dev/null
+++ b/examples/evalboards/TMC2240/register_dump.py
@@ -0,0 +1,66 @@
+"""
+Dump all register values of the TMC2240 IC.
+
+The connection to a Landungsbrücke is established over USB. TMCL commands are used for communicating with the IC.
+"""
+import time
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.evalboards import TMC2240_eval
+
+pytrinamic.show_info()
+
+with ConnectionManager().connect() as my_interface:
+ print(my_interface)
+
+ eval_board = TMC2240_eval(my_interface)
+ mc = eval_board.ics[0]
+
+
+ print("GCONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.GCONF)))
+ print("GSTAT: 0x{0:08X}".format(eval_board.read_register(mc.REG.GSTAT)))
+ print("IFCNT: 0x{0:08X}".format(eval_board.read_register(mc.REG.IFCNT)))
+ print("SLAVECONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.SLAVECONF)))
+ print("IOIN: 0x{0:08X}".format(eval_board.read_register(mc.REG.IOIN)))
+ print("DRV_CONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.DRV_CONF)))
+ print("GLOBAL_SCALE 0x{0:08X}".format(eval_board.read_register(mc.REG.GLOBAL_SCALER)))
+ print("IHOLD_IRUN: 0x{0:08X}".format(eval_board.read_register(mc.REG.IHOLD_IRUN)))
+ print("TPOWERDOWN: 0x{0:08X}".format(eval_board.read_register(mc.REG.TPOWERDOWN)))
+ print("TSTEP: 0x{0:08X}".format(eval_board.read_register(mc.REG.TSTEP)))
+ print("TPWMTHRS: 0x{0:08X}".format(eval_board.read_register(mc.REG.TPWMTHRS)))
+ print("TCOOLTHRS: 0x{0:08X}".format(eval_board.read_register(mc.REG.TCOOLTHRS)))
+ print("THIGH: 0x{0:08X}".format(eval_board.read_register(mc.REG.THIGH)))
+ print("DIRECT_MODE: 0x{0:08X}".format(eval_board.read_register(mc.REG.DIRECT_MODE)))
+ print("ENCMODE: 0x{0:08X}".format(eval_board.read_register(mc.REG.ENCMODE)))
+ print("X_ENC: 0x{0:08X}".format(eval_board.read_register(mc.REG.X_ENC)))
+ print("ENC_CONST: 0x{0:08X}".format(eval_board.read_register(mc.REG.ENC_CONST)))
+ print("ENC_STATUS: 0x{0:08X}".format(eval_board.read_register(mc.REG.ENC_STATUS)))
+ print("ENC_LATCH: 0x{0:08X}".format(eval_board.read_register(mc.REG.ENC_LATCH)))
+ print("ADC_VSUPPLY_AIN: 0x{0:08X}".format(eval_board.read_register(mc.REG.ADC_VSUPPLY_AIN)))
+ print("ADC_TEMP: 0x{0:08X}".format(eval_board.read_register(mc.REG.ADC_TEMP)))
+ print("OTW_OV_VTH: 0x{0:08X}".format(eval_board.read_register(mc.REG.OTW_OV_VTH)))
+ print("MSLUT_0: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_0)))
+ print("MSLUT_1: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_1)))
+ print("MSLUT_2: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_2)))
+ print("MSLUT_3: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_3)))
+ print("MSLUT_4: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_4)))
+ print("MSLUT_5: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_5)))
+ print("MSLUT_6: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_6)))
+ print("MSLUT_7: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUT_7)))
+ print("MSLUTSEL: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUTSEL)))
+ print("MSLUTSTART: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSLUTSTART)))
+ print("MSCNT: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSCNT)))
+ print("MSCURACT: 0x{0:08X}".format(eval_board.read_register(mc.REG.MSCURACT)))
+ print("CHOPCONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.CHOPCONF)))
+ print("COOLCONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.COOLCONF)))
+ print("DCCTRL: 0x{0:08X}".format(eval_board.read_register(mc.REG.DCCTRL)))
+ print("DRV_STATUS: 0x{0:08X}".format(eval_board.read_register(mc.REG.DRV_STATUS)))
+ print("PWMCONF: 0x{0:08X}".format(eval_board.read_register(mc.REG.PWMCONF)))
+ print("PWM_SCALE: 0x{0:08X}".format(eval_board.read_register(mc.REG.PWM_SCALE)))
+ print("PWM_AUTO: 0x{0:08X}".format(eval_board.read_register(mc.REG.PWM_AUTO)))
+ print("SG4_THRS: 0x{0:08X}".format(eval_board.read_register(mc.REG.SG4_THRS)))
+ print("SG4_RESULT: 0x{0:08X}".format(eval_board.read_register(mc.REG.SG4_RESULT)))
+ print("SG4_IND: 0x{0:08X}".format(eval_board.read_register(mc.REG.SG4_IND)))
+
+
+print("\nReady.")
diff --git a/examples/evalboards/TMC2240/rotate_demo.py b/examples/evalboards/TMC2240/rotate_demo.py
new file mode 100644
index 00000000..f6575590
--- /dev/null
+++ b/examples/evalboards/TMC2240/rotate_demo.py
@@ -0,0 +1,45 @@
+"""
+Move a motor back and forth using velocity and position mode of the TMC2240
+"""
+import time
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.evalboards import TMC2240_eval
+
+pytrinamic.show_info()
+
+with ConnectionManager().connect() as my_interface:
+ print(my_interface)
+
+ # Create TMC2240-EVAL class which communicates over the Landungsbrücke via TMCL
+ eval_board = TMC2240_eval(my_interface)
+ mc = eval_board.ics[0]
+ motor = eval_board.motors[0]
+
+ print("Preparing parameter...")
+ motor.set_axis_parameter(motor.AP.MaxAcceleration, 20000)
+ motor.set_axis_parameter(motor.AP.MaxVelocity, 100000)
+ motor.set_axis_parameter(motor.AP.MaxCurrent, 30)
+
+ # Clear actual positions
+ motor.actual_position = 0
+
+ print("Rotating...")
+ motor.rotate(7*25600)
+ time.sleep(5)
+
+ print("Stopping...")
+ motor.stop()
+ time.sleep(1)
+
+ print("Moving back to 0...")
+ motor.move_to(0, 100000)
+
+ # Wait until position 0 is reached
+ while motor.actual_position != 0:
+ print("Actual position: " + str(motor.actual_position))
+ time.sleep(0.2)
+
+ print("Reached position 0")
+
+print("\nReady.")
diff --git a/examples/evalboards/TMC4671/TMC4671_eval_BLDC_open_loop.py b/examples/evalboards/TMC4671/TMC4671_eval_BLDC_open_loop.py
index c1f63e4f..16d8d19b 100644
--- a/examples/evalboards/TMC4671/TMC4671_eval_BLDC_open_loop.py
+++ b/examples/evalboards/TMC4671/TMC4671_eval_BLDC_open_loop.py
@@ -8,7 +8,6 @@
pytrinamic.show_info()
with ConnectionManager().connect() as my_interface:
- # my_interface.enable_debug(True)
print(my_interface)
if isinstance(my_interface, UartIcInterface):
diff --git a/examples/evalboards/TMC4671/TMC4671_eval_TMC6100_eval_BLDC_open_loop.py b/examples/evalboards/TMC4671/TMC4671_eval_TMC6100_eval_BLDC_open_loop.py
index af29b61f..1f306a26 100644
--- a/examples/evalboards/TMC4671/TMC4671_eval_TMC6100_eval_BLDC_open_loop.py
+++ b/examples/evalboards/TMC4671/TMC4671_eval_TMC6100_eval_BLDC_open_loop.py
@@ -7,7 +7,6 @@
pytrinamic.show_info()
with ConnectionManager().connect() as my_interface:
- # my_interface.enable_debug(True)
print(my_interface)
# Create a TMC4671-EVAL and TMC6100-EVAL which communicates over the Landungsbrücke via TMCL
diff --git a/examples/evalboards/TMC4671/TMC4671_eval_TMC6200_eval_BLDC_open_loop.py b/examples/evalboards/TMC4671/TMC4671_eval_TMC6200_eval_BLDC_open_loop.py
index c5ec69eb..250ebbca 100644
--- a/examples/evalboards/TMC4671/TMC4671_eval_TMC6200_eval_BLDC_open_loop.py
+++ b/examples/evalboards/TMC4671/TMC4671_eval_TMC6200_eval_BLDC_open_loop.py
@@ -7,7 +7,6 @@
pytrinamic.show_info()
with ConnectionManager().connect() as my_interface:
- # myInterface.enable_debug(True)
print(my_interface)
# Create a TMC4671-EVAL and TMC6200-EVAL which communicates over the Landungsbrücke via TMCL
diff --git a/examples/evalboards/TMC5160/rotate_demo.py b/examples/evalboards/TMC5160/rotate_demo.py
index f07d3be3..bbdab8d0 100644
--- a/examples/evalboards/TMC5160/rotate_demo.py
+++ b/examples/evalboards/TMC5160/rotate_demo.py
@@ -1,5 +1,9 @@
"""
Move a motor back and forth using velocity and position mode of the TMC5160
+
+Line 31, we set a lower run/standby current for the motor. Using NEMA17, this should result in a coil current around 800mA.
+If the motor is stalling due to too low current, set motorCurrent higher.
+If a lower value still is needed, set GLOBAL_SCALER register to 128 to half motor current.
"""
import time
import pytrinamic
@@ -16,7 +20,7 @@
mc = eval_board.ics[0]
motor = eval_board.motors[0]
- print("Preparing parameter...")
+ print("Preparing parameters...")
eval_board.write_register(mc.REG.A1, 1000)
eval_board.write_register(mc.REG.V1, 50000)
eval_board.write_register(mc.REG.D1, 500)
@@ -25,11 +29,16 @@
eval_board.write_register(mc.REG.VSTOP, 10)
eval_board.write_register(mc.REG.AMAX, 1000)
+ # Set lower run/standby current
+ motorCurrent = 2
+ motor.set_axis_parameter(motor.AP.MaxCurrent, motorCurrent)
+ motor.set_axis_parameter(motor.AP.StandbyCurrent, motorCurrent)
+
# Clear actual positions
motor.actual_position = 0
print("Rotating...")
- motor.rotate(7*25600)
+ motor.rotate(7 * 25600)
time.sleep(5)
print("Stopping...")
@@ -37,7 +46,7 @@
time.sleep(1)
print("Moving back to 0...")
- motor.move_to(0, 100000)
+ motor.move_to(0, 7 * 25600)
# Wait until position 0 is reached
while motor.actual_position != 0:
diff --git a/examples/modules/TMCM1231/TMCL/StallGuard2_demo.py b/examples/modules/TMCM1231/TMCL/StallGuard2_demo.py
new file mode 100644
index 00000000..36077618
--- /dev/null
+++ b/examples/modules/TMCM1231/TMCL/StallGuard2_demo.py
@@ -0,0 +1,78 @@
+"""
+Sets the StallGuard2 threshold such that the stall guard value (i.e SG value) is zero
+when the motor comes close to stall and also sets the stop on stall velocity to a value
+one less than the actual velocity of the motor
+"""
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM1231
+import time
+
+def stallguard2_init(motor, init_velocity):
+ # Resetting SG2 threshold and stop on stall velocity to zero
+ motor.stallguard2.set_threshold(0)
+ motor.stallguard2.stop_velocity = 0
+ print("Initial StallGuard2 values:")
+ print(motor.stallguard2)
+ print("Rotating...")
+ motor.rotate(init_velocity)
+ sgthresh = 0
+ sgt = 0
+ load_samples = []
+ while (sgt == 0) and (sgthresh < 64):
+ load_samples = []
+ motor.stallguard2.set_threshold(sgthresh)
+ time.sleep(0.2)
+ sgthresh += 1
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+ if not any(load_samples):
+ sgt = 0
+ else:
+ sgt = max(load_samples)
+ while 1:
+ load_samples = []
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+ if 0 in load_samples:
+ motor.drive_settings.max_current = motor.drive_settings.max_current - 1
+ else:
+ break
+
+ motor.stallguard2.stop_velocity = motor.get_actual_velocity() - 1
+ print("Configured StallGuard2 parameters:")
+ print(motor.stallguard2)
+
+def main():
+ pytrinamic.show_info()
+
+ # This example is using PCAN, if you want to use another connection please change the next line.
+ connection_manager = ConnectionManager("--interface pcan_tmcl")
+ with connection_manager.connect() as my_interface:
+ module = TMCM1231(my_interface)
+ motor = module.motors[0]
+
+ print("Preparing parameters")
+ # preparing drive settings
+ motor.drive_settings.max_current = 20
+ motor.drive_settings.standby_current = 8
+ motor.drive_settings.boost_current = 0
+ motor.drive_settings.microstep_resolution = motor.ENUM.microstep_resolution_256_microsteps
+ print(motor.drive_settings)
+ print(motor.linear_ramp)
+
+ time.sleep(1.0)
+
+ # clear position counter
+ motor.actual_position = 0
+
+ # set up StallGuard2
+ print("Configuring StallGuard2 parameters...")
+ stallguard2_init(motor, init_velocity = 10000)
+ print("Apply load and try to stall the motor...")
+ while not (motor.actual_velocity == 0):
+ pass
+ print("Motor stopped by StallGuard2!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/examples/modules/TMCM1231/TMCL/rotate_demo.py b/examples/modules/TMCM1231/TMCL/rotate_demo.py
new file mode 100644
index 00000000..c4eeed1f
--- /dev/null
+++ b/examples/modules/TMCM1231/TMCL/rotate_demo.py
@@ -0,0 +1,68 @@
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM1231
+import time
+
+pytrinamic.show_info()
+
+# This example is using PCAN, if you want to use another connection please change the next line.
+connectionManager = ConnectionManager("--interface pcan_tmcl")
+with connectionManager.connect() as myInterface:
+ module = TMCM1231(myInterface)
+ motor = module.motors[0]
+
+ # Please be sure not to use a too high current setting for your motor.
+
+ print("Preparing parameters")
+ # preparing drive settings
+ motor.drive_settings.max_current = 128
+ motor.drive_settings.standby_current = 0
+ motor.drive_settings.boost_current = 0
+ motor.drive_settings.microstep_resolution = motor.ENUM.microstep_resolution_256_microsteps
+ print(motor.drive_settings)
+
+
+ # preparing linear ramp settings
+ motor.max_acceleration = 51200
+ motor.max_velocity = 51200
+
+ # reset actual position
+ motor.actual_position = 0
+
+ print(motor.linear_ramp)
+
+ # start rotating motor in different directions
+ print("Rotating")
+ motor.rotate(51200)
+ time.sleep(5)
+
+ # stop rotating motor
+ print("Stopping")
+ motor.stop()
+
+ # read actual position
+ print("ActualPostion = {}".format(motor.actual_position))
+ time.sleep(2)
+
+ # doubling moved distance
+ print("Doubling moved distance")
+ motor.move_by(motor.actual_position)
+
+ # wait till position_reached
+ while not(motor.get_position_reached()):
+ print("target position motor: " + str(motor.target_position) + " actual position motor: " + str(motor.actual_position))
+
+ time.sleep(0.2)
+ print("Furthest point reached")
+ print("ActualPostion motor = {}".format(motor.actual_position))
+
+ # short delay and move back to start
+ time.sleep(2)
+ print("Moving back to 0")
+ motor.move_to(0)
+
+ # wait until position 0 is reached
+ while not(motor.get_position_reached()):
+ print("target position motor: " + str(motor.target_position) + " actual position motor: " + str(motor.actual_position))
+
+ print("Reached Position 0")
diff --git a/examples/modules/TMCM1240/TMCL/StallGuard2_demo.py b/examples/modules/TMCM1240/TMCL/StallGuard2_demo.py
new file mode 100644
index 00000000..b866efd8
--- /dev/null
+++ b/examples/modules/TMCM1240/TMCL/StallGuard2_demo.py
@@ -0,0 +1,81 @@
+"""
+Sets the StallGuard2 threshold such that the stall guard value (i.e SG value) is zero
+when the motor comes close to stall and also sets the stop on stall velocity to a value
+one less than the actual velocity of the motor
+"""
+
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM1240
+import time
+
+def stallguard2_init(motor, init_velocity):
+ # Resetting SG2 threshold and stop on stall velocity to zero
+ motor.stallguard2.set_threshold(0)
+ motor.stallguard2.stop_velocity = 0
+ print("Initial StallGuard2 values:")
+ print(motor.stallguard2)
+ print("Rotating...")
+ motor.rotate(init_velocity)
+ sgthresh = 0
+ sgt = 0
+ load_samples = []
+ while (sgt == 0) and (sgthresh < 64):
+ load_samples = []
+ motor.stallguard2.set_threshold(sgthresh)
+ time.sleep(0.2)
+ sgthresh += 1
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+
+ if not any(load_samples):
+ sgt = 0
+ else:
+ sgt = max(load_samples)
+ while 1:
+ load_samples = []
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+ if 0 in load_samples:
+ motor.drive_settings.max_current = motor.drive_settings.max_current - 8
+ else:
+ break
+
+ motor.stallguard2.stop_velocity = motor.get_actual_velocity() - 1
+ print("Configured StallGuard2 parameters:")
+ print(motor.stallguard2)
+
+def main():
+ pytrinamic.show_info()
+
+ connection_manager = ConnectionManager()
+ with connection_manager.connect() as my_interface:
+ module = TMCM1240(my_interface)
+ motor = module.motors[0]
+
+
+ print("Preparing parameters")
+ # preparing drive settings
+ motor.drive_settings.max_current = 60
+ motor.drive_settings.standby_current = 8
+ motor.drive_settings.boost_current = 0
+ motor.drive_settings.microstep_resolution = motor.ENUM.MicrostepResolution256Microsteps
+
+ print(motor.drive_settings)
+ print(motor.linear_ramp)
+
+ time.sleep(1.0)
+
+ # clear position counter
+ motor.actual_position = 0
+
+ # set up StallGuard2
+ print("Configuring StallGuard2 parameters...")
+ stallguard2_init(motor,init_velocity = 10000)
+ print("Apply load and try to stall the motor...")
+ while not (motor.actual_velocity == 0):
+ pass
+ print("Motor stopped by StallGuard2!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/examples/modules/TMCM1276/TMCL/six_point_ramp_demo.py b/examples/modules/TMCM1276/TMCL/six_point_ramp_demo.py
new file mode 100644
index 00000000..48dbc954
--- /dev/null
+++ b/examples/modules/TMCM1276/TMCL/six_point_ramp_demo.py
@@ -0,0 +1,73 @@
+import dataclasses
+
+import matplotlib.pyplot as plt
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM1276
+import time
+
+
+@dataclasses.dataclass
+class Sample:
+ timestamp: float
+ position: int
+ velocity: int
+
+
+pytrinamic.show_info()
+
+# This example is using PCAN, if you want to use another connection please change the next line.
+connectionManager = ConnectionManager("--interface pcan_tmcl")
+
+with connectionManager.connect() as myInterface:
+ module = TMCM1276(myInterface)
+ motor = module.motors[0]
+
+ # Setting axis parameters for configuring SixPoint ramp
+ motor.set_axis_parameter(motor.AP.MaxVelocity, 40000)
+ motor.set_axis_parameter(motor.AP.MaxAcceleration, 30000)
+ motor.set_axis_parameter(motor.AP.A1, 5000)
+ motor.set_axis_parameter(motor.AP.V1, 10000)
+ motor.set_axis_parameter(motor.AP.MaxDeceleration, 20000)
+ motor.set_axis_parameter(motor.AP.D1, 5000)
+ motor.set_axis_parameter(motor.AP.StartVelocity, 5000)
+ motor.set_axis_parameter(motor.AP.StopVelocity, 5000)
+ motor.set_axis_parameter(motor.AP.RampWaitTime, 31250)
+
+ # Setting initial position to zero
+ motor.actual_position = 0
+
+ samples = []
+ motor.move_to(100000)
+ while not motor.get_position_reached():
+ samples.append(Sample(time.perf_counter(), format(motor.actual_position), format(motor.actual_velocity)))
+
+ motor.move_to(0)
+ while not motor.get_position_reached():
+ samples.append(Sample(time.perf_counter(), format(motor.actual_position), format(motor.actual_velocity)))
+
+ fig, ax = plt.subplots(2)
+ t = [float(s.timestamp - samples[0].timestamp) for s in samples]
+ pos = [float(s.position) for s in samples]
+ vel = [float(s.velocity) for s in samples]
+
+ ax[0].plot(t, pos, label='Position')
+ ax[0].set_title('Pos vs Time')
+ ax[0].set_xlabel('Time')
+ ax[0].set_ylabel('Pos')
+ ax[0].legend()
+ ax[0].grid()
+
+ ax[1].plot(t, vel, label='Velocity')
+ ax[1].set_title('Vel vs Time')
+ ax[1].set_xlabel('Time')
+ ax[1].set_ylabel('Vel')
+ ax[1].legend()
+ ax[1].grid()
+ plt.show()
+
+
+
+
+
+
diff --git a/examples/modules/TMCM1276/TMCL/stop_switch_demo.py b/examples/modules/TMCM1276/TMCL/stop_switch_demo.py
new file mode 100644
index 00000000..a9c2fe47
--- /dev/null
+++ b/examples/modules/TMCM1276/TMCL/stop_switch_demo.py
@@ -0,0 +1,32 @@
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM1276
+import time
+
+pytrinamic.show_info()
+
+# This example is using PCAN, if you want to use another connection please change the next line.
+connectionManager = ConnectionManager("--interface pcan_tmcl")
+
+with connectionManager.connect() as myInterface:
+ module = TMCM1276(myInterface)
+ motor = module.motors[0]
+
+ print("Preparing parameters")
+ # preparing linear ramp settings
+ motor.max_acceleration = 20000
+
+ while 1:
+ if motor.get_axis_parameter(motor.AP.RightEndstop):
+ motor.stop()
+ time.sleep(5)
+ print("Rotating in opposite direction")
+ motor.rotate(-50000)
+ time.sleep(5)
+ motor.stop()
+ break
+ else:
+ print("Rotating")
+ motor.rotate(50000)
+ time.sleep(5)
+
diff --git a/examples/modules/TMCM6214/TMCL/StallGuard2_demo.py b/examples/modules/TMCM6214/TMCL/StallGuard2_demo.py
new file mode 100644
index 00000000..f9e1d3b5
--- /dev/null
+++ b/examples/modules/TMCM6214/TMCL/StallGuard2_demo.py
@@ -0,0 +1,80 @@
+"""
+Sets the StallGuard2 threshold such that the stall guard value (i.e SG value) is zero
+when the motor comes close to stall and also sets the stop on stall velocity to a value
+one less than the actual velocity of the motor
+"""
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM6214
+import time
+
+def stallguard2_init(motor, init_velocity):
+ # Resetting SG2 threshold and stop on stall velocity to zero
+ motor.stallguard2.set_threshold(0)
+ motor.stallguard2.stop_velocity = 0
+ print("Initial StallGuard2 values:")
+ print(motor.stallguard2)
+ print("Rotating...")
+ motor.rotate(init_velocity)
+ sgthresh = 0
+ sgt = 0
+ load_samples = []
+ while (sgt == 0) and (sgthresh < 64):
+ load_samples = []
+ motor.stallguard2.set_threshold(sgthresh)
+ time.sleep(0.2)
+ sgthresh += 1
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+ if not any(load_samples):
+ sgt = 0
+ else:
+ sgt = max(load_samples)
+ while 1:
+ load_samples = []
+ for i in range(50):
+ load_samples.append(motor.stallguard2.get_load_value())
+ print(load_samples)
+ if 0 in load_samples:
+ motor.drive_settings.max_current = motor.drive_settings.max_current - 8
+ else:
+ break
+
+ motor.stallguard2.stop_velocity = motor.get_actual_velocity() - 1
+ print("Configured StallGuard2 parameters:")
+ print(motor.stallguard2)
+
+def main():
+ pytrinamic.show_info()
+
+ connection_manager = ConnectionManager()
+ with connection_manager.connect() as my_interface:
+ module = TMCM6214(my_interface)
+ motor = module.motors[0]
+
+ print("Preparing parameters")
+ # preparing drive settings
+ motor.drive_settings.max_current = 128
+ motor.drive_settings.standby_current = 8
+ motor.drive_settings.boost_current = 0
+ motor.drive_settings.microstep_resolution = motor.ENUM.MicrostepResolution256Microsteps
+ print(motor.drive_settings)
+ print(motor.linear_ramp)
+
+ time.sleep(1.0)
+
+ # clear position counter
+ motor.actual_position = 0
+
+ # set up StallGuard2
+ print("Configuring StallGuard2 parameters...")
+ stallguard2_init(motor, init_velocity = 10000)
+ print("Apply load and try to stall the motor...")
+ while not (motor.actual_velocity == 0):
+ pass
+ print("Motor stopped by StallGuard2!")
+
+if __name__ == "__main__":
+ main()
+
+
diff --git a/examples/modules/TMCM6214/TMCL/rotate_demo.py b/examples/modules/TMCM6214/TMCL/rotate_demo.py
new file mode 100644
index 00000000..0802b1b5
--- /dev/null
+++ b/examples/modules/TMCM6214/TMCL/rotate_demo.py
@@ -0,0 +1,47 @@
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM6214
+import time
+
+pytrinamic.show_info()
+connectionManager = ConnectionManager()
+
+with connectionManager.connect() as myInterface:
+ module = TMCM6214(myInterface)
+ motor_0 = module.motors[0]
+
+ print("Preparing parameters")
+ motor_0.max_acceleration = 20000
+
+ print("Rotating")
+ motor_0.rotate(50000)
+
+ time.sleep(5)
+
+ print("Stopping")
+ motor_0.stop()
+
+ print("ActualPostion = {}".format(motor_0.actual_position))
+
+ time.sleep(5)
+
+ print("Doubling moved distance")
+ motor_0.move_by(motor_0.actual_position, 50000)
+ while not(motor_0.get_position_reached()):
+ pass
+
+ print("Furthest point reached")
+ print("ActualPostion = {}".format(motor_0.actual_position))
+
+ time.sleep(5)
+
+ print("Moving back to 0")
+ motor_0.move_to(0, 100000)
+
+ # Wait until position 0 is reached
+ while not(motor_0.get_position_reached()):
+ pass
+
+ print("Reached Position 0")
+
+
diff --git a/examples/modules/TMCM6214/TMCL/six_point_ramp_demo.py b/examples/modules/TMCM6214/TMCL/six_point_ramp_demo.py
new file mode 100644
index 00000000..13c34d27
--- /dev/null
+++ b/examples/modules/TMCM6214/TMCL/six_point_ramp_demo.py
@@ -0,0 +1,66 @@
+import dataclasses
+import matplotlib.pyplot as plt
+import pytrinamic
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.modules import TMCM6214
+import time
+
+
+@dataclasses.dataclass
+class Sample:
+ timestamp: float
+ position: int
+ velocity: int
+
+
+pytrinamic.show_info()
+# This example is using USB.
+connectionManager = ConnectionManager()
+
+with connectionManager.connect() as myInterface:
+ module = TMCM6214(myInterface)
+ motor = module.motors[0]
+
+ # Setting axis parameters for configuring SixPoint ramp
+ motor.set_axis_parameter(motor.AP.MaxVelocity, 40000)
+ motor.set_axis_parameter(motor.AP.MaxAcceleration, 30000)
+ motor.set_axis_parameter(motor.AP.A1, 5000)
+ motor.set_axis_parameter(motor.AP.V1, 10000)
+ motor.set_axis_parameter(motor.AP.MaxDeceleration, 20000)
+ motor.set_axis_parameter(motor.AP.D1, 5000)
+ motor.set_axis_parameter(motor.AP.StartVelocity, 5000)
+ motor.set_axis_parameter(motor.AP.StopVelocity, 5000)
+ motor.set_axis_parameter(motor.AP.RampWaitTime, 31250)
+
+ # Setting initial position to zero
+ motor.actual_position = 0
+
+ samples = []
+ motor.move_to(100000)
+ while not motor.get_position_reached():
+ samples.append(Sample(time.perf_counter(), format(motor.actual_position), format(motor.actual_velocity)))
+
+ motor.move_to(0)
+ while not motor.get_position_reached():
+ samples.append(Sample(time.perf_counter(), format(motor.actual_position), format(motor.actual_velocity)))
+
+ fig, ax = plt.subplots(2)
+ t = [float(s.timestamp - samples[0].timestamp) for s in samples]
+ pos = [float(s.position) for s in samples]
+ vel = [float(s.velocity) for s in samples]
+
+ ax[0].plot(t, pos, label='Position')
+ ax[0].set_title('Pos vs Time')
+ ax[0].set_xlabel('Time')
+ ax[0].set_ylabel('Pos')
+ ax[0].legend()
+ ax[0].grid()
+
+ ax[1].plot(t, vel, label='Velocity')
+ ax[1].set_title('Vel vs Time')
+ ax[1].set_xlabel('Time')
+ ax[1].set_ylabel('Vel')
+ ax[1].legend()
+ ax[1].grid()
+ plt.show()
+
diff --git a/examples/tools/FirmwareUpdate.py b/examples/tools/FirmwareUpdate.py
index 45b89e88..64747e30 100644
--- a/examples/tools/FirmwareUpdate.py
+++ b/examples/tools/FirmwareUpdate.py
@@ -71,26 +71,30 @@
print("Connecting")
myInterface = connectionManager.connect()
-# Send the boot command
-print("Switching to bootloader mode")
-myInterface.send_boot(1)
-myInterface.close()
-
-# Reconnect after a small delay
-print("Reconnecting")
-timestamp = time.time()
-while (time.time() - timestamp) < SERIAL_BOOT_TIMEOUT:
- try:
- # Attempt to connect
- myInterface = connectionManager.connect()
- # If no exception occurred, exit the retry loop
- break
- except (ConnectionError, TypeError):
- myInterface = None
+# If not already in bootloader, enter it
+if not "B" in myInterface.get_version_string().upper():
+ # Send the boot command
+ print("Switching to bootloader mode")
+ myInterface.send_boot(1)
+ myInterface.close()
+
+ # Reconnect after a small delay
+ print("Reconnecting")
+ timestamp = time.time()
+ while (time.time() - timestamp) < SERIAL_BOOT_TIMEOUT:
+ try:
+ # Attempt to connect
+ myInterface = connectionManager.connect()
+ # If no exception occurred, exit the retry loop
+ break
+ except (ConnectionError, TypeError):
+ myInterface = None
+
+ if not myInterface:
+ print("Error: Timeout when attempting to reconnect to bootloader")
+ exit(1)
-if not myInterface:
- print("Error: Timeout when attempting to reconnect to bootloader")
- exit(1)
+time.sleep(1)
# Retrieve the bootloader version
bootloaderVersion = myInterface.get_version_string(1)
@@ -123,6 +127,9 @@
print("Error: No matching version string found in firmware image")
exit(1)
+start = file.minaddr()
+length = file.maxaddr() - start
+
print("Bootloader version: " + bootloaderVersion)
print("Firmware version: " + found.group(0))
diff --git a/pytrinamic/RAMDebug.py b/pytrinamic/RAMDebug.py
index b1f13bc7..50a34f4c 100644
--- a/pytrinamic/RAMDebug.py
+++ b/pytrinamic/RAMDebug.py
@@ -1,4 +1,4 @@
-from pytrinamic.tmcl import TMCLCommand
+from pytrinamic.tmcl import TMCLCommand, TMCLReplyStatusError, TMCLStatus
from enum import IntEnum
class RAMDebug_Command(IntEnum):
@@ -56,6 +56,12 @@ class RAMDebug_State(IntEnum):
COMPLETE = 3
PRETRIGGER = 4
+ UNKNOWN_STATUS = -1 # Placeholder value in case invalid state values were returned
+
+ @classmethod
+ def _missing_(cls, value):
+ return cls.UNKNOWN_STATUS
+
class Channel():
def __init__(self, channel_type, value, address = 0, signed = False, mask = 0xFFFF_FFFF, shift = 0): #TODO: add signed
self.value = value
@@ -132,7 +138,7 @@ def __init__(self, connection):
self._trigger_mask = 0x0000_0000
self._trigger_shift = 0x0000_0000
self.channels = []
- self.samples = []
+ self.samples = None
def get_sample_count(self):
return self._sample_count
@@ -147,26 +153,54 @@ def set_process_frequency(self, process_frequency):
self._process_frequency = process_frequency
def set_prescaler(self, prescaler):
+ """
+ Set the capture prescaler to divide the capture frequency.
+ The actual capture frequency is MAX_FREQUENCY/(prescaler+1).
+ """
self._prescaler = prescaler
+ def set_divider(self, divider):
+ """
+ Set the capture prescaler to divide the capture frequency.
+ The actual capture frequency is MAX_FREQUENCY/divider.
+ """
+ if not (1 <= divider <= 0xFFFF_FFFF):
+ raise ValueError("Invalid divider value. Possible divider values are [1; 2^32-1]")
+
+ self._prescaler = divider-1
+
def set_trigger_type(self, trigger_type):
- if isinstance(trigger_type, RAMDebug_Trigger):
- self._trigger_type = trigger_type
+ if not isinstance(trigger_type, RAMDebug_Trigger):
+ raise ValueError("Invalid trigger type - you must pass a RAMDebug_Trigger object")
+
+ self._trigger_type = trigger_type
def set_trigger_threshold(self, trigger_threshold):
self._trigger_threshold = trigger_threshold
def set_trigger_channel(self, channel):
- if isinstance(channel, Channel):
- self._trigger_channel = channel
- self._trigger_mask = channel._mask
- self._trigger_shift = channel._shift
+ if not isinstance(channel, Channel):
+ raise ValueError("Invalid channel - you must pass a Channel object")
+ self._trigger_channel = channel
+ self._trigger_mask = channel.mask
+ self._trigger_shift = channel.shift
+
+ def set_trigger(self, trigger_channel, trigger_type, trigger_threshold):
+ """
+ Fully configure the RAMDebug trigger
+ """
+ self.set_trigger_type(trigger_type)
+ self.set_trigger_threshold(trigger_threshold)
+ self.set_trigger_channel(trigger_channel)
def set_pretrigger_samples(self, pretrigger_samples):
self._pretrigger_samples = pretrigger_samples
def set_channel(self, channel):
+ if not isinstance(channel, Channel):
+ raise ValueError("Invalid channel - you must pass a Channel object")
+
if self.channel_count() >= self.MAX_CHANNELS:
raise RuntimeError("Out of channels!")
@@ -175,12 +209,47 @@ def set_channel(self, channel):
def get_channels(self):
return self.channels
- def start_measurement(self):
+ def start_measurement(self, *, strict=True):
+ """
+ Start the measurement.
+ If you are waiting for a trigger, wait until is_pretriggering() returns false before causing
+ your trigger event.
+
+ Arguments:
+ - strict:
+ When set to True, reject invalid sample counts.
+ When set to False, automatically adjust too high sample counts.
+ """
+ samples = self.get_total_samples()
+ if self.get_total_samples() > self.MAX_ELEMENTS:
+ if strict:
+ raise RuntimeError(f"Too many samples requested! Requested {self.get_total_samples()} ({self._sample_count} for {self.channel_count()} channels). Maximum available samples: {self.MAX_ELEMENTS}. Either adjust your sample count or pass strict=False to this function to let RAMDebug reduce sample count automatically.")
+ else:
+ # Non-strict mode: Limit the sample count
+ samples = self.MAX_ELEMENTS - (self.MAX_ELEMENTS % self.channel_count())
+
+ pretrigger_samples = self._pretrigger_samples * self.channel_count()
+ if pretrigger_samples > samples:
+ if strict:
+ raise RuntimeError(f"Too many pretrigger samples requested! Requested {pretrigger_samples} pretrigger samples, but only capturing {samples} samples.")
+ else:
+ # Non-strict mode: Limit the pretrigger sample count
+ pretrigger_samples = samples
+
self._command(RAMDebug_Command.INIT.value, 0, 0)
- self._command(RAMDebug_Command.SET_SAMPLE_COUNT.value, 0, self.get_total_samples())
- self._command(RAMDebug_Command.SET_PROCESS_FREQUENCY, 0, self._process_frequency)
+ self._command(RAMDebug_Command.SET_SAMPLE_COUNT.value, 0, samples)
self._command(RAMDebug_Command.SET_PRESCALER.value, 0, self._prescaler)
+ try:
+ self._command(RAMDebug_Command.SET_PROCESS_FREQUENCY, 0, self._process_frequency)
+ except TMCLReplyStatusError as e:
+ if e.status_code == TMCLStatus.WRONG_TYPE:
+ # SET_PROCESS_FREQUENCY not supported -> skip exception
+ pass
+ else:
+ # A different error occurred -> reraise exception
+ raise e
+
for channel in self.channels:
self._command(RAMDebug_Command.SET_CHANNEL.value, channel.type.value, channel.value)
@@ -189,14 +258,21 @@ def start_measurement(self):
self._command(RAMDebug_Command.SET_TRIGGER_CHANNEL.value, self._trigger_channel.type.value, self._trigger_channel.value)
self._command(RAMDebug_Command.ENABLE_TRIGGER.value, self._trigger_type.value, self._trigger_threshold)
+ def is_pretriggering(self):
+ return self.get_state() == RAMDebug_State.PRETRIGGER
+
def is_measurement_done(self):
- return self.get_state() == RAMDebug_State.COMPLETE.value
+ return self.get_state() == RAMDebug_State.COMPLETE
def get_samples(self):
+ # If the samples were already downloaded, just return them
+ if self.samples:
+ return self.samples
+
i = 0
data = []
- while i < self.get_total_samples():
+ while i < min(self.get_total_samples(), self.MAX_ELEMENTS):
reply = self._command(RAMDebug_Command.GET_SAMPLE.value, 0, i)
done = reply.status != 0x64
if done:
@@ -242,7 +318,10 @@ def channel_count(self):
return len(self.channels)
def get_state(self):
- return self._command(RAMDebug_Command.GET_STATE.value, 0, 0).value
+ """
+ Returns the state of this measurement as a RAMDebug_State enum
+ """
+ return RAMDebug_State(self._command(RAMDebug_Command.GET_STATE.value, 0, 0).value)
def __str__(self):
text = f"RAMDebug handler for connection {self._connection}\n"
diff --git a/pytrinamic/connections/__init__.py b/pytrinamic/connections/__init__.py
index ddae80e1..6f98e0eb 100644
--- a/pytrinamic/connections/__init__.py
+++ b/pytrinamic/connections/__init__.py
@@ -1,10 +1,10 @@
from .dummy_tmcl_interface import DummyTmclInterface
-from .pcan_tmcl_interface import PcanTmclInterface
-from .socketcan_tmcl_interface import SocketcanTmclInterface
-from .kvaser_tmcl_interface import KvaserTmclInterface
+from .can_tmcl.pcan_tmcl_interface import PcanTmclInterface
+from .can_tmcl.socketcan_tmcl_interface import SocketcanTmclInterface
+from .can_tmcl.kvaser_tmcl_interface import KvaserTmclInterface
from .serial_tmcl_interface import SerialTmclInterface
from .uart_ic_interface import UartIcInterface
from .usb_tmcl_interface import UsbTmclInterface
-from .slcan_tmcl_interface import SlcanTmclInterface
-from .ixxat_tmcl_interface import IxxatTmclInterface
+from .can_tmcl.slcan_tmcl_interface import SlcanTmclInterface
+from .can_tmcl.ixxat_tmcl_interface import IxxatTmclInterface
from .connection_manager import ConnectionManager
diff --git a/pytrinamic/connections/can_tmcl/__init__.py b/pytrinamic/connections/can_tmcl/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/pytrinamic/connections/can_tmcl/ixxat_tmcl_interface.py b/pytrinamic/connections/can_tmcl/ixxat_tmcl_interface.py
new file mode 100644
index 00000000..4f198c98
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl/ixxat_tmcl_interface.py
@@ -0,0 +1,58 @@
+import can
+from ...connections.can_tmcl_interface import CanTmclInterface
+
+
+class IxxatTmclInterface(CanTmclInterface):
+ """
+ This class implements a TMCL connection for IXXAT USB-to-CAN adapter.
+ Backend is provided by the IXXAT Virtual CAN Interface V3 SDK.
+
+ Port number is assigned as adapters are plugged in, arbitrarely,
+ it is possible to use multiple channels of one IXXAT, CAN1 is port "0", CAN2 is port "1", etc.
+
+ This class, and the parser implementation DOES NOT support multiple IXXATs connected to one computer.
+
+ To add this functionality, python-can version 4.0.0 is necessary as it allows enumerating IXXAT devices IDs,
+ (see https://github.com/hardbyte/python-can/pull/926).
+ To use multiple IXXAT devices, you must provide hardware IDs (this needs to be added to this class and to the parser).
+
+ Snippet to list IXXAT by hardware ID using python-can 4.0.0:
+ from can.interfaces.ixxat import IXXATBus
+ for hwid in IXXATBus.list_adapters():
+ print("Found IXXAT adapter with hardware id '%s'." % hwid)
+ """
+
+ # Providing 5 channels here, this should cover all use cases.
+ # Ixxat USB-to-CAN provides 1, 2 or 4 channels.
+ _CHANNELS = ["0", "1", "2", "3", "4"]
+
+ def __init__(self, port="0", datarate=1000000, host_id=2, module_id=1, timeout_s=5):
+ if not isinstance(port, str):
+ raise TypeError
+
+ if port not in self._CHANNELS:
+ raise ValueError("Invalid port!")
+
+ CanTmclInterface.__init__(self, port, datarate, host_id, module_id, timeout_s)
+
+ self.logger.info("Connect to bus with bit-rate %s.", self._bitrate)
+ try:
+ self._connection = can.Bus(interface="ixxat",
+ channel=self._channel,
+ bitrate=self._bitrate,
+ can_filters=[{"can_id": host_id, "can_mask": 0x7F}])
+ except can.CanError as e:
+ self._connection = None
+ raise ConnectionError(
+ f"Failed to connect to {self.__class__.__name__} on channel {str(self._channel)}"
+ ) from e
+
+ @classmethod
+ def list(cls):
+ """
+ Return a list of available connection ports as a list of strings.
+
+ This function is required for using this interface with the
+ connection manager.
+ """
+ return cls._CHANNELS
diff --git a/pytrinamic/connections/can_tmcl/kvaser_tmcl_interface.py b/pytrinamic/connections/can_tmcl/kvaser_tmcl_interface.py
new file mode 100644
index 00000000..d6015f73
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl/kvaser_tmcl_interface.py
@@ -0,0 +1,39 @@
+import can
+from ...connections.can_tmcl_interface import CanTmclInterface
+
+
+class KvaserTmclInterface(CanTmclInterface):
+ """
+ This class implements a TMCL connection for Kvaser adapter using CANLIB.
+ Try 0 as default channel.
+ """
+ _CHANNELS = ["0", "1", "2"]
+
+ def __init__(self, port="0", datarate=1000000, host_id=2, module_id=1, timeout_s=5):
+ if not isinstance(port, str):
+ raise TypeError
+
+ if port not in self._CHANNELS:
+ raise ValueError("Invalid port!")
+
+ CanTmclInterface.__init__(self, port, datarate, host_id, module_id, timeout_s)
+
+ self.logger.info("Connect to bus with bit-rate %s.", self._bitrate)
+ try:
+ self._connection = can.Bus(interface="kvaser",
+ channel=self._channel,
+ bitrate=self._bitrate,
+ can_filters=[{"can_id": host_id, "can_mask": 0x7F}])
+ except can.CanError as e:
+ self._connection = None
+ raise ConnectionError("Failed to connect to Kvaser CAN bus") from e
+
+ @classmethod
+ def list(cls):
+ """
+ Return a list of available connection ports as a list of strings.
+
+ This function is required for using this interface with the
+ connection manager.
+ """
+ return cls._CHANNELS
diff --git a/pytrinamic/connections/can_tmcl/pcan_tmcl_interface.py b/pytrinamic/connections/can_tmcl/pcan_tmcl_interface.py
new file mode 100644
index 00000000..6d82078a
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl/pcan_tmcl_interface.py
@@ -0,0 +1,62 @@
+
+import can
+from can.interfaces.pcan.pcan import PcanError
+from ...connections.can_tmcl_interface import CanTmclInterface
+
+
+class PcanTmclInterface(CanTmclInterface):
+ """
+ This class implements a TMCL connection over a PCAN adapter.
+ """
+ _CHANNELS = [
+ "PCAN_USBBUS1", "PCAN_USBBUS2", "PCAN_USBBUS3", "PCAN_USBBUS4",
+ "PCAN_USBBUS5", "PCAN_USBBUS6", "PCAN_USBBUS7", "PCAN_USBBUS8",
+ "PCAN_USBBUS9", "PCAN_USBBUS10", "PCAN_USBBUS11", "PCAN_USBBUS12",
+ "PCAN_USBBUS13", "PCAN_USBBUS14", "PCAN_USBBUS15", "PCAN_USBBUS16",
+
+ "PCAN_ISABUS1", "PCAN_ISABUS2", "PCAN_ISABUS3", "PCAN_ISABUS4",
+ "PCAN_ISABUS5", "PCAN_ISABUS6", "PCAN_ISABUS7", "PCAN_ISABUS8",
+
+ "PCAN_DNGBUS1",
+
+ "PCAN_PCIBUS1", "PCAN_PCIBUS2", "PCAN_PCIBUS3", "PCAN_PCIBUS4",
+ "PCAN_PCIBUS5", "PCAN_PCIBUS6", "PCAN_PCIBUS7", "PCAN_PCIBUS8",
+ "PCAN_PCIBUS9", "PCAN_PCIBUS10", "PCAN_PCIBUS11", "PCAN_PCIBUS12",
+ "PCAN_PCIBUS13", "PCAN_PCIBUS14", "PCAN_PCIBUS15", "PCAN_PCIBUS16",
+
+ "PCAN_PCCBUS1", "PCAN_PCCBUS2",
+
+ "PCAN_LANBUS1", "PCAN_LANBUS2", "PCAN_LANBUS3", "PCAN_LANBUS4",
+ "PCAN_LANBUS5", "PCAN_LANBUS6", "PCAN_LANBUS7", "PCAN_LANBUS8",
+ "PCAN_LANBUS9", "PCAN_LANBUS10", "PCAN_LANBUS11", "PCAN_LANBUS12",
+ "PCAN_LANBUS13", "PCAN_LANBUS14", "PCAN_LANBUS15", "PCAN_LANBUS16"
+ ]
+
+ def __init__(self, port, datarate=1000000, host_id=2, module_id=1, timeout_s=5):
+ if not isinstance(port, str):
+ raise TypeError
+
+ if port not in self._CHANNELS:
+ raise ValueError("Invalid port!")
+
+ CanTmclInterface.__init__(self, port, datarate, host_id, module_id, timeout_s)
+ self._channel = port
+ self._bitrate = datarate
+
+ self.logger.info("Connect to bus with bit-rate %s.", self._bitrate)
+ try:
+ self._connection = can.Bus(interface="pcan", channel=self._channel, bitrate=self._bitrate)
+ self._connection.set_filters([{"can_id": host_id, "can_mask": 0xFFFFFFFF}])
+ except PcanError as e:
+ self._connection = None
+ raise ConnectionError("Failed to connect to PCAN bus") from e
+
+ @classmethod
+ def list(cls):
+ """
+ Return a list of available connection ports as a list of strings.
+
+ This function is required for using this interface with the
+ connection manager.
+ """
+ return cls._CHANNELS
diff --git a/pytrinamic/connections/can_tmcl/slcan_tmcl_interface.py b/pytrinamic/connections/can_tmcl/slcan_tmcl_interface.py
new file mode 100644
index 00000000..ff3096eb
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl/slcan_tmcl_interface.py
@@ -0,0 +1,45 @@
+import can
+from can import CanError
+from serial.tools.list_ports import comports
+from ...connections.can_tmcl_interface import CanTmclInterface
+
+
+class SlcanTmclInterface(CanTmclInterface):
+ """
+ This class implements a TMCL connection for CAN over Serial / SLCAN.
+ Compatible with CANable running slcan firmware and similar.
+ Set underlying serial device as channel. (e.g. /dev/ttyUSB0, COM8, …)
+ Maybe SerialBaudrate has to be changed based on adapter.
+ """
+
+ def __init__(self, com_port, datarate=1000000, host_id=2, module_id=1, timeout_s=5, serial_baudrate=115200):
+ if not isinstance(com_port, str):
+ raise TypeError
+
+ CanTmclInterface.__init__(self, com_port, datarate, host_id, module_id, timeout_s)
+ self._serial_baudrate = serial_baudrate
+
+ self.logger.info("Connect to bus. (Baudrate=%s)", self._serial_baudrate)
+ try:
+ self._connection = can.Bus(interface='slcan',
+ channel=self._channel,
+ bitrate=self._bitrate,
+ ttyBaudrate=self._serial_baudrate)
+ self._connection.set_filters([{"can_id": host_id, "can_mask": 0x7F}])
+ except CanError as e:
+ self._connection = None
+ raise ConnectionError("Failed to connect to CAN bus") from e
+
+ @classmethod
+ def list(cls):
+ """
+ Return a list of available connection ports as a list of strings.
+
+ This function is required for using this interface with the
+ connection manager.
+ """
+ connected = []
+ for element in sorted(comports()):
+ connected.append(element.device)
+
+ return connected
diff --git a/pytrinamic/connections/can_tmcl/socketcan_tmcl_interface.py b/pytrinamic/connections/can_tmcl/socketcan_tmcl_interface.py
new file mode 100644
index 00000000..3886177f
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl/socketcan_tmcl_interface.py
@@ -0,0 +1,40 @@
+import can
+from can import CanError
+from ...connections.can_tmcl_interface import CanTmclInterface
+
+
+class SocketcanTmclInterface(CanTmclInterface):
+ """
+ This class implements a TMCL connection over a SocketCAN adapter.
+
+ Use following command under linux to activate can socket
+ sudo ip link set can0 down type can bitrate 1000000
+ """
+ _CHANNELS = ["can0", "can1", "can2", "can3", "can4", "can5", "can6", "can7"]
+
+ def __init__(self, port, datarate=1000000, host_id=2, module_id=1, timeout_s=5):
+ if not isinstance(port, str):
+ raise TypeError
+
+ if port not in self._CHANNELS:
+ raise ValueError("Invalid port")
+
+ CanTmclInterface.__init__(self, port, datarate, host_id, module_id, timeout_s)
+
+ self.logger.info("Connect to bus with bit-rate %s.", self._bitrate)
+ try:
+ self._connection = can.Bus(interface="socketcan", channel=self._channel, bitrate=self._bitrate)
+ self._connection.set_filters([{"can_id": host_id, "can_mask": 0x7F}])
+ except CanError as e:
+ self._connection = None
+ raise ConnectionError("Failed to connect to SocketCAN bus") from e
+
+ @classmethod
+ def list(cls):
+ """
+ Return a list of available connection ports as a list of strings.
+
+ This function is required for using this interface with the
+ connection manager.
+ """
+ return cls._CHANNELS
diff --git a/pytrinamic/connections/can_tmcl_interface.py b/pytrinamic/connections/can_tmcl_interface.py
new file mode 100644
index 00000000..d8813505
--- /dev/null
+++ b/pytrinamic/connections/can_tmcl_interface.py
@@ -0,0 +1,85 @@
+
+import logging
+import can
+from ..connections.tmcl_interface import TmclInterface
+
+
+class CanTmclInterface(TmclInterface):
+ """Generic CAN interface class for the CAN adapters."""
+
+ def __init__(self, channel, datarate, host_id, default_module_id, timeout_s):
+
+ TmclInterface.__init__(self, host_id, default_module_id)
+ self._connection = None
+ self._channel = channel
+ self._bitrate = datarate
+ if timeout_s == 0:
+ self._timeout_s = None
+ else:
+ self._timeout_s = timeout_s
+
+ self.logger = logging.getLogger(f"{self.__class__.__name__}.{self._channel}")
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exit_type, value, traceback):
+ """
+ Close the connection at the end of a with-statement block.
+ """
+ del exit_type, value, traceback
+ self.close()
+
+ def close(self):
+ self.logger.info("Shutdown.")
+
+ self._connection.shutdown()
+
+ def _send(self, host_id, module_id, data):
+ """
+ Send the bytearray parameter [data].
+
+ This is a required override function for using the tmcl_interface class.
+ """
+ del host_id
+
+ msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
+
+ try:
+ self._connection.send(msg)
+ except can.CanError as e:
+ raise ConnectionError(
+ f"Failed to send a TMCL message on {self.__class__.__name__} (channel {str(self._channel)})"
+ ) from e
+
+ def _recv(self, host_id, module_id):
+ """
+ Read 9 bytes and return them as a bytearray.
+
+ This is a required override function for using the tmcl_interface class.
+ """
+ del module_id
+
+ try:
+ msg = self._connection.recv(timeout=self._timeout_s)
+ except can.CanError as e:
+ raise ConnectionError(
+ f"Failed to receive a TMCL message from {self.__class__.__name__} (channel {str(self._channel)})"
+ ) from e
+
+ if not msg:
+ raise ConnectionError(f"Recv timed out ({self.__class__.__name__}, on channel {str(self._channel)})")
+
+ if msg.arbitration_id != host_id:
+ # The filter shouldn't let wrong messages through.
+ # This is just a sanity check
+ self.logger.warning("Received a CAN Frame with unexpected ID (received: %d; expected: %d)", msg.arbitration_id, host_id)
+
+ return bytearray([msg.arbitration_id]) + msg.data
+
+ @staticmethod
+ def supports_tmcl():
+ return True
+
+ def __str__(self):
+ return f"Connection: Type = {self.__class__.__name__}, Channel = {self._channel}, Bitrate = {self._bitrate}"
diff --git a/pytrinamic/connections/connection_manager.py b/pytrinamic/connections/connection_manager.py
index d85ea916..f149a31e 100644
--- a/pytrinamic/connections/connection_manager.py
+++ b/pytrinamic/connections/connection_manager.py
@@ -1,4 +1,5 @@
import sys
+import logging
import argparse
from ..connections import DummyTmclInterface
@@ -11,6 +12,8 @@
from ..connections import SlcanTmclInterface
from ..connections import IxxatTmclInterface
+logger = logging.getLogger(__name__)
+
class ConnectionManager:
"""
@@ -63,7 +66,17 @@ class which allows repeated connect() and disconnect() calls.
interpreted depends on the interface used. E.g. the serial
connection uses this value as the baud rate.
- Default value: 115200
+ The Default value also depends on the interface.
+ * for any CAN interface its 1000000
+ * for the serial_tmcl and uard_id interface it is 9600
+ * for usb_tmcl it is 115200
+
+ --timeout
+ The rx timeout in seconds. Accepts only values >= 0.
+ If 0 is given the rx function will block forever.
+ This might be useful for debugging.
+
+ Default value: 5.0
--host-id
The host id to use with a TMCL connection.
@@ -90,24 +103,23 @@ class which allows repeated connect() and disconnect() calls.
("ixxat_tmcl", IxxatTmclInterface, 1000000),
]
- def __init__(self, arg_list=None, connection_type="any", debug=False):
+ def __init__(self, arg_list=None, connection_type="any"):
# Attributes
- self.__debug = debug
self.__connection = None
arg_parser = argparse.ArgumentParser(description='ConnectionManager to setup connections dynamically and interactively')
ConnectionManager.argparse(arg_parser)
if not arg_list:
- if self.__debug:
- print("Using arguments from the command line")
+ logger.info("Using arguments from the command line.")
arg_list = sys.argv
if isinstance(arg_list, str):
arg_list = arg_list.split()
- if self.__debug:
- print("Splitting string:", arg_list)
+ logger.debug("List of input arguments: %s", arg_list)
+
+ # Parse the command line
args = arg_parser.parse_known_args(arg_list)[0]
# Argument storage - default parameters are set here
@@ -118,11 +130,7 @@ def __init__(self, arg_list=None, connection_type="any", debug=False):
self.__host_id = 2
self.__module_id = 1
- # Parse the command line
- if self.__debug:
- print("Commandline argument list: {0:s}".format(str(arg_list)))
- print("Parsed commandline arguments: {0:s}".format(str(args)))
- print()
+ logger.debug("Combined default and parsed arguments: %s", args)
# ## Interpret given arguments
# Interface
@@ -156,6 +164,9 @@ def __init__(self, arg_list=None, connection_type="any", debug=False):
# No data rate has been set -> keep old value
pass
+ # Timeout
+ self.__timeout_s = args.timeout_s
+
# Host ID
try:
self.__host_id = int(args.host_id[0])
@@ -168,17 +179,18 @@ def __init__(self, arg_list=None, connection_type="any", debug=False):
except ValueError as exc:
raise ValueError("Invalid module id: " + args.module_id[0]) from exc
- if self.__debug:
- print("Connection parameters:")
- print("\tInterface: " + self.__interface.__qualname__)
- print("\tPort: " + self.__port)
- print("\tBlacklist: " + str(self.__no_port))
- print("\tData rate: " + str(self.__data_rate))
- print("\tHost ID: " + str(self.__host_id))
- print("\tModule ID: " + str(self.__module_id))
- print()
-
- def connect(self, debug_interface=None):
+ logger.info("ConnectionManager created with ["
+ "Interface: %s; "
+ "Port: %s; "
+ "Blacklist: %s; "
+ "Data rate: %s; "
+ "Timeout: %s;"
+ "Host ID: %s; "
+ "Module ID: %s]",
+ self.__interface.__qualname__, self.__port, self.__no_port, self.__data_rate, self.__timeout_s,
+ self.__host_id, self.__module_id)
+
+ def connect(self):
"""
Attempt to connect to a module with the stored connection parameters.
@@ -187,19 +199,7 @@ def connect(self, debug_interface=None):
If no connections are available or a connection attempt fails, a
ConnectionError exception is raised
-
- Parameters:
- debug_interface:
- Type: bool, optional, default value: None
- Control whether the connection should be created in
- debug mode. A boolean value will enable or disable the debug mode,
- a None value will set the connections debug mode according to the
- ConnectionManagers debug mode.
"""
- # If no debug selection has been passed, inherit the debug state from the connection manager
- if debug_interface is None:
- debug_interface = self.__debug
-
# Get all available ports
port_list = self.list_connections()
@@ -243,10 +243,10 @@ def connect(self, debug_interface=None):
if self.__interface.supports_tmcl():
# Open the connection to a TMCL interface
self.__connection = self.__interface(port, self.__data_rate, self.__host_id, self.__module_id,
- debug=debug_interface)
+ timeout_s=self.__timeout_s)
else:
# Open the connection to a direct IC interface
- self.__connection = self.__interface(port, self.__data_rate, debug=debug_interface)
+ self.__connection = self.__interface(port, self.__data_rate, timeout_s=self.__timeout_s)
except ConnectionError as e:
raise ConnectionError("Couldn't connect to port " + port + ". Connection failed.") from e
@@ -303,6 +303,16 @@ def argparse(arg_parser):
script, this function adds the arguments of the ConnectionManager to the
argparse parser.
"""
+ def _positive_float(value):
+ """
+ Argparse checker for float a positive float type.
+ """
+ value_float = float(value)
+ if value_float < 0:
+ raise argparse.ArgumentTypeError("Expected a positive float, got {}".format(value_float))
+
+ return value_float
+
group = arg_parser.add_argument_group("ConnectionManager options")
group.add_argument('--interface', dest='interface', action='store', nargs=1, type=str,
choices=[actual_interface[0] for actual_interface in ConnectionManager.INTERFACES],
@@ -313,6 +323,8 @@ def argparse(arg_parser):
help='Exclude ports')
group.add_argument('--data-rate', dest='data_rate', action='store', nargs=1, type=int,
help='Connection data-rate (default: %(default)s)')
+ group.add_argument('--timeout', dest='timeout_s', action='store', type=_positive_float, default=5.0,
+ help='Connection rx timeout in seconds (default: %(default)s)', metavar="SECONDS")
group = arg_parser.add_argument_group("ConnectionManager TMCL options")
diff --git a/pytrinamic/connections/dummy_tmcl_interface.py b/pytrinamic/connections/dummy_tmcl_interface.py
index 537f82c7..f9dfd88e 100644
--- a/pytrinamic/connections/dummy_tmcl_interface.py
+++ b/pytrinamic/connections/dummy_tmcl_interface.py
@@ -1,22 +1,22 @@
+import logging
+
from ..connections.tmcl_interface import TmclInterface
class DummyTmclInterface(TmclInterface):
- def __init__(self, port, datarate=115200, host_id=2, module_id=1, debug=True):
+ def __init__(self, port, datarate=115200, host_id=2, module_id=1, timeout_s=5):
"""
Opens a dummy TMCL connection
"""
if not isinstance(port, str):
raise TypeError
- TmclInterface.__init__(self, host_id, module_id, debug)
+ TmclInterface.__init__(self, host_id, module_id)
+
+ self.logger = logging.getLogger("{}.{}".format(self.__class__.__name__, port))
- if self._debug:
- print("Opened dummy TMCL interface on port '" + port + "'")
- print("\tData rate: " + str(datarate))
- print("\tHost ID: " + str(host_id))
- print("\tModule ID: " + str(module_id))
+ self.logger.debug("Opening port (baudrate=%s).", datarate)
def __enter__(self):
return self
@@ -32,8 +32,7 @@ def close(self):
"""
Closes the dummy TMCL connection
"""
- if self._debug:
- print("Closed dummy TMCL interface")
+
def _send(self, host_id, module_id, data):
"""
diff --git a/pytrinamic/connections/ixxat_tmcl_interface.py b/pytrinamic/connections/ixxat_tmcl_interface.py
deleted file mode 100644
index b0513eab..00000000
--- a/pytrinamic/connections/ixxat_tmcl_interface.py
+++ /dev/null
@@ -1,149 +0,0 @@
-import can
-from can import CanError
-from ..connections.tmcl_interface import TmclInterface
-
-# Providing 5 channels here, this should cover all use cases.
-# Ixxat USB-to-CAN provides 1, 2 or 4 channels.
-_CHANNELS = ["0", "1", "2", "3", "4"]
-
-
-class IxxatTmclInterface(TmclInterface):
- """
- This class implements a TMCL connection for IXXAT USB-to-CAN adapter.
- Backend is provided by the IXXAT Virtual CAN Interface V3 SDK.
-
- Port number is assigned as adapters are plugged in, arbitrarely,
- it is possible to use multiple channels of one IXXAT, CAN1 is port "0", CAN2 is port "1", etc.
-
- This class, and the parser implementation DOES NOT support multiple IXXATs connected to one computer.
-
- To add this functionality, python-can version 4.0.0 is necessary as it allows enumerating IXXAT devices IDs,
- (see https://github.com/hardbyte/python-can/pull/926).
- To use multiple IXXAT devices, you must provide hardware IDs (this needs to be added to this class and to the parser).
-
- Snippet to list IXXAT by hardware ID using python-can 4.0.0:
- from can.interfaces.ixxat import IXXATBus
- for hwid in IXXATBus.list_adapters():
- print("Found IXXAT adapter with hardware id '%s'." % hwid)
- """
-
- def __init__(self, port="0", datarate=1000000, host_id=2, module_id=1, debug=False):
- if not isinstance(port, str):
- raise TypeError
-
- if port not in _CHANNELS:
- raise ValueError("Invalid port!")
-
- TmclInterface.__init__(self, host_id, module_id, debug)
- self._channel = port
- self._bitrate = datarate
-
- try:
- if self._debug:
- self._connection = can.Bus(interface="ixxat", channel=self._channel, bitrate=self._bitrate)
- else:
- self._connection = can.Bus(
- interface="ixxat",
- channel=self._channel,
- bitrate=self._bitrate,
- can_filters=[{"can_id": host_id, "can_mask": 0x7F}],
- )
- except CanError as e:
- self._connection = None
- raise ConnectionError(
- f"Failed to connect to {self.__class__.__name__} on channel {str(self._channel)}"
- ) from e
-
- if self._debug:
- print(f"Opened {self.__class__.__name__} on channel {str(self._channel)}")
-
- def __enter__(self):
- return self
-
- def __exit__(self, exit_type, value, traceback):
- """
- Close the connection at the end of a with-statement block.
- """
- del exit_type, value, traceback
- self.close()
-
- def close(self):
- if self._debug:
- print(f"Closing {self.__class__.__name__} (channel {str(self._channel)})")
-
- self._connection.shutdown()
-
- def _send(self, host_id, module_id, data):
- """
- Send the bytearray parameter [data].
-
- This is a required override function for using the tmcl_interface class.
- """
- del host_id
-
- msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
-
- try:
- self._connection.send(msg)
- except CanError as e:
- raise ConnectionError(
- f"Failed to send a TMCL message on {self.__class__.__name__} (channel {str(self._channel)})"
- ) from e
-
- def _recv(self, host_id, module_id):
- """
- Read 9 bytes and return them as a bytearray.
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del module_id
-
- try:
- msg = self._connection.recv(timeout=3)
- except CanError as e:
- raise ConnectionError(
- f"Failed to receive a TMCL message from {self.__class__.__name__} (channel {str(self._channel)})"
- ) from e
-
- if not msg:
- # Todo: Timeout retry mechanism
- raise ConnectionError(f"Recv timed out ({self.__class__.__name__}, on channel {str(self._channel)})")
-
- if msg.arbitration_id != host_id:
- # The filter shouldn't let wrong messages through.
- # This is just a sanity check
- if self._debug:
- print(f"Received wrong ID ({self.__class__.__name__}, on channel {str(self._channel)})")
-
- return bytearray([msg.arbitration_id]) + msg.data
-
- @staticmethod
- def supports_tmcl():
- return True
-
- @staticmethod
- def list():
- """
- Return a list of available connection ports as a list of strings.
-
- This function is required for using this interface with the
- connection manager.
- """
- return _CHANNELS
-
- def __str__(self):
- return f"Connection: Type = {self.__class__.__name__}, Channel = {self._channel}, Bitrate = {self._bitrate}"
-
-
-"""
-# Snippet to test connection between one (or two) ports of a single IXXAT USB-to-CAN and TMCL device(s).
-if __name__ == "__main__":
- CAN1 = IxxatTmclInterface(port="0")
- # CAN2 = IxxatTmclInterface(port="1")
- print(CAN1)
- # print(CAN2)
-
- print(CAN1.get_version_string())
- # print(CAN2.get_version_string())
-"""
diff --git a/pytrinamic/connections/kvaser_tmcl_interface.py b/pytrinamic/connections/kvaser_tmcl_interface.py
deleted file mode 100644
index 1df9e7fe..00000000
--- a/pytrinamic/connections/kvaser_tmcl_interface.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import can
-from can import CanError
-from ..connections.tmcl_interface import TmclInterface
-
-_CHANNELS = ["0", "1", "2"]
-
-
-class KvaserTmclInterface(TmclInterface):
- """
- This class implements a TMCL connection for Kvaser adapter using CANLIB.
- Try 0 as default channel.
- """
- def __init__(self, port="0", datarate=1000000, host_id=2, module_id=1, debug=False):
- if not isinstance(port, str):
- raise TypeError
-
- if port not in _CHANNELS:
- raise ValueError("Invalid port!")
-
- TmclInterface.__init__(self, host_id, module_id, debug)
- self._channel = port
- self._bitrate = datarate
-
- try:
- if self._debug:
- self._connection = can.Bus(interface="kvaser", channel=self._channel, bitrate=self._bitrate)
- else:
- self._connection = can.Bus(interface="kvaser", channel=self._channel, bitrate=self._bitrate,
- can_filters=[{"can_id": host_id, "can_mask": 0x7F}])
- except CanError as e:
- self._connection = None
- raise ConnectionError("Failed to connect to Kvaser CAN bus") from e
-
- if self._debug:
- print("Opened bus on channel " + str(self._channel))
-
- def __enter__(self):
- return self
-
- def __exit__(self, exit_type, value, traceback):
- """
- Close the connection at the end of a with-statement block.
- """
- del exit_type, value, traceback
- self.close()
-
- def close(self):
- if self._debug:
- print("Closing CAN bus")
-
- self._connection.shutdown()
-
- def _send(self, host_id, module_id, data):
- """
- Send the bytearray parameter [data].
-
- This is a required override function for using the tmcl_interface class.
- """
- del host_id
-
- msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
-
- try:
- self._connection.send(msg)
- except CanError as e:
- raise ConnectionError("Failed to send a TMCL message") from e
-
- def _recv(self, host_id, module_id):
- """
- Read 9 bytes and return them as a bytearray.
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del module_id
-
- try:
- msg = self._connection.recv(timeout=3)
- except CanError as e:
- raise ConnectionError("Failed to receive a TMCL message") from e
-
- if not msg:
- # Todo: Timeout retry mechanism
- raise ConnectionError("Recv timed out")
-
- if msg.arbitration_id != host_id:
- # The filter shouldn't let wrong messages through.
- # This is just a sanity check
- if self._debug:
- print("Received wrong ID")
-
- return bytearray([msg.arbitration_id]) + msg.data
-
- @staticmethod
- def supports_tmcl():
- return True
-
- @staticmethod
- def list():
- """
- Return a list of available connection ports as a list of strings.
-
- This function is required for using this interface with the
- connection manager.
- """
- return _CHANNELS
-
- def __str__(self):
- return "Connection: type={} channel={} bitrate={}".format(type(self).__name__, self._channel, self._bitrate)
diff --git a/pytrinamic/connections/pcan_tmcl_interface.py b/pytrinamic/connections/pcan_tmcl_interface.py
deleted file mode 100644
index 5611b007..00000000
--- a/pytrinamic/connections/pcan_tmcl_interface.py
+++ /dev/null
@@ -1,128 +0,0 @@
-
-import can
-from can import CanError
-from can.interfaces.pcan.pcan import PcanError
-from ..connections.tmcl_interface import TmclInterface
-
-_CHANNELS = [
- "PCAN_USBBUS1", "PCAN_USBBUS2", "PCAN_USBBUS3", "PCAN_USBBUS4",
- "PCAN_USBBUS5", "PCAN_USBBUS6", "PCAN_USBBUS7", "PCAN_USBBUS8",
- "PCAN_USBBUS9", "PCAN_USBBUS10", "PCAN_USBBUS11", "PCAN_USBBUS12",
- "PCAN_USBBUS13", "PCAN_USBBUS14", "PCAN_USBBUS15", "PCAN_USBBUS16",
-
- "PCAN_ISABUS1", "PCAN_ISABUS2", "PCAN_ISABUS3", "PCAN_ISABUS4",
- "PCAN_ISABUS5", "PCAN_ISABUS6", "PCAN_ISABUS7", "PCAN_ISABUS8",
-
- "PCAN_DNGBUS1",
-
- "PCAN_PCIBUS1", "PCAN_PCIBUS2", "PCAN_PCIBUS3", "PCAN_PCIBUS4",
- "PCAN_PCIBUS5", "PCAN_PCIBUS6", "PCAN_PCIBUS7", "PCAN_PCIBUS8",
- "PCAN_PCIBUS9", "PCAN_PCIBUS10", "PCAN_PCIBUS11", "PCAN_PCIBUS12",
- "PCAN_PCIBUS13", "PCAN_PCIBUS14", "PCAN_PCIBUS15", "PCAN_PCIBUS16",
-
- "PCAN_PCCBUS1", "PCAN_PCCBUS2",
-
- "PCAN_LANBUS1", "PCAN_LANBUS2", "PCAN_LANBUS3", "PCAN_LANBUS4",
- "PCAN_LANBUS5", "PCAN_LANBUS6", "PCAN_LANBUS7", "PCAN_LANBUS8",
- "PCAN_LANBUS9", "PCAN_LANBUS10", "PCAN_LANBUS11", "PCAN_LANBUS12",
- "PCAN_LANBUS13", "PCAN_LANBUS14", "PCAN_LANBUS15", "PCAN_LANBUS16"
- ]
-
-
-class PcanTmclInterface(TmclInterface):
- """
- This class implements a TMCL connection over a PCAN adapter.
- """
- def __init__(self, port, datarate=1000000, host_id=2, module_id=1, debug=False):
- if not isinstance(port, str):
- raise TypeError
-
- if port not in _CHANNELS:
- raise ValueError("Invalid port!")
-
- TmclInterface.__init__(self, host_id, module_id, debug)
- self._channel = port
- self._bitrate = datarate
-
- try:
- self._connection = can.Bus(interface="pcan", channel=self._channel, bitrate=self._bitrate)
- self._connection.set_filters([{"can_id": host_id, "can_mask": 0xFFFFFFFF}])
- except PcanError as e:
- self._connection = None
- raise ConnectionError("Failed to connect to PCAN bus") from e
-
- if self._debug:
- print("Opened bus on channel " + self._channel)
-
- def __enter__(self):
- return self
-
- def __exit__(self, exit_type, value, traceback):
- """
- Close the connection at the end of a with-statement block.
- """
- del exit_type, value, traceback
- self.close()
-
- def close(self):
- if self._debug:
- print("Closing PCAN bus")
-
- self._connection.shutdown()
-
- def _send(self, host_id, module_id, data):
- """
- Send the bytearray parameter [data].
-
- This is a required override function for using the tmcl_interface class.
- """
- del host_id
-
- msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
-
- try:
- self._connection.send(msg)
- except CanError as e:
- raise ConnectionError("Failed to send a TMCL message") from e
-
- def _recv(self, host_id, module_id):
- """
- Read 9 bytes and return them as a bytearray.
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del module_id
-
- try:
- msg = self._connection.recv(timeout=3)
- except CanError as e:
- raise ConnectionError("Failed to receive a TMCL message") from e
-
- if not msg:
- # Todo: Timeout retry mechanism
- raise ConnectionError("Recv timed out")
-
- if msg.arbitration_id != host_id:
- # The filter shouldn't let wrong messages through.
- # This is just a sanity check
- raise ConnectionError("Received wrong ID")
-
- return bytearray([msg.arbitration_id]) + msg.data
-
- @staticmethod
- def supports_tmcl():
- return True
-
- @staticmethod
- def list():
- """
- Return a list of available connection ports as a list of strings.
-
- This function is required for using this interface with the
- connection manager.
- """
- return _CHANNELS
-
- def __str__(self):
- return "Connection: type={} channel={} bitrate={}".format(type(self).__name__, self._channel, self._bitrate)
diff --git a/pytrinamic/connections/serial_tmcl_interface.py b/pytrinamic/connections/serial_tmcl_interface.py
index 2a83848d..ab612cc7 100644
--- a/pytrinamic/connections/serial_tmcl_interface.py
+++ b/pytrinamic/connections/serial_tmcl_interface.py
@@ -1,3 +1,5 @@
+import logging
+
from serial import Serial, SerialException
import serial.tools.list_ports
from ..connections.tmcl_interface import TmclInterface
@@ -8,23 +10,23 @@ class SerialTmclInterface(TmclInterface):
"""
Opens a serial TMCL connection
"""
- def __init__(self, com_port, datarate=115200, host_id=2, module_id=1, debug=False):
+ def __init__(self, com_port, datarate=115200, host_id=2, module_id=1, timeout_s=5):
if not isinstance(com_port, str):
raise TypeError
- TmclInterface.__init__(self, host_id, module_id, debug)
+ TmclInterface.__init__(self, host_id, module_id)
self._baudrate = datarate
+ if timeout_s == 0:
+ timeout_s = None
+
+ self.logger = logging.getLogger("{}.{}".format(self.__class__.__name__, com_port))
+ self.logger.debug("Opening port (baudrate=%s).", datarate)
try:
- self._serial = Serial(com_port, self._baudrate)
+ self._serial = Serial(com_port, self._baudrate, timeout=timeout_s)
except SerialException as e:
raise ConnectionError from e
- self._serial.timeout = 5
-
- if self._debug:
- print("Opened port: " + self._serial.portstr)
-
def __enter__(self):
return self
@@ -36,9 +38,7 @@ def __exit__(self, exit_type, value, traceback):
self.close()
def close(self):
- if self._debug:
- print("Closing port: " + self._serial.portstr)
-
+ self.logger.info("Closing port.")
self._serial.close()
def _send(self, host_id, module_id, data):
diff --git a/pytrinamic/connections/slcan_tmcl_interface.py b/pytrinamic/connections/slcan_tmcl_interface.py
deleted file mode 100644
index 35e0e92a..00000000
--- a/pytrinamic/connections/slcan_tmcl_interface.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import can
-from can import CanError
-from serial.tools.list_ports import comports
-from ..connections.tmcl_interface import TmclInterface
-
-
-class SlcanTmclInterface(TmclInterface):
- """
- This class implements a TMCL connection for CAN over Serial / SLCAN.
- Compatible with CANable running slcan firmware and similar.
- Set underlying serial device as channel. (e.g. /dev/ttyUSB0, COM8, …)
- Maybe SerialBaudrate has to be changed based on adapter.
- """
-
- def __init__(self, com_port, datarate=1000000, host_id=2, module_id=1, debug=True, serial_baudrate=115200):
- if not isinstance(com_port, str):
- raise TypeError
-
- TmclInterface.__init__(self, host_id, module_id, debug)
- self._bitrate = datarate
- self._port = com_port
- self._serial_baudrate = serial_baudrate
-
- try:
- self._connection = can.Bus(interface='slcan', channel=self._port, bitrate=self._bitrate,
- ttyBaudrate=self._serialBaudrate)
- self._connection.set_filters([{"can_id": host_id, "can_mask": 0x7F}])
- except CanError as e:
- self.__connection = None
- raise ConnectionError("Failed to connect to CAN bus") from e
-
- if self._debug:
- print("Opened slcan bus on channel " + self._port)
-
- def __enter__(self):
- return self
-
- def __exit__(self, exit_type, value, traceback):
- """
- Close the connection at the end of a with-statement block.
- """
- del exit_type, value, traceback
- self.close()
-
- def close(self):
- if self._debug:
- print("Closing CAN bus")
-
- self._connection.shutdown()
-
- def _send(self, host_id, module_id, data):
- """
- Send the bytearray parameter [data].
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del host_id
-
- msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
-
- try:
- self.__connection.send(msg)
- except CanError as e:
- raise ConnectionError("Failed to send a TMCL message") from e
-
- def _recv(self, host_id, module_id):
- """
- Read 9 bytes and return them as a bytearray.
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del module_id
-
- try:
- msg = self._connection.recv(timeout=3)
- except CanError as e:
- raise ConnectionError("Failed to receive a TMCL message") from e
-
- if not msg:
- # Todo: Timeout retry mechanism
- raise ConnectionError("Recv timed out")
-
- if msg.arbitration_id != host_id:
- # The filter shouldn't let wrong messages through.
- # This is just a sanity check
- raise ConnectionError("Received wrong ID")
-
- return bytearray([msg.arbitration_id]) + msg.data
-
- @staticmethod
- def supports_tmcl():
- return True
-
- @staticmethod
- def list():
- """
- Return a list of available connection ports as a list of strings.
-
- This function is required for using this interface with the
- connection manager.
- """
- connected = []
- for element in sorted(comports()):
- connected.append(element.device)
-
- return connected
-
- def __str__(self):
- return "Connection: type={} channel={} bitrate={}".format(type(self).__name__, self._port, self._bitrate)
diff --git a/pytrinamic/connections/socketcan_tmcl_interface.py b/pytrinamic/connections/socketcan_tmcl_interface.py
deleted file mode 100644
index d2a5b89e..00000000
--- a/pytrinamic/connections/socketcan_tmcl_interface.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import can
-from can import CanError
-from ..connections.tmcl_interface import TmclInterface
-
-_CHANNELS = ["can0", "can1", "can2", "can3", "can4", "can5", "can6", "can7"]
-
-
-class SocketcanTmclInterface(TmclInterface):
- """
- This class implements a TMCL connection over a SocketCAN adapter.
-
- Use following command under linux to activate can socket
- sudo ip link set can0 down type can bitrate 1000000
- """
- def __init__(self, port, datarate=1000000, host_id=2, module_id=1, debug=False):
- if not isinstance(port, str):
- raise TypeError
-
- if port not in _CHANNELS:
- raise ValueError("Invalid port")
-
- TmclInterface.__init__(self, host_id, module_id, debug)
- self._channel = port
- self._bitrate = datarate
-
- try:
- self._connection = can.Bus(interface="socketcan", channel=self._channel, bitrate=self._bitrate)
- self._connection.set_filters([{"can_id": host_id, "can_mask": 0x7F}])
-
- except CanError as e:
- self._connection = None
- raise ConnectionError("Failed to connect to SocketCAN bus") from e
-
- if self._debug:
- print("Opened bus on channel " + self._channel)
-
- def __enter__(self):
- return self
-
- def __exit__(self, exit_type, value, traceback):
- """
- Close the connection at the end of a with-statement block.
- """
- del exit_type, value, traceback
- self.close()
-
- def close(self):
- if self._debug:
- print("Closing socketcan bus")
-
- self._connection.shutdown()
-
- def _send(self, host_id, module_id, data):
- """
- Send the bytearray parameter [data].
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del host_id
-
- msg = can.Message(arbitration_id=module_id, is_extended_id=False, data=data[1:])
-
- try:
- self._connection.send(msg)
- except CanError as e:
- raise ConnectionError("Failed to send a TMCL message") from e
-
- def _recv(self, host_id, module_id):
- """
- Read 9 bytes and return them as a bytearray.
-
- This is a required override function for using the tmcl_interface
- class.
- """
- del module_id
-
- try:
- msg = self._connection.recv(timeout=3)
- except CanError as e:
- raise ConnectionError("Failed to receive a TMCL message") from e
-
- if not msg:
- # Todo: Timeout retry mechanism
- raise ConnectionError("Recv timed out")
-
- if msg.arbitration_id != host_id:
- # The filter shouldn't let wrong messages through.
- # This is just a sanity check
- raise ConnectionError("Received wrong ID")
-
- return bytearray([msg.arbitration_id]) + msg.data
-
- @staticmethod
- def supports_tmcl():
- return True
-
- @staticmethod
- def list():
- """
- Return a list of available connection ports as a list of strings.
-
- This function is required for using this interface with the
- connection manager.
- """
- return _CHANNELS
-
- def __str__(self):
- return "Connection: type={} channel={} bitrate={}".format(type(self).__name__, self._channel, self._bitrate)
diff --git a/pytrinamic/connections/tmcl_interface.py b/pytrinamic/connections/tmcl_interface.py
index dae5c686..5f1b6543 100644
--- a/pytrinamic/connections/tmcl_interface.py
+++ b/pytrinamic/connections/tmcl_interface.py
@@ -1,3 +1,4 @@
+import logging
from abc import ABC
from ..tmcl import TMCL, TMCLRequest, TMCLCommand, TMCLReply, TMCLReplyChecksumError, TMCLReplyStatusError
from ..helpers import TMC_helpers
@@ -19,13 +20,10 @@ class TmclInterface(ABC):
_send(self, host_id, module_id, data)
_recv(self, host_id, module_id)
- A subclass may use the boolean _debug attribute to toggle printing further
- debug output.
-
A subclass may read the _host_id and _module_id parameters.
"""
- def __init__(self, host_id=2, default_module_id=1, debug=False):
+ def __init__(self, host_id=2, default_module_id=1):
"""
Parameters:
host_id:
@@ -38,40 +36,14 @@ def __init__(self, host_id=2, default_module_id=1, debug=False):
tmcl_interface functions. When only communicating with one
module a script can omit the moduleID for all TMCL interface
calls by declaring this default value once at the start.
- debug:
- Type: bool, optional, default: False
- A switch for enabling debug mode. Can be changed with
- enableDebug(). In debug mode all sent and received TMCL packets
- get dumped to stdout. The boolean _debug attribute holds the
- current state of debug mode - subclasses may read it to print
- further debug output.
"""
+ self.logger = logging.getLogger("TmclInterfaceAbstractBaseClassObject") # Will be overwritten in derived classes
TMCL.validate_host_id(host_id)
TMCL.validate_module_id(default_module_id)
- if not isinstance(debug, bool):
- raise TypeError
-
self._host_id = host_id
self._module_id = default_module_id
- self._debug = debug
-
- def enable_debug(self, enable):
- """
- Set the debug mode, which dumps all TMCL datagrams written and read.
- """
- if not isinstance(enable, bool):
- raise TypeError("Expected boolean value")
-
- self._debug = enable
-
- def print_info(self):
- info = "ConnectionInterface {"
- info += "'debug_enabled':" + str(self._debug) + ", "
- info = info[:-2]
- info += "}"
- print(info)
def _send(self, host_id, module_id, data):
"""
@@ -106,14 +78,12 @@ def send_request(self, request):
Send a TMCL_Request and read back a TMCL_Reply. This function blocks until
the reply has been received.
"""
- if self._debug:
- request.dump()
+ self.logger.debug("Tx: %s", request)
self._send(self._host_id, request.moduleAddress, request.to_buffer())
reply = TMCLReply.from_buffer(self._recv(self._host_id, request.moduleAddress))
- if self._debug:
- reply.dump()
+ self.logger.debug("Rx: %s", reply)
self._reply_check(reply)
@@ -151,8 +121,7 @@ def send_boot(self, module_id=None):
request = TMCLRequest(module_id, TMCLCommand.BOOT, 0x81, 0x92, 0xA3B4C5D6)
- if self._debug:
- request.dump()
+ self.logger.debug("Tx: %s", request)
# Send the request
self._send(self._host_id, module_id, request.to_buffer())
diff --git a/pytrinamic/connections/uart_ic_interface.py b/pytrinamic/connections/uart_ic_interface.py
index cd6f6132..48f8ac3d 100644
--- a/pytrinamic/connections/uart_ic_interface.py
+++ b/pytrinamic/connections/uart_ic_interface.py
@@ -1,3 +1,4 @@
+import logging
import struct
from serial import Serial
import serial.tools.list_ports
@@ -14,8 +15,8 @@ def __init__(self, address, value):
def to_buffer(self):
return struct.pack(REGISTER_PACKAGE_STRUCTURE, self.address, self.value)
- def dump(self):
- print("RegisterRequest: " + str(self.address) + "," + str(self.value))
+ def __str__(self):
+ return "RegisterRequest: [Addr:{:02x}, Value:{}]".format(self.address, self.value)
class RegisterReply:
@@ -23,8 +24,8 @@ def __init__(self, reply_struct):
self.address = reply_struct[0]
self.value = reply_struct[1]
- def dump(self):
- print("RegisterReply: " + str(self.address) + "," + str(self.value))
+ def __str__(self):
+ return "RegisterReply: [Addr:{:02x}, Value:{}]".format(self.address, self.value)
def value(self):
return self.value
@@ -32,11 +33,15 @@ def value(self):
class UartIcInterface:
- def __init__(self, com_port, datarate=9600, debug=False):
- self._debug = debug
+ def __init__(self, com_port, datarate=9600, timeout_s=5):
self.baudrate = datarate
- self.serial = Serial(com_port, self.baudrate)
- print("Open port: " + self.serial.portstr)
+ if timeout_s == 0:
+ timeout_s = None
+
+ self.logger = logging.getLogger("{}.{}".format(self.__class__.__name__, com_port))
+
+ self.logger.debug("Opening port (baudrate=%s).", datarate)
+ self.serial = Serial(com_port, self.baudrate, timeout=timeout_s)
def __enter__(self):
return self
@@ -49,18 +54,13 @@ def __exit__(self, exit_type, value, traceback):
self.close()
def close(self):
- if self._debug:
- print("Close port: " + self.serial.portstr)
-
+ self.logger.info("Closing port.")
self.serial.close()
def send_datagram(self, data, recv_size):
self.serial.write(data)
return self.serial.read(recv_size)
- def enable_debug(self, enable):
- self._debug = enable
-
@staticmethod
def supports_tmcl():
return False
@@ -69,15 +69,11 @@ def send(self, address, value):
# prepare TMCL request
request = RegisterRequest(address, value)
- if self._debug:
- request.dump()
-
# send request, wait, and handle reply
+ self.logger.debug("Tx %s", request)
self.serial.write(request.to_buffer())
reply = RegisterReply(struct.unpack(REGISTER_PACKAGE_STRUCTURE, self.serial.read(REGISTER_PACKAGE_LENGTH)))
-
- if self._debug:
- reply.dump()
+ self.logger.debug("Rx %s", reply)
return reply
diff --git a/pytrinamic/evalboards/MAX22216_eval.py b/pytrinamic/evalboards/MAX22216_eval.py
index 17c767a1..9e1bf822 100644
--- a/pytrinamic/evalboards/MAX22216_eval.py
+++ b/pytrinamic/evalboards/MAX22216_eval.py
@@ -17,23 +17,21 @@ def __init__(self, connection, module_id=1):
values have to be configured with the module first.
"""
TMCLEval.__init__(self, connection, module_id)
- self.motors = [self._MotorTypeA(self, 0)]
+ self.motors = [
+ self._MotorTypeA(self, 0),
+ self._MotorTypeA(self, 1),
+ self._MotorTypeA(self, 2),
+ self._MotorTypeA(self, 3)
+ ]
self.ics = [MAX22216(self)]
# Use the driver controller functions for register access
def write_register(self, register_address, value):
- return self._connection.write_mc(register_address, value, self._module_id)
+ return self._connection.write_drv(register_address, value, self._module_id)
def read_register(self, register_address, signed=False):
- return self._connection.read_mc(register_address, self._module_id, signed)
-
- def write_register_field(self, field, value):
- return self.write_register(field[0], TMC_helpers.field_set(self.read_register(field[0]),
- field[1], field[2], value))
-
- def read_register_field(self, field):
- return TMC_helpers.field_get(self.read_register(field[0]), field[1], field[2])
+ return self._connection.read_drv(register_address, self._module_id, signed)
class _MotorTypeA(object):
"""
diff --git a/pytrinamic/evalboards/TMC2240_eval.py b/pytrinamic/evalboards/TMC2240_eval.py
new file mode 100644
index 00000000..c3091265
--- /dev/null
+++ b/pytrinamic/evalboards/TMC2240_eval.py
@@ -0,0 +1,129 @@
+from pytrinamic.evalboards import TMCLEval
+from pytrinamic.ic import TMC2240
+from pytrinamic.features import MotorControlModule
+from pytrinamic.helpers import TMC_helpers
+
+
+class TMC2240_eval(TMCLEval):
+ """
+ This class represents a TMC2240 Evaluation board.
+
+ Communication is done over the TMCL commands writeMC and readMC. An
+ implementation without TMCL may still use this class if these two functions
+ are provided properly. See __init__ for details on the function
+ requirements.
+ """
+ def __init__(self, connection, module_id=1):
+ """
+ Parameters:
+ connection:
+ Type: class
+ A class that provides the necessary functions for communicating
+ with a TMC2240. The required functions are
+ connection.writeMC(registerAddress, value, moduleID)
+ connection.readMC(registerAddress, moduleID, signed)
+ for writing/reading to registers of the TMC2240.
+ module_id:
+ Type: int, optional, default value: 1
+ The TMCL module ID of the TMC2240. This ID is used as a
+ parameter for the writeMC and readMC functions.
+ """
+ TMCLEval.__init__(self, connection, module_id)
+ self.motors = [self._MotorTypeA(self, 0)]
+ self.ics = [TMC2240()]
+
+ # Use the motion controller functions for register access
+
+ def write_register(self, register_address, value):
+ return self._connection.read_drv(register_address, value, self._module_id)
+
+ def read_register(self, register_address, signed=False):
+ return self._connection.read_drv(register_address, self._module_id, signed)
+
+ def write_register_field(self, field, value):
+ return self.write_register(field[0], TMC_helpers.field_set(self.read_register(field[0]),
+ field[1], field[2], value))
+
+ def read_register_field(self, field):
+ return TMC_helpers.field_get(self.read_register(field[0]), field[1], field[2])
+
+ # Motion control functions
+
+ def rotate(self, motor, value):
+ self._connection.rotate(motor, value)
+
+ def stop(self, motor):
+ self._connection.stop(motor)
+
+ def move_to(self, motor, position, velocity=None):
+ if velocity and velocity != 0:
+ # Set maximum positioning velocity
+ self.motors[motor].set_axis_parameter(self.motors[motor].AP.MaxVelocity, velocity)
+ self._connection.move_to(motor, position, self._module_id)
+
+ class _MotorTypeA(MotorControlModule):
+ def __init__(self, eval_board, axis):
+ MotorControlModule.__init__(self, eval_board, axis, self.AP)
+
+ class AP:
+ TargetPosition = 0
+ ActualPosition = 1
+ TargetVelocity = 2
+ ActualVelocity = 3
+ MaxVelocity = 4
+ MaxAcceleration = 5
+ MaxCurrent = 6
+ StandbyCurrent = 7
+ PositionReachedFlag = 8
+ THIGH = 26
+ HighSpeedChopperMode = 28
+ HighSpeedFullstepMode = 29
+ MeasuredSpeed = 30
+ internal_Rsense = 34
+ GlobalCurrentScaler = 35
+ MicrostepResolution = 140
+ ChopperBlankTime = 162
+ ConstantTOffMode = 163
+ DisableFastDecayComparator = 164
+ ChopperHysteresisEnd = 165
+ ChopperHysteresisStart = 166
+ TOff = 167
+ SEIMIN = 168
+ SECDS = 169
+ smartEnergyHysteresis = 170
+ SECUS = 171
+ smartEnergyHysteresisStart = 172
+ SG2FilterEnable = 173
+ SG2Threshold = 174
+ smartEnergyActualCurrent = 180
+ smartEnergyStallVelocity = 181
+ smartEnergyThresholdSpeed = 182
+ SG4FilterEnable = 183
+ SGAngleOffset = 184
+ ChopperSynchronization = 185
+ PWMThresholdSpeed = 186
+ PWMGrad = 187
+ PWMAmplitude = 188
+ PWMFrequency = 191
+ PWMAutoscale = 192
+ PWMScaleSum = 193
+ MSCNT = 194
+ MEAS_SD_EN = 195
+ DIS_REG_STST = 196
+ FreewheelingMode = 204
+ LoadValue = 206
+ EncoderPosition = 209
+ EncoderResolution = 210
+ CurrentScalingSelector = 211
+ CurrentRange = 212
+ ADCTemperature = 213
+ ADCIN = 214
+ ADCSupply = 215
+ ADCOvervoltageLimit = 216
+ ADCOvertemperatureWarningLimit = 217
+ Temperature = 218
+ AIN = 219
+ VSupply = 220
+ OvervoltageLimit = 221
+ OvertemperatureWarningLimit = 222
+ nSLEEP = 223
diff --git a/pytrinamic/evalboards/__init__.py b/pytrinamic/evalboards/__init__.py
index 386690a7..078e6791 100644
--- a/pytrinamic/evalboards/__init__.py
+++ b/pytrinamic/evalboards/__init__.py
@@ -7,6 +7,7 @@
from .TMC2209_eval import TMC2209_eval
from .TMC2224_eval import TMC2224_eval
from .TMC2225_eval import TMC2225_eval
+from .TMC2240_eval import TMC2240_eval
from .TMC2300_eval import TMC2300_eval
from .TMC2590_eval import TMC2590_eval
from .TMC2660_eval import TMC2660_eval
diff --git a/pytrinamic/features/solenoid_ic.py b/pytrinamic/features/solenoid_ic.py
index 904372fe..d4074b6b 100644
--- a/pytrinamic/features/solenoid_ic.py
+++ b/pytrinamic/features/solenoid_ic.py
@@ -94,7 +94,8 @@ def set_voltage_high(self, u_dc_h):
vdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.VDR_NDUTY) == 1)
cdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE) == 1)
fsf = self.__map_fsf.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE), 1.0)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_H, self.__u_dc_value(u_dc_h, self.__u_supply, vdr, cdr, fsf))
+ print("U_DC_H {}".format(round(self.__u_dc_value(u_dc_h, self.__u_supply, vdr, cdr, fsf))))
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_H, round(self.__u_dc_value(u_dc_h, self.__u_supply, vdr, cdr, fsf)))
def get_voltage_high(self):
"""
@@ -120,7 +121,7 @@ def set_voltage_low(self, u_dc_l):
vdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.VDR_NDUTY) == 1)
cdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE) == 1)
fsf = self.__map_fsf.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE), 1.0)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_L, self.__u_dc_value(u_dc_l, self.__u_supply, vdr, cdr, fsf))
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_L, round(self.__u_dc_value(u_dc_l, self.__u_supply, vdr, cdr, fsf)))
def get_voltage_low(self):
"""
@@ -146,7 +147,7 @@ def set_voltage_low_high(self, u_dc_l2h):
vdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.VDR_NDUTY) == 1)
cdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE) == 1)
fsf = self.__map_fsf.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE), 1.0)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_L2H, self.__u_dc_value(u_dc_l2h, self.__u_supply, vdr, cdr, fsf))
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_L2H, round(self.__u_dc_value(u_dc_l2h, self.__u_supply, vdr, cdr, fsf)))
def get_voltage_low_high(self):
"""
@@ -172,7 +173,7 @@ def set_voltage_high_low(self, u_dc_h2l):
vdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.VDR_NDUTY) == 1)
cdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE) == 1)
fsf = self.__map_fsf.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.CTRL_MODE), 1.0)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_H2L, self.__u_dc_value(u_dc_h2l, self.__u_supply, vdr, cdr, fsf))
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.DC_H2L, round(self.__u_dc_value(u_dc_h2l, self.__u_supply, vdr, cdr, fsf)))
def get_voltage_high_low(self):
"""
@@ -196,8 +197,8 @@ def set_frequency(self, u_ac_freq):
Parameters:
u_ac_freq: AC frequency.
"""
- pwm_freq = __map_pwm_freq.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.F_PWM_M), 100E3)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.DELTA_PHI, self.__delta_phi(pwm_freq, u_ac_freq))
+ pwm_freq = self.__map_pwm_freq.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.F_PWM_M), 100E3)
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.DELTA_PHI, round(self.__delta_phi(pwm_freq, u_ac_freq)))
def get_frequency(self):
"""
@@ -208,7 +209,7 @@ def get_frequency(self):
Returns:
AC frequency.
"""
- pwm_freq = __map_pwm_freq.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.F_PWM_M), 100E3)
+ pwm_freq = self.__map_pwm_freq.get(self._parent.read_axis_field(self._axis, self._ic.FIELD.F_PWM_M), 100E3)
return self.__f_AC(pwm_freq, self._parent.read_axis_field(self._axis, self._ic.FIELD.DELTA_PHI))
def set_voltage_ac(self, u_ac):
@@ -220,7 +221,7 @@ def set_voltage_ac(self, u_ac):
u_ac: AC voltage.
"""
vdr = (self._parent.read_axis_field(self._axis, self._ic.FIELD.VDR_NDUTY) == 1)
- self._parent.write_axis_field(self._axis, self._ic.FIELD.U_AC, self.__u_ac_value(u_ac, self.__u_supply, vdr))
+ self._parent.write_axis_field(self._axis, self._ic.FIELD.U_AC, round(self.__u_ac_value(u_ac, self.__u_supply, vdr)))
def get_voltage_ac(self):
"""
diff --git a/pytrinamic/ic/TMC2240.py b/pytrinamic/ic/TMC2240.py
new file mode 100644
index 00000000..c5e20454
--- /dev/null
+++ b/pytrinamic/ic/TMC2240.py
@@ -0,0 +1,229 @@
+from ..ic.tmc_ic import TMCIc
+
+
+class TMC2240(TMCIc):
+ """
+ The TMC2240-A is a stepper motor controller and driver IC with serial communication interfaces.
+ Supply voltage: 4.5-36V.
+ """
+ def __init__(self):
+ super().__init__("TMC2240", self.__doc__)
+
+ class REG:
+ """
+ Define all registers of the TMC2240.
+ """
+ GCONF = 0x00
+ GSTAT = 0x01
+ IFCNT = 0x02
+ SLAVECONF = 0x03
+ IOIN = 0x04
+ DRV_CONF = 0x0A
+ GLOBAL_SCALER = 0x0B
+ IHOLD_IRUN = 0x10
+ TPOWERDOWN = 0x11
+ TSTEP = 0x12
+ TPWMTHRS = 0x13
+ TCOOLTHRS = 0x14
+ THIGH = 0x15
+ DIRECT_MODE = 0x2D
+ ENCMODE = 0x38
+ X_ENC = 0x39
+ ENC_CONST = 0x3A
+ ENC_STATUS = 0x3B
+ ENC_LATCH = 0x3C
+ ADC_VSUPPLY_AIN = 0x50
+ ADC_TEMP = 0x51
+ OTW_OV_VTH = 0x52
+ MSLUT_0 = 0x60
+ MSLUT_1 = 0x61
+ MSLUT_2 = 0x62
+ MSLUT_3 = 0x63
+ MSLUT_4 = 0x64
+ MSLUT_5 = 0x65
+ MSLUT_6 = 0x66
+ MSLUT_7 = 0x67
+ MSLUTSEL = 0x68
+ MSLUTSTART = 0x69
+ MSCNT = 0x6A
+ MSCURACT = 0x6B
+ CHOPCONF = 0x6C
+ COOLCONF = 0x6D
+ DCCTRL = 0x6E
+ DRV_STATUS = 0x6F
+ PWMCONF = 0x70
+ PWM_SCALE = 0x71
+ PWM_AUTO = 0x72
+ SG4_THRS = 0x74
+ SG4_RESULT = 0x75
+ SG4_IND = 0x76
+
+ class FIELD:
+ """
+ Define all register bitfields of the TMC2240.
+
+ Each field is defined as a tuple consisting of ( Address, Mask, Shift ).
+
+ The name of the register is written as a comment behind each tuple. This is
+ intended for IDE users viewing the definition of a field by hovering over
+ it. This allows the user to see the corresponding register name of a field
+ without opening this file and searching for the definition.
+ """
+
+ FAST_STANDSTILL = ( 0x00, 0x00000002, 1 )
+ EN_PWM_MODE = ( 0x00, 0x00000004, 2 )
+ MULTISTEP_FILT = ( 0x00, 0x00000008, 3 )
+ SHAFT = ( 0x00, 0x00000010, 4 )
+ DIAG0_ERROR = ( 0x00, 0x00000020, 5 )
+ DIAG0_OTPW = ( 0x00, 0x00000040, 6 )
+ DIAG0_STALL = ( 0x00, 0x00000080, 7 )
+ DIAG1_STALL = ( 0x00, 0x00000100, 8 )
+ DIAG1_INDEX = ( 0x00, 0x00000200, 9 )
+ DIAG1_ONSTATE = ( 0x00, 0x00000400, 10 )
+ DIAG0_PUSHPULL = ( 0x00, 0x00001000, 12 )
+ DIAG1_PUSHPULL = ( 0x00, 0x00002000, 13 )
+ SMALL_HYSTERESIS = ( 0x00, 0x00004000, 14 )
+ STOP_ENABLE = ( 0x00, 0x00008000, 15 )
+ DIRECT_MODE = ( 0x00, 0x00010000, 16 )
+ RESET = ( 0x01, 0x00000001, 0 )
+ DRV_ERR = ( 0x01, 0x00000002, 1 )
+ UV_CP = ( 0x01, 0x00000004, 2 )
+ REGISTER_RESET = ( 0x01, 0x00000008, 3 )
+ VM_UVLO = ( 0x01, 0x00000010, 4 )
+ IFCNT = ( 0x02, 0x000000FF, 0 )
+ SLAVEADDR = ( 0x03, 0x000000FF, 0 )
+ SENDDELAY = ( 0x03, 0x00000F00, 8 )
+ REFL_STEP = ( 0x04, 0x00000001, 0 )
+ REFR_DIR = ( 0x04, 0x00000002, 1 )
+ ENCB_CFG4 = ( 0x04, 0x00000004, 2 )
+ ENCA_CFG5 = ( 0x04, 0x00000008, 3 )
+ DRV_ENN = ( 0x04, 0x00000010, 4 )
+ ENCN_CFG6 = ( 0x04, 0x00000020, 5 )
+ UART_EN = ( 0x04, 0x00000040, 6 )
+ RESERVED = ( 0x04, 0x00000080, 7 )
+ COMP_A = ( 0x04, 0x00000100, 8 )
+ COMP_B = ( 0x04, 0x00000200, 9 )
+ COMP_A1_A2 = ( 0x04, 0x00000400, 10 )
+ COMP_B1_B2 = ( 0x04, 0x00000800, 11 )
+ OUTPUT = ( 0x04, 0x00001000, 12 )
+ EXT_RES_DET = ( 0x04, 0x00002000, 13 )
+ EXT_CLK = ( 0x04, 0x00004000, 14 )
+ ADC_ERR = ( 0x04, 0x00008000, 15 )
+ SILICON_RV = ( 0x04, 0x00070000, 16 )
+ VERSION = ( 0x04, 0xFF000000, 24 )
+ CURRENT_RANGE = ( 0x0A, 0x00000003, 0 )
+ SLOPE_CONTROL = ( 0x0A, 0x00000030, 4 )
+ GLOBALSCALER = ( 0x0B, 0x000000FF, 0 )
+ IHOLD = ( 0x10, 0x0000001F, 0 )
+ IRUN = ( 0x10, 0x00001F00, 8 )
+ IHOLDDELAY = ( 0x10, 0x000F0000, 16 )
+ IRUNDELAY = ( 0x10, 0x0F000000, 24 )
+ TPOWERDOWN = ( 0x11, 0x000000FF, 0 )
+ TSTEP = ( 0x12, 0x000FFFFF, 0 )
+ TPWMTHRS = ( 0x13, 0x000FFFFF, 0 )
+ TCOOLTHRS = ( 0x14, 0x000FFFFF, 0 )
+ THIGH = ( 0x15, 0x000FFFFF, 0 )
+ DIRECT_COIL_A = ( 0x2D, 0x000001FF, 0 )
+ DIRECT_COIL_B = ( 0x2D, 0x01FF0000, 16 )
+ POL_A = ( 0x38, 0x00000001, 0 )
+ POL_B = ( 0x38, 0x00000002, 1 )
+ POL_N = ( 0x38, 0x00000004, 2 )
+ IGNORE_AB = ( 0x38, 0x00000008, 3 )
+ CLR_CONT = ( 0x38, 0x00000010, 4 )
+ CLR_ONCE = ( 0x38, 0x00000020, 5 )
+ POS_NEG_EDGE = ( 0x38, 0x000000C0, 6 )
+ CLR_ENC_X = ( 0x38, 0x00000100, 8 )
+ LATCH_X_ACT = ( 0x38, 0x00000200, 9 )
+ ENC_SEL_DECIMAL = ( 0x38, 0x00000400, 10 )
+ X_ENC = ( 0x39, 0xFFFFFFFF, 0 )
+ ENC_CONST = ( 0x3A, 0xFFFFFFFF, 0 )
+ N_EVENT = ( 0x3B, 0x00000001, 0 )
+ DEVIATION_WARN = ( 0x3B, 0x00000002, 1 )
+ ENC_LATCH = ( 0x3C, 0xFFFFFFFF, 0 )
+ ADC_VSUPPLY = ( 0x50, 0x00001FFF, 0 )
+ ADC_AIN = ( 0x50, 0x1FFF0000, 16 )
+ ADC_TEMP = ( 0x51, 0x00001FFF, 0 )
+ #RESERVED = ( 0x51, 0x1FFF0000, 16 )
+ OVERVOLTAGE_VTH = ( 0x52, 0x00001FFF, 0 )
+ OVERTEMPPREWARNING_VTH = ( 0x52, 0x1FFF0000, 16 )
+ MSLUT__ = ( 0x60, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x61, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x62, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x63, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x64, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x65, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x66, 0xFFFFFFFF, 0 )
+ #MSLUT__ = ( 0x67, 0xFFFFFFFF, 0 )
+ W0 = ( 0x68, 0x00000003, 0 )
+ W1 = ( 0x68, 0x0000000C, 2 )
+ W2 = ( 0x68, 0x00000030, 4 )
+ W3 = ( 0x68, 0x000000C0, 6 )
+ X1 = ( 0x68, 0x0000FF00, 8 )
+ X2 = ( 0x68, 0x00FF0000, 16 )
+ X3 = ( 0x68, 0xFF000000, 24 )
+ START_SIN = ( 0x69, 0x000000FF, 0 )
+ START_SIN90 = ( 0x69, 0x00FF0000, 16 )
+ OFFSET_SIN90 = ( 0x69, 0xFF000000, 24 )
+ MSCNT = ( 0x6A, 0x000003FF, 0 )
+ CUR_B = ( 0x6B, 0x000001FF, 0 )
+ CUR_A = ( 0x6B, 0x01FF0000, 16 )
+ TOFF = ( 0x6C, 0x0000000F, 0 )
+ HSTRT_TFD210 = ( 0x6C, 0x00000070, 4 )
+ HEND_OFFSET = ( 0x6C, 0x00000780, 7 )
+ FD3 = ( 0x6C, 0x00000800, 11 )
+ DISFDCC = ( 0x6C, 0x00001000, 12 )
+ CHM = ( 0x6C, 0x00004000, 14 )
+ TBL = ( 0x6C, 0x00018000, 15 )
+ VHIGHFS = ( 0x6C, 0x00040000, 18 )
+ VHIGHCHM = ( 0x6C, 0x00080000, 19 )
+ TPFD = ( 0x6C, 0x00F00000, 20 )
+ MRES = ( 0x6C, 0x0F000000, 24 )
+ INTPOL = ( 0x6C, 0x10000000, 28 )
+ DEDGE = ( 0x6C, 0x20000000, 29 )
+ DISS2G = ( 0x6C, 0x40000000, 30 )
+ DISS2VS = ( 0x6C, 0x80000000, 31 )
+ SEMIN = ( 0x6D, 0x0000000F, 0 )
+ SEUP = ( 0x6D, 0x00000060, 5 )
+ SEMAX = ( 0x6D, 0x00000F00, 8 )
+ SEDN = ( 0x6D, 0x00006000, 13 )
+ SEIMIN = ( 0x6D, 0x00008000, 15 )
+ SGT = ( 0x6D, 0x007F0000, 16 )
+ SFILT = ( 0x6D, 0x01000000, 24 )
+ DC_TIME = ( 0x6E, 0x000003FF, 0 )
+ DC_SG = ( 0x6E, 0x00FF0000, 16 )
+ SG_RESULT = ( 0x6F, 0x000003FF, 0 )
+ S2VSA = ( 0x6F, 0x00001000, 12 )
+ S2VSB = ( 0x6F, 0x00002000, 13 )
+ STEALTH = ( 0x6F, 0x00004000, 14 )
+ FSACTIVE = ( 0x6F, 0x00008000, 15 )
+ CS_ACTUAL = ( 0x6F, 0x001F0000, 16 )
+ STALLGUARD = ( 0x6F, 0x01000000, 24 )
+ OT = ( 0x6F, 0x02000000, 25 )
+ OTPW = ( 0x6F, 0x04000000, 26 )
+ S2GA = ( 0x6F, 0x08000000, 27 )
+ S2GB = ( 0x6F, 0x10000000, 28 )
+ OLA = ( 0x6F, 0x20000000, 29 )
+ OLB = ( 0x6F, 0x40000000, 30 )
+ STST = ( 0x6F, 0x80000000, 31 )
+ PWM_OFS = ( 0x70, 0x000000FF, 0 )
+ PWM_GRAD = ( 0x70, 0x0000FF00, 8 )
+ PWM_FREQ = ( 0x70, 0x00030000, 16 )
+ PWM_AUTOSCALE = ( 0x70, 0x00040000, 18 )
+ PWM_AUTOGRAD = ( 0x70, 0x00080000, 19 )
+ FREEWHEEL = ( 0x70, 0x00300000, 20 )
+ PWM_MEAS_SD_ENABLE = ( 0x70, 0x00400000, 22 )
+ PWM_DIS_REG_STST = ( 0x70, 0x00800000, 23 )
+ PWM_REG = ( 0x70, 0x0F000000, 24 )
+ PWM_LIM = ( 0x70, 0xF0000000, 28 )
+ PWM_SCALE_SUM = ( 0x71, 0x000003FF, 0 )
+ PWM_SCALE_AUTO = ( 0x71, 0x01FF0000, 16 )
+ PWM_OFS_AUTO = ( 0x72, 0x000000FF, 0 )
+ PWM_GRAD_AUTO = ( 0x72, 0x00FF0000, 16 )
+ SG4_THRS = ( 0x74, 0x000000FF, 0 )
+ SG4_FILT_EN = ( 0x74, 0x00000100, 8 )
+ SG_ANGLE_OFFSET = ( 0x74, 0x00000200, 9 )
+ SG4_RESULT = ( 0x75, 0x000003FF, 0 )
+ SG4_IND_0 = ( 0x76, 0x000000FF, 0 )
+ SG4_IND_1 = ( 0x76, 0x0000FF00, 8 )
+ SG4_IND_2 = ( 0x76, 0x00FF0000, 16 )
+ SG4_IND_3 = ( 0x76, 0xFF000000, 24 )
\ No newline at end of file
diff --git a/pytrinamic/ic/__init__.py b/pytrinamic/ic/__init__.py
index d7e8a836..1a84dec6 100644
--- a/pytrinamic/ic/__init__.py
+++ b/pytrinamic/ic/__init__.py
@@ -6,6 +6,7 @@
from .TMC2209 import TMC2209
from .TMC2224 import TMC2224
from .TMC2225 import TMC2225
+from .TMC2240 import TMC2240
from .TMC2300 import TMC2300
from .TMC2590 import TMC2590
from .TMC2660 import TMC2660
diff --git a/pytrinamic/modules/TMCM1231.py b/pytrinamic/modules/TMCM1231.py
new file mode 100644
index 00000000..68042100
--- /dev/null
+++ b/pytrinamic/modules/TMCM1231.py
@@ -0,0 +1,179 @@
+from ..modules import TMCLModule
+
+# features
+from ..features import MotorControlModule, DriveSettingModule, LinearRampModule
+from ..features import StallGuard2Module, CoolStepModule
+
+
+class TMCM1231(TMCLModule):
+ """
+ The TMCM-1231 is a single axis controller/driver module for 2-phase bipolar stepper motors.
+ """
+ def __init__(self, connection, module_id=1):
+ super().__init__(connection, module_id)
+ self.name = "TMCM-1231"
+ self.desc = self.__doc__
+ self.motors = [self._MotorTypeA(self, 0)]
+
+ def rotate(self, axis, velocity):
+ self.connection.rotate(axis, velocity, self.module_id)
+
+ def stop(self, axis):
+ self.connection.stop(axis, self.module_id)
+
+ def move_to(self, axis, position, velocity=None):
+ if velocity:
+ self.motors[axis].linear_ramp.max_velocity = velocity
+ self.connection.move_to(axis, position, self.module_id)
+
+ def move_by(self, axis, difference, velocity=None):
+ if velocity:
+ self.motors[axis].linear_ramp.max_velocity = velocity
+ self.connection.move_by(axis, difference, self.module_id)
+
+ class _MotorTypeA(MotorControlModule):
+
+ def __init__(self, module, axis):
+ MotorControlModule.__init__(self, module, axis, self.AP)
+ self.drive_settings = DriveSettingModule(module, axis, self.AP)
+ self.linear_ramp = LinearRampModule(module, axis, self.AP)
+ self.stallguard2 = StallGuard2Module(module, axis, self.AP)
+ self.coolstep = CoolStepModule(module, axis, self.AP, self.stallguard2)
+
+ def get_position_reached(self):
+ return self.get_axis_parameter(self.AP.PositionReachedFlag)
+
+ class AP:
+ TargetPosition = 0
+ ActualPosition = 1
+ TargetVelocity = 2
+ ActualVelocity = 3
+ MaxVelocity = 4
+ MaxAcceleration = 5
+ MaxCurrent = 6
+ RunCurrent = MaxCurrent
+ StandbyCurrent = 7
+ PositionReachedFlag = 8
+ HomeSwitch = 9
+ RightEndstop = 10
+ LeftEndstop = 11
+ RightLimit = 12
+ LeftLimit = 13
+ RampType = 14
+ StartVelocity = 15
+ StartAcceleration = 16
+ MaxDeceleration = 17
+ BreakVelocity = 18
+ FinalDeceleration = 19
+ StopVelocity = 20
+ StopDeceleration = 21
+ Bow1 = 22
+ Bow2 = 23
+ Bow3 = 24
+ Bow4 = 25
+ VirtualStopLeft = 26
+ VirtualStopRight = 27
+ VirtualStopEnable = 28
+ VirtualStopMode = 29
+ SwapStopSwitches = 33
+ EnableSoftStop = 34
+ RelativePositioningOption = 127
+ MicrostepResolution = 140
+ ChopperBlankTime = 162
+ ConstantTOffMode = 163
+ DisableFastDecayComparator = 164
+ ChopperHysteresisEnd = 165
+ ChopperHysteresisStart = 166
+ TOff = 167
+ SEIMIN = 168
+ SECDS = 169
+ SmartEnergyHysteresis = 170
+ SECUS = 171
+ SmartEnergyHysteresisStart = 172
+ SG2FilterEnable = 173
+ SG2Threshold = 174
+ SmartEnergyActualCurrent = 180
+ SmartEnergyStallVelocity = 181
+ SmartEnergyThresholdSpeed = 182
+ RandomTOffMode = 184
+ ChopperSynchronization = 185
+ PWMThresholdSpeed = 186
+ PWMGrad = 187
+ PWMAmplitude = 188
+ PWMScale = 189
+ PWMMode = 190
+ PWMFrequency = 191
+ PWMAutoscale = 192
+ ReferenceSearchMode = 193
+ ReferenceSearchSpeed = 194
+ RefSwitchSpeed = 195
+ EndSwitchDistance = 196
+ LastReferencePosition = 197
+ LatchedActualPosition = 198
+ LatchedEncoderPosition = 199
+ BoostCurrent = 200
+ EncoderMode = 201
+ MotorFullStepResolution = 202
+ FreewheelingMode = 204
+ LoadValue = 206
+ ExtendedErrorFlags = 207
+ DriverErrorFlags = 208
+ EncoderPosition = 209
+ EncoderResolution = 210
+ MaxPositionEncoderDeviation = 212
+ MaxVelocityEncoderDeviation = 213
+ PowerDownDelay = 214
+ ReverseShaft = 251
+ StepDirectionMode = 254
+
+ class ENUM:
+ microstep_resolution_fullstep = 0
+ microstep_resolution_halfstep = 1
+ microstep_resolution_4_microsteps = 2
+ microstep_resolution_8_microsteps = 3
+ microstep_resolution_16_microsteps = 4
+ microstep_resolution_32_microsteps = 5
+ microstep_resolution_64_microsteps = 6
+ microstep_resolution_128_microsteps = 7
+ microstep_resolution_256_microsteps = 8
+
+ class GP0:
+ SerialBaudRate = 65
+ SerialAddress = 66
+ SerialHearbeat = 68
+ CANBitRate = 69
+ CANsendID = 70
+ CANreceiveID = 71
+ TelegramPauseTime = 75
+ SerialHostAddress = 76
+ AutoStartMode = 77
+ TMCLCodeProtection = 81
+ CANHeartbeat = 82
+ CANSecondaryAddress = 83
+ eepromCoordinateStore = 84
+ zeroUserVariables = 85
+ serialSecondaryAddress = 87
+ ApplicationStatus = 128
+ DownloadMode = 129
+ ProgramCounter = 130
+ TickTimer = 132
+ RandomNumber = 133
+ SuppressReply = 255
+
+ class GP3:
+ timer_0 = 0
+ timer_1 = 1
+ timer_2 = 2
+ stopLeft_0 = 27
+ stopRight_0 = 28
+ input_0 = 39
+ input_1 = 40
+ input_2 = 41
+ input_3 = 42
+
+ class IO:
+ GPO0 = 0
+ AIN0 = 0
+ GPI0 = 2
+ GPI1 = 3
+
diff --git a/pytrinamic/modules/TMCM6214.py b/pytrinamic/modules/TMCM6214.py
new file mode 100644
index 00000000..016c7a91
--- /dev/null
+++ b/pytrinamic/modules/TMCM6214.py
@@ -0,0 +1,274 @@
+from ..modules import TMCLModule
+
+# features
+from ..features import MotorControlModule, DriveSettingModule, LinearRampModule
+from ..features import StallGuard2Module, CoolStepModule
+
+
+class TMCM6214(TMCLModule):
+ """
+ The TMCM-6214 is a six axis controller/driver module. Supply voltage is 24V.
+ """
+ def __init__(self, connection, module_id=1):
+ super().__init__(connection, module_id)
+ self.name = "TMCM-6214"
+ self.desc = self.__doc__
+ self.motors = [self._MotorTypeA(self, 0), self._MotorTypeA(self, 1), self._MotorTypeA(self, 2),
+ self._MotorTypeA(self, 3), self._MotorTypeA(self, 4), self._MotorTypeA(self, 5)]
+
+ def rotate(self, axis, velocity):
+ """
+ Rotates the motor on the given axis with the given velocity.
+
+ Parameters:
+ axis: Axis index.
+ velocity: Target velocity to rotate the motor with. Units are module specific.
+
+ Returns: None
+ """
+ self.connection.rotate(axis, velocity, self.module_id)
+
+ def stop(self, axis):
+ """
+ Stops the motor on the given axis.
+
+ Parameters:
+ axis: Axis index.
+
+ Returns: None
+ """
+ self.connection.stop(axis, self.module_id)
+
+ def move_to(self, axis, position, velocity=None):
+ """
+ Moves the motor on the given axis to the given target position.
+
+ Parameters:
+ axis: Axis index.
+ position: Target position to move the motor to. Units are module specific.
+ velocity: Maximum position velocity to position the motor. Units are module specific.
+ If no velocity is given, the previously configured maximum positioning velocity (AP 4)
+ will be used.
+
+ Returns: None
+ """
+ if velocity:
+ self.motors[axis].linear_ramp.max_velocity = velocity
+ self.connection.move_to(axis, position, self.module_id)
+
+ def move_by(self, axis, difference, velocity=None):
+ """
+ Moves the motor on the given axis by the given position difference.
+
+ Parameters:
+ axis: Axis index.
+ difference: Position difference to move the motor by. Units are module specific.
+ velocity: Maximum position velocity to position the motor. Units are module specific.
+ If no velocity is given, the previously configured maximum positioning velocity (AP 4)
+ will be used.
+
+ Returns: None
+ """
+ if velocity:
+ self.motors[axis].linear_ramp.max_velocity = velocity
+ self.connection.move_by(axis, difference, self.module_id)
+
+ class _MotorTypeA(MotorControlModule):
+ """
+ Motor class for the motor on axis 0.
+ """
+ def __init__(self, module, axis):
+ MotorControlModule.__init__(self, module, axis, self.AP)
+ self.drive_settings = DriveSettingModule(module, axis, self.AP)
+ self.linear_ramp = LinearRampModule(module, axis, self.AP)
+ self.stallguard2 = StallGuard2Module(module, axis, self.AP)
+ self.coolstep = CoolStepModule(module, axis, self.AP, self.stallguard2)
+
+ def get_position_reached(self):
+ """
+ Indicates whether a positioning task has been completed.
+
+ Returns:
+ 1, if target position has been reached.
+ 0, if target position has not been reached.
+ """
+ return self.get_axis_parameter(self.AP.PositionReachedFlag)
+
+ class AP:
+ # Axis parameter map for this axis.
+ TargetPosition = 0
+ ActualPosition = 1
+ TargetVelocity = 2
+ ActualVelocity = 3
+ MaxVelocity = 4
+ MaxAcceleration = 5
+ MaxCurrent = 6
+ RunCurrent = MaxCurrent
+ StandbyCurrent = 7
+ PositionReachedFlag = 8
+ HomeSwitch = 9
+ RightEndstop = 10
+ LeftEndstop = 11
+ RightLimitSwitchDisable = 12
+ LeftLimitSwitchDisable = 13
+ SwapLimitSwitches = 14
+ A1 = 15
+ V1 = 16
+ MaxDeceleration = 17
+ D1 = 18
+ StartVelocity = 19
+ StopVelocity = 20
+ RampWaitTime = 21
+ HighSpeedTheshold = 22
+ MinDcStepSpeed = 23
+ RightSwitchPolarity = 24
+ LeftSwitchPolarity = 25
+ Softstop = 26
+ HighSpeedChopperMode = 27
+ HighSpeedFullstepMode = 28
+ MeasuredSpeed = 29
+ PowerDownRamp = 31
+ DcStepTime = 32
+ DcStepStallGuard = 33
+ PositionCompareStart = 40
+ PositionCompareDistance = 41
+ PositionCompareOutput = 42
+ PositionCompareOutputPulseLength = 43
+ RelativePositioningOptionCode = 127
+ MicrostepResolution = 140
+ ChopperBlankTime = 162
+ ConstantTOffMode = 163
+ DisableFastDecayComparator = 164
+ ChopperHysteresisEnd = 165
+ ChopperHysteresisStart = 166
+ TOff = 167
+ SEIMIN = 168
+ SECDS = 169
+ SmartEnergyHysteresis = 170
+ SECUS = 171
+ SmartEnergyHysteresisStart = 172
+ SG2FilterEnable = 173
+ SG2Threshold = 174
+ ShortToGroundProtection = 177
+ VSense = 179
+ SmartEnergyActualCurrent = 180
+ SmartEnergyStallVelocity = 181
+ SmartEnergyThresholdSpeed = 182
+ RandomTOffMode = 184
+ ChopperSynchronization = 185
+ PWMThresholdSpeed = 186
+ PWMGrad = 187
+ PWMAmplitude = 188
+ PWMScale = 189
+ PWMMode = 190
+ PWMFrequency = 191
+ PWMAutoscale = 192
+ ReferenceSearchMode = 193
+ ReferenceSearchSpeed = 194
+ RefSwitchSpeed = 195
+ RightLimitSwitchPosition = 196
+ LastReferencePosition = 197
+ LatchedActualPosition = 198
+ LatchedEncoderPosition = 199
+ EncoderMode = 201
+ MotorFullStepResolution = 202
+ FreewheelingMode = 204
+ LoadValue = 206
+ ErrorFlags = 207
+ StatusFlags = 208
+ EncoderPosition = 209
+ EncoderResolution = 210
+ EncoderDeviationMax = 212
+ GroupIndex = 213
+ PowerDownDelay = 214
+ DeviationAction = 240
+ ReverseShaft = 251
+ UnitMode = 255
+
+ class ENUM:
+ """
+ Constant enums for parameters of this module.
+ """
+ MicrostepResolutionFullstep = 0
+ MicrostepResolutionHalfstep = 1
+ MicrostepResolution4Microsteps = 2
+ MicrostepResolution8Microsteps = 3
+ MicrostepResolution16Microsteps = 4
+ MicrostepResolution32Microsteps = 5
+ MicrostepResolution64Microsteps = 6
+ MicrostepResolution128Microsteps = 7
+ MicrostepResolution256Microsteps = 8
+
+ class GP0:
+ """
+ Global parameter map for this module.
+ """
+ RS485Baudrate = 65
+ SerialAddress = 66
+ SerialHeartbeat = 68
+ CANBitrate = 69
+ CANSendId = 70
+ CANReceiveId = 71
+ CANSecondaryId = 72 #not in datasheet
+ TelegramPauseTime = 75
+ SerialHostAddress = 76
+ AutoStartMode = 77
+ ProtectionMode = 81
+ CANHeartbeat = 82
+ CANSecondaryAddress = 83
+ EepromCoordinateStore = 84
+ ZeroUserVariables = 85
+ SerialSecondaryAddress = 86
+ ApplicationStatus = 128
+ DownloadMode = 129
+ ProgramCounter = 130
+ TickTimer = 132
+ RandomNumber = 133
+ SuppressReply = 255
+
+ class GP3:
+ Timer_0 = 0
+ Timer_1 = 1
+ Timer_2 = 2
+ StopLeft_0 = 27
+ StopRight_0 = 28
+ StopLeft_1 = 29
+ StopRight_1 = 30
+ StopLeft_2 = 31
+ StopRight_2 = 32
+ StopLeft_3 = 33
+ StopRight_3 = 34
+ StopLeft_4 = 35
+ StopRight_4 = 36
+ StopLeft_5 = 37
+ StopRight_5 = 38
+ Input_0 = 39
+ Input_1 = 40
+ Input_2 = 41
+ Input_3 = 42
+ Input_4 = 43
+ Input_5 = 44
+ Input_6 = 45
+ Input_7 = 46
+
+
+ class IO:
+ OUT0 = 0
+ OUT1 = 1
+ OUT2 = 2
+ OUT3 = 3
+ OUT4 = 4
+ OUT5 = 5
+ OUT6 = 6
+ OUT7 = 7
+ AIN0 = 0
+ IN1 = 1
+ IN2 = 2
+ IN3 = 3
+ AIN4 = 4
+ IN5 = 5
+ IN6 = 6
+ IN7 = 7
+ STO = 10
+ STO1 = 13
+ STO2 = 14
diff --git a/pytrinamic/modules/__init__.py b/pytrinamic/modules/__init__.py
index ef54fd69..c0babf3b 100644
--- a/pytrinamic/modules/__init__.py
+++ b/pytrinamic/modules/__init__.py
@@ -22,3 +22,5 @@
from .TMCM3351 import TMCM3351
from .TMCM6110 import TMCM6110
from .TMCM6212 import TMCM6212
+from .TMCM6214 import TMCM6214
+from .TMCM1231 import TMCM1231
\ No newline at end of file
diff --git a/pytrinamic/tmcl.py b/pytrinamic/tmcl.py
index 0b79f439..edc780b4 100644
--- a/pytrinamic/tmcl.py
+++ b/pytrinamic/tmcl.py
@@ -151,9 +151,6 @@ def __str__(self):
self.checksum
)
- def dump(self):
- print(self)
-
class TMCLReply:
def __init__(self, reply_address, module_address, status, command, value, checksum=None, special=False):
@@ -194,9 +191,6 @@ def __str__(self):
self.checksum
)
- def dump(self):
- print(self)
-
def value(self):
return self.value
diff --git a/pytrinamic/tools/__init__.py b/pytrinamic/tools/__init__.py
new file mode 100644
index 00000000..31a979de
--- /dev/null
+++ b/pytrinamic/tools/__init__.py
@@ -0,0 +1 @@
+from .velocity_ramp_runner import VelocityRampRunner
diff --git a/pytrinamic/tools/tests/test_ramp_runner.py b/pytrinamic/tools/tests/test_ramp_runner.py
new file mode 100644
index 00000000..c78107c4
--- /dev/null
+++ b/pytrinamic/tools/tests/test_ramp_runner.py
@@ -0,0 +1,82 @@
+"""Testing the ramp module
+
+Run this test using ether the command-line of the unittest framework [1] or simply call the script directly
+
+[1]: https://docs.python.org/3/library/unittest.html#command-line-interface
+"""
+
+import time
+import unittest
+from unittest.mock import Mock, call
+
+from pytrinamic.tools import VelocityRampRunner
+
+
+class TestVelocityRampRunner(unittest.TestCase):
+ """Contains the tests for the VelocityRampRunner"""
+
+ def test_fixed_cycle_time(self):
+ """Run a linear ramp and see if the velocity is updated as expected."""
+ update_mock = Mock()
+ ramp_runner = VelocityRampRunner(update_mock, update_cycle_time_ms=10)
+
+ # call the method under test
+ ramp_runner.run_linear_ramp(0, 100, 40)
+
+ expected_velocity_update_calls = [call(0), call(25), call(50), call(75), call(100)]
+ update_mock.assert_has_calls(expected_velocity_update_calls)
+
+ def test_fixed_cycle_time_delay_time(self):
+ """Run a linear ramp and see if the delay is plausible"""
+ update_mock = Mock()
+ ramp_runner = VelocityRampRunner(update_mock, update_cycle_time_ms=100)
+
+ # call the method under test
+ start_time = time.perf_counter()
+ ramp_runner.run_linear_ramp(0, 100, 400)
+ stop_time = time.perf_counter()
+
+ delay_s = stop_time-start_time
+ assert 0.35 < delay_s < 0.45
+
+ def test_fixed_cycle_time_imprecise_25(self):
+ """Run a linear ramp with an interval that is not a multiple of update_cycle_time_ms"""
+ update_mock = Mock()
+ ramp_runner = VelocityRampRunner(update_mock, update_cycle_time_ms=10)
+
+ # call the method under test
+ ramp_runner.run_linear_ramp(0, 100, 25)
+
+ expected_velocity_update_calls = [call(0), call(40), call(80), call(100)]
+ update_mock.assert_has_calls(expected_velocity_update_calls)
+
+ def test_fixed_cycle_time_imprecise_35(self):
+ """Run a linear ramp with an interval that is not a multiple of update_cycle_time_ms"""
+ update_mock = Mock()
+ ramp_runner = VelocityRampRunner(update_mock, update_cycle_time_ms=10)
+
+ # call the method under test
+ ramp_runner.run_linear_ramp(0, 100, 35)
+
+ expected_velocity_update_calls = [call(0), call(28), call(57), call(85), call(100)]
+ update_mock.assert_has_calls(expected_velocity_update_calls)
+
+ def test_without_given_cycle_time(self):
+ """Run a linear ramp and see if the velocity is updated as expected."""
+ update_mock = Mock()
+ ramp_runner = VelocityRampRunner(update_mock)
+
+ # we mock the internal time method to emulate calls to time.time
+ ramp_runner._time_ms = Mock()
+ # the _time_ms method is about to return thees values
+ ramp_runner._time_ms.side_effect = [10, 30, 50]
+
+ # call the method under test
+ ramp_runner.run_linear_ramp(0, 100, 40)
+
+ expected_velocity_update_calls = [call(0), call(50), call(100)]
+ update_mock.assert_has_calls(expected_velocity_update_calls)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/pytrinamic/tools/velocity_ramp_runner.py b/pytrinamic/tools/velocity_ramp_runner.py
new file mode 100644
index 00000000..8d1fc207
--- /dev/null
+++ b/pytrinamic/tools/velocity_ramp_runner.py
@@ -0,0 +1,78 @@
+"""Module to run different ramp motions.
+
+Please note that thees functions work in blocking mode.
+"""
+
+import time
+import math
+
+
+class VelocityRampRunner:
+ """The VelocityRampRunner allows you to ramp up or down the velocity of a motor.
+
+ This is helpful for modules/drives that do not have a velocity ramp features build in.
+
+ You just give a start velocity, a target velocity and the time in which you like the velocity to go up/down from
+ the start to the target velocity, and run_linear_ramp will update the velocity at the maximum possible interval, or
+ by the interval given in the constructor (update_cycle_time_ms).
+ """
+
+ def __init__(self, velocity_update_callback, update_cycle_time_ms=0):
+ """If update_cycle_time_ms is set to 0, the velocity is updated as fast as possible.
+
+ :argument velocity_update_callback: You need to give a function that updates the velocity of your motor.
+ The signature of the function should look like this:
+ def callback(int velocity):
+ :argument update_cycle_time_ms: Optional update interval, if set to zero or omitted the velocity will be updated
+ as fast as possible.
+ """
+ self._velocity_update_callback = velocity_update_callback
+ self._update_cycle_time_ms = update_cycle_time_ms
+
+ def run_linear_ramp(self, start_velocity_rpm, target_velocity_rpm, time_delta_ms):
+ """Update the velocity on a linear basis from start_velocity to the target_velocity within the given time."""
+ if self._update_cycle_time_ms:
+ self._velocity_ramp_fixed_cycle(start_velocity_rpm, target_velocity_rpm, time_delta_ms)
+ else:
+ self._velocity_ramp_fast(start_velocity_rpm, target_velocity_rpm, time_delta_ms)
+
+ def _velocity_ramp_fixed_cycle(self, start_velocity_rpm, target_velocity_rpm, time_delta_ms):
+ """Sub function that updates the velocity on a update_cycle_time_ms basis.
+
+ Note that if time_delta_ms can not be divided by update_cycle_time_ms,
+ the linear ramp will be longer than expected. This way we make sure there is
+ no high acceleration at the end.
+ """
+ update_cycles = math.ceil(time_delta_ms / self._update_cycle_time_ms)
+ acceleration = (target_velocity_rpm - start_velocity_rpm) / time_delta_ms
+ for i in range(update_cycles):
+ start_time = self._time_ms()
+ velocity_update = acceleration * (i * self._update_cycle_time_ms) + start_velocity_rpm
+ self._velocity_update_callback(int(velocity_update))
+ stop_time = self._time_ms()
+ delay_time_ms = (stop_time - start_time)
+ if delay_time_ms < self._update_cycle_time_ms:
+ time.sleep((self._update_cycle_time_ms - delay_time_ms) / 1000)
+ # always set the target_velocity_rpm at the end
+ self._velocity_update_callback(int(target_velocity_rpm))
+
+ def _velocity_ramp_fast(self, start_velocity_rpm, target_velocity_rpm, time_delta_ms):
+ """Sub function that updates the velocity as fast as possible.
+
+ Remark: Seems to update the velocity on a 1/4 ms basis on my machine
+ """
+ start_time = self._time_ms()
+ acceleration = (target_velocity_rpm - start_velocity_rpm) / time_delta_ms
+ self._velocity_update_callback(start_velocity_rpm)
+ stop_time = self._time_ms()
+ delay_time_ms = (stop_time - start_time)
+ while delay_time_ms < time_delta_ms:
+ velocity_update = acceleration * delay_time_ms + start_velocity_rpm
+ self._velocity_update_callback(int(velocity_update))
+ stop_time = self._time_ms()
+ delay_time_ms = (stop_time - start_time)
+ self._velocity_update_callback(target_velocity_rpm)
+
+ @classmethod
+ def _time_ms(cls):
+ return time.perf_counter() * 1000
diff --git a/pytrinamic/version.py b/pytrinamic/version.py
index 1f4ca897..d31c31ea 100644
--- a/pytrinamic/version.py
+++ b/pytrinamic/version.py
@@ -1,2 +1 @@
-__version__ = "0.2.2"
-
+__version__ = "0.2.3"
diff --git a/tests/test_can_adapters.py b/tests/test_can_adapters.py
new file mode 100644
index 00000000..9bc6aff5
--- /dev/null
+++ b/tests/test_can_adapters.py
@@ -0,0 +1,81 @@
+"""Testing the all CAN Adapters in a combined Network
+
+To recreate the tests create a CAN network by combining:
+ * Kvaser Leaf Light v2
+ * PEAK PCAN
+ * Ixxat USB-to-CAN
+ * CANable (Original)
+ * 3x TMCM-1241 with CAN-ID 3,4 and 5
+"""
+
+import pytest
+
+from pytrinamic.modules import TMCLModule
+
+from pytrinamic.connections import ConnectionManager
+
+from pytrinamic.connections import KvaserTmclInterface
+from pytrinamic.connections import PcanTmclInterface
+from pytrinamic.connections import SlcanTmclInterface
+from pytrinamic.connections import IxxatTmclInterface
+
+slcan_com_port = 'COM15'
+
+ap = {
+ 'Maximum current': 6,
+ 'Microstep resolution': 140,
+}
+
+
+@pytest.fixture(scope='function', params=[
+ KvaserTmclInterface,
+ PcanTmclInterface,
+ SlcanTmclInterface,
+ IxxatTmclInterface,
+])
+def can_adapter(request):
+ can_tmcl_interface_class = request.param
+ if can_tmcl_interface_class == PcanTmclInterface:
+ ports = PcanTmclInterface.list()
+ adptr = can_tmcl_interface_class(port=ports[0])
+ elif can_tmcl_interface_class == SlcanTmclInterface:
+ adptr = can_tmcl_interface_class(com_port=slcan_com_port)
+ else:
+ adptr = can_tmcl_interface_class()
+ yield adptr
+ adptr.close()
+
+
+def test_adapter_classes(can_adapter):
+ tmcm1241s = [TMCLModule(can_adapter, module_id=mid) for mid in range(3, 6)]
+ for tmcm1241 in tmcm1241s:
+ assert tmcm1241.get_global_parameter(71, 0) == tmcm1241.module_id
+ assert tmcm1241.get_axis_parameter(ap['Microstep resolution'], 0) == 8
+ for tmcm1241 in tmcm1241s:
+ tmcm1241.set_axis_parameter(ap['Maximum current'], 0, 10+tmcm1241.module_id)
+ assert tmcm1241.get_axis_parameter(ap['Maximum current'], 0) == 10+tmcm1241.module_id
+ for tmcm1241 in tmcm1241s:
+ tmcm1241.set_axis_parameter(ap['Maximum current'], 0, 20+tmcm1241.module_id)
+ assert tmcm1241.get_axis_parameter(ap['Maximum current'], 0) == 20+tmcm1241.module_id
+
+
+@pytest.mark.parametrize('cm_call', [
+ f"--interface ixxat_tmcl",
+ f"--interface kvaser_tmcl",
+ f"--interface pcan_tmcl",
+ f"--interface slcan_tmcl --port {slcan_com_port}",
+])
+def test_connection_manager(cm_call):
+ cm = ConnectionManager(cm_call)
+ with cm.connect() as interface:
+ tmcm1241s = [TMCLModule(interface, module_id=mid) for mid in range(3, 6)]
+ for tmcm1241 in tmcm1241s:
+ assert tmcm1241.get_global_parameter(71, 0) == tmcm1241.module_id
+ assert tmcm1241.get_axis_parameter(ap['Microstep resolution'], 0) == 8
+ for tmcm1241 in tmcm1241s:
+ tmcm1241.set_axis_parameter(ap['Maximum current'], 0, 10+tmcm1241.module_id)
+ assert tmcm1241.get_axis_parameter(ap['Maximum current'], 0) == 10+tmcm1241.module_id
+ for tmcm1241 in tmcm1241s:
+ tmcm1241.set_axis_parameter(ap['Maximum current'], 0, 20+tmcm1241.module_id)
+ assert tmcm1241.get_axis_parameter(ap['Maximum current'], 0) == 20+tmcm1241.module_id
+
diff --git a/tests/test_interface_timeouts.py b/tests/test_interface_timeouts.py
new file mode 100644
index 00000000..d3f5c839
--- /dev/null
+++ b/tests/test_interface_timeouts.py
@@ -0,0 +1,108 @@
+"""Test for the ConnectionManager timeout parameter.
+
+A (virtual) comport and a Kvaser CAN-Adapter are needed to run these tests.
+Note, you may need change the comport number.
+"""
+
+import time
+
+import pytest
+
+from pytrinamic.connections import ConnectionManager
+from pytrinamic.connections import KvaserTmclInterface
+from pytrinamic.connections import SerialTmclInterface
+from pytrinamic.connections import UartIcInterface
+
+
+@pytest.mark.parametrize('interface,con_man_parameters', [
+ ('serial_tmcl', ' --port COM4 --data-rate 115200'),
+ ('uart_ic', ' --port COM4'),
+ ('kvaser_tmcl', '')
+])
+@pytest.mark.parametrize('add_argument,expected_timeout', [
+ ('', 5),
+ (' --timeout 7', 7),
+ (' --timeout 200', 200),
+ (' --timeout 33.3', 33.3),
+ (' --timeout 0.0', None),
+ (' --timeout 0', None),
+ (' --timeout -0', None),
+])
+def test_valid_input_cm(interface, con_man_parameters, add_argument, expected_timeout):
+ """Check if the timeout is forwarded to the serial interface."""
+
+ cm = ConnectionManager(f"--interface {interface}{con_man_parameters}" + add_argument)
+
+ with cm.connect() as myinterface:
+ if interface == 'serial_tmcl':
+ assert myinterface._serial.timeout == expected_timeout
+ elif interface == 'uart_ic':
+ assert myinterface.serial.timeout == expected_timeout
+ elif interface == 'kvaser_tmcl':
+ assert myinterface._timeout_s == expected_timeout
+ else:
+ pytest.fail('Unexpected interface!')
+
+
+@pytest.mark.parametrize('interface_class', [
+ 'kvaser_tmcl',
+ 'serial_tmcl',
+ 'uart_ic',
+])
+@pytest.mark.parametrize('timeout_input,expected_timeout', [
+ ('', 5),
+ (None, None),
+ (0, None),
+ (7, 7),
+ (33.3, 33.3),
+])
+def test_valid_input_direct(interface_class, timeout_input, expected_timeout):
+ """Test like the test_valid_input_cm() but without the connection manager."""
+ if interface_class == 'kvaser_tmcl':
+ if timeout_input == '':
+ myinterface = KvaserTmclInterface()
+ else:
+ myinterface = KvaserTmclInterface(timeout_s=timeout_input)
+ assert myinterface._timeout_s == expected_timeout
+ elif interface_class == 'serial_tmcl':
+ if timeout_input == '':
+ myinterface = SerialTmclInterface('COM4')
+ else:
+ myinterface = SerialTmclInterface('COM4', timeout_s=timeout_input)
+ assert myinterface._serial.timeout == expected_timeout
+ elif interface_class == 'uart_ic':
+ if timeout_input == '':
+ myinterface = UartIcInterface('COM4')
+ else:
+ myinterface = UartIcInterface('COM4', timeout_s=timeout_input)
+ assert myinterface.serial.timeout == expected_timeout
+
+
+@pytest.mark.parametrize('con_man_call,expected_exception', [
+ ('--interface serial_tmcl --port COM4 --data-rate 115200', RuntimeError),
+ ('--interface kvaser_tmcl', ConnectionError),
+])
+def test_actual_timeout(con_man_call, expected_exception):
+ """We just call the receive-function without sending anything and check if a timeout will be raised."""
+
+ cm = ConnectionManager(f"{con_man_call} --timeout 1.5")
+
+ with pytest.raises(expected_exception):
+ with cm.connect() as myinterface:
+ start_time = time.perf_counter()
+ myinterface._recv(0, 0)
+ stop_time = time.perf_counter()
+ duration = stop_time - start_time
+ assert 1.5 < duration < 1.7
+
+
+@pytest.mark.parametrize('timeout_argument', [
+ 'string',
+ '0x1F',
+ '-0.1',
+ '-100',
+])
+def test_invalid_timeout(timeout_argument):
+ """Test some invalid arguments for the timeout value."""
+ with pytest.raises(SystemExit) as exec_info:
+ ConnectionManager(f"--interface serial_tmcl --port COM4 --data-rate 115200 --timeout {timeout_argument}")