What we’re talking about here is implementing two unidirectional communication lines in a device to a single bidirectional communications line on another device.
In the device with two unidirectional communications lines, one is a “TX” line which is always driving an output and one is an “RX” line which is always an input. I don’t know if it’s obvious to people but when the TX line is driving out a value there should never be another driver from another device connected directly to it.
The problem in this case is what happens when the two drivers are outputting different values - the output pin driving a high voltage level is sourcing current to the output pin sinking current to provide the low voltage. This is called “backdriving” and the actual voltage on the line is considered to be “indeterminate” - it is possible, if one driver is much stronger than the other, that one of the output pins will be damaged due to the other pin sourcing/sinking more current than it is designed for.
The most common way to avoid backdriving is to place a resistor between the two outputs. When the outputs are at different voltage levels, a small amount of current will flow, much less than there would be in the case previously where there was no resistor.
This is what was done, back in the day when only Arduinos were available as 3D printer Main Controller Boards, so that the MCU’s “TX” pin would not backdrive the TMC stepper driver’s PDN_UART
pin when the PDN_UART
was driving a value out.
By connecting the MCU’s “RX” pin to the side of the resistor connected to the TMC stepper driver’s PDN_UART
pin, the value received by the “RX” pin would either be “TX” pin’s output (when a command was being sent by the MCU) or from the TMC stepper driver’s PDN_UART
pin when it is sending requested information.
This is illustrated here:
In Cases 1 and 2, the MCU is driving and the Device’s IO pin is in a high impedance (“HighZ”) state so it is not driving the line. The RX pin reads what’s on the TX pin.
Case 3 has both the MCU and the Device are both driving a “High” voltage so, even if there was not a resistor between them, there would be no backdriving.
Case 4 shows what happens when the MCU’s TX pin and the Device’s pin are driving different voltage levels. The actual voltage read at “RX” is what is coming out of the Device’s IO pin and current is passing through the 1k resistor so there is no significant (or possibly damaging) backdriving of the MCU’s TX pin.
You could say the 1k resistor “allows” the Device’s IO pin to override the MCU’s TX pin and you’d get part marks on an electrical engineering exam.
Going back to the question at hand, I took an SKR Pico board, loaded it with Klipper, set up the default printer.cfg
and then commented out the tx_pin:
lines as so (I should note that I brought up the [mcu]
and [printer]
statements to the top of the file and added [include mainsail.cfg]
but made no other changes):
# This file contains common pin mappings for the BIGTREETECH SKR Pico V1.0
# To use this config, the firmware should be compiled for the RP2040 with
# USB communication.
# The "make flash" command does not work on the SKR Pico V1.0. Instead,
# after running "make", copy the generated "out/klipper.uf2" file
# to the mass storage device in RP2040 boot mode
# See docs/Config_Reference.md for a description of parameters.
[include mainsail.cfg]
[mcu]
serial: /dev/serial/by-id/usb-Klipper_rp2040_45503571290FAAA8-if00
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[stepper_x]
step_pin: gpio11
dir_pin: !gpio10
enable_pin: !gpio12
microsteps: 16
rotation_distance: 40
endstop_pin: ^gpio4
position_endstop: 0
position_max: 235
homing_speed: 50
[tmc2209 stepper_x]
uart_pin: gpio9
# tx_pin: gpio8
uart_address: 0
run_current: 0.580
stealthchop_threshold: 999999
[stepper_y]
step_pin: gpio6
dir_pin: !gpio5
enable_pin: !gpio7
microsteps: 16
rotation_distance: 40
endstop_pin: ^gpio3
position_endstop: 0
position_max: 235
homing_speed: 50
[tmc2209 stepper_y]
uart_pin: gpio9
# tx_pin: gpio8
uart_address: 2
run_current: 0.580
stealthchop_threshold: 999999
[stepper_z]
step_pin: gpio19
dir_pin: gpio28
enable_pin: !gpio2
microsteps: 16
rotation_distance: 8
endstop_pin: ^gpio25
position_endstop: 0.0
position_max: 250
[tmc2209 stepper_z]
uart_pin: gpio9
# tx_pin: gpio8
uart_address: 1
run_current: 0.580
stealthchop_threshold: 999999
[extruder]
step_pin: gpio14
dir_pin: !gpio13
enable_pin: !gpio15
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: gpio23
sensor_type: EPCOS 100K B57560G104F
sensor_pin: gpio27
control: pid
pid_Kp: 21.527
pid_Ki: 1.063
pid_Kd: 108.982
min_temp: 0
max_temp: 250
[tmc2209 extruder]
uart_pin: gpio9
# tx_pin: gpio8
uart_address: 3
run_current: 0.650
stealthchop_threshold: 999999
[heater_bed]
heater_pin: gpio21
sensor_type: ATC Semitec 104GT-2
sensor_pin: gpio26
control: pid
pid_Kp: 54.027
pid_Ki: 0.770
pid_Kd: 948.182
min_temp: 0
max_temp: 130
[fan]
pin: gpio17
[heater_fan heatbreak_cooling_fan]
pin: gpio18
[heater_fan controller_fan]
pin: gpio20
[temperature_sensor pico]
sensor_type: temperature_mcu
[neopixel board_neopixel]
pin: gpio24
chain_count: 1
color_order: GRB
initial_RED: 0.3
initial_GREEN: 0.3
initial_BLUE: 0.3
#[bltouch]
#sensor_pin: gpio22
#control_pin: gpio29
#[filament_switch_sensor runout_sensor]
#switch_pin: ^gpio16
and everything works fine.
When researching this, I did find my discussion with Kevin on this in November of '22:
So I did remember correctly - Klipper does not require hardware UARTs for communicating with TMC220x stepper drivers and, by doing so, can act dynamically as an input or an output pin.
In you highlighted screenshot of the Klipper documentation:
I’m noting the “If” at the start of the sentence. “separate receive and transmit lines to communicate” are NOT REQUIRED.
As for what Marlin does - as I said I found one reference indicating that a single line could be used for communications but I also found the same information in the configuration.h and configuration_adv.h files as you put in. Regardless, we’re talking about Klipper here.
So, to net things out, in Klipper only the uart:
line (and no tx_pin:
line) is required in the printer.cfg
for serial communications with TMC stepper drivers, whether there is a separate “uart” line for each TMC stepper driver in the system or if there are multiple TMC stepper drivers, each with a different address.
This should probably go into the Knowledge Base as it’s useful for anybody using a main controller board with TMC220x that are being interfaced with serially or for someone like @Mago3D who is designing their own hardware.