Single-board computer (SBC) gained more and more popularity during the last years due its opensource software and the simple hardware. With these SBC it is possible to develop quickly and easily prototype systems or even full products. The standard Linux distributions have no real-time capabilities. For many projects like a digital weather station, this is not crucial. For motion control, on the other hand, it is mandatory to have real-time access for making clean acceleration ramps, position control and closed-loop operation. All of this is possible with motion control ICs from TRINAMIC without needing any real-time operating system on the SBC.
The project below shows how to do precise autonomous cyclic movements with the TMC4361A without any MCU power. The Raspberry Pi 3 is used to set up the needed registers and from there the TMC4361A takes care of the movement. The used system consists besides the Raspberry Pi 3 of a TMC4361A-BOB and a TMC5160-BOB, which is used a plain pre-driver stage, so the internal motion controller is deactivated. The usage of the TRINAMIC’s API – TMC-API – is minimizing the software effort.
The motor will do alternating exactly 5 rotations at 2.5 RPS and 5 rotations at 5 RPS. The switching points are handled by the TMC4361A in hardware. So there is no need for real-time software capabilities. The following steps need to be done to setup the TMC4361A:
- Basic configuration of TMC4361A for controlling the TMC5160 family in step/dir mode
- Basic configuration of TMC5160 for step/dir mode via cover datagrams
- Activation of circular motion, see chapter 8.5.2. of TMC4361A Datasheet
- Setting up a one stage shadow register set, see chapter 9.2.1 of TMC4361A Datasheet
- Configuring a cyclic pipeline structure, see chapter 9.3.4. and 9.3.5. of TMC4361A Datasheet
Software Preparation
In this tutorial we are using a fresh Raspbian Stretch System (Version November 2017 – Release date 2017-11-29) with the latest updates:
sudo apt update sudo apt upgrade
Download and install the bcm2835 library. Note XXX is a placeholder for the latest version. For this guide, version “1.52” was used. In this case, XXX is replaced by “1.52”. You can check the latest version by scrolling to the bottom of the following page: http://www.airspayce.com/mikem/bcm2835/
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-XXX.tar.gz tar zxvf bcm2835-XXX.tar cd bcm2835-XXX ./configure make sudo make check sudo make install
Create a project folder and download the latest TMC-API. Note X.XX is a placeholder for the latest version. For this guide, version “3.02” was used. In this case, X.XX is replaced by “3.02”. You can check the latest version by scrolling to the bottom of the following page: https://www.trinamic.com/support/software/access-package/
mkdir ~/TMC_EXAMPLE cd ~/TMC_EXAMPLE wget https://www.trinamic.com/fileadmin/assets/Products/Eval_Software/TMC-API_Release_vX.XX.zip unzip TMC-API_Release_vX.XX.zip mv TMC-API_Release_vX.XX TMC-API
Enable step/dir Mode on TMC5160-BOB
The TMC5160-BOB allows the TMC5160 to use in 2 different modes: With integrated motion controller and in step/dir mode. For controlling the TMC5160 by the TMC4361A the TMC5160 has to be in step/dir. On the TMC5160-BOB this can easily be done by desoldering the R2 and soldering it to R1.

Wiring
The wiring is very simple. You will need 18 jumper wires. As a reference, you can use the TMC4361A-BOB_datasheet_Rev1.0.pdf and TMC5160-BOB_datasheet_Rev1.0.pdf.

Rasbperry Pi to TMC4361A-BOB
Signal | Raspberry Pi 3 | TMC4361-BOB |
VCC_IO | 3.3v (1) | VCC_IO (1) |
GND | GND (39) | GND (2) |
TMC4361A CLK | GPIO04 (7) | CLK (3) |
TMC4361A NRST | GPIO15 (10) | NRST (4) |
SPI Chip select (CS) | SPI0 SCLK (23) | CSN (5) |
SPI CLK | SPI0 SCLK (23) | SCK (6) |
MOSI | SPI0 MOSI (19) | SDI (7) |
MISO | SPI0 MISO (21) | SDO (8) |

