TMC2226 UART communication with ESP32-WROOM-32E (Stallguard, Stealthchop)

I'm trying to get the UART communication between my TMC2226 and my ESP32 working so that I can implement Stealthchop and Stallguard in my system. Before I start trying to do too much coding, I want to make sure the hardware is wired sufficiently, so I know it can work. Here are the connections on the current PCB (I have one ESP32 and two TMC2226's):

The following pins on the TMC2226 are connected to their own GPIO pins on the ESP32

  • ENN
  • DIR
  • STEP
  • DIAG
  • INDEX

Now here's where things get weird, the RX pin (on the esp32) is connected to the PDN_UART pin on both TMC 2226 drivers. And the TX pin (on the esp32) is connected to the RX pin through a 1kOhm resistor.


This seems to correlate with this image from the TMC2226 datasheet:

I'm only concerned that this may be problematic because there's a comment in the pin definitions code that says:

#define Z_MOTOR_DIAG_PIN			GPIO_NUM_39  // Don't use this as the X and Z diag pins are tied together.

Now, this doesn't make sense because the DIAG pins are not tied together, but maybe they were referring to the pdn_uart pins which are tied together.

Right now, the code is failing to get a UART connection between the chip and drivers, returning the message:

Thu Jan  1 00:00:07 1970 - This UART controller (1) is all ready installed
uart status = -1
I (7399) gpio: GPIO[21]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
E (7409) uart: uart_write_bytes(1281): uart_num error
E (7419) uart: uart_wait_tx_done(1151): uart_num error
Thu Jan  1 00:00:07 1970 - Wrote -1 bytes  with wait status -1
[0]0x55  [1]0x0  [2]0x80  [3]0x0  [4]0x0  [5]0x1  [6]0x41  [7]0x2b  
E (17449) uart: uart_write_bytes(1281): uart_num error
E (17449) uart: uart_wait_tx_done(1151): uart_num error

Here is the code that initializes the tmc2226:

Motor_Driver.h

#define TCM2226_ENABLE_PIN		GPIO_NUM_25



class TMC2226
{
	public:
    CoreLib::GpioOutput& GetEnableOutput() {
        return enable_output;
    }

    void SetEnableOutputState(bool state) {
        if (state)
            enable_output.on();
        else
            enable_output.off();
    }
	public:
		void writeRegister(uint8_t address, uint32_t value);
		/**
		 * Creates a TMC 2226 motor driver
		 * Default Constructor, uses the following options.
		 *
		 * UART_NUM_1 as serial interface 	\n
		 * @param tmc_rx_pin The pin to be used for receiving data from TMC2226
		 * @param tmc_tx_pin The pin to be used for transmitting data to TMC2226
		 * @param tmc_enable_pin The pin to be used for enabling/disabling the TMC2226
		 * @param tmc_dev_addr The device address to be used when communicating with the TMC2226
		 * @see TMC2226(int uart_num, int rx_pin, int tx_pin)
		 */
		TMC2226(int tmc_tx_pin, int tmc_rx_pin, gpio_num_t tmc_enable_pin, uint8_t tmc_dev_addr, CoreLib::Logger *tmc_logger);

		/**
		 * Creates a TMC 2226 motor driver
		 * @brief Constructor with level.
		 * @param tmc_uart_num the UART to use for communications with the TMC2226
		 * @param tmc_rx_pin The pin to be used for receiving data from TMC2226
		 * @param tmc_tx_pin The pin to be used for transmitting data to TMC2226
		 * @param tmc_enable_pin The pin to be used for enabling/disabling the TMC2226
		 * @param tmc_dev_addr The device address to be used when communicating with the TMC2226
		 *
		 */
		TMC2226(uart_port_t tmc_uart_num, int tmc_tx_pin, int tmc_rx_pin, gpio_num_t tmc_enable_pin, uint8_t tmc_dev_addr, CoreLib::Logger *tmc_logger);


		int SendDatagram(uint8_t reg_addr, uint32_t value);
		int SendDatagram(TMC2226_DATAGRAM &send_datagram);
		int SendReqDatagram(uint8_t reg_addr);
		int SendRegDatagram(TMC2226_DATAGRAM &req_datagram);

