HF-TMC9660 Driver 0.1.0-dev
Hardware Agnostic C++ Driver for the TMC9660
Loading...
Searching...
No Matches
esp32_tmc9660_bus.hpp
Go to the documentation of this file.
1
14#pragma once
15
17#include "esp_log.h"
18
19using namespace tmc9660;
20#include "driver/spi_master.h"
21#include "driver/uart.h"
22#include "driver/gpio.h"
23#include "freertos/FreeRTOS.h"
24#include "freertos/task.h"
25#include "esp_rom_sys.h"
26#include <array>
27#include <cstdarg>
28#include <cstring>
29#include <cstdint>
30#include <memory>
31
32static const char* BUS_TAG = "TMC9660_Bus";
33
40class Esp32Tmc9660SpiBus : public SpiCommInterface<Esp32Tmc9660SpiBus> {
41public:
56 Esp32Tmc9660SpiBus(spi_host_device_t host,
57 gpio_num_t mosi_pin,
58 gpio_num_t miso_pin,
59 gpio_num_t sclk_pin,
60 gpio_num_t cs_pin,
61 gpio_num_t rst_pin,
62 gpio_num_t drv_en_pin,
63 gpio_num_t faultn_pin,
64 gpio_num_t wake_pin,
65 uint32_t clock_speed_hz = 10000000,
66 uint8_t mode = 0) noexcept
67 : SpiCommInterface<Esp32Tmc9660SpiBus>(true, true, false, false), // RST: HIGH, DRV_EN: HIGH, WAKE: LOW, FAULTN: LOW
68 host_(host), mosi_pin_(mosi_pin), miso_pin_(miso_pin),
69 sclk_pin_(sclk_pin), cs_pin_(cs_pin), rst_pin_(rst_pin),
70 drv_en_pin_(drv_en_pin), faultn_pin_(faultn_pin), wake_pin_(wake_pin),
71 clock_speed_hz_(clock_speed_hz), mode_(mode), device_handle_(nullptr),
72 initialized_(false) {
73 }
74
80 }
81
86 bool initialize() noexcept {
87 if (initialized_) {
88 return true;
89 }
90
91 // Configure GPIO pins
92 if (!configureGpioPins()) {
93 ESP_LOGE(BUS_TAG, "Failed to configure GPIO pins");
94 return false;
95 }
96
97 // Configure SPI bus
98 spi_bus_config_t bus_config = {};
99 bus_config.mosi_io_num = mosi_pin_;
100 bus_config.miso_io_num = miso_pin_;
101 bus_config.sclk_io_num = sclk_pin_;
102 bus_config.quadwp_io_num = -1;
103 bus_config.quadhd_io_num = -1;
104 bus_config.max_transfer_sz = 8; // TMC9660 uses 8-byte transfers
105 bus_config.flags = SPICOMMON_BUSFLAG_MASTER;
106
107 esp_err_t ret = spi_bus_initialize(host_, &bus_config, SPI_DMA_CH_AUTO);
108 if (ret != ESP_OK) {
109 ESP_LOGE(BUS_TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
110 return false;
111 }
112
113 // Configure SPI device
114 spi_device_interface_config_t dev_config = {};
115 dev_config.clock_speed_hz = clock_speed_hz_;
116 dev_config.mode = mode_;
117 dev_config.spics_io_num = cs_pin_;
118 dev_config.queue_size = 7;
119 dev_config.command_bits = 0;
120 dev_config.address_bits = 0;
121 dev_config.dummy_bits = 0;
122 dev_config.duty_cycle_pos = 128;
123 dev_config.cs_ena_pretrans = 2;
124 dev_config.cs_ena_posttrans = 2;
125 dev_config.flags = 0;
126 dev_config.input_delay_ns = 0;
127 dev_config.pre_cb = nullptr;
128 dev_config.post_cb = nullptr;
129
130 ret = spi_bus_add_device(host_, &dev_config, &device_handle_);
131 if (ret != ESP_OK) {
132 ESP_LOGE(BUS_TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
133 spi_bus_free(host_);
134 return false;
135 }
136
137 initialized_ = true;
138 ESP_LOGI(BUS_TAG, "SPI interface initialized successfully");
139 return true;
140 }
141
146 bool deinitialize() noexcept {
147 if (!initialized_) {
148 return true;
149 }
150
151 if (device_handle_) {
152 spi_bus_remove_device(device_handle_);
153 device_handle_ = nullptr;
154 }
155
156 spi_bus_free(host_);
157 initialized_ = false;
158 ESP_LOGI(BUS_TAG, "SPI interface deinitialized");
159 return true;
160 }
161
168 bool spiTransferTMCL(std::array<uint8_t, 8> &tx, std::array<uint8_t, 8> &rx) noexcept {
169 if (!initialized_ || !device_handle_) {
170 ESP_LOGE(BUS_TAG, "SPI interface not initialized");
171 return false;
172 }
173
174 spi_transaction_t trans = {};
175 trans.length = 64; // 8 bytes * 8 bits
176 trans.tx_buffer = tx.data();
177 trans.rx_buffer = rx.data();
178
179 esp_err_t ret = spi_device_transmit(device_handle_, &trans);
180 if (ret != ESP_OK) {
181 ESP_LOGE(BUS_TAG, "SPI transfer failed: %s", esp_err_to_name(ret));
182 return false;
183 }
184
185 return true;
186 }
187
188 bool spiTransferBootloader(std::array<uint8_t, 5> &tx, std::array<uint8_t, 5> &rx) noexcept {
189 if (!initialized_ || !device_handle_) {
190 ESP_LOGE(BUS_TAG, "SPI interface not initialized");
191 return false;
192 }
193
194 spi_transaction_t trans = {};
195 trans.length = 40; // 5 bytes * 8 bits
196 trans.tx_buffer = tx.data();
197 trans.rx_buffer = rx.data();
198
199 esp_err_t ret = spi_device_transmit(device_handle_, &trans);
200 if (ret != ESP_OK) {
201 ESP_LOGE(BUS_TAG, "SPI 5-byte transfer failed: %s", esp_err_to_name(ret));
202 return false;
203 }
204
205 return true;
206 }
207
212 CommMode mode() const noexcept {
213 return CommMode::SPI;
214 }
215
222 bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept {
223 gpio_num_t gpio_pin = getGpioPin(pin);
224 if (gpio_pin == GPIO_NUM_NC) {
225 ESP_LOGE(BUS_TAG, "Invalid GPIO pin for TMC9660 control pin");
226 return false;
227 }
228
229 // Convert signal state to physical GPIO level using base class helper
230 uint32_t gpio_level = signalToGpioLevel(pin, signal) ? 1 : 0;
231
232 esp_err_t ret = gpio_set_level(gpio_pin, gpio_level);
233 if (ret != ESP_OK) {
234 ESP_LOGE(BUS_TAG, "Failed to set GPIO level: %s", esp_err_to_name(ret));
235 return false;
236 }
237 return true;
238 }
239
246 bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept {
247 gpio_num_t gpio_pin = getGpioPin(pin);
248 if (gpio_pin == GPIO_NUM_NC) {
249 ESP_LOGE(BUS_TAG, "Invalid GPIO pin for TMC9660 control pin");
250 return false;
251 }
252
253 int gpio_level = gpio_get_level(gpio_pin);
254 if (gpio_level < 0) {
255 ESP_LOGE(BUS_TAG, "Failed to read GPIO level");
256 return false;
257 }
258 // Convert physical GPIO level to signal state using base class helper
259 signal = gpioLevelToSignal(pin, gpio_level == 1);
260 return true;
261 }
262
270 void debugLog(int level, const char* tag, const char* format, va_list args) noexcept {
271 // Route to appropriate ESP-IDF log level
272 switch (level) {
273 case 0: // Error
274 esp_log_writev(ESP_LOG_ERROR, tag, format, args);
275 break;
276 case 1: // Warning
277 esp_log_writev(ESP_LOG_WARN, tag, format, args);
278 break;
279 case 2: // Info
280 esp_log_writev(ESP_LOG_INFO, tag, format, args);
281 break;
282 case 3: // Debug
283 esp_log_writev(ESP_LOG_DEBUG, tag, format, args);
284 break;
285 case 4: // Verbose
286 esp_log_writev(ESP_LOG_VERBOSE, tag, format, args);
287 break;
288 default:
289 esp_log_writev(ESP_LOG_INFO, tag, format, args);
290 break;
291 }
292 }
293
294 void delayMs(uint32_t ms) noexcept {
295 vTaskDelay(pdMS_TO_TICKS(ms));
296 }
297
298 void delayUs(uint32_t us) noexcept {
299 // ESP32: Use esp_rom_delay_us for accurate microsecond delays
300 // For FreeRTOS tasks, convert >= 1ms delays to task delays to avoid blocking
301 if (us >= 1000) {
302 // For >= 1ms, use task delay (non-blocking)
303 vTaskDelay(pdMS_TO_TICKS((us + 999) / 1000));
304 } else {
305 // For < 1ms, use busy-wait delay (accurate but blocks)
306 esp_rom_delay_us(us);
307 }
308 }
309
310private:
315 bool configureGpioPins() noexcept {
316 // Configure control pins as outputs
317 gpio_config_t output_config = {};
318 output_config.intr_type = GPIO_INTR_DISABLE;
319 output_config.mode = GPIO_MODE_OUTPUT;
320 output_config.pin_bit_mask = (1ULL << rst_pin_) | (1ULL << drv_en_pin_) | (1ULL << wake_pin_);
321 output_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
322 output_config.pull_up_en = GPIO_PULLUP_DISABLE;
323
324 esp_err_t ret = gpio_config(&output_config);
325 if (ret != ESP_OK) {
326 ESP_LOGE(BUS_TAG, "Failed to configure output GPIO pins: %s", esp_err_to_name(ret));
327 return false;
328 }
329
330 // Configure status pin as input
331 gpio_config_t input_config = {};
332 input_config.intr_type = GPIO_INTR_DISABLE;
333 input_config.mode = GPIO_MODE_INPUT;
334 input_config.pin_bit_mask = (1ULL << faultn_pin_);
335 input_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
336 input_config.pull_up_en = GPIO_PULLUP_ENABLE; // Enable pull-up for open-drain FAULTN
337
338 ret = gpio_config(&input_config);
339 if (ret != ESP_OK) {
340 ESP_LOGE(BUS_TAG, "Failed to configure input GPIO pin: %s", esp_err_to_name(ret));
341 return false;
342 }
343
344 // Set initial states
345 gpio_set_level(rst_pin_, 0); // RST active HIGH, start with inactive (low)
346 gpio_set_level(drv_en_pin_, 0); // DRV_EN active HIGH, start with disabled (low)
347 gpio_set_level(wake_pin_, 1); // WAKE active LOW, start with inactive (high)
348
349 return true;
350 }
351
357 gpio_num_t getGpioPin(TMC9660CtrlPin pin) const noexcept {
358 switch (pin) {
359 case TMC9660CtrlPin::RST:
360 return rst_pin_;
361 case TMC9660CtrlPin::DRV_EN:
362 return drv_en_pin_;
363 case TMC9660CtrlPin::FAULTN:
364 return faultn_pin_;
365 case TMC9660CtrlPin::WAKE:
366 return wake_pin_;
367 default:
368 return GPIO_NUM_NC;
369 }
370 }
371
372 spi_host_device_t host_;
373 gpio_num_t mosi_pin_;
374 gpio_num_t miso_pin_;
375 gpio_num_t sclk_pin_;
376 gpio_num_t cs_pin_;
377 gpio_num_t rst_pin_;
378 gpio_num_t drv_en_pin_;
379 gpio_num_t faultn_pin_;
380 gpio_num_t wake_pin_;
382 uint8_t mode_;
383 spi_device_handle_t device_handle_;
385};
386
393class Esp32Tmc9660UartBus : public UartCommInterface<Esp32Tmc9660UartBus> {
394public:
407 Esp32Tmc9660UartBus(uart_port_t uart_num,
408 gpio_num_t tx_pin,
409 gpio_num_t rx_pin,
410 gpio_num_t rst_pin,
411 gpio_num_t drv_en_pin,
412 gpio_num_t faultn_pin,
413 gpio_num_t wake_pin,
414 uint32_t baud_rate = 115200,
415 uint8_t address = 0) noexcept
416 : UartCommInterface<Esp32Tmc9660UartBus>(true, true, false, false), // RST: HIGH, DRV_EN: HIGH, WAKE: LOW, FAULTN: LOW
417 uart_num_(uart_num), tx_pin_(tx_pin), rx_pin_(rx_pin),
418 rst_pin_(rst_pin), drv_en_pin_(drv_en_pin), faultn_pin_(faultn_pin), wake_pin_(wake_pin),
419 baud_rate_(baud_rate), address_(address), initialized_(false) {
420 }
421
426 deinitialize();
427 }
428
433 bool initialize() noexcept {
434 if (initialized_) {
435 return true;
436 }
437
438 // Configure GPIO pins
439 if (!configureGpioPins()) {
440 ESP_LOGE(BUS_TAG, "Failed to configure GPIO pins");
441 return false;
442 }
443
444 // Configure UART
445 uart_config_t uart_config = {};
446 uart_config.baud_rate = baud_rate_;
447 uart_config.data_bits = UART_DATA_8_BITS;
448 uart_config.parity = UART_PARITY_DISABLE;
449 uart_config.stop_bits = UART_STOP_BITS_1;
450 uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
451 uart_config.source_clk = UART_SCLK_DEFAULT;
452
453 esp_err_t ret = uart_driver_install(uart_num_, 1024, 1024, 0, nullptr, 0);
454 if (ret != ESP_OK) {
455 ESP_LOGE(BUS_TAG, "Failed to install UART driver: %s", esp_err_to_name(ret));
456 return false;
457 }
458
459 ret = uart_param_config(uart_num_, &uart_config);
460 if (ret != ESP_OK) {
461 ESP_LOGE(BUS_TAG, "Failed to configure UART: %s", esp_err_to_name(ret));
462 uart_driver_delete(uart_num_);
463 return false;
464 }
465
466 ret = uart_set_pin(uart_num_, tx_pin_, rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
467 if (ret != ESP_OK) {
468 ESP_LOGE(BUS_TAG, "Failed to set UART pins: %s", esp_err_to_name(ret));
469 uart_driver_delete(uart_num_);
470 return false;
471 }
472
473 initialized_ = true;
474 ESP_LOGI(BUS_TAG, "UART interface initialized successfully");
475 return true;
476 }
477
482 bool deinitialize() noexcept {
483 if (!initialized_) {
484 return true;
485 }
486
487 uart_driver_delete(uart_num_);
488 initialized_ = false;
489 ESP_LOGI(BUS_TAG, "UART interface deinitialized");
490 return true;
491 }
492
498 bool uartSendTMCL(const std::array<uint8_t, 9> &data) noexcept {
499 if (!initialized_) {
500 ESP_LOGE(BUS_TAG, "UART interface not initialized");
501 return false;
502 }
503
504 int bytes_written = uart_write_bytes(uart_num_, data.data(), data.size());
505 if (bytes_written != static_cast<int>(data.size())) {
506 ESP_LOGE(BUS_TAG, "UART write failed: expected %zu, wrote %d", data.size(), bytes_written);
507 return false;
508 }
509
510 // Wait for transmission to complete
511 uart_wait_tx_done(uart_num_, portMAX_DELAY);
512 return true;
513 }
514
520 bool uartReceiveTMCL(std::array<uint8_t, 9> &data) noexcept {
521 if (!initialized_) {
522 ESP_LOGE(BUS_TAG, "UART interface not initialized");
523 return false;
524 }
525
526 int bytes_read = uart_read_bytes(uart_num_, data.data(), data.size(), pdMS_TO_TICKS(10));
527 if (bytes_read != static_cast<int>(data.size())) {
528 ESP_LOGE(BUS_TAG, "UART read failed: expected %zu, read %d", data.size(), bytes_read);
529 return false;
530 }
531
532 return true;
533 }
534
544 bool transferTMCL(const TMCLFrame &tx, TMCLReply &reply, uint8_t address,
545 TMCLReply *firstReply, const TMCLFrame *secondCommand) noexcept {
546 // UART doesn't use the two-transaction pattern, so firstReply and secondCommand are ignored
547 (void)firstReply; // Suppress unused parameter warning
548 (void)secondCommand; // Suppress unused parameter warning
549 if (!initialized_) {
550 ESP_LOGE(BUS_TAG, "UART interface not initialized");
551 return false;
552 }
553
554 // Clear RX buffer before sending
555 uart_flush_input(uart_num_);
556
557 // ✅ FIX: Use TMCLFrame::toUart() for correct frame encoding
558 // Previous manual packing had multiple bugs:
559 // - Wrong sync bit position (should be bit 0, not 0x55 in byte 0)
560 // - Only wrote lower 8 bits of 16-bit type field
561 // - Array indexing off by one (overwrote checksum with value LSB)
562 std::array<uint8_t, 9> uart_frame;
563 tx.toUart(address, uart_frame); // Correctly encodes all fields + checksum
564
565 // Send 9-byte UART frame
566 int bytes_written = uart_write_bytes(uart_num_, uart_frame.data(), uart_frame.size());
567 if (bytes_written != static_cast<int>(uart_frame.size())) {
568 ESP_LOGE(BUS_TAG, "UART write failed: expected %zu, wrote %d", uart_frame.size(), bytes_written);
569 return false;
570 }
571
572 // Log transmitted bytes
573 logDebug(2, "UART_TMCL", "[UART TX] %02X %02X %02X %02X %02X %02X %02X %02X %02X",
574 uart_frame[0], uart_frame[1], uart_frame[2], uart_frame[3],
575 uart_frame[4], uart_frame[5], uart_frame[6], uart_frame[7], uart_frame[8]);
576
577 // Wait for transmission to complete
578 uart_wait_tx_done(uart_num_, portMAX_DELAY);
579
580 // Receive 9-byte UART reply
581 std::array<uint8_t, 9> uart_reply;
582 int bytes_read = uart_read_bytes(uart_num_, uart_reply.data(), uart_reply.size(), pdMS_TO_TICKS(10));
583 if (bytes_read != static_cast<int>(uart_reply.size())) {
584 ESP_LOGE(BUS_TAG, "UART read failed: expected %zu, read %d", uart_reply.size(), bytes_read);
585 // Log partial data if any was received
586 if (bytes_read > 0) {
587 logDebug(2, "UART_TMCL", "[UART RX] (partial %d bytes) %02X %02X %02X %02X %02X %02X %02X %02X %02X",
588 bytes_read,
589 bytes_read > 0 ? uart_reply[0] : 0, bytes_read > 1 ? uart_reply[1] : 0,
590 bytes_read > 2 ? uart_reply[2] : 0, bytes_read > 3 ? uart_reply[3] : 0,
591 bytes_read > 4 ? uart_reply[4] : 0, bytes_read > 5 ? uart_reply[5] : 0,
592 bytes_read > 6 ? uart_reply[6] : 0, bytes_read > 7 ? uart_reply[7] : 0,
593 bytes_read > 8 ? uart_reply[8] : 0);
594 }
595 return false;
596 }
597
598 // Log received bytes
599 logDebug(2, "UART_TMCL", "[UART RX] %02X %02X %02X %02X %02X %02X %02X %02X %02X",
600 uart_reply[0], uart_reply[1], uart_reply[2], uart_reply[3],
601 uart_reply[4], uart_reply[5], uart_reply[6], uart_reply[7], uart_reply[8]);
602
603 // Use TMCLReply::fromUart() for correct decoding with command context
604 // This handles checksum verification and proper field extraction
605 // Pass command context (opcode, type) for handling special reply formats like GetVersion string
606 if (!TMCLReply::fromUart(uart_reply, address, reply, tx.opcode, tx.type)) {
607 ESP_LOGE(BUS_TAG, "Failed to parse UART reply (checksum or address mismatch)");
608 return false;
609 }
610
611 return true;
612 }
613
620 bool uartTransferBootloader(const std::array<uint8_t, 8> &tx, std::array<uint8_t, 8> &rx) noexcept {
621 if (!initialized_) {
622 ESP_LOGE(BUS_TAG, "UART interface not initialized");
623 return false;
624 }
625
626 // Clear RX buffer before sending
627 uart_flush_input(uart_num_);
628
629 // Send 8-byte bootloader command
630 int bytes_written = uart_write_bytes(uart_num_, tx.data(), tx.size());
631 if (bytes_written != static_cast<int>(tx.size())) {
632 ESP_LOGE(BUS_TAG, "UART bootloader write failed: expected %zu, wrote %d", tx.size(), bytes_written);
633 return false;
634 }
635
636 // Wait for transmission to complete
637 uart_wait_tx_done(uart_num_, portMAX_DELAY);
638
639 // Receive 8-byte bootloader reply
640 int bytes_read = uart_read_bytes(uart_num_, rx.data(), rx.size(), pdMS_TO_TICKS(10));
641 if (bytes_read != static_cast<int>(rx.size())) {
642 ESP_LOGE(BUS_TAG, "UART bootloader read failed: expected %zu, read %d", rx.size(), bytes_read);
643 return false;
644 }
645
646 return true;
647 }
648
653 CommMode mode() const noexcept {
654 return CommMode::UART;
655 }
656
663 bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept {
664 gpio_num_t gpio_pin = getGpioPin(pin);
665 if (gpio_pin == GPIO_NUM_NC) {
666 ESP_LOGE(BUS_TAG, "Invalid GPIO pin for TMC9660 control pin");
667 return false;
668 }
669
670 // Convert signal state to physical GPIO level using base class helper
671 uint32_t gpio_level = signalToGpioLevel(pin, signal) ? 1 : 0;
672
673 esp_err_t ret = gpio_set_level(gpio_pin, gpio_level);
674 if (ret != ESP_OK) {
675 ESP_LOGE(BUS_TAG, "Failed to set GPIO level: %s", esp_err_to_name(ret));
676 return false;
677 }
678 return true;
679 }
680
687 bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept {
688 gpio_num_t gpio_pin = getGpioPin(pin);
689 if (gpio_pin == GPIO_NUM_NC) {
690 ESP_LOGE(BUS_TAG, "Invalid GPIO pin for TMC9660 control pin");
691 return false;
692 }
693
694 int gpio_level = gpio_get_level(gpio_pin);
695 if (gpio_level < 0) {
696 ESP_LOGE(BUS_TAG, "Failed to read GPIO level");
697 return false;
698 }
699 // Convert physical GPIO level to signal state using base class helper
700 signal = gpioLevelToSignal(pin, gpio_level == 1);
701 return true;
702 }
703
711 void debugLog(int level, const char* tag, const char* format, va_list args) noexcept {
712 // Route to appropriate ESP-IDF log level
713 switch (level) {
714 case 0: // Error
715 esp_log_writev(ESP_LOG_ERROR, tag, format, args);
716 break;
717 case 1: // Warning
718 esp_log_writev(ESP_LOG_WARN, tag, format, args);
719 break;
720 case 2: // Info
721 esp_log_writev(ESP_LOG_INFO, tag, format, args);
722 break;
723 case 3: // Debug
724 esp_log_writev(ESP_LOG_DEBUG, tag, format, args);
725 break;
726 case 4: // Verbose
727 esp_log_writev(ESP_LOG_VERBOSE, tag, format, args);
728 break;
729 default:
730 esp_log_writev(ESP_LOG_INFO, tag, format, args);
731 break;
732 }
733 }
734
735 void delayMs(uint32_t ms) noexcept {
736 vTaskDelay(pdMS_TO_TICKS(ms));
737 }
738
739 void delayUs(uint32_t us) noexcept {
740 // ESP32: Use esp_rom_delay_us for accurate microsecond delays
741 // For FreeRTOS tasks, convert >= 1ms delays to task delays to avoid blocking
742 if (us >= 1000) {
743 // For >= 1ms, use task delay (non-blocking)
744 vTaskDelay(pdMS_TO_TICKS((us + 999) / 1000));
745 } else {
746 // For < 1ms, use busy-wait delay (accurate but blocks)
747 esp_rom_delay_us(us);
748 }
749 }
750
751private:
756 bool configureGpioPins() noexcept {
757 // Configure control pins as outputs
758 gpio_config_t output_config = {};
759 output_config.intr_type = GPIO_INTR_DISABLE;
760 output_config.mode = GPIO_MODE_OUTPUT;
761 output_config.pin_bit_mask = (1ULL << rst_pin_) | (1ULL << drv_en_pin_) | (1ULL << wake_pin_);
762 output_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
763 output_config.pull_up_en = GPIO_PULLUP_DISABLE;
764
765 esp_err_t ret = gpio_config(&output_config);
766 if (ret != ESP_OK) {
767 ESP_LOGE(BUS_TAG, "Failed to configure output GPIO pins: %s", esp_err_to_name(ret));
768 return false;
769 }
770
771 // Configure status pin as input
772 gpio_config_t input_config = {};
773 input_config.intr_type = GPIO_INTR_DISABLE;
774 input_config.mode = GPIO_MODE_INPUT;
775 input_config.pin_bit_mask = (1ULL << faultn_pin_);
776 input_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
777 input_config.pull_up_en = GPIO_PULLUP_ENABLE; // Enable pull-up for open-drain FAULTN
778
779 ret = gpio_config(&input_config);
780 if (ret != ESP_OK) {
781 ESP_LOGE(BUS_TAG, "Failed to configure input GPIO pin: %s", esp_err_to_name(ret));
782 return false;
783 }
784
785 // Set initial states
786 gpio_set_level(rst_pin_, 0); // RST active HIGH, start with inactive (low)
787 gpio_set_level(drv_en_pin_, 0); // DRV_EN active HIGH, start with disabled (low)
788 gpio_set_level(wake_pin_, 1); // WAKE active LOW, start with inactive (high)
789
790 return true;
791 }
792
798 gpio_num_t getGpioPin(TMC9660CtrlPin pin) const noexcept {
799 switch (pin) {
800 case TMC9660CtrlPin::RST:
801 return rst_pin_;
802 case TMC9660CtrlPin::DRV_EN:
803 return drv_en_pin_;
804 case TMC9660CtrlPin::FAULTN:
805 return faultn_pin_;
806 case TMC9660CtrlPin::WAKE:
807 return wake_pin_;
808 default:
809 return GPIO_NUM_NC;
810 }
811 }
812
813 uart_port_t uart_num_;
814 gpio_num_t tx_pin_;
815 gpio_num_t rx_pin_;
816 gpio_num_t rst_pin_;
817 gpio_num_t drv_en_pin_;
818 gpio_num_t faultn_pin_;
819 gpio_num_t wake_pin_;
820 uint32_t baud_rate_;
821 uint8_t address_;
823};
824
832 // SPI Configuration
833 struct {
834 spi_host_device_t host = SPI2_HOST;
835 gpio_num_t mosi_pin = GPIO_NUM_7;
836 gpio_num_t miso_pin = GPIO_NUM_2;
837 gpio_num_t sclk_pin = GPIO_NUM_6;
838 gpio_num_t cs_pin = GPIO_NUM_18;
839 uint32_t clock_speed_hz = 1000000; // 1 MHz
840 uint8_t mode = 3; // ⚠️ CRITICAL: TMC9660 requires SPI MODE 3 (CPOL=1, CPHA=1)
842
843 // UART Configuration
844 struct {
845 uart_port_t uart_num = UART_NUM_1;
846 gpio_num_t tx_pin = GPIO_NUM_5;
847 gpio_num_t rx_pin = GPIO_NUM_4;
848 uint32_t baud_rate = 115200;
849 uint8_t address = 0;
851
852 // GPIO Configuration for TMC9660 control pins
853 struct {
854 gpio_num_t rst_pin = GPIO_NUM_22; // RST control pin (active HIGH)
855 gpio_num_t drv_en_pin = GPIO_NUM_20; // DRV_EN control pin (active HIGH)
856 gpio_num_t faultn_pin = GPIO_NUM_19; // FAULTN status pin (active LOW, open drain)
857 gpio_num_t wake_pin = GPIO_NUM_21; // WAKE control pin (active LOW)
859};
860
866inline std::unique_ptr<Esp32Tmc9660SpiBus> CreateEsp32Tmc9660SpiBus(
867 const Esp32Tmc9660BusConfig& config = Esp32Tmc9660BusConfig{}) noexcept {
868
869 auto interface = std::make_unique<Esp32Tmc9660SpiBus>(
870 config.spi.host,
871 config.spi.mosi_pin,
872 config.spi.miso_pin,
873 config.spi.sclk_pin,
874 config.spi.cs_pin,
875 config.gpio.rst_pin,
876 config.gpio.drv_en_pin,
877 config.gpio.faultn_pin,
878 config.gpio.wake_pin,
879 config.spi.clock_speed_hz,
880 config.spi.mode
881 );
882
883 if (!interface->initialize()) {
884 ESP_LOGE(BUS_TAG, "Failed to initialize SPI interface");
885 return nullptr;
886 }
887
888 return interface;
889}
890
896inline std::unique_ptr<Esp32Tmc9660UartBus> CreateEsp32Tmc9660UartBus(
897 const Esp32Tmc9660BusConfig& config = Esp32Tmc9660BusConfig{}) noexcept {
898
899 auto interface = std::make_unique<Esp32Tmc9660UartBus>(
900 config.uart.uart_num,
901 config.uart.tx_pin,
902 config.uart.rx_pin,
903 config.gpio.rst_pin,
904 config.gpio.drv_en_pin,
905 config.gpio.faultn_pin,
906 config.gpio.wake_pin,
907 config.uart.baud_rate,
908 config.uart.address
909 );
910
911 if (!interface->initialize()) {
912 ESP_LOGE(BUS_TAG, "Failed to initialize UART interface");
913 return nullptr;
914 }
915
916 return interface;
917}
ESP32 SPI implementation of TMC9660CommInterface.
Definition esp32_tmc9660_bus.hpp:40
bool initialized_
Definition esp32_tmc9660_bus.hpp:384
void delayUs(uint32_t us) noexcept
Definition esp32_tmc9660_bus.hpp:298
void debugLog(int level, const char *tag, const char *format, va_list args) noexcept
Debug logging function that routes logs through ESP-IDF logging system.
Definition esp32_tmc9660_bus.hpp:270
~Esp32Tmc9660SpiBus() noexcept
Destructor - cleans up SPI resources.
Definition esp32_tmc9660_bus.hpp:78
bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept
Read GPIO pin level for TMC9660 status pins.
Definition esp32_tmc9660_bus.hpp:246
gpio_num_t miso_pin_
Definition esp32_tmc9660_bus.hpp:374
Esp32Tmc9660SpiBus(spi_host_device_t host, gpio_num_t mosi_pin, gpio_num_t miso_pin, gpio_num_t sclk_pin, gpio_num_t cs_pin, gpio_num_t rst_pin, gpio_num_t drv_en_pin, gpio_num_t faultn_pin, gpio_num_t wake_pin, uint32_t clock_speed_hz=10000000, uint8_t mode=0) noexcept
Construct ESP32 SPI communication interface.
Definition esp32_tmc9660_bus.hpp:56
bool configureGpioPins() noexcept
Configure GPIO pins for TMC9660 control and status.
Definition esp32_tmc9660_bus.hpp:315
gpio_num_t drv_en_pin_
Definition esp32_tmc9660_bus.hpp:378
bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept
Set GPIO pin signal state for TMC9660 control pins.
Definition esp32_tmc9660_bus.hpp:222
bool spiTransferBootloader(std::array< uint8_t, 5 > &tx, std::array< uint8_t, 5 > &rx) noexcept
Definition esp32_tmc9660_bus.hpp:188
CommMode mode() const noexcept
Get communication mode.
Definition esp32_tmc9660_bus.hpp:212
gpio_num_t mosi_pin_
Definition esp32_tmc9660_bus.hpp:373
void delayMs(uint32_t ms) noexcept
Definition esp32_tmc9660_bus.hpp:294
gpio_num_t sclk_pin_
Definition esp32_tmc9660_bus.hpp:375
uint32_t clock_speed_hz_
Definition esp32_tmc9660_bus.hpp:381
spi_host_device_t host_
Definition esp32_tmc9660_bus.hpp:372
bool initialize() noexcept
Initialize the SPI interface.
Definition esp32_tmc9660_bus.hpp:86
bool spiTransferTMCL(std::array< uint8_t, 8 > &tx, std::array< uint8_t, 8 > &rx) noexcept
Perform SPI transfer for TMC9660 TMCL parameter mode communication.
Definition esp32_tmc9660_bus.hpp:168
gpio_num_t wake_pin_
Definition esp32_tmc9660_bus.hpp:380
spi_device_handle_t device_handle_
Definition esp32_tmc9660_bus.hpp:383
uint8_t mode_
Definition esp32_tmc9660_bus.hpp:382
gpio_num_t faultn_pin_
Definition esp32_tmc9660_bus.hpp:379
bool deinitialize() noexcept
Deinitialize the SPI interface.
Definition esp32_tmc9660_bus.hpp:146
gpio_num_t getGpioPin(TMC9660CtrlPin pin) const noexcept
Map TMC9660 control pins to ESP32 GPIO pins.
Definition esp32_tmc9660_bus.hpp:357
gpio_num_t cs_pin_
Definition esp32_tmc9660_bus.hpp:376
gpio_num_t rst_pin_
Definition esp32_tmc9660_bus.hpp:377
ESP32 UART implementation of TMC9660CommInterface.
Definition esp32_tmc9660_bus.hpp:393
bool deinitialize() noexcept
Deinitialize the UART interface.
Definition esp32_tmc9660_bus.hpp:482
bool configureGpioPins() noexcept
Configure GPIO pins for TMC9660 control and status.
Definition esp32_tmc9660_bus.hpp:756
gpio_num_t drv_en_pin_
Definition esp32_tmc9660_bus.hpp:817
uint32_t baud_rate_
Definition esp32_tmc9660_bus.hpp:820
uart_port_t uart_num_
Definition esp32_tmc9660_bus.hpp:813
gpio_num_t rst_pin_
Definition esp32_tmc9660_bus.hpp:816
gpio_num_t getGpioPin(TMC9660CtrlPin pin) const noexcept
Map TMC9660 control pins to ESP32 GPIO pins.
Definition esp32_tmc9660_bus.hpp:798
~Esp32Tmc9660UartBus() noexcept
Destructor - cleans up UART resources.
Definition esp32_tmc9660_bus.hpp:425
bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept
Read GPIO pin level for TMC9660 status pins.
Definition esp32_tmc9660_bus.hpp:687
void delayMs(uint32_t ms) noexcept
Definition esp32_tmc9660_bus.hpp:735
CommMode mode() const noexcept
Get communication mode.
Definition esp32_tmc9660_bus.hpp:653
bool transferTMCL(const TMCLFrame &tx, TMCLReply &reply, uint8_t address, TMCLReply *firstReply, const TMCLFrame *secondCommand) noexcept
Transfer TMCL frame over UART for parameter mode communication.
Definition esp32_tmc9660_bus.hpp:544
uint8_t address_
Definition esp32_tmc9660_bus.hpp:821
bool uartSendTMCL(const std::array< uint8_t, 9 > &data) noexcept
Send raw 9-byte UART TMCL datagram for parameter mode communication.
Definition esp32_tmc9660_bus.hpp:498
gpio_num_t tx_pin_
Definition esp32_tmc9660_bus.hpp:814
gpio_num_t wake_pin_
Definition esp32_tmc9660_bus.hpp:819
gpio_num_t rx_pin_
Definition esp32_tmc9660_bus.hpp:815
void debugLog(int level, const char *tag, const char *format, va_list args) noexcept
Debug logging function that routes logs through ESP-IDF logging system.
Definition esp32_tmc9660_bus.hpp:711
bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept
Set GPIO pin signal state for TMC9660 control pins.
Definition esp32_tmc9660_bus.hpp:663
Esp32Tmc9660UartBus(uart_port_t uart_num, gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t rst_pin, gpio_num_t drv_en_pin, gpio_num_t faultn_pin, gpio_num_t wake_pin, uint32_t baud_rate=115200, uint8_t address=0) noexcept
Construct ESP32 UART communication interface.
Definition esp32_tmc9660_bus.hpp:407
bool uartReceiveTMCL(std::array< uint8_t, 9 > &data) noexcept
Receive raw 9-byte UART TMCL datagram for parameter mode communication.
Definition esp32_tmc9660_bus.hpp:520
gpio_num_t faultn_pin_
Definition esp32_tmc9660_bus.hpp:818
bool initialize() noexcept
Initialize the UART interface.
Definition esp32_tmc9660_bus.hpp:433
bool initialized_
Definition esp32_tmc9660_bus.hpp:822
void delayUs(uint32_t us) noexcept
Definition esp32_tmc9660_bus.hpp:739
bool uartTransferBootloader(const std::array< uint8_t, 8 > &tx, std::array< uint8_t, 8 > &rx) noexcept
Transfer 8-byte UART bootloader datagram (send and receive)
Definition esp32_tmc9660_bus.hpp:620
void logDebug(int level, const char *tag, const char *format,...) noexcept
Public debug logging wrapper for external classes.
Definition tmc9660_comm_interface.hpp:795
bool signalToGpioLevel(TMC9660CtrlPin pin, GpioSignal signal) const noexcept
Convert signal state to physical GPIO level.
Definition tmc9660_comm_interface.hpp:598
GpioSignal gpioLevelToSignal(TMC9660CtrlPin pin, bool gpio_level) const noexcept
Convert physical GPIO level to signal state.
Definition tmc9660_comm_interface.hpp:609
CRTP-based SPI implementation of TMC9660CommInterface.
Definition tmc9660_comm_interface.hpp:855
CRTP-based UART implementation of TMC9660CommInterface.
Definition tmc9660_comm_interface.hpp:1066
static const char * BUS_TAG
Definition esp32_tmc9660_bus.hpp:32
std::unique_ptr< Esp32Tmc9660UartBus > CreateEsp32Tmc9660UartBus(const Esp32Tmc9660BusConfig &config=Esp32Tmc9660BusConfig{}) noexcept
Create and initialize UART communication interface.
Definition esp32_tmc9660_bus.hpp:896
std::unique_ptr< Esp32Tmc9660SpiBus > CreateEsp32Tmc9660SpiBus(const Esp32Tmc9660BusConfig &config=Esp32Tmc9660BusConfig{}) noexcept
Create and initialize SPI communication interface.
Definition esp32_tmc9660_bus.hpp:866
Definition bootloader_config.hpp:9
TMC9660CtrlPin
TMC9660 control pin identifiers with board-agnostic naming.
Definition tmc9660_comm_interface.hpp:126
CommMode
Supported physical communication modes for TMC9660.
Definition tmc9660_comm_interface.hpp:113
GpioSignal
GPIO signal states with board-agnostic naming.
Definition tmc9660_comm_interface.hpp:140
Common bus configuration for ESP32 TMC9660 tests.
Definition esp32_tmc9660_bus.hpp:831
gpio_num_t drv_en_pin
Definition esp32_tmc9660_bus.hpp:855
gpio_num_t sclk_pin
Definition esp32_tmc9660_bus.hpp:837
gpio_num_t mosi_pin
Definition esp32_tmc9660_bus.hpp:835
gpio_num_t faultn_pin
Definition esp32_tmc9660_bus.hpp:856
gpio_num_t rx_pin
Definition esp32_tmc9660_bus.hpp:847
gpio_num_t cs_pin
Definition esp32_tmc9660_bus.hpp:838
struct Esp32Tmc9660BusConfig::@22 uart
gpio_num_t miso_pin
Definition esp32_tmc9660_bus.hpp:836
uint8_t address
Definition esp32_tmc9660_bus.hpp:849
struct Esp32Tmc9660BusConfig::@23 gpio
struct Esp32Tmc9660BusConfig::@21 spi
uart_port_t uart_num
Definition esp32_tmc9660_bus.hpp:845
gpio_num_t wake_pin
Definition esp32_tmc9660_bus.hpp:857
uint8_t mode
Definition esp32_tmc9660_bus.hpp:840
uint32_t baud_rate
Definition esp32_tmc9660_bus.hpp:848
gpio_num_t tx_pin
Definition esp32_tmc9660_bus.hpp:846
gpio_num_t rst_pin
Definition esp32_tmc9660_bus.hpp:854
spi_host_device_t host
Definition esp32_tmc9660_bus.hpp:834
uint32_t clock_speed_hz
Definition esp32_tmc9660_bus.hpp:839
Frame structure for TMCL commands.
Definition tmc9660_comm_interface.hpp:369
Reply structure returned by TMCL command operations.
Definition tmc9660_comm_interface.hpp:189
static bool fromUart(std::span< const uint8_t, 9 > in, uint8_t addr, TMCLReply &r, uint8_t sent_opcode=0, uint16_t sent_type=0) noexcept
Decode and validate reply from UART TMCL datagram.
Definition tmc9660_comm_interface.hpp:320
Communication interfaces for TMC9660 Parameter Mode devices using TMCL protocol over SPI.