Rasbperry Pi to TMC5160-BOB
Signal | Raspberry Pi 3 | TMC5160-BOB |
VCC_IO | GPIO02 (3) | VCC_IO (1) |
TMC5160 CLK | GPIO14 (8) | CLK (8) |
TMC5160 DRV_ENN | GPIO03 (5) | DRV_ENN(9) |
TMC4361A-BOB to TMC5160-BOB
Signal | TMC4361A | TMC5160-BOB |
GND | GND (2) | GND (2) |
SPI Chip select (CS) | CSN_DRV (18) | CSN (3) |
SPI CLK | SCK_DRV (19) | SCK (4) |
MOSI | SDI_DRV (21) | SDI (5) |
MISO | SDO_DRV (20) | SDO (6) |
STEP | STPOUT (22) | REFL (10) |
DIR | STPDIR (23) | REFR (11) |

Raspberry Pi Code
An example code to initialize the TMC5160 is shown below. These files need to be placed in the same project folder as the TMC-API, in this case into ~/TMC_EXAMPLE. First, +3.3V is supplied to VCC_IO, the driver stages are enabled by pulling down DRV_ENN and the internal clock is used by pulling down CLK16. Afterwards, the TMC5160 is initialized and a simple move to position cycle is executed. The sequence will rotate a 200 full stepper motor 10 revolutions clockwise and 10 revolutions counterclockwise – depending on the wiring of the stepper motor. Please refer to the TMC5160 datasheet or the TMCL-IDE as a reference for the different registers.
You can also download the source files directly with your Pi:
cd ~/TMC_EXAMPLE wget http://blog.trinamic.com/wp-content/uploads/2018/02/TMC4361A_and_TMC5160_TMCAPI_EXAMPLE.tar.gz tar zxvf TMC5160_TMCAPI_EXAMPLE.tar.gz
#include <stdio.h> #include <wiringPi.h> #include <bcm2835.h> #include <signal.h> #include "SPI_TMC.h" // Include the IC(s) you want to use #include "TMC-API/tmc/ic/TMC5160/TMC5160.h" #include "TMC-API/tmc/ic/TMC4361A/TMC4361A.h" #define MOTOR0 0 // Motor 0 void resetMotorDrivers(uint8 motor); void signal_callback_handler(int signum); int main(int argc, char **argv) { // Register signal and signal handler signal(SIGINT, signal_callback_handler); if (!bcm2835_init()) return 1; wiringPiSetupGpio(); // Initiate SPI bcm2835_spi_begin(); bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // MSB first bcm2835_spi_setDataMode(BCM2835_SPI_MODE3); // SPI Mode 3 bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_256); // 1 MHz clock bcm2835_spi_chipSelect(BCM2835_SPI_CS0); // define CS pin bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // set CS polarity to low /***** IO control of TMC4361A-BOB & TMC5160-BOB *****/ pinMode(2, OUTPUT); digitalWrite(2, HIGH); // TMC5160: Apply VCC_IO voltage pinMode(3, OUTPUT); digitalWrite(3, LOW); // TMC5160: Use internal clock pinMode(14, OUTPUT); digitalWrite(14, LOW); // TMC5160: Enable driver stage pinMode (4, GPIO_CLOCK); gpioClockSet (4, 9600000); // TMC4361A: Set clock to 9.6MHz pinMode(15, OUTPUT); digitalWrite(15, HIGH); // TMC4361A: Low active reset // Reset the TMC4361A & TMC5160 resetMotorDrivers(MOTOR0); /***** Configuring TMC4361A & TMC5160 *****/ // CLK_FREQ=9.6MHz tmc4361A_writeInt(TMC4361A_CLK_FREQ, 0x00927C00); // SR_ENC_IN=2, FILT_L_ENC_IN=2, SR_REF=0, FILT_L_REF=0, SR_S=4, FILT_L_S=5, SR_ENC_OUT=0, FILT_L_ENC_OUT=0 tmc4361A_writeInt(TMC4361A_INPUT_FILT_CONF, 0x00540022); // SPI_OUTPUT_FORMAT=12, DISABLE_POLLING=1, COVER_DATA_LENGTH=0, SPI_OUT_LOW_TIME=4, SPI_OUT_HIGH_TIME=4, SPI_OUT_BLOCK_TIME=8 tmc4361A_writeInt(TMC4361A_SPIOUT_CONF, 0x8440004C); // DIRECT_ACC_VAL_EN=1, DIRECT_BOW_VAL_EN=1, STDBY_CLK_PIN_ASSIGNMENT=3 tmc4361A_writeInt(TMC4361A_GENERAL_CONF, 0x00006006); // STP_LENGTH=4, DIR_SETUP_TIME=6 tmc4361A_writeInt(TMC4361A_STP_LENGTH_ADD, 0x00060004); /***** TMC5160 Settings *****/ // EN_PWM_MODE=1 enables stealthChop, MULTISTEP_FILT=1, DIRECT_MODE=0 (off) tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, TMC5160_GCONF | 0x80); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, 0x0000000C); delay(1); // COVER_DONE flag: ~90µs -> 1 ms more than enough // TOFF=3, HSTRT=4, HEND=1, TBL=2, CHM=0 (spreadCycle
) tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, TMC5160_CHOPCONF | 0x80); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, 0x000100C3); delay(1); // COVER_DONE flag: ~90µs -> 1 ms more than enough // IHOLD=8, IRUN=15 (max. current), IHOLDDELAY=6 tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, TMC5160_IHOLD_IRUN | 0x80); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, 0x00080F0A); delay(1); // COVER_DONE flag: ~90µs -> 1 ms more than enough // TPOWERDOWN=10: Delay before power down in stand still tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, TMC5160_TPOWERDOWN | 0x80); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, 0x0000000A); delay(1); // COVER_DONE flag: ~90µs -> 1 ms more than enough // TPWMTHRS=5000 tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, TMC5160_TPWMTHRS| 0x80); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, 0x00001388); delay(1); // COVER_DONE flag: ~90µs -> 1 ms more than enough /***** END of TMC5160 Settings *****/ //MOTION_PROFILE = 2 (s-shaped ramp), OPERATION_MODE = 0 (velocity mode) tmc4361A_writeInt(TMC4361A_RAMPMODE, TMC4361A_RAMP_SSHAPE); // Values for speed and acceleration of 1st profile tmc4361A_writeInt(TMC4361A_AMAX, 0x00001000); // AMAX = 4096 pps² tmc4361A_writeInt(TMC4361A_DMAX, 0x00001000); // DMAX = 4096 pps² tmc4361A_writeInt(TMC4361A_BOW1, 0x00000200); // BOW1 = 512 pps³ tmc4361A_writeInt(TMC4361A_BOW2, 0x00000200); // BOW2 = 512 pps³ tmc4361A_writeInt(TMC4361A_BOW3, 0x00000200); // BOW3 = 512 pps³ tmc4361A_writeInt(TMC4361A_BOW4, 0x00000200); // BOW4 = 512 pps³ tmc4361A_writeInt(TMC4361A_VMAX, 0x01F40000); // VMAX = 128k pps = 2.5 RPS (1.8° motor) // Values for speed and acceleration of 2nd profile tmc4361A_writeInt(TMC4361A_SH_REG1, 0x00001000); // AMAX = 4096 pps² tmc4361A_writeInt(TMC4361A_SH_REG2, 0x00001000); // DMAX = 4096 pps² tmc4361A_writeInt(TMC4361A_SH_REG8, 0x00000200); // BOW1 = 512 pps³ tmc4361A_writeInt(TMC4361A_SH_REG9, 0x00000200); // BOW2 = 512 pps³ tmc4361A_writeInt(TMC4361A_SH_REG10, 0x00000200); // BOW3 = 512 pps³ tmc4361A_writeInt(TMC4361A_SH_REG11, 0x00000200); // BOW4 = 512 pps³ tmc4361A_writeInt(TMC4361A_SH_REG0, 0x03E80000); // VMAX = 256k pps = 5 RPS (1.8° motor) /***** Setting up the TMC4361A for circular motion *****/ // START_EN = 8, TRIGGER_EVENTS = 8, PIPELINE_EN = 2, CYCLIC_SHADOW_REGS = 1, XPIPE_REWRITE_REG = 1 tmc4361A_writeInt(TMC4361A_START_CONF, 0x01042110); // XRANGE = 256k (10 rotations) tmc4361A_writeInt(TMC4361A_X_RANGE_WR, 0x0003E800); // CIRCULAR_MOVEMENT_EN = 1 tmc4361A_writeInt(TMC4361A_REFERENCE_CONF, 0x00400000); // POS_COMP = -256k (5 rotations) tmc4361A_writeInt(TMC4361A_POS_COMP, 0xFFFC1800); // 1st pipeline stage of POS_COMP = 0 tmc4361A_writeInt(TMC4361A_X_PIPE0, 0x00000000); printf("Press \"CTRL + C\" to stop the motor(s) and to close the program\n"); delay(1); while(1){} // End SPI communication bcm2835_spi_end(); bcm2835_close(); return 0; } void resetMotorDrivers(uint8 motor) { if(!tmc4361A_readInt(TMC4361A_VACTUAL)) { digitalWrite(2, LOW); // TMC5160: Reset delay(10); digitalWrite(2, HIGH); delay(10); digitalWrite(15, LOW); // TMC4361A Reset delay(10); digitalWrite(15, HIGH); delay(10); tmc4361A_writeInt(TMC4361A_RESET_REG, 0x52535400); } } void signal_callback_handler(int signum) { tmc4361A_writeInt(TMC4361A_VMAX, 0x00000000); // VMAX = 0 pps digitalWrite(14, HIGH); // TMC5160: Disable driver stage // Exit program exit(signum); }
/* * SPI_TMC.h * * Created on: 12.01.2018 * Author: MN */ #ifndef SPI_TMC_H #define SPI_TMC_H #include "TMC-API/tmc/helpers/API_Header.h" void initSPI(void); // TMC5160 SPI wrapper void tmc5160_writeDatagram(uint8 motor, uint8 address, uint8 x1, uint8 x2, uint8 x3, uint8 x4); void tmc5160_writeInt(uint8 motor, uint8 address, int value); int tmc5160_readInt(u8 motor, uint8 address); // TMC4361A SPI wrapper void tmc4361A_writeDatagram(uint8 address, uint8 x1, uint8 x2, uint8 x3, uint8 x4); void tmc4361A_writeInt(uint8 address, int value); int tmc4361A_readInt(uint8 address); unsigned char tmc4361A_cover(unsigned char data, unsigned char lastTransfer); // General SPI functions void tmc40bit_writeInt(u8 motor, uint8 address, int value); int tmc40bit_readInt(u8 motor, uint8 address); #endif /* SPI_TMC_H */
#include <bcm2835.h> #include "SPI_TMC.h" #include "TMC-API/tmc/ic/TMC4361A/TMC4361A_Register.h" // TMC5160 SPI wrapper void tmc5160_writeDatagram(uint8 motor, uint8 address, uint8 x1, uint8 x2, uint8 x3, uint8 x4) { int value = x1; value <<= 8; value |= x2; value <<= 8; value |= x3; value <<= 8; value |= x4; tmc40bit_writeInt(motor, address, value); } void tmc5160_writeInt(uint8 motor, uint8 address, int value) { tmc40bit_writeInt(motor, address, value); } int tmc5160_readInt(u8 motor, uint8 address) { tmc40bit_readInt(motor, address); return tmc40bit_readInt(motor, address); } // TMC4361 SPI wrapper void tmc4361A_writeDatagram(uint8 address, uint8 x1, uint8 x2, uint8 x3, uint8 x4) { int value = x1; value <<= 8; value |= x2; value <<= 8; value |= x3; value <<= 8; value |= x4; tmc40bit_writeInt(0, address, value); } void tmc4361A_writeInt(uint8 address, int value) { tmc40bit_writeInt(0, address, value); } int tmc4361A_readInt(uint8 address) { tmc40bit_readInt(0, address); return tmc40bit_readInt(0, address); } unsigned char tmc4361A_cover(unsigned char data, unsigned char lastTransfer) { static uint64 coverIn = 0; // read from squirrel static uint64 coverOut = 0; // write to squirrel static uint8 coverLength = 0; // data to be written uint8 out = 0; // return value of this function // buffer outgoing data coverOut <<= 8; // shift left by one byte to make room for the next byte coverOut |= data; // add new byte to be written coverLength++; // count outgoing bytes // return read and buffered byte to be returned out = coverIn >> 56; // output last received byte coverIn <<= 8; // shift by one byte to read this next time if(lastTransfer) { /* Write data to cover register(s). The lower 4 bytes go into the cover low register, * the higher 4 bytes, if present, go into the cover high register. * The datagram needs to be sent twice, otherwise the read buffer will be delayed by * one read/write datagram. */ // Send the buffered datagram & wait a bit before continuing so the 4361 can complete the datagram to the driver // measured delay between COVER_LOW transmission and COVER_DONE flag: ~90�s -> 1 ms more than enough // todo CHECK 3: Delay measurement only done on TMC4361, not 4361A - make sure the required delay didnt change (LH) #1 if(coverLength > 4) tmc4361A_writeInt(TMC4361A_COVER_HIGH_WR, coverOut >> 32); tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, coverOut & 0xFFFFFFFF); delay(1); // Trigger a re-send by writing the low register again tmc4361A_writeInt(TMC4361A_COVER_LOW_WR, coverOut & 0xFFFFFFFF); // Read the reply coverIn = 0; if(coverLength > 4) coverIn |= (uint64) tmc4361A_readInt(TMC4361A_COVER_DRV_HIGH_RD) << 32; coverIn |= tmc4361A_readInt(TMC4361A_COVER_DRV_LOW_RD); coverIn <<= (8-coverLength) * 8; // Shift the highest byte of the reply to the highest byte of the buffer uint64 // Clear write buffer coverOut = 0; coverLength=0; } return out; // return buffered read byte } // General SPI decription void tmc40bit_writeInt(u8 motor, uint8 address, int value) { char tbuf[5]; tbuf[0] = address | 0x80; tbuf[1] = 0xFF & (value>>24); tbuf[2] = 0xFF & (value>>16); tbuf[3] = 0xFF & (value>>8); tbuf[4] = 0xFF & value; bcm2835_spi_writenb (tbuf, 5); } int tmc40bit_readInt(u8 motor, uint8 address) { char tbuf[5], rbuf[5]; int value; // clear write bit tbuf[0] = address & 0x7F; bcm2835_spi_transfernb (tbuf, rbuf, 5); value =rbuf[1]; value <<= 8; value |= rbuf[2]; value <<= 8; value |= rbuf[3]; value <<= 8; value |= rbuf[4]; return value; }
TARGET_EXEC ?= TMCAPI_EXAMPLE BUILD_DIR ?= ./bin CC = gcc CXX = g++ # C Fags CFLAGS += -Wall CFLAGS += -g LDFLAGS += -lbcm2835 LDFLAGS += -lwiringPi # define the C source files SRCS += main.c SRCS += SPI_TMC.c # used functions from TMC_API SRCS += TMC-API/tmc/helpers/Debug.c #SRCS += TMC-API/tmc/ic/TMC2130/TMC2130.c #SRCS += TMC-API/tmc/ic/TMC2208/TMC2208.c #SRCS += TMC-API/tmc/ic/TMC2224/TMC2224.c #SRCS += TMC-API/tmc/ic/TMC2660/TMC2660.c #SRCS += TMC-API/tmc/ic/TMC5130/TMC5130.c SRCS += TMC-API/tmc/ic/TMC5160/TMC5160.c #SRCS += TMC-API/tmc/ic/TMC4330/TMC4330.c #SRCS += TMC-API/tmc/ic/TMC4331/TMC4331.c #SRCS += TMC-API/tmc/ic/TMC4361/TMC4361.c SRCS += TMC-API/tmc/ic/TMC4361A/TMC4361A.c #SRCS += TMC-API/tmc/ic/TMC4670/TMC4670.c #SRCS += TMC-API/tmc/ic/TMC4671/TMC4671.c #SRCS += TMC-API/tmc/ic/TMC4672/TMC4672.c #SRCS += TMC-API/tmc/ic/TMCC160/TMCC160.c #SRCS += TMC-API/tmc/ic/TMC5041/TMC5041.c #SRCS += TMC-API/tmc/ic/TMC5062/TMC5062.c #SRCS += TMC-API/tmc/ic/TMC5072/TMC5072.c OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) DEPS := $(OBJS:.o=.d) $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) $(CC) $(OBJS) -o $@ $(LDFLAGS) # assembly $(BUILD_DIR)/%.s.o: %.s $(MKDIR_P) $(dir $@) $(AS) $(ASFLAGS) -c $< -o $@ $(LDFLAGS) # c source $(BUILD_DIR)/%.c.o: %.c $(MKDIR_P) $(dir $@) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(LDFLAGS) # c++ source $(BUILD_DIR)/%.cpp.o: %.cpp $(MKDIR_P) $(dir $@) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ $(LDFLAGS) .PHONY: clean clean: $(RM) -r $(BUILD_DIR) -include $(DEPS) MKDIR_P ?= mkdir -p
Compiling and running the code
Use the following command to compile the code.
cd ~/TMC_EXAMPLE make
Now you are able to execute this example.
sudo ~/TMCAPI_test/bin/TMCAPI_EXAMPLE
Be aware that the motor runs as soon as you execute the program.