Robohouse ’26 Library
Contents

Chapter 7

The TMC2209 in detail

5 sections · about 9 minutes

7.1 What it is

The TMC2209 is a Trinamic (now part of Analog Devices) stepper driver with an integrated power stage. It handles one bipolar stepper, up to 2 A peak per phase or about 1.4 A RMS continuous, from a supply of 4.75 V to 29 V. Its distinguishing features are StealthChop2 for silent operation, SpreadCycle for high-speed torque, StallGuard4 for sensorless load measurement, CoolStep for automatic current reduction, MicroPlyer interpolation to 1/256, and a single-wire UART interface that gives you full register-level control.

The UART is what makes it a good fit for a robot arm rather than a 3D printer: you can set current per joint from software, read back driver diagnostics, tune StallGuard for homing, and detect faults, all without touching a screwdriver.

7.2 The pins, and what each one does

Grouped by function:

Power pins

VM is the motor supply, 4.75 to 29 V. This is the rail the H-bridges switch, and where your bulk capacitor goes. GND is the power return. VCP is the charge-pump capacitor pin — the driver needs a voltage above VM to fully turn on its high-side N-channel transistors, and it generates that with a charge pump requiring an external capacitor to VM. 5VOUT is an internal 5 V regulator output; it powers the chip's own logic and can supply a small amount externally, and it needs a decoupling capacitor. VREF is the analogue current-reference input discussed in Chapter 6. VIO is the logic supply, and whatever you put on it sets the logic level of all the digital pins. Connect it to the Teensy's 3.3 V and every digital pin becomes 3.3 V logic, safe for the Teensy. Connect it to 5 V, as many 3D printer boards do, and the driver's outputs will drive 5 V into your Teensy and damage it.

Motor output pins

OA1, OA2 are the two ends of coil A. OB1, OB2 are coil B. The two wires of a single coil must go to OA1/OA2 as a pair; split a coil across the two outputs and the motor vibrates without turning. If you are unsure which wires pair up, measure resistance between them — the two ends of one coil read a couple of ohms, and wires from different coils read open circuit. Swapping the two wires within a pair simply reverses that phase, which reverses the motor's direction — a legitimate way to fix a joint that homes the wrong way if you would rather not do it in software.

BRA, BRB are the sense-resistor connections. On a breakout board these are already wired to the sense resistors and you do not touch them.

Step/direction control pins

STEP advances the microstep counter on each rising edge. DIR selects direction; it is sampled at the step edge, so it must be stable beforehand. EN is active-low enable: low turns the outputs on, high puts them in high-impedance and the motor freewheels. As discussed, pull it up so the default state is disabled.

Configuration pins

MS1 and MS2 do double duty, which is the most confusing part of the chip.

In standalone mode, where the driver runs without UART, MS1 and MS2 select the microstep resolution:

MS2MS1Microsteps
001/8
011/2
101/4
111/16

Note that these are the input step resolutions; with MicroPlyer interpolation, the driver internally runs at 1/256 regardless, so the motor motion is smooth even at 1/2 input stepping. Note also that the ordering is not the obvious one: MS2=0, MS1=1 gives 1/2, not 1/4.

In UART mode, MS1 and MS2 stop being microstep selectors — microstepping moves to the MRES field of the CHOPCONF register — and instead become the address pins. MS1 is address bit 0 and MS2 is address bit 1, giving four addresses, 0 through 3. This is how you put multiple drivers on one UART line.

PDN_UART is the single-wire UART pin. In standalone mode it is a power-down input with a specific meaning; the moment you connect a UART to it, it becomes the bidirectional serial line.

SPREAD selects the chopper mode in standalone: high for SpreadCycle, low for StealthChop. In UART mode this is overridden by the en_spreadcycle bit in GCONF.

CLK is an optional external clock input. Left unconnected, the chip uses its internal ~12 MHz oscillator, which is fine for almost everything. Feed it an accurate external clock only if you need repeatable chopper and StallGuard behaviour across temperature, which is worth remembering if StallGuard thresholds drift as the driver warms up.

Output pins

DIAG is a push-pull output that signals either a StallGuard stall or a driver error, depending on configuration. This is the pin you wire to a Teensy input for sensorless homing; Chapter 8 covers it.

INDEX outputs a pulse each time the microstep counter passes position zero — that is, once per full step boundary in the sine table. It can be reconfigured to output other internal signals. It is useful for verification: count INDEX pulses against the full steps you think you commanded and you have an independent check that the driver is stepping.

7.3 The single-wire UART

The TMC2209's UART is unusual in that it uses one wire for both directions. Both the master and the slave transmit on the same line, taking turns.

To connect it to a Teensy, you have two options. The simple one is to tie the Teensy's TX and RX for that port together through a 1 kΩ resistor and connect the junction to PDN_UART. The resistor prevents the Teensy's driver from fighting the TMC2209's when the driver replies, and the Teensy sees its own transmissions echoed back, which you simply discard. The alternative is to connect TX through a 1 kΩ resistor to PDN_UART and RX directly, which achieves the same thing with slightly cleaner signal integrity.

Multiple drivers share the line. Each is given a distinct address by strapping its MS1 and MS2 pins, and each ignores datagrams addressed to another. Four drivers per UART is the limit, so a six-axis arm needs two UART ports: for example Serial1 carrying J1–J4 at addresses 0–3, and Serial2 carrying J5 and J6 at addresses 0 and 1.

The protocol is simple enough to learn, and when something is not working you will want to read the bytes.

A write is eight bytes: a sync byte of 0x05, the slave address, the register address with its top bit set to indicate a write, four data bytes most-significant first, and a CRC byte.