		int ReceiveDataGram(TMC2226_DATAGRAM &recv_datagram);

		TMC2226_DATAGRAM GetVersion(void);

		/**
		 * @brief Set logger
		 * @param logger_ptr A function pointer to the logger for this state machine
		 */
		void SetErrorLogger(CoreLib::Logger *logger_ptr) { logger = logger_ptr; }


	private:
		CoreLib::UART *tmc_uart;
		CoreLib::GpioOutput enable_output;
		uart_port_t uart_num;
		int rx_pin;
		int tx_pin;
		gpio_num_t enable_pin;
		uint8_t dev_addr;

		void Init(void);
		_tmc2226_datagram rx_datagram;
		_tmc2226_datagram tx_datagram;
		void BuildDatagram(uint8_t addr, uint32_t value, uint8_t crc);

		CoreLib::Error *error = { nullptr };
		CoreLib::Logger *logger = { nullptr };
};

Motor_Driver.cpp

TMC2226::TMC2226(int tmc_tx_pin, int tmc_rx_pin, gpio_num_t tmc_enable_pin, uint8_t tmc_dev_addr, Logger *tmc_logger) {
	uart_num = UART_NUM_1;
	rx_pin = tmc_rx_pin;
	tx_pin = tmc_tx_pin;
	enable_pin = tmc_enable_pin;
	dev_addr = tmc_dev_addr;
	logger = tmc_logger;
	Init();

}

TMC2226::TMC2226(uart_port_t tmc_uart_num, int tmc_tx_pin, int tmc_rx_pin, gpio_num_t tmc_enable_pin, uint8_t tmc_dev_addr, Logger *tmc_logger) {
	uart_num = tmc_uart_num;
	rx_pin = tmc_rx_pin;
	tx_pin = tmc_tx_pin;
	enable_pin = tmc_enable_pin;
	dev_addr = tmc_dev_addr;
	logger = tmc_logger;
	Init();
}

void TMC2226::Init(void) {
	int status;

	// Set up the UART for communication with the device
//	tmc_uart = UART(uart_num, tx_pin, rx_pin, 1024, 1024, status, logger);
//	printf ("uart status %d\n", status);

	tmc_uart = new UART(UART_NUM_1, 17, 18, 1024, 1024, status, logger);

	printf ("uart status = %d\n", status);
#ifdef NODEF
	int write_ret_val;
	static const char *buffer = "A write to UART 0\r\n";
    write_ret_val = tmc_uart->Write(buffer, strlen(buffer));
	printf ("Wrote %d bytes to UART\n", write_ret_val);
#endif

	// TMC2225 enable is active low so indicate that now and enable device
	enable_output = CoreLib::GpioOutput { enable_pin, true};
	enable_output.on();

	// initialize the CRC tables
	tmc_fillCRC8Table(0x07, true, 1);
}

int TMC2226::SendDatagram(uint8_t reg_addr, uint32_t value) {
	int status = 0;

	tx_datagram.all_fields.sync = DATAGRAM_SYNC;
	tx_datagram.request_fields.reserved = DATAGRAM_SYNC;
	tx_datagram.all_fields.dev_addr = dev_addr;
	tx_datagram.all_fields.reg_addr = reg_addr | TMC2226_WRITE_REG;

	tx_datagram.data_array[3] = value >> 24;
	tx_datagram.data_array[4] = value >> 16;
	tx_datagram.data_array[5] = value >> 8;
	tx_datagram.data_array[6] = value;

	tx_datagram.all_fields.crc = GitHubcalcCRC(tx_datagram.data_array, DATAGRAM_DATARRAY_BYTES_TO_CRC);

	status  = tmc_uart->Write(tx_datagram.data_array, sizeof(tx_datagram.data_array));
	esp_err_t wait_status = tmc_uart->WaitForTX(pdMS_TO_TICKS(10));
	if (logger != nullptr) {
		logger->Log(Logger::LOG_LEVEL_INFO, "Wrote %d bytes  with wait status %d\n", status, wait_status);
		logger->SetUseTimeStamp(false);
		for (int i = 0; i < sizeof(tx_datagram.all_fields); i++) {
			logger->Log(Logger::LOG_LEVEL_INFO, "[%d]0x%x  ", i, tx_datagram.data_array[i], status);
		}
		logger->Log(Logger::LOG_LEVEL_INFO, "\n");
	}
	vTaskDelay(pdMS_TO_TICKS(5));
	return status;
}

