Skip to content

Instantly share code, notes, and snippets.

@Micrified
Created February 1, 2026 18:34
Show Gist options
  • Select an option

  • Save Micrified/2e8114a338c9b1565bd1d537efc4e44c to your computer and use it in GitHub Desktop.

Select an option

Save Micrified/2e8114a338c9b1565bd1d537efc4e44c to your computer and use it in GitHub Desktop.
PD42-1370-TMCL Test Script
# Script for controlling Trinamics PD42-1370
# Application Notes
# * TMCL Direct Mode (binary commands)
# - Strict master/slave relation between host (PC/PLC) and PD42-1370
# - (1) Master sends command
# - (2) Slave TMCL interpreter on module interprets and acts
# - (3) Slave TMCL module sends response to master.
# - Only after (3) can (1) be performed again.
# * Commands
# - Composition (7B)
# - (1) [1B] Command Field
# - (2) [1B] Type Field
# - (3) [1B] Motor/Bank field
# - (4) [4B] Value field
# * Protocol Considerations (RS-232, RS-485, RS-422, USB)
# - Message begins with address byte
# - Message ends with checksum byte
# - A command is therefore typically *9B* in size
# Message Format
# +------+-----------------------+------------------------------------------+
# | Byte | Meaning | Description |
# +------+-----------------------+------------------------------------------+
# | 1 | Module Address | Target device ID (Default is 1) |
# | 2 | Command Number / Inst | The instruction (e.g., ROL, MST, SAP) |
# | 3 | Type Number | Command subtype or parameter index |
# | 4 | Motor/Bank Number | Target axis (usually 0 for single-axis) |
# | 5 | Value (Byte 3 / MSB) | Data payload - Most Significant Byte |
# | 6 | Value (Byte 2) | Data payload |
# | 7 | Value (Byte 1) | Data payload |
# | 8 | Value (Byte 0 / LSB) | Data payload - Least Significant Byte |
# | 9 | Checksum | Arithmetic sum of Bytes 1-8 (8-bit) |
# +------+-----------------------+------------------------------------------+
# Reply Format
# +------+-----------------------+------------------------------------------+
# | Byte | Meaning | Description |
# +------+-----------------------+------------------------------------------+
# | 1 | Reply Address | Address of sender |
# | 2 | Module Address | Target device ID |
# | 3 | Status | Status (e.g., 100 means no error) |
# | 4 | Command Number / Inst | Instruction (echo'd I presume?) |
# | 5 | Value (Byte 3 / MSB) | Data payload - Most Significant Byte |
# | 6 | Value (Byte 2) | Data payload |
# | 7 | Value (Byte 1) | Data payload |
# | 8 | Value (Byte 0 / LSB) | Data payload - Least Significant Byte |
# | 9 | Checksum | Arithmetic sum of Bytes 1-8 (8-bit) |
# +------+-----------------------+------------------------------------------+
#
# TMCL Status Codes
# +------+------------------------------------------+
# | Code | Meaning |
# +------+------------------------------------------+
# | 100 | Successfully executed (no error) |
# | 101 | Command loaded into TMCL program EEPROM |
# | 1 | Wrong checksum |
# | 2 | Invalid command |
# | 3 | Wrong type |
# | 4 | Invalid value |
# | 5 | Configuration EEPROM locked |
# | 6 | Command not available |
# +------+------------------------------------------+
class TMCL(object):
class Frame(object):
# Compose a message frame
def __init__(self, addr, obj):
self.addr = addr
self.obj = obj
# Return the TMCL frame checksum (sum of all values)
@staticmethod
def checksum(buf):
assert(len(buf) == 8)
return sum(buf) & 0xFF
def buffer(self):
buf = self.addr.to_bytes(length=1, byteorder='big', signed=False) + \
self.obj.buffer()
chk = TMCL.Frame.checksum(buf)
return buf + chk.to_bytes(length=1, byteorder='big', signed=False)
# Default print (compact)
def __str__(self):
return "[" + ",".join(["{:08b}".format(b) for b in self.buffer()]) + "]"
class Command(object):
# Compose a command message
def __init__(self, cmd, typ, mtr, val):
self.cmd = cmd
self.typ = typ
self.mtr = mtr
self.val = val
# Return message buffer
def buffer(self):
return self.cmd.to_bytes(length=1, byteorder='big', signed=False) + \
self.typ.to_bytes(length=1, byteorder='big', signed=False) + \
self.mtr.to_bytes(length=1, byteorder='big', signed=False) + \
self.val.to_bytes(length=4, byteorder='big', signed=False)
# Default print (compact)
def __str__(self):
return "[" + ",".join(["{:08b}".format(b) for b in self.buffer()]) + "]"
# Control commands
class Control(object):
# Get the firmware version
@staticmethod
def GET_FIRMWARE_VERSION(command=136):
return TMCL.Command(cmd=command, typ=1, mtr=0, val=0)
# Motion commands
class Motion(object):
# Rotate right with specified velocity (microsteps/pulse per second)
@staticmethod
def ROR(motor, velocity, command=1):
return TMCL.Command(cmd=command, typ=0, mtr=0, val=velocity)
# Rotate left with specified velocity (microsteps/pulse per second)
@staticmethod
def ROL(motor, velocity, command=2):
return TMCL.Command(cmd=command, typ=0, mtr=0, val=velocity)
# Stop motor movement
@staticmethod
def MST(motor, command=3):
return TMCL.Command(cmd=command, typ=0, mtr=0, val=0)
ror = TMCL.Frame(addr=0x01, obj=TMCL.Command.Motion.ROR(motor=0, velocity=102400))
print("ROR: {}".format(ror))
mst = TMCL.Frame(addr=0x01, obj=TMCL.Command.Motion.MST(motor=0))
print("MST: {}".format(mst))
# Test with RS485
import serial, time
if __name__ == "__main__":
ver = TMCL.Frame(addr=0x01, obj=TMCL.Command.Control.GET_FIRMWARE_VERSION())
device = serial.Serial(port="/dev/tty.usbmodem01234567891", baudrate=9600, timeout=1)
print("Writing: {}".format(ver))
device.write(ver.buffer())
print("Got response: {}".format(device.read(size=9)))
# Routine: Move the motor
print("Writing: {}".format(ror))
device.write(ror.buffer())
print("Got response: {}".format(device.read(size=9)))
print("Waiting 5s ...")
time.sleep(10)
print("Writing: {}".format(mst))
device.write(mst.buffer())
print("Got response: {}".format(device.read(size=9)))
print("Done")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment