seveibar/nema17-stepper-motor-driver-tmc2209-usb-pd
RP2040-based USB-C PD stepper-motor controller with TMC2209 drive, AS5600 position feedback, TMP102 thermal shutdown, current/voltage telemetry, RGB status LED, and protected buzzer alarm circuitry.
- Version
- 0.2.0
- License
- unset
- Stars
- 0
firmware/tmc2209.py
"""TMC2209 UART0 adapter, address 0, 0.15 ohm external sense resistors.
Reference code, not tested on hardware. Caller must hold GPIO22 low on setup.
"""
from machine import UART, Pin
from time import sleep_ms, ticks_ms, ticks_diff
def crc8(data):
crc = 0
for value in data:
for _ in range(8):
crc = ((crc << 1) ^ (7 if (crc >> 7) ^ (value & 1) else 0)) & 255
value >>= 1
return crc
class TMC2209:
def __init__(self, uart=None):
self.uart = uart or UART(0, baudrate=115200, tx=Pin(20), rx=Pin(21), timeout=10)
self.configured = False
def read(self, register):
self.uart.read() # flush old local echo/data
packet = bytes((5, 0, register & 127))
self.uart.write(packet + bytes((crc8(packet),)))
started = ticks_ms()
buf = b''
while ticks_diff(ticks_ms(), started) < 40:
buf += self.uart.read() or b''
# Skip the local transmit echo; device replies have master address FF.
for i in range(max(0, len(buf) - 7)):
frame = buf[i:i+8]
if frame[:3] == bytes((5, 255, register & 127)) and crc8(frame[:7]) == frame[7]:
return int.from_bytes(frame[3:7], 'big')
sleep_ms(1)
raise OSError('TMC2209 UART timeout or CRC failure')
def write(self, register, value):
before = self.read(2) & 255 # IFCNT confirms CRC-valid writes
packet = bytes((5, 0, register | 128)) + value.to_bytes(4, 'big')
self.uart.write(packet + bytes((crc8(packet),)))
sleep_ms(2)
if self.read(2) & 255 != (before + 1) & 255:
raise OSError('TMC2209 rejected register write')
def configure(self):
self.configured = False
# Version 0x21 identifies TMC2209. Read also confirms motor-side power.
if (self.read(6) >> 24) != 0x21:
raise OSError('Unexpected TMC2209 IOIN version')
# Digital current scale, external Rsense, UART enabled, register microsteps.
self.write(0, (1 << 6) | (1 << 7) | (1 << 8))
self.write(0x10, (6 << 16) | (14 << 8) | 5) # run ~0.63 A RMS, hold ~0.25 A
self.write(0x11, 20)
# 16 microsteps, interpolation, vsense=0, default blanking/hysteresis, TOFF=3.
self.write(0x6C, 0x14010053)
self.write(0x22, 0) # VACTUAL=0: external STEP only
self.write(1, 7) # acknowledge startup GSTAT reset/driver/charge-pump flags
self.configured = True
self.check()
def check(self):
if not self.configured:
raise OSError('TMC2209 not configured')
if self.read(0) != ((1 << 6) | (1 << 7) | (1 << 8)) or self.read(1) & 7:
self.configured = False
raise OSError('TMC2209 reset or power/driver fault; re-arm to reconfigure')
# OTPW, OT, short-to-ground and low-side-short flags. Open-load status is
# unreliable at standstill and deliberately not treated as a hard fault.
if self.read(0x6F) & 0x3F:
raise OSError('TMC2209 temperature or short-circuit warning')