int TMC2226::SendDatagram(TMC2226_DATAGRAM &send_datagram){
	int status = 0;

	tx_datagram.all_fields.sync = DATAGRAM_SYNC;
	tx_datagram.request_fields.reserved = DATAGRAM_SYNC;
	tx_datagram.all_fields.dev_addr = dev_addr;
	tx_datagram.all_fields.reg_addr |= TMC2226_WRITE_REG;
	tx_datagram = send_datagram;
	tx_datagram.all_fields.crc = tmc_CRC8(tx_datagram.data_array, DATAGRAM_DATARRAY_BYTES_TO_CRC, 1);
	status  = tmc_uart->Write(tx_datagram.data_array, sizeof(tx_datagram.data_array));
	esp_err_t wait_status = tmc_uart->WaitForTX(pdMS_TO_TICKS(10));
	if (logger != nullptr) {
		logger->Log(Logger::LOG_LEVEL_INFO, "Wrote %d bytes  with wait status %d\n", status, wait_status);
		logger->SetUseTimeStamp(false);
		for (int i = 0; i < sizeof(tx_datagram.all_fields); i++) {
			logger->Log(Logger::LOG_LEVEL_INFO, "[%d]0x%x  ", i, tx_datagram.data_array[i], status);
		}
		logger->Log(Logger::LOG_LEVEL_INFO, "\n");
	}
	vTaskDelay(pdMS_TO_TICKS(5));
	return status;
}


int TMC2226::SendReqDatagram(uint8_t reg_addr) {
	int status = 0;

	tx_datagram.request_fields.sync = DATAGRAM_SYNC;
	tx_datagram.request_fields.reserved = DATAGRAM_SYNC;
	tx_datagram.request_fields.dev_addr = dev_addr;
//	tx_datagram.request_fields.reg_addr = reg_addr | TMC2226_WRITE_REG;
	tx_datagram.request_fields.reg_addr = reg_addr;
//	tx_datagram.request_fields.crc = tmc_CRC8(tx_datagram.data_array, sizeof(tx_datagram.request_fields), 1);
	CalcCRC(tx_datagram.data_array, sizeof(tx_datagram.request_fields));
	status  = tmc_uart->Write(tx_datagram.data_array, sizeof(sizeof(tx_datagram.request_fields)));
	esp_err_t wait_status = tmc_uart->WaitForTX(pdMS_TO_TICKS(10));
	if (logger != nullptr) {
		logger->Log(Logger::LOG_LEVEL_INFO, "Wrote %d bytes  with wait status %d\n", status, wait_status);
		logger->SetUseTimeStamp(false);
		for (int i = 0; i < sizeof(tx_datagram.request_fields); i++) {
			logger->Log(Logger::LOG_LEVEL_INFO, "[%d]0x%x  ", i, tx_datagram.data_array[i], status);
		}
		logger->Log(Logger::LOG_LEVEL_INFO, "\n");
	}
	vTaskDelay(pdMS_TO_TICKS(5));
	return status;
}

