HF-TMC9660 Driver 0.1.0-dev
Hardware Agnostic C++ Driver for the TMC9660
Loading...
Searching...
No Matches
tmc9660_comm_interface.hpp
Go to the documentation of this file.
1
73#pragma once
74#include <array>
75#include <cstdarg>
76#include <cstdint>
77#include <cstdio>
78#include <cstdlib>
79#include <cstring>
80#include <span>
81#include <string>
82
83namespace tmc9660 {
84
99#ifndef TMC9660_DISABLE_DEBUG_LOGGING
100// Debug logging enabled - use actual function call
101#define TMC9660_LOG_DEBUG(comm_obj, level, tag, ...) (comm_obj).logDebug(level, tag, __VA_ARGS__)
102#else
103// Debug logging disabled - optimize out completely (arguments not evaluated)
104#define TMC9660_LOG_DEBUG(comm_obj, level, tag, ...) ((void)0)
105#endif
106
113enum class CommMode {
114 SPI,
115 UART
117};
118
126enum class TMC9660CtrlPin {
127 RST,
128 DRV_EN,
129 FAULTN,
130 WAKE
131};
132
140enum class GpioSignal {
141 INACTIVE = 0,
142 ACTIVE = 1
143};
144
151enum class SPIStatus : uint8_t {
152 OK = 0xFF,
153 CHECKSUM_ERROR = 0x00,
154 FIRST_CMD = 0x0C,
155 NOT_READY = 0xF0,
156};
157
170static constexpr uint8_t tmclChecksum(const uint8_t* bytes, size_t n, uint8_t start = 0) noexcept {
171 uint8_t sum = 0;
172 for (size_t i = start; i < n; ++i)
173 sum += bytes[i];
174 return sum;
175}
176
177// -----------------------------------------------------------------------
178// TMCL scripting support structures and enums
179// -----------------------------------------------------------------------
180
189struct TMCLReply {
191 uint8_t status = 0;
192 uint8_t opcode = 0;
193 uint32_t value = 0;
194
195 // ✅ NEW: Raw bytes for advanced parsing (handle protocol mismatches)
196 std::array<uint8_t, 8> rawBytes =
197 {};
198
199 [[nodiscard]] bool isOK() const noexcept {
200 return spi_status == SPIStatus::OK && (status == 100 || status == 101);
201 }
202
211 [[nodiscard]] uint32_t extractRawValue(size_t offset) const noexcept {
212 if (offset + 3 >= rawBytes.size())
213 return 0;
214 return (static_cast<uint32_t>(rawBytes[offset]) << 24) |
215 (static_cast<uint32_t>(rawBytes[offset + 1]) << 16) |
216 (static_cast<uint32_t>(rawBytes[offset + 2]) << 8) |
217 static_cast<uint32_t>(rawBytes[offset + 3]);
218 }
219
227 [[nodiscard]] std::string getVersionString() const noexcept {
228 if (opcode != 136)
229 return ""; // Not a GetVersion reply
230
231 std::string version;
232 // Extract version string from bytes 1-8 (skip host address at byte 0)
233 for (size_t i = 1; i < 8; ++i) {
234 if (rawBytes[i] >= 0x20 && rawBytes[i] <= 0x7E) { // Printable ASCII
235 version += static_cast<char>(rawBytes[i]);
236 } else {
237 break; // Stop at first non-printable character
238 }
239 }
240 return version;
241 }
242
259 static bool fromSpi(std::span<const uint8_t, 8> in, TMCLReply& r, uint8_t sent_opcode = 0,
260 uint16_t sent_type = 0) noexcept {
261 // Store raw bytes for advanced parsing (ALWAYS, even if parsing fails)
262 std::copy(in.begin(), in.end(), r.rawBytes.begin());
263
264 // Check for SESSION_START status codes (bootloader or parameter mode)
265 uint8_t raw_status = in[0];
266 if (raw_status == 0x13) {
267 // SESSION_START from bootloader (0x13)
268 // These use different protocols, so TMCL checksum will fail
269 // But we still want to return success so caller can use rawBytes
270 r.spi_status = static_cast<SPIStatus>(raw_status);
271 r.status = in[1];
272 r.opcode = in[2];
273 r.value = (static_cast<uint32_t>(in[3]) << 24) | (static_cast<uint32_t>(in[4]) << 16) |
274 (static_cast<uint32_t>(in[5]) << 8) | static_cast<uint32_t>(in[6]);
275 return true; // Allow SESSION_START even if checksum fails
276 }
277
278 // Check for GetVersion string format (Command 136, Type 0)
279 // GetVersion with Type=0 returns a special string format without checksum
280 // Format: [Host Address][Version String 8 chars]
281 if (sent_opcode == 136 && sent_type == 0) {
282 // We sent GetVersion Type=0, expect string format reply (no checksum validation)
283 r.spi_status = SPIStatus::OK; // Assume OK for version string
284 r.status = 100; // REPLY_OK for successful version retrieval
285 r.opcode = 136; // GetVersion command
286 r.value = 0; // No value field for string format
287 return true; // Success - skip checksum validation for string format
288 }
289
290 // Standard TMCL reply validation
291 if (tmclChecksum(in.data(), 7) != in[7])
292 return false;
293 r.spi_status = static_cast<SPIStatus>(in[0]);
295 return false;
296 r.status = in[1];
297 r.opcode = in[2];
298 r.value = (static_cast<uint32_t>(in[3]) << 24) | (static_cast<uint32_t>(in[4]) << 16) |
299 (static_cast<uint32_t>(in[5]) << 8) | static_cast<uint32_t>(in[6]);
300 return true;
301 }
302
320 static bool fromUart(std::span<const uint8_t, 9> in, uint8_t addr, TMCLReply& r,
321 uint8_t sent_opcode = 0, uint16_t sent_type = 0) noexcept {
322 // Store raw bytes (map 9-byte UART to 8-byte format, skip first byte)
323 std::copy(in.begin() + 1, in.end(), r.rawBytes.begin());
324
325 // UART Reply Format (per datasheet):
326 // byte0: Host Address (8 bits)
327 // byte1: Sync bit (bit 0) + Module Address (bits 1-7)
328 // byte2: TMCL Status
329 // byte3: Operation
330 // byte4-7: Data (big-endian)
331 // byte8: Checksum
332
333 // Check for GetVersion string format (Command 136, Type 0)
334 // GetVersion with Type=0 returns a special string format without valid checksum
335 // Format: [Host Address][Sync+Address][Version String 7 chars]
336 if (sent_opcode == 136 && sent_type == 0) {
337 // We sent GetVersion Type=0, expect string format reply (no checksum validation)
338 r.spi_status = SPIStatus::OK; // Assume OK for version string
339 r.status = 100; // REPLY_OK for successful version retrieval
340 r.opcode = 136; // GetVersion command
341 r.value = 0; // No value field for string format
342 return true; // Success - skip checksum validation for string format
343 }
344
345 // Verify module address in byte 1 (bits 1-7)
346 // "The module address reuses the upper 7 bits of the bootloader device address"
347 // Compare the upper 7 bits of byte 1 with the upper 7 bits of the address
348 if ((in[1] & 0xFE) != (addr & 0xFE))
349 return false;
350
351 // Verify checksum over first 8 bytes
352 if (tmclChecksum(in.data(), 8) != in[8])
353 return false;
354
355 r.spi_status = SPIStatus::OK; // UART has no SPI status field
356 r.status = in[2]; // TMCL Status at byte 2
357 r.opcode = in[3]; // Operation at byte 3
358 r.value = (static_cast<uint32_t>(in[4]) << 24) | (static_cast<uint32_t>(in[5]) << 16) |
359 (static_cast<uint32_t>(in[6]) << 8) | static_cast<uint32_t>(in[7]);
360 return true;
361 }
362};
363
369struct TMCLFrame {
370 uint8_t opcode = 0;
371 uint16_t type = 0;
372 uint8_t motor = 0;
373 uint32_t value = 0;
374
379 void toSpi(std::span<uint8_t, 8> out) const noexcept {
380 out[0] = opcode;
381 out[1] = static_cast<uint8_t>(type & 0xFF); // Type lower 8 bits (bits 0-7)
382 // BYTE2: bits 20-23 (high nibble) = Motor/Bank, bits 16-19 (upper nibble) = Type upper 4 bits
383 out[2] = static_cast<uint8_t>(((motor & 0x0F) << 4) | ((type & 0xF00) >> 8));
384 out[3] = static_cast<uint8_t>(value >> 24);
385 out[4] = static_cast<uint8_t>(value >> 16);
386 out[5] = static_cast<uint8_t>(value >> 8);
387 out[6] = static_cast<uint8_t>(value);
388 out[7] = tmclChecksum(out.data(), 7);
389 }
390
396 void toUart(uint8_t addr, std::span<uint8_t, 9> out) const noexcept {
397 // Byte 0: [Sync bit (bit 0)] [Module Address (bits 1-7)]
398 // "The module address reuses the upper 7 bits of the bootloader device address"
399 // This means we take bits 7-1 of the address and place them in bits 7-1 of byte 0
400 // Then set bit 0 (sync bit) to 1
401 out[0] = (addr & 0xFE) | 0x01; // Keep upper 7 bits of address, set sync bit
402 out[1] = opcode;
403 out[2] = static_cast<uint8_t>(type & 0xFF); // Type field (8 bits)
404 out[3] = static_cast<uint8_t>(motor & 0x0F); // Motor/Bank field (4 bits)
405 out[4] = static_cast<uint8_t>(value >> 24);
406 out[5] = static_cast<uint8_t>(value >> 16);
407 out[6] = static_cast<uint8_t>(value >> 8);
408 out[7] = static_cast<uint8_t>(value);
409 out[8] = tmclChecksum(out.data(), 8);
410 }
411
417 static TMCLFrame fromSpi(std::span<const uint8_t, 8> in) noexcept {
418 TMCLFrame f;
419 f.opcode = in[0];
420 // BYTE1: Type lower 8 bits (bits 0-7)
421 // BYTE2: bits 20-23 (high nibble) = Motor/Bank, bits 16-19 (low nibble) = Type upper 4 bits
422 f.type = static_cast<uint16_t>(in[1] | ((in[2] & 0x0F) << 8));
423 f.motor = (in[2] >> 4) & 0x0F;
424 f.value = (static_cast<uint32_t>(in[3]) << 24) | (static_cast<uint32_t>(in[4]) << 16) |
425 (static_cast<uint32_t>(in[5]) << 8) | static_cast<uint32_t>(in[6]);
426 return f;
427 }
428
435 static bool fromSpiChecked(std::span<const uint8_t, 8> in, TMCLFrame& out_frame) noexcept {
436 TMCLReply reply{};
437 if (!TMCLReply::fromSpi(in, reply))
438 return false;
439 out_frame.opcode = reply.opcode;
440 out_frame.value = reply.value;
441 return true;
442 }
443
451 static bool fromUart(std::span<const uint8_t, 9> in, uint8_t expected_addr,
452 TMCLFrame& out_frame) noexcept {
453 TMCLReply rep{};
454 if (!TMCLReply::fromUart(in, expected_addr, rep))
455 return false;
456 out_frame.opcode = rep.opcode;
457 out_frame.value = rep.value;
458 return true;
459 }
460
467 static constexpr uint8_t calculateChecksum(const uint8_t* bytes, size_t n) noexcept {
468 uint8_t sum = 0;
469 for (size_t i = 0; i < n; ++i)
470 sum += bytes[i];
471 return sum;
472 }
473};
474
513template <typename Derived>
515public:
523 CommInterface(bool rst_active_level, bool drv_en_active_level, bool wake_active_level,
524 bool faultn_active_level) noexcept
525 : pinActiveLevels_{rst_active_level, drv_en_active_level, wake_active_level, faultn_active_level} {}
526
536 CommMode mode() const noexcept {
537 return static_cast<const Derived*>(this)->mode();
538 }
539
556 bool transferTMCL(const TMCLFrame& tx, TMCLReply& reply, uint8_t address,
557 TMCLReply* first_reply = nullptr,
558 const TMCLFrame* second_command = nullptr) noexcept {
559 return static_cast<Derived*>(this)->transferTMCL(tx, reply, address, first_reply, second_command);
560 }
561
573 bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept {
574 return static_cast<Derived*>(this)->gpioSet(pin, signal);
575 }
576
588 bool gpioRead(TMC9660CtrlPin pin, GpioSignal& signal) noexcept {
589 return static_cast<Derived*>(this)->gpioRead(pin, signal);
590 }
591
598 bool signalToGpioLevel(TMC9660CtrlPin pin, GpioSignal signal) const noexcept {
599 bool active_level = pinActiveLevels_[static_cast<int>(pin)];
600 return (signal == GpioSignal::ACTIVE) ? active_level : !active_level;
601 }
602
609 GpioSignal gpioLevelToSignal(TMC9660CtrlPin pin, bool gpio_level) const noexcept {
610 bool active_level = pinActiveLevels_[static_cast<int>(pin)];
611 return (gpio_level == active_level) ? GpioSignal::ACTIVE : GpioSignal::INACTIVE;
612 }
613
620 bool gpioSetActive(TMC9660CtrlPin pin) noexcept {
621 return gpioSet(pin, GpioSignal::ACTIVE);
622 }
623
630 bool gpioSetInactive(TMC9660CtrlPin pin) noexcept {
631 return gpioSet(pin, GpioSignal::INACTIVE);
632 }
633
645 bool set_pin_active_level(TMC9660CtrlPin pin, bool active_level) noexcept {
646 pinActiveLevels_[static_cast<int>(pin)] = active_level;
647 return true;
648 }
649
659 void setSpiRetryMaxCount(uint8_t max_retries) noexcept {
660 spiRetryMaxCount_ = max_retries;
661 }
662
667 uint8_t getSpiRetryMaxCount() const noexcept {
668 return spiRetryMaxCount_;
669 }
670
680 void setSpiRetryInterval(uint32_t interval_us) noexcept {
681 spiRetryIntervalUs_ = interval_us;
682 }
683
688 uint32_t getSpiRetryInterval() const noexcept {
689 return spiRetryIntervalUs_;
690 }
691
692protected:
706
714 uint8_t spiRetryMaxCount_ = 3;
715
723 uint32_t spiRetryIntervalUs_ = 100;
724
737 void debugLog(int level, const char* tag, const char* format, va_list args) noexcept {
738 static_cast<Derived*>(this)->debugLog(level, tag, format, args);
739 }
740
741public:
748 void delayMs(uint32_t ms) noexcept {
749 static_cast<Derived*>(this)->delayMs(ms);
750 }
751
760 void delayUs(uint32_t us) noexcept {
761 static_cast<Derived*>(this)->delayUs(us);
762 }
763
764protected:
769 ~CommInterface() = default;
770
771 // Prevent copying
772 CommInterface(const CommInterface&) = delete;
774
775 // Allow moving
778
779public:
794#ifndef TMC9660_DISABLE_DEBUG_LOGGING
795 void logDebug(int level, const char* tag, const char* format, ...) noexcept {
796 va_list args;
797 va_start(args, format);
798
799 // Check if format string already ends with newline
800 size_t format_len = strlen(format);
801 const char* final_format = format;
802 char* modified_format = nullptr;
803
804 if (format_len == 0 || format[format_len - 1] != '\n') {
805 // Allocate buffer for format + "\n"
806 modified_format = new char[format_len + 2];
807 strcpy(modified_format, format);
808 strcat(modified_format, "\n");
809 final_format = modified_format;
810 }
811
812 debugLog(level, tag, final_format, args);
813
814 if (modified_format) {
815 delete[] modified_format;
816 }
817
818 va_end(args);
819 }
820#else
821 // Debug logging disabled - function optimized out completely
822 inline void logDebug(int /*level*/, const char* /*tag*/, const char* /*format*/, ...) noexcept {
823 // Empty function body - all logging optimized out
824 }
825#endif
826};
827
854template <typename Derived>
855class SpiCommInterface : public CommInterface<Derived> {
856public:
864 SpiCommInterface(bool rst_active_level, bool drv_en_active_level, bool wake_active_level,
865 bool faultn_active_level) noexcept
867 }
868
874 return CommMode::SPI;
875 }
876
884 bool spiTransferTMCL(std::array<uint8_t, 8>& tx, std::array<uint8_t, 8>& rx) noexcept {
885 return static_cast<Derived*>(this)->spiTransferTMCL(tx, rx);
886 }
887
900 bool spiTransferBootloader(std::array<uint8_t, 5>& tx, std::array<uint8_t, 5>& rx) noexcept {
901 return static_cast<Derived*>(this)->spiTransferBootloader(tx, rx);
902 }
903
911 return static_cast<Derived*>(this)->gpioSet(pin, signal);
912 }
913
921 return static_cast<Derived*>(this)->gpioRead(pin, signal);
922 }
923
925 const TMCLFrame* second_command) noexcept {
926 std::array<uint8_t, 8> tx_buf, rx_buf;
927 tx.toSpi(tx_buf);
928
929 // Log raw SPI bytes being transmitted
930 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 2, "SPI_TMCL", "[TMCL TX 1 ] %02X %02X %02X %02X %02X %02X %02X %02X",
931 tx_buf[0], tx_buf[1], tx_buf[2], tx_buf[3], tx_buf[4], tx_buf[5], tx_buf[6],
932 tx_buf[7]);
933
934 // Transaction 1: Send command, receive first reply
936 return false;
937
938 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 2, "SPI_TMCL", "[TMCL RX 1 ] %02X %02X %02X %02X %02X %02X %02X %02X",
939 rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3], rx_buf[4], rx_buf[5], rx_buf[6],
940 rx_buf[7]);
941
942 // Optionally capture first reply (this is the reply to the PREVIOUS command due to SPI delay)
943 if (first_reply) {
946 *static_cast<Derived*>(this), 2, "SPI_TMCL",
947 " └─> SPI_Status=0x%02X, TMCL_Status=0x%02X, Value=0x%08X (parse %s)",
948 static_cast<uint8_t>(first_reply->spi_status), first_reply->status, first_reply->value,
949 first_parse_ok ? "OK" : "failed");
950 // Note: We don't return false here because SESSION_START may have non-standard format
951 // The caller can check rawBytes[0] for SESSION_START status codes
952 }
953
954 // Transaction 2: Send second command (or NO_OP), receive final reply
955 // This transaction is wrapped in retry logic for SPI_STATUS_NOT_READY
956 TMCLFrame cmd2 = second_command ? *second_command : TMCLFrame{}; // NO_OP if not provided
957 cmd2.toSpi(tx_buf);
958
959 // Retry loop for SPI_STATUS_NOT_READY responses
961 while (true) {
962 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 2, "SPI_TMCL",
963 "[TMCL TX 2 ] %02X %02X %02X %02X %02X %02X %02X %02X%s", tx_buf[0],
964 tx_buf[1], tx_buf[2], tx_buf[3], tx_buf[4], tx_buf[5], tx_buf[6], tx_buf[7],
965 retry_count > 0 ? " (retry)" : "");
966
968 return false;
969
970 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 2, "SPI_TMCL",
971 "[TMCL RX 2 ] %02X %02X %02X %02X %02X %02X %02X %02X", rx_buf[0], rx_buf[1],
972 rx_buf[2], rx_buf[3], rx_buf[4], rx_buf[5], rx_buf[6], rx_buf[7]);
973
974 // Check SPI status byte (first byte) for NOT_READY
975 SPIStatus spi_status = static_cast<SPIStatus>(rx_buf[0]);
976
977 if (spi_status == SPIStatus::NOT_READY) {
978 // System not ready - retry if we haven't exceeded max retries
980 retry_count++;
982 *static_cast<Derived*>(this), 2, "SPI_TMCL",
983 "⚠️ SPI_STATUS_NOT_READY received, retrying (attempt %u/%u) after %u us", retry_count,
984 this->spiRetryMaxCount_ + 1, this->spiRetryIntervalUs_);
985 this->delayUs(this->spiRetryIntervalUs_);
986 continue; // Retry the transaction
987 } else {
988 // Max retries exceeded - parse reply manually since fromSpi returns false for NOT_READY
989 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 1, "SPI_TMCL",
990 "❌ SPI_STATUS_NOT_READY: Max retries (%u) exceeded",
991 this->spiRetryMaxCount_);
992 // Validate checksum before parsing
993 if (tmclChecksum(rx_buf.data(), 7) != rx_buf[7]) {
994 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 1, "SPI_TMCL", "⚠️ Checksum error in NOT_READY response");
995 return false;
996 }
997 // Manually populate reply structure for NOT_READY response
998 std::copy(rx_buf.begin(), rx_buf.end(), reply.rawBytes.begin());
999 reply.spi_status = SPIStatus::NOT_READY;
1000 reply.status = rx_buf[1]; // TMCL status
1001 reply.opcode = rx_buf[2]; // Operation code
1002 reply.value = (static_cast<uint32_t>(rx_buf[3]) << 24) |
1003 (static_cast<uint32_t>(rx_buf[4]) << 16) |
1004 (static_cast<uint32_t>(rx_buf[5]) << 8) | static_cast<uint32_t>(rx_buf[6]);
1005 return false; // Return false to indicate failure
1006 }
1007 }
1008
1009 // Not NOT_READY - parse and return normally
1010 break;
1011 }
1012
1013 // Parse reply with command context (for handling special reply formats like GetVersion string)
1014 bool parse_ok = TMCLReply::fromSpi(rx_buf, reply, tx.opcode, tx.type);
1015 if (!parse_ok && static_cast<SPIStatus>(rx_buf[0]) == SPIStatus::NOT_READY) {
1016 // This shouldn't happen due to retry logic, but handle it just in case
1017 TMC9660_LOG_DEBUG(*static_cast<Derived*>(this), 1, "SPI_TMCL", "⚠️ Unexpected NOT_READY after retry loop");
1018 }
1019 return parse_ok;
1020 }
1021
1022protected:
1028
1029 // Prevent copying
1032
1033 // Allow moving
1036};
1037
1065template <typename Derived>
1066class UartCommInterface : public CommInterface<Derived> {
1067public:
1079
1085 return CommMode::UART;
1086 }
1087
1094 bool uartSendTMCL(const std::array<uint8_t, 9>& data) noexcept {
1095 return static_cast<Derived*>(this)->uartSendTMCL(data);
1096 }
1097
1104 bool uartReceiveTMCL(std::array<uint8_t, 9>& data) noexcept {
1105 return static_cast<Derived*>(this)->uartReceiveTMCL(data);
1106 }
1107
1120 bool uartTransferBootloader(const std::array<uint8_t, 8>& tx, std::array<uint8_t, 8>& rx) noexcept {
1121 return static_cast<Derived*>(this)->uartTransferBootloader(tx, rx);
1122 }
1123
1131 return static_cast<Derived*>(this)->gpioSet(pin, signal);
1132 }
1133
1141 return static_cast<Derived*>(this)->gpioRead(pin, signal);
1142 }
1143
1145 const TMCLFrame* second_command) noexcept {
1146 // UART doesn't use the two-transaction pattern, so first_reply and second_command are ignored
1147 (void)first_reply; // Suppress unused parameter warning
1148 (void)second_command; // Suppress unused parameter warning
1149
1150 std::array<uint8_t, 9> frame;
1151 tx.toUart(address, frame);
1152 if (!uartSendTMCL(frame))
1153 return false;
1154 if (!uartReceiveTMCL(frame))
1155 return false;
1156 // Parse reply with command context (for handling special reply formats like GetVersion string)
1157 return TMCLReply::fromUart(frame, address, reply, tx.opcode, tx.type);
1158 }
1159
1160protected:
1166
1167 // Prevent copying
1170
1171 // Allow moving
1174};
1175
1176} // namespace tmc9660
SPI status codes as per TMC9660 Parameter Mode.
Definition tmc9660_comm_interface.hpp:514
bool set_pin_active_level(TMC9660CtrlPin pin, bool active_level) noexcept
Configure the active level for a specific pin.
Definition tmc9660_comm_interface.hpp:645
void logDebug(int level, const char *tag, const char *format,...) noexcept
Public debug logging wrapper for external classes.
Definition tmc9660_comm_interface.hpp:795
uint8_t spiRetryMaxCount_
Maximum number of retries for SPI_STATUS_NOT_READY responses.
Definition tmc9660_comm_interface.hpp:714
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
~CommInterface()=default
Protected destructor.
CommInterface(const CommInterface &)=delete
bool gpioSetActive(TMC9660CtrlPin pin) noexcept
Set GPIO pin to active state (convenience method).
Definition tmc9660_comm_interface.hpp:620
CommInterface(bool rst_active_level, bool drv_en_active_level, bool wake_active_level, bool faultn_active_level) noexcept
Construct communication interface with pin active level configuration.
Definition tmc9660_comm_interface.hpp:523
void setSpiRetryInterval(uint32_t interval_us) noexcept
Set delay interval between retry attempts for SPI_STATUS_NOT_READY.
Definition tmc9660_comm_interface.hpp:680
void setSpiRetryMaxCount(uint8_t max_retries) noexcept
Set maximum number of retries for SPI_STATUS_NOT_READY responses.
Definition tmc9660_comm_interface.hpp:659
uint32_t getSpiRetryInterval() const noexcept
Get current retry interval for SPI_STATUS_NOT_READY.
Definition tmc9660_comm_interface.hpp:688
CommInterface & operator=(CommInterface &&)=default
bool transferTMCL(const TMCLFrame &tx, TMCLReply &reply, uint8_t address, TMCLReply *first_reply=nullptr, const TMCLFrame *second_command=nullptr) noexcept
Perform a full duplex TMCL transfer for parameter mode communication.
Definition tmc9660_comm_interface.hpp:556
CommInterface(CommInterface &&)=default
uint32_t spiRetryIntervalUs_
Delay interval in microseconds between retry attempts.
Definition tmc9660_comm_interface.hpp:723
bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept
Read GPIO pin signal state (input state).
Definition tmc9660_comm_interface.hpp:588
uint8_t getSpiRetryMaxCount() const noexcept
Get current maximum retry count for SPI_STATUS_NOT_READY.
Definition tmc9660_comm_interface.hpp:667
void delayUs(uint32_t us) noexcept
Delay execution for specified microseconds.
Definition tmc9660_comm_interface.hpp:760
bool gpioSetInactive(TMC9660CtrlPin pin) noexcept
Set GPIO pin to inactive state (convenience method).
Definition tmc9660_comm_interface.hpp:630
void debugLog(int level, const char *tag, const char *format, va_list args) noexcept
Debug logging function for detailed debugging information.
Definition tmc9660_comm_interface.hpp:737
bool pinActiveLevels_[4]
Pin active level configuration storage.
Definition tmc9660_comm_interface.hpp:705
CommInterface & operator=(const CommInterface &)=delete
void delayMs(uint32_t ms) noexcept
Delay execution for specified milliseconds.
Definition tmc9660_comm_interface.hpp:748
CommMode mode() const noexcept
Get the underlying communication mode used by this interface.
Definition tmc9660_comm_interface.hpp:536
bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept
Set GPIO pin signal state (output control).
Definition tmc9660_comm_interface.hpp:573
CRTP-based SPI implementation of TMC9660CommInterface.
Definition tmc9660_comm_interface.hpp:855
bool spiTransferBootloader(std::array< uint8_t, 5 > &tx, std::array< uint8_t, 5 > &rx) noexcept
Low-level SPI transfer for bootloader communication.
Definition tmc9660_comm_interface.hpp:900
SpiCommInterface & operator=(const SpiCommInterface &)=delete
bool spiTransferTMCL(std::array< uint8_t, 8 > &tx, std::array< uint8_t, 8 > &rx) noexcept
Low-level SPI transfer for TMCL parameter mode communication.
Definition tmc9660_comm_interface.hpp:884
CommMode mode() const noexcept
Get communication mode (always SPI for this interface)
Definition tmc9660_comm_interface.hpp:873
bool transferTMCL(const TMCLFrame &tx, TMCLReply &reply, uint8_t, TMCLReply *first_reply, const TMCLFrame *second_command) noexcept
Definition tmc9660_comm_interface.hpp:924
SpiCommInterface(bool rst_active_level, bool drv_en_active_level, bool wake_active_level, bool faultn_active_level) noexcept
Construct SPI communication interface with pin active level configuration.
Definition tmc9660_comm_interface.hpp:864
SpiCommInterface(const SpiCommInterface &)=delete
SpiCommInterface & operator=(SpiCommInterface &&)=default
~SpiCommInterface()=default
Protected destructor.
bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept
Set GPIO pin signal state for SPI interface.
Definition tmc9660_comm_interface.hpp:910
SpiCommInterface(SpiCommInterface &&)=default
bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept
Read GPIO pin signal state for SPI interface.
Definition tmc9660_comm_interface.hpp:920
CRTP-based UART implementation of TMC9660CommInterface.
Definition tmc9660_comm_interface.hpp:1066
bool uartReceiveTMCL(std::array< uint8_t, 9 > &data) noexcept
Receive raw 9-byte UART TMCL datagram for parameter mode communication.
Definition tmc9660_comm_interface.hpp:1104
CommMode mode() const noexcept
Get communication mode (always UART for this interface)
Definition tmc9660_comm_interface.hpp:1084
bool gpioSet(TMC9660CtrlPin pin, GpioSignal signal) noexcept
Set GPIO pin signal state for UART interface.
Definition tmc9660_comm_interface.hpp:1130
UartCommInterface(const UartCommInterface &)=delete
UartCommInterface(UartCommInterface &&)=default
bool gpioRead(TMC9660CtrlPin pin, GpioSignal &signal) noexcept
Read GPIO pin signal state for UART interface.
Definition tmc9660_comm_interface.hpp:1140
~UartCommInterface()=default
Protected destructor.
UartCommInterface & operator=(const UartCommInterface &)=delete
bool uartSendTMCL(const std::array< uint8_t, 9 > &data) noexcept
Send raw 9-byte UART TMCL datagram for parameter mode communication.
Definition tmc9660_comm_interface.hpp:1094
bool transferTMCL(const TMCLFrame &tx, TMCLReply &reply, uint8_t address, TMCLReply *first_reply, const TMCLFrame *second_command) noexcept
Definition tmc9660_comm_interface.hpp:1144
UartCommInterface & operator=(UartCommInterface &&)=default
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 tmc9660_comm_interface.hpp:1120
UartCommInterface(bool rst_active_level, bool drv_en_active_level, bool wake_active_level, bool faultn_active_level) noexcept
Construct UART communication interface with pin active level configuration.
Definition tmc9660_comm_interface.hpp:1075
Definition bootloader_config.hpp:9
static constexpr uint8_t tmclChecksum(const uint8_t *bytes, size_t n, uint8_t start=0) noexcept
Calculate 8-bit checksum using sum of bytes method.
Definition tmc9660_comm_interface.hpp:170
TMC9660CtrlPin
TMC9660 control pin identifiers with board-agnostic naming.
Definition tmc9660_comm_interface.hpp:126
@ DRV_EN
Driver enable input (pin 21) - Enables motor driver outputs.
@ FAULTN
FAULT output signal (pin 20) - Open drain fault indication.
@ WAKE
Wake input (pin 19) - Wake from hibernation mode.
@ RST
External System Reset Input (pin 22) - Active high reset signal.
CommMode
Supported physical communication modes for TMC9660.
Definition tmc9660_comm_interface.hpp:113
@ SPI
SPI (Serial Peripheral Interface) mode - 4-wire synchronous communication.
GpioSignal
GPIO signal states with board-agnostic naming.
Definition tmc9660_comm_interface.hpp:140
@ ACTIVE
Active signal state (logical high)
@ INACTIVE
Inactive signal state (logical low)
@ OK
Command executed successfully.
SPIStatus
SPI status codes as per TMC9660 Parameter Mode specification.
Definition tmc9660_comm_interface.hpp:151
@ FIRST_CMD
First command after initialization.
@ NOT_READY
System busy, resend datagram.
@ OK
Operation successful.
@ CHECKSUM_ERROR
Checksum verification failed.
Frame structure for TMCL commands.
Definition tmc9660_comm_interface.hpp:369
static TMCLFrame fromSpi(std::span< const uint8_t, 8 > in) noexcept
Deserialize an SPI buffer into a TMCLFrame without status check.
Definition tmc9660_comm_interface.hpp:417
uint8_t motor
Motor or bank identifier (BYTE 2, bits 20-23).
Definition tmc9660_comm_interface.hpp:372
static bool fromSpiChecked(std::span< const uint8_t, 8 > in, TMCLFrame &out_frame) noexcept
Deserialize an SPI buffer with status and checksum validation.
Definition tmc9660_comm_interface.hpp:435
static bool fromUart(std::span< const uint8_t, 9 > in, uint8_t expected_addr, TMCLFrame &out_frame) noexcept
Deserialize a UART buffer with address and checksum validation.
Definition tmc9660_comm_interface.hpp:451
uint32_t value
32-bit data value (BYTE 3-6, bits 24-55).
Definition tmc9660_comm_interface.hpp:373
static constexpr uint8_t calculateChecksum(const uint8_t *bytes, size_t n) noexcept
Calculate 8-bit checksum (sum of bytes).
Definition tmc9660_comm_interface.hpp:467
uint16_t type
Parameter or command type (BYTE 1-2, bits 8-19).
Definition tmc9660_comm_interface.hpp:371
uint8_t opcode
Operation code field (BYTE 0, bits 0-7).
Definition tmc9660_comm_interface.hpp:370
void toUart(uint8_t addr, std::span< uint8_t, 9 > out) const noexcept
Serialize frame into 9-byte UART buffer, including sync bit and checksum.
Definition tmc9660_comm_interface.hpp:396
void toSpi(std::span< uint8_t, 8 > out) const noexcept
Serialize frame into 8-byte SPI buffer.
Definition tmc9660_comm_interface.hpp:379
Reply structure returned by TMCL command operations.
Definition tmc9660_comm_interface.hpp:189
bool isOK() const noexcept
Definition tmc9660_comm_interface.hpp:199
uint32_t extractRawValue(size_t offset) const noexcept
Extract 32-bit value from raw bytes at specified offset (MSB-first)
Definition tmc9660_comm_interface.hpp:211
static bool fromSpi(std::span< const uint8_t, 8 > in, TMCLReply &r, uint8_t sent_opcode=0, uint16_t sent_type=0) noexcept
Decode and validate reply from SPI TMCL datagram.
Definition tmc9660_comm_interface.hpp:259
SPIStatus spi_status
SPI status byte.
Definition tmc9660_comm_interface.hpp:190
uint32_t value
Optional returned value.
Definition tmc9660_comm_interface.hpp:193
std::array< uint8_t, 8 > rawBytes
Raw SPI bytes (8-byte) or UART bytes mapped to 8-byte format.
Definition tmc9660_comm_interface.hpp:196
std::string getVersionString() const noexcept
Extract version string from GetVersion reply (Command 136, Type 0)
Definition tmc9660_comm_interface.hpp:227
uint8_t opcode
Echoed operation code.
Definition tmc9660_comm_interface.hpp:192
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
uint8_t status
TMCL status code (100=OK, 101=LOADED)
Definition tmc9660_comm_interface.hpp:191
#define TMC9660_LOG_DEBUG(comm_obj, level, tag,...)
Compile-time debug logging control for TMC9660 library.
Definition tmc9660_comm_interface.hpp:101