A read request is four bytes: sync 0x05, slave address, register address with the top bit clear, and CRC. The driver replies with eight bytes: sync 0x05, the master address 0xFF, the register address, four data bytes, and a CRC.

The CRC is an 8-bit CRC with polynomial x⁸ + x² + x + 1, processed least-significant-bit first. The datasheet gives the reference implementation:

uint8_t tmc_crc(const uint8_t *data, uint8_t len) {
  uint8_t crc = 0;
  for (uint8_t i = 0; i < len; i++) {
    uint8_t b = data[i];
    for (uint8_t j = 0; j < 8; j++) {
      if ((crc >> 7) ^ (b & 0x01)) crc = (crc << 1) ^ 0x07;
      else                          crc = (crc << 1);
      b >>= 1;
    }
  }
  return crc;
}

The sync byte's low nibble contains a fixed 0101 pattern that the driver uses for automatic baud-rate detection, so you do not need to configure a baud rate on the driver — it works it out from the sync byte. 115200 baud is a sensible choice and is what most libraries default to.

One very useful register is IFCNT at address 0x02. It counts successfully received write datagrams. Read it before and after a write, and if it did not increment, the write did not land — which settles the question of whether the UART wiring is right.

7.4 The registers you will actually use

The TMC2209 has a lot of registers. These are the ones that matter for a robot arm.

GCONF (0x00) is the global configuration. The bits you care about are i_scale_analog (bit 0 — set to 0 to use the internal reference and ignore VREF), en_spreadcycle (bit 2 — 0 for StealthChop, 1 for SpreadCycle), shaft (bit 3 — inverts motor direction in software, useful when a joint homes the wrong way), index_step (bit 6 — makes INDEX output step pulses instead of the zero-position marker), and mstep_reg_select (bit 7 — set this to take microstep resolution from the register rather than from the MS1/MS2 pins, which you must do when using UART).

GSTAT (0x01) reports reset, driver error, and undervoltage since last read. Reading it clears it. Poll it occasionally — a driver that has quietly reset has lost its configuration.

IFCNT (0x02) is the write counter described above.

IHOLD_IRUN (0x10) packs IHOLD in bits 0–4, IRUN in bits 8–12, and IHOLDDELAY in bits 16–19. This is the register you write to set current.

TPOWERDOWN (0x11) sets the delay before dropping to hold current.

TSTEP (0x12) is read-only and reports the measured time between steps, in units of the internal clock. It is how the chip knows how fast the motor is going, and it is what the velocity thresholds compare against. A large TSTEP means slow.

TPWMTHRS (0x13) is the velocity threshold above which the driver leaves StealthChop for SpreadCycle. Because it is expressed in TSTEP units, larger numbers mean lower crossover speeds. Set it to 0 to disable the automatic switch entirely.

TCOOLTHRS (0x14) is the lower velocity limit for StallGuard and CoolStep. Below this speed — meaning TSTEP greater than TCOOLTHRS — StallGuard is disabled and DIAG will not fire. It exists because StallGuard is unreliable at very low speeds, and it is the setting most often forgotten when sensorless homing does not work.

VACTUAL (0x22) lets you command a constant velocity using the driver's own internal step generator, without sending any STEP pulses at all. It is a useful debugging tool: write a value and the motor spins, which exercises power, wiring, current, and the driver independently of your step-generation code. Set it back to 0 to return control to the STEP pin.

SGTHRS (0x40) is the StallGuard threshold, and SG_RESULT (0x41) is the StallGuard load measurement. Chapter 8 is about these.

COOLCONF (0x42) configures CoolStep.

CHOPCONF (0x6C) holds the chopper configuration, including TOFF in bits 0–3 (which must be non-zero for the driver to operate at all — setting it to 0 is how you disable the driver in software), vsense at bit 17, MRES in bits 24–27 for microstep resolution, and intpol at bit 28 to enable MicroPlyer interpolation.

The MRES encoding is: 0 = 1/256, 1 = 1/128, 2 = 1/64, 3 = 1/32, 4 = 1/16, 5 = 1/8, 6 = 1/4, 7 = 1/2, 8 = full step. Note that it counts down. For the PAROL6's 1/32 microstepping you write MRES = 3.

DRV_STATUS (0x6F) is the diagnostic register, and the one worth polling periodically. It contains the live SG_RESULT in bits 0–9, over-temperature pre-warning otpw at bit 26, over-temperature shutdown ot at bit 25, short-to-ground flags for each coil, short-to-supply flags, open-load flags, a set of temperature threshold flags at 120/143/150/157 °C, the actual current scale CS_ACTUAL in bits 16–20, a stealth flag at bit 30 showing which chopper mode is active, and stst at bit 31 indicating standstill.

PWMCONF (0x70) configures StealthChop. The defaults with pwm_autoscale and pwm_autograd enabled are good; leave them alone unless you have a specific reason.

7.5 A configuration sequence

Bringing up a TMC2209 over UART works best in a fixed order. Power up with EN held high so the driver is disabled. Wait a few milliseconds for the internal regulator to stabilise. Read IFCNT and note it. Write GCONF with i_scale_analog = 0, mstep_reg_select = 1, and StealthChop selected. Read IFCNT again and confirm it incremented; if it did not, fix the wiring before going further. Write CHOPCONF with TOFF non-zero (3 to 5 is typical), MRES = 3 for 1/32, and intpol = 1. Write IHOLD_IRUN with your computed currents. Write TPOWERDOWN. Write TPWMTHRS if you want the SpreadCycle crossover. Read DRV_STATUS and confirm no fault flags. Only then pull EN low to enable the outputs.

Done per driver, with the IFCNT check at each stage, this turns "the arm doesn't move" into a fault you can locate.