int TMC2226::SendRegDatagram(TMC2226_DATAGRAM &req_datagram) {
	int status = 0;

	tx_datagram.request_fields.sync = DATAGRAM_SYNC;
	tx_datagram.request_fields.reserved = DATAGRAM_SYNC;
	tx_datagram.request_fields.dev_addr = dev_addr;
	tx_datagram.request_fields.reg_addr |= TMC2226_WRITE_REG;
//	tx_datagram.request_fields.crc = tmc_CRC8(tx_datagram.data_array, sizeof(tx_datagram.request_fields), 1);
	CalcCRC(tx_datagram.data_array, sizeof(tx_datagram.request_fields));
	status  = tmc_uart->Write(tx_datagram.data_array, sizeof(tx_datagram.request_fields));
	esp_err_t wait_status = tmc_uart->WaitForTX(pdMS_TO_TICKS(10));
	if (logger != nullptr) {
		logger->Log(Logger::LOG_LEVEL_INFO, "Wrote %d bytes  with wait status %d\n", status, wait_status);
		logger->SetUseTimeStamp(false);
		for (int i = 0; i < sizeof(tx_datagram.request_fields); i++) {
			logger->Log(Logger::LOG_LEVEL_INFO, "[%d]0x%x  ", i, tx_datagram.data_array[i], status);
		}
		logger->Log(Logger::LOG_LEVEL_INFO, "\n");
	}

	return status;
}

int TMC2226::ReceiveDataGram(TMC2226_DATAGRAM &recv_datagram){
	int status = 0;

	status = tmc_uart->Read(recv_datagram.data_array, sizeof(rx_datagram.all_fields), tmc_uart->DelayInMS(100));
	if (logger != nullptr) {
		logger->Log(Logger::LOG_LEVEL_INFO, "Read %d bytes\n", status);
	}
//	recv_datagram = rx_datagram;
	// TODO check CRC
	return status;
}

TMC2226_DATAGRAM TMC2226::GetVersion(void) {
	int status = 0;

	// Request the data for the version which is in the IOIN register
	status = SendReqDatagram(TMC2226_IOIN);
	tmc_uart->FlushRxBuffer();

	// Give the device a chance to respond
	vTaskDelay(pdMS_TO_TICKS(5));

	// Get the register data
	memset(rx_datagram.data_array, 0, sizeof(rx_datagram.data_array));

	status = ReceiveDataGram(rx_datagram);
	printf("GetVersion status = %d   version = 0x%x\n", status, (uint32_t)rx_datagram.all_fields.sync);

	return rx_datagram;

}

maincode.cpp:

#ifdef SERIAL_MOTOR_CONTROL
	logger.SetLogLevel(Logger::LOG_LEVEL_VERBOSE);
	TMC2226 tmc2226_serial_control (UART_NUM_0, (gpio_num_t)18, (gpio_num_t)17, (gpio_num_t)21, 0, &logger);
	tmc2226_serial_control.SendDatagram(0x00, 0x00000141UL);
	while(1) {
		vTaskDelay(pdMS_TO_TICKS(5000));
		tmc2226_serial_control.GetVersion();
	}
#endif

Please provide a link to the documentation you are using for your device so we don't have to guess!

It says I can't upload attachments, but here is the datasheet for the TMC2226:

Not sure what else I can provide, It's just a basic tmc2226 driver, not the EVAL or BOB or BIGTREETECH versions. It's just the driver on a custom pcb.

Is this the very first test of the custom circuit board? Was the design tested before the board was created?

I'm not sure what you're asking. The custom circuit board has been in use for a while, but not with uart completely configured. The only difference is now I'm trying to get a uart connection going between the TMC2226 and esp32 on the existing board. So I guess the "design" hasn't been tested yet.

UPDATE:

I have been able to successfully read and write to driver registers. I believe the change I made that caused it to work was deleting the UART driver right before initializing it. Still working on tuning Stallguard as that is proving to be a bit of a challenge.

The DIAG pin does start at zero when there is no load on the motor, but instead of a consistent HIGH value when stalled, it jumps around and rarely gets to a full 3.33V.

How are you monitoring the DIAG pin? The datasheet says "DIAG is pulsed by StallGuard, when SG_RESULT falls below SGTHRS." I interpret that as a transient HIGH signal, however it may not stay HIGH, which is what people seem to expect.

I think the correct way to handle DIAG is to interrupt on pin change going HIGH, and stop sending STEP pulses.

Yes I think you're correct. I was monitoring first with a multimeter on the DIAG pin and getting steady values around 2 Volts when stalled. But now I have it connecting to a GPIO on my CPU. And even when reading the analog signal, it is only reading zero or 100%.