HF-TMC9660 Driver 0.1.0-dev
Hardware Agnostic C++ Driver for the TMC9660
Loading...
Searching...
No Matches
tmc9660_bootloader.cpp
Go to the documentation of this file.
1
6#ifndef TMC9660_BOOTLOADER_IMPL
7#define TMC9660_BOOTLOADER_IMPL
8
9#ifdef TMC9660_BOOTLOADER_HEADER_INCLUDED
11#else
13#endif
14
16#include <type_traits>
17
18using namespace tmc9660;
19
20// Helper type traits to check if CommType inherits from SpiCommInterface or UartCommInterface
21template<typename T>
22struct is_spi_interface : std::is_base_of<SpiCommInterface<T>, T> {};
23
24template<typename T>
25struct is_uart_interface : std::is_base_of<UartCommInterface<T>, T> {};
26
27// tmc9660_bootloader.cpp - Template implementation of TMC9660Bootloader
28// This file is included at the end of tmc9660_bootloader.hpp
29// DO NOT include this file directly - include tmc9660_bootloader.hpp instead
30//
31// Note: This file contains all template function implementations for the TMC9660Bootloader.
32// It follows the same pattern as other HardFOC drivers (AS5047U, BNO08x) where template
33// implementations are in a .cpp file that is included at the end of the header.
34
35// tmc9660_bootloader.tpp - Template implementation of TMC9660Bootloader
36// This file is included at the end of tmc9660_bootloader.hpp
37// DO NOT include this file directly - include tmc9660_bootloader.hpp instead
38
39// Note: The header (tmc9660_bootloader.hpp) must be included before this file
40// All necessary includes and namespace declarations are in the header
41
50template <typename CommType>
52 : comm_(comm), deviceAddr_(1), hostAddr_(255) {
53 // Default UART addresses: device=1, host=255 (as per spec)
54}
55
68template <typename CommType>
70 // Dispatch to appropriate protocol based on interface type
71 // Use compile-time type checking to only instantiate the appropriate function
72 if constexpr (std::is_base_of_v<SpiCommInterface<CommType>, CommType>) {
73 if (comm_.mode() == CommMode::SPI) {
74 return sendCommandSPI(cmd, value, reply);
75 }
76 }
77 if constexpr (std::is_base_of_v<UartCommInterface<CommType>, CommType>) {
78 if (comm_.mode() == CommMode::UART) {
79 return sendCommandUART(cmd, value, reply);
80 }
81 }
82 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Unsupported interface mode for bootloader");
83 return false;
84}
85
100template <typename CommType>
102 if (comm_.mode() != CommMode::SPI) {
103 return false;
104 }
105 // At this point, we know comm_.mode() == CommMode::SPI, so CommType must inherit from
106 // SpiCommInterface<CommType>. We cast to the CRTP base class to access the public method,
107 // which will use CRTP (static_cast<Derived*>(this)) internally to call the derived implementation.
108 // This is safe because:
109 // 1. Runtime check ensures mode() == SPI, so CommType is an SPI interface
110 // 2. CommType (e.g., Esp32SPITMC9660CommInterface) inherits from SpiCommInterface<CommType>
111 // 3. The CRTP base class method will properly dispatch to the derived implementation
113
114 TMC9660_LOG_DEBUG(comm_, 4, "TMC9660Bootloader",
115 "Sending SPI bootloader command: cmd=0x%02X, value=0x%08X", cmd, value);
116
117 std::array<uint8_t, 5> txBuf, rxBuf;
118 tx.toBuffer(txBuf);
119
120 // TMC9660_LOG_DEBUG(comm_,2, "TMC9660Bootloader", "[BL TX CMD ] %02X %02X %02X %02X %02X",
121 // txBuf[0], txBuf[1], txBuf[2], txBuf[3], txBuf[4]);
122
123 // Call the method directly on comm_ - since CommType inherits from SpiCommInterface<CommType>,
124 // it has spiTransferBootloader() available through inheritance. The CRTP base class method
125 // will internally use static_cast<Derived*>(this) to call the derived implementation.
126 // Step 1: Send the command
127 // NOTE: In SPI, the reply to THIS command will come in the NEXT transaction.
128 // The rxBuf here contains the reply from the PREVIOUS command (we ignore it).
129 if (!comm_.spiTransferBootloader(txBuf, rxBuf)) {
130 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to send SPI command (cmd=0x%02X)",
131 cmd);
132 return false;
133 }
134
135 // TMC9660_LOG_DEBUG(comm_,2, "TMC9660Bootloader", "[BL RX PREV] %02X %02X %02X %02X %02X
136 // (ignored)",
137 // rxBuf[0], rxBuf[1], rxBuf[2], rxBuf[3], rxBuf[4]);
138
139 // Step 2: Send NO_OP command to clock out the reply to our command
140 // This is standard SPI behavior - replies are delayed by one transaction.
141 std::array<uint8_t, 5> dummyTx = {
142 static_cast<uint8_t>(BootloaderCommand::NO_OP), // NO_OP command
143 0, 0, 0, 0 // Dummy value (all zeros)
144 };
145 std::array<uint8_t, 5> replyBuf;
146
147 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "[BL TX NOOP] %02X %02X %02X %02X %02X",
148 dummyTx[0], dummyTx[1], dummyTx[2], dummyTx[3], dummyTx[4]);
149
150 // Step 2: Send NO_OP to receive the reply to our command
151 if (!comm_.spiTransferBootloader(dummyTx, replyBuf)) {
152 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to receive SPI reply (cmd=0x%02X)",
153 cmd);
154 return false;
155 }
156
157 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "[BL RX RPLY] %02X %02X %02X %02X %02X",
158 replyBuf[0], replyBuf[1], replyBuf[2], replyBuf[3], replyBuf[4]);
159
160 // Parse reply from the second transaction
162
163 if (!rep.isOK()) {
164 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
165 "SPI command failed: status=0x%02X (cmd=0x%02X)", rep.status, cmd);
166 return false;
167 }
168
169 if (reply)
170 *reply = rep.value;
171
172 TMC9660_LOG_DEBUG(comm_, 4, "TMC9660Bootloader",
173 "SPI command successful: status=0x%02X, value=0x%08X (cmd=0x%02X)", rep.status,
174 rep.value, cmd);
175 return true;
176}
177
190template <typename CommType>
192 if (comm_.mode() != CommMode::UART) {
193 return false;
194 }
195 // At this point, we know comm_.mode() == CommMode::UART, so CommType must be a UART interface
196 // Call uartTransferBootloader directly on comm_ - if CommType is UART, it will have this method
197 // We use a helper function pointer to avoid template instantiation issues
198 BootloaderCommandUART tx{deviceAddr_, cmd, value};
199
200 TMC9660_LOG_DEBUG(comm_, 4, "TMC9660Bootloader",
201 "Sending UART bootloader command: cmd=0x%02X, value=0x%08X", cmd, value);
202
203 std::array<uint8_t, 8> txBuf, rxBuf;
204 tx.toBuffer(txBuf);
205
206 // Log transmitted bytes
207 // TMC9660_LOG_DEBUG(comm_,2, "TMC9660Bootloader", "[UART BL TX] %02X %02X %02X %02X %02X %02X
208 // %02X %02X",
209 // txBuf[0], txBuf[1], txBuf[2], txBuf[3], txBuf[4], txBuf[5], txBuf[6], txBuf[7]);
210
211 // UART: Send command and receive reply in same transaction (no delayed reply like SPI)
212 // Call the method directly on comm_ - since CommType inherits from UartCommInterface<CommType>,
213 // it has uartTransferBootloader() available through inheritance. The CRTP base class method
214 // will internally use static_cast<Derived*>(this) to call the derived implementation.
215 if (!comm_.uartTransferBootloader(txBuf, rxBuf)) {
216 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to send UART command (cmd=0x%02X)",
217 cmd);
218 return false;
219 }
220
221 // Log received bytes
222 // TMC9660_LOG_DEBUG(comm_,2, "TMC9660Bootloader", "[UART BL RX] %02X %02X %02X %02X %02X %02X
223 // %02X %02X",
224 // rxBuf[0], rxBuf[1], rxBuf[2], rxBuf[3], rxBuf[4], rxBuf[5], rxBuf[6], rxBuf[7]);
225
226 // Parse reply
228
229 // Verify CRC
230 if (!rep.verifyCRC(rxBuf)) {
231 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "UART reply CRC mismatch (cmd=0x%02X)", cmd);
232 return false;
233 }
234
235 if (!rep.isOK()) {
236 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
237 "UART command failed: status=0x%02X (cmd=0x%02X)", rep.status, cmd);
238 return false;
239 }
240
241 if (reply)
242 *reply = rep.value;
243
244 TMC9660_LOG_DEBUG(comm_, 4, "TMC9660Bootloader",
245 "UART command successful: status=0x%02X, value=0x%08X (cmd=0x%02X)", rep.status,
246 rep.value, cmd);
247 return true;
248}
249
250//==================================================
251// BASIC MEMORY OPERATIONS
252//==================================================
253
263template <typename CommType>
265 return sendCommand(static_cast<uint8_t>(BootloaderCommand::SET_BANK), bank);
266}
267
277template <typename CommType>
279 return sendCommand(static_cast<uint8_t>(BootloaderCommand::SET_ADDRESS), addr);
280}
281
291template <typename CommType>
293 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_8), v);
294}
295
305template <typename CommType>
307 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_16), v);
308}
309
319template <typename CommType>
321 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_32), v);
322}
323
324template <typename CommType>
325
327 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_8_INC), v);
328}
329
330template <typename CommType>
331
333 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_16_INC), v);
334}
335
336template <typename CommType>
337
339 return sendCommand(static_cast<uint8_t>(BootloaderCommand::WRITE_32_INC), v);
340}
341
353template <typename CommType>
355 for (size_t i = 0; i < count; ++i) {
356 if (!write32Inc(vals[i]))
357 return false;
358 }
359 return true;
360}
361
362template <typename CommType>
363
365 return sendCommand(static_cast<uint8_t>(BootloaderCommand::NO_OP), 0, reply);
366}
367
368//==================================================
369// OTP OPERATIONS
370//==================================================
371
384template <typename CommType>
386 if (!result) {
387 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "otpLoad: null result pointer");
388 return false;
389 }
390
391 uint32_t reply = 0;
392 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::OTP_LOAD), page, &reply)) {
393 return false;
394 }
395
397 return true;
398}
399
400template <typename CommType>
401
402bool TMC9660Bootloader<CommType>::otpLoad(uint8_t page, uint8_t* errorCount, uint8_t* pageTag) noexcept {
404 if (!otpLoad(page, &result)) {
405 return false;
406 }
407
408 if (errorCount)
409 *errorCount = result.errorCount;
410 if (pageTag)
411 *pageTag = result.pageTag;
412
413 return true;
414}
415
430template <typename CommType>
432 if (!result) {
433 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "otpBurn: null result pointer");
434 return false;
435 }
436
437 uint32_t value = (static_cast<uint32_t>(pageAddr) << 8) | page;
438 uint32_t reply = 0;
439
440 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::OTP_BURN), value, &reply)) {
441 return false;
442 }
443
444 // Check if the command returned an error status
445 if (reply == 0) {
446 // Success case - no error code
448 } else {
449 // Error case - parse error code from reply
450 int8_t error_code = static_cast<int8_t>(reply);
452 }
453
454 return true;
455}
456
457template <typename CommType>
458
461 if (!otpBurn(page, pageAddr, &result)) {
462 return false;
463 }
464
465 return result.isSuccess;
466}
467
497template <typename CommType>
499 uint32_t vdrvWaitMs) noexcept {
500 if (!result) {
501 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "otpBurnWithWorkaround: null result pointer");
502 return false;
503 }
504
505 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
506 "Starting OTP burn with Erratum 1 workaround (page=%d, addr=0x%02X)", page,
507 pageAddr);
508
509 // Step 1: Send SET_BANK, value 0
510 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 1: Setting bank to 0");
511 if (!setBank(0)) {
512 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set bank 0");
513 *result = OtpBurnResult::createError(OtpBurnError::BURN_PROCEDURE_FAILED);
514 return false;
515 }
516
517 // Step 2: Send SET_ADDRESS, value 0x4801B010
518 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 2: Setting address to 0x4801B010");
519 if (!setAddress(0x4801B010)) {
520 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set address 0x4801B010");
521 *result = OtpBurnResult::createError(OtpBurnError::BURN_PROCEDURE_FAILED);
522 return false;
523 }
524
525 // Step 3: Send READ_32
526 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 3: Reading 32-bit value");
528 if (!read32(&read_value)) {
529 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read 32-bit value");
530 *result = OtpBurnResult::createError(OtpBurnError::BURN_PROCEDURE_FAILED);
531 return false;
532 }
533
534 // Step 4: Clear bit 0 of the read value (0x00000001)
535 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 4: Clearing bit 0 (read=0x%08X)",
536 read_value);
537 uint32_t modified_value = read_value & ~0x00000001;
538
539 // Step 5: Send WRITE_32 with the modified read value
540 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 5: Writing modified value 0x%08X",
542 if (!write32(modified_value)) {
543 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write modified value");
544 *result = OtpBurnResult::createError(OtpBurnError::BURN_PROCEDURE_FAILED);
545 return false;
546 }
547
548 // Step 6: Wait for VDRV voltage to drop below 8.4V
549 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
550 "Step 6: Waiting %dms for VDRV voltage to drop below 8.4V", vdrvWaitMs);
551 comm_.delayMs(vdrvWaitMs);
552
553 // Step 7: Send OTP_BURN
554 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 7: Sending OTP_BURN command");
555 uint32_t value = (static_cast<uint32_t>(pageAddr) << 8) | page;
556 uint32_t reply = 0;
557
558 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::OTP_BURN), value, &reply)) {
559 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to send OTP_BURN command");
560 *result = OtpBurnResult::createError(OtpBurnError::BURN_PROCEDURE_FAILED);
561 return false;
562 }
563
564 // Parse the result
565 if (reply == 0) {
566 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "OTP burn command completed successfully");
568 } else {
569 int8_t error_code = static_cast<int8_t>(reply);
570 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "OTP burn command failed with error code: %d",
571 error_code);
573 }
574
575 return true;
576}
577
578template <typename CommType>
579
581 if (!result) {
582 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "checkOtpBurnStatus: null result pointer");
583 return false;
584 }
585
586 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
587 "Checking OTP burn status using Erratum 1 workaround");
588
589 // Step 1: Configure clock settings to have PLL active, SYS_CLK_DIV set to 3 (15MHz)
590 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
591 "Step 1: Configuring clock for 15MHz system clock");
592 if (!setBank(5)) { // CONFIG bank
593 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set CONFIG bank");
594 return false;
595 }
596
597 // Read current clock configuration
598 if (!setAddress(0x00020018)) { // Clock config offset
599 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set clock config address");
600 return false;
601 }
602
604 if (!read32(&clock_config)) {
605 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read clock configuration");
606 return false;
607 }
608
609 // Set SYS_CLK_DIV to 3 (15MHz system clock)
610 uint32_t modified_clock_config = (clock_config & ~0x03000000) | (3 << 24);
611 if (!write32(modified_clock_config)) {
612 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
613 "Failed to write modified clock configuration");
614 return false;
615 }
616
617 // Wait for clock configuration to take effect
618 comm_.delayMs(100);
619
620 // Step 2: Send SET_BANK, value 0
621 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 2: Setting bank to 0");
622 if (!setBank(0)) {
623 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set bank 0");
624 return false;
625 }
626
627 // Step 3: Send SET_ADDRESS, value 0x48020014
628 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 3: Setting address to 0x48020014");
629 if (!setAddress(0x48020014)) {
630 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set address 0x48020014");
631 return false;
632 }
633
634 // Step 4: Send READ_16
635 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 4: Reading 16-bit value");
637 if (!read16(&read_value)) {
638 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read 16-bit value");
639 return false;
640 }
641
642 // Step 5: Check if read value is 0x80 or 0x84 (successful burn)
643 *result = (read_value == 0x80) || (read_value == 0x84);
644 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "OTP burn status check: read=0x%04X, success=%s",
645 read_value, *result ? "true" : "false");
646
647 // Step 6: Set SYS_CLK_DIV back to 0 (40MHz)
648 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Step 6: Restoring clock to 40MHz");
649 if (!setBank(5)) { // CONFIG bank
650 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set CONFIG bank for clock restore");
651 return false;
652 }
653
654 if (!setAddress(0x00020018)) { // Clock config offset
655 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
656 "Failed to set clock config address for restore");
657 return false;
658 }
659
660 if (!write32(clock_config)) { // Restore original clock config
661 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
662 "Failed to restore original clock configuration");
663 return false;
664 }
665
666 // Wait for clock configuration to take effect
667 comm_.delayMs(100);
668
669 return true;
670}
671
672//==================================================
673// EXTERNAL MEMORY OPERATIONS
674//==================================================
675
676template <typename CommType>
677
679 uint32_t reply = 0;
680 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONFIGURED),
681 static_cast<uint32_t>(bank), &reply))
682 return false;
683
684 if (is_configured)
685 *is_configured = (reply != 0);
686
687 return true;
688}
689
690template <typename CommType>
691
693 uint32_t reply = 0;
694 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONNECTED),
695 static_cast<uint32_t>(bank), &reply))
696 return false;
697
698 if (is_connected)
699 *is_connected = (reply != 0);
700
701 return true;
702}
703
704template <typename CommType>
705
707 uint32_t reply = 0;
708 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_BUSY),
709 static_cast<uint32_t>(bank), &reply))
710 return false;
711
712 if (is_busy)
713 *is_busy = (reply != 0);
714
715 return true;
716}
717
718//==================================================
719// SPI FLASH OPERATIONS
720//==================================================
721
722template <typename CommType>
723
725 // Bits 31-28 = 0 (load), bits 27-24 = offset, bits 23-0 = data
726 uint32_t value = (static_cast<uint32_t>(offset & 0x0F) << 24) | (data & 0x00FFFFFF);
727 return sendCommand(static_cast<uint8_t>(BootloaderCommand::FLASH_SEND_CMD), value);
728}
729
730template <typename CommType>
731
733 // Bits 31-28 = 1 (read), bits 27-24 = offset
734 uint32_t value = (1U << 28) | (static_cast<uint32_t>(offset & 0x0F) << 24);
735 uint32_t reply = 0;
736 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::FLASH_SEND_CMD), value, &reply))
737 return false;
738
739 if (data)
740 *data = reply & 0x00FFFFFF; // Bits 23-0 contain the data
741
742 return true;
743}
744
745template <typename CommType>
746
748 // Bits 31-28 = 2 (send), bits 27-24 = number of bytes
749 uint32_t value = (2U << 28) | (static_cast<uint32_t>(numBytes & 0x0F) << 24);
750 return sendCommand(static_cast<uint8_t>(BootloaderCommand::FLASH_SEND_CMD), value);
751}
752
753template <typename CommType>
754
756 return sendCommand(static_cast<uint8_t>(BootloaderCommand::FLASH_ERASE_SECTOR),
757 address & 0x00FFFFFF);
758}
759
760template <typename CommType>
761
763 // Example from documentation: Read JEDEC ID
764 // 1. Load buffer with 0x9F, 0x00
765 if (!flashLoadBuffer(0, 0x009F0000))
766 return false;
767
768 // 2. Send 2 bytes
769 if (!flashSendDatagram(2))
770 return false;
771
772 // 3. Read buffer to get manufacturer ID (bits 15-8)
773 uint32_t data = 0;
774 if (!flashReadBuffer(0, &data))
775 return false;
776
777 if (manufacturerId)
778 *manufacturerId = static_cast<uint8_t>((data >> 8) & 0xFF);
779
780 return true;
781}
782
783//==================================================
784// RS485 CONFIGURATION
785//==================================================
786
787template <typename CommType>
789 uint8_t deviceAddr) noexcept {
790 // Byte 0: TX_EN pin, Byte 1: pre-delay, Byte 2: host address, Byte 3: device address
791 uint32_t value = (static_cast<uint32_t>(deviceAddr) << 24) |
792 (static_cast<uint32_t>(hostAddr) << 16) |
793 (static_cast<uint32_t>(preDelay) << 8) | txEnPin;
794
795 bool result = sendCommand(static_cast<uint8_t>(BootloaderCommand::BOOTSTRAP_RS485), value);
796
797 // Update internal addresses if successful
798 if (result) {
799 deviceAddr_ = deviceAddr;
800 hostAddr_ = hostAddr;
801 }
802
803 return result;
804}
805
806//==================================================
807// INFORMATION QUERIES
808//==================================================
809
821template <typename CommType>
823 return sendCommand(static_cast<uint8_t>(BootloaderCommand::GET_INFO),
824 static_cast<uint32_t>(query), value);
825}
826
836template <typename CommType>
838 if (!version) {
839 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getBootloaderVersion: null pointer");
840 return false;
841 }
842
843 uint32_t value;
844 if (!getInfo(InfoQuery::BL_VERSION, &value)) {
845 return false;
846 }
847
849 return true;
850}
851
852template <typename CommType>
853
855 if (!features) {
856 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getFeatures: null pointer");
857 return false;
858 }
859
860 uint32_t value;
861 if (!getInfo(InfoQuery::FEATURES, &value)) {
862 return false;
863 }
864
866 return true;
867}
868
869template <typename CommType>
870
872 if (!gitInfo) {
873 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getGitInfo: null pointer");
874 return false;
875 }
876
877 uint32_t value;
878 if (!getInfo(InfoQuery::GIT_INFO, &value)) {
879 return false;
880 }
881
882 *gitInfo = GitInfo::fromValue(value);
883 return true;
884}
885
886template <typename CommType>
887
889 if (!version) {
890 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getPartitionVersion: null pointer");
891 return false;
892 }
893
894 uint32_t value;
895 if (!getInfo(InfoQuery::PARTITION_VERSION, &value)) {
896 return false;
897 }
898
900 return true;
901}
902
955template <typename CommType>
957 bool failOnVerifyError) noexcept {
958 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Starting bootloader configuration application");
959
960 if (!setBank(5)) {
961 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set bank 5");
962 return false;
963 }
964 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Successfully set bank 5 (CONFIG memory bank)");
965
966 // LDO configuration (offset 0x00)
967 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring LDO settings");
968 if (!setAddress(bootaddr::LDO_CONFIG)) {
969 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set LDO config register");
970 return false;
971 }
972 uint16_t ldo = 0;
973 ldo |= static_cast<uint16_t>(cfg.ldo.vext1) & 0x3; // Bits 0-1: VEXT1
974 ldo |= (static_cast<uint16_t>(cfg.ldo.vext2) & 0x3) << 2; // Bits 2-3: VEXT2
975 ldo |= (static_cast<uint16_t>(cfg.ldo.slope_vext1) & 0x3) << 4; // Bits 4-5: SS_VEXT1
976 ldo |= (static_cast<uint16_t>(cfg.ldo.slope_vext2) & 0x3) << 6; // Bits 6-7: SS_VEXT2
977 ldo |= (cfg.ldo.ldo_short_fault ? 1u : 0u) << 8; // Bit 8: LDO_SHORT_FAULT
978 if (!write16(ldo)) {
979 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write LDO config (0x%04X)", ldo);
980 return false;
981 }
982 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "LDO configured (VEXT1: %d, VEXT2: %d)",
983 static_cast<int>(cfg.ldo.vext1), static_cast<int>(cfg.ldo.vext2));
984
985 // Verify LDO configuration was written correctly
986 if (!readAndVerify16(ldo, "LDO config")) {
987 if (failOnVerifyError) {
988 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "LDO configuration verification failed");
989 return false;
990 } else {
991 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
992 "⚠️ LDO configuration verification failed (continuing)");
993 }
994 }
995
996 // ⚠️ CRITICAL: Clock configuration (offset 0x18) - MUST BE DONE RIGHT BEFORE MOTOR CONTROL START!
997 // Clock reconfiguration takes time and the TMC9660 cannot respond during this process.
998 // This must happen just before starting motor control to ensure proper timing.
999 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1000 "Configuring clock settings (FINAL STEP BEFORE MOTOR CONTROL)");
1001 if (!setAddress(bootaddr::CLOCK_CONFIG)) {
1002 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set clock config register");
1003 return false;
1004 }
1005
1006 // Test: Read current clock configuration before writing
1007 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Reading current clock configuration...");
1009 if (!read32(&current_clock)) {
1010 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1011 "⚠️ Failed to read current clock config (continuing)");
1012 } else {
1013 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Current clock config: 0x%08X (PLL_STATUS=%s)",
1014 current_clock, (current_clock & (1u << 30)) ? "SET" : "CLEAR");
1015 }
1016 // ⚠️ CRITICAL: Bits 0-6 MUST be set to 99 (0x63) per datasheet requirement!
1017 // "RESERVED_1: Bits 0-6. Reserved. Must always stay set to 99."
1018 uint32_t clk = 99u; // Start with required reserved value
1019 clk |= (static_cast<uint32_t>(cfg.clock.use_external) & 0x1) << 8; // EXT_NOT_INT [0:6]
1020 clk |= (static_cast<uint32_t>(cfg.clock.xtal_drive) & 0x7) << 9; // XTAL_CFG [7:9]
1021 clk |= (cfg.clock.xtal_boost ? 1u : 0u) << 12; // XTAL_BOOST [12:12]
1022 clk |= (static_cast<uint32_t>(cfg.clock.ext_source_type) & 0x1) << 13; // EXT_NOT_XTAL [13:13]
1023 clk |= (static_cast<uint32_t>(cfg.clock.pll_selection) & 0x3) << 16; // PLL_OUT_SEL [16:17]
1024 clk |= (cfg.clock.rdiv & 0x1F) << 18; // RDIV [18:22]
1025 clk |= (static_cast<uint32_t>(cfg.clock.sysclk_div) & 0x3) << 23; // SYS_CLK_DIV [23:24]
1026 if (!write32(clk)) {
1027 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write clock config (0x%08X)", clk);
1028 return false;
1029 }
1030 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1031 "Clock configuration written (external: %s, xtal_drive: %d, rdiv: %d)",
1032 cfg.clock.use_external == bootcfg::ClockSource::External ? "yes" : "no",
1033 static_cast<int>(cfg.clock.xtal_drive), cfg.clock.rdiv);
1034
1035 // ⚠️ CRITICAL: Wait for clock reconfiguration to complete
1036 // According to datasheet: "Any change to the clock configuration takes multiple milliseconds to
1037 // apply. The TMC9660 cannot respond to commands during clock reconfiguration."
1038 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1039 "Waiting for clock reconfiguration to complete...");
1040
1041 // Fixed delay to allow clock reconfiguration to complete
1042 // The TMC9660 cannot respond to commands during this process
1043 comm_.delayMs(100); // 100ms should be sufficient for clock reconfiguration
1044
1045 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Clock reconfiguration delay completed");
1046
1047 // Verify clock config was written correctly and PLL_STATUS is SET
1049 if (!read32(&actual_clock)) {
1050 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read back clock config");
1051 if (failOnVerifyError) {
1052 return false;
1053 } else {
1054 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ Clock config read failed (continuing)");
1055 }
1056 } else {
1057 // First check: PLL_STATUS must be SET after clock reconfiguration
1058 if (!(actual_clock & (1u << 30))) {
1059 if (failOnVerifyError) {
1060 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1061 "❌ PLL_STATUS not set after clock reconfiguration: 0x%08X", actual_clock);
1062 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1063 " Clock reconfiguration may have failed or needs more time");
1064 return false;
1065 } else {
1066 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1067 "⚠️ PLL_STATUS not set after clock reconfiguration: 0x%08X (continuing)",
1068 actual_clock);
1069 }
1070 } else {
1071 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "✅ PLL_STATUS confirmed SET: 0x%08X",
1072 actual_clock);
1073 }
1074
1075 // Second check: Verify the actual configuration matches expected (excluding PLL_STATUS)
1077 uint32_t actual_config = actual_clock & ~(1u << 30); // Mask out PLL_STATUS bit for comparison
1078
1080 if (failOnVerifyError) {
1081 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1082 "❌ Clock config verification failed: expected=0x%08X, actual=0x%08X",
1084 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1085 " Full actual value: 0x%08X (PLL_STATUS=%s)", actual_clock,
1086 (actual_clock & (1u << 30)) ? "SET" : "CLEAR");
1087 return false;
1088 } else {
1090 comm_, 1, "TMC9660Bootloader",
1091 "⚠️ Clock config verification failed: expected=0x%08X, actual=0x%08X (continuing)",
1093 }
1094 } else {
1095 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1096 "✅ Clock config verified: 0x%08X (PLL_STATUS=%s)", actual_clock,
1097 (actual_clock & (1u << 30)) ? "SET" : "CLEAR");
1098 }
1099 }
1100
1101 // UART addresses (offset 0x02)
1102 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring UART addresses");
1103 if (!setAddress(bootaddr::UART_ADDR)) {
1104 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set UART address register");
1105 return false;
1106 }
1107 uint16_t addr_word = static_cast<uint16_t>(cfg.uart.device_address) |
1108 (static_cast<uint16_t>(cfg.uart.host_address) << 8);
1109 if (!write16(addr_word)) {
1110 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write UART address word (0x%04X)",
1111 addr_word);
1112 return false;
1113 }
1114 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1115 "UART addresses configured (device: %d, host: %d)", cfg.uart.device_address,
1116 cfg.uart.host_address);
1117
1118 // Verify UART addresses were written correctly
1119 if (!readAndVerify16(addr_word, "UART addresses")) {
1120 if (failOnVerifyError) {
1121 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "UART addresses verification failed");
1122 return false;
1123 } else {
1124 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1125 "⚠️ UART addresses verification failed (continuing)");
1126 }
1127 }
1128
1129 // RS485 delays
1130 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring RS485 delays");
1131 if (!setAddress(bootaddr::RS485_DELAY)) {
1132 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set RS485 delay register");
1133 return false;
1134 }
1135 uint16_t rs485 = static_cast<uint16_t>(cfg.rs485.txen_post_delay) |
1136 (static_cast<uint16_t>(cfg.rs485.txen_pre_delay) << 8);
1137 if (!write16(rs485)) {
1138 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write RS485 delay config (0x%08X)",
1139 rs485);
1140 return false;
1141 }
1142 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "RS485 delays configured (pre: %d, post: %d)",
1143 cfg.rs485.txen_pre_delay, cfg.rs485.txen_post_delay);
1144
1145 // Verify RS485 delays were written correctly
1146 if (!readAndVerify16(rs485, "RS485 delays")) {
1147 if (failOnVerifyError) {
1148 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "RS485 delays verification failed");
1149 return false;
1150 } else {
1151 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1152 "⚠️ RS485 delays verification failed (continuing)");
1153 }
1154 }
1155
1156 // Communication configuration (UART/SPI/RS485) - SHARED REGISTER with SPI Flash
1157 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1158 "Configuring communication settings (shared register with SPI flash)");
1159 if (!setAddress(bootaddr::COMM_CONFIG)) {
1160 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set communication config register");
1161 return false;
1162 }
1163
1164 // Read current communication config before overwriting
1166 if (read16(&current_comm)) {
1167 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Current COMM_CONFIG before overwrite: 0x%04X",
1168 current_comm);
1169 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 0 (UART disabled): %d",
1170 (current_comm >> 0) & 0x1);
1171 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 1 (SPI disabled): %d",
1172 (current_comm >> 1) & 0x1);
1173 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 2 (SPI SELECT): %d",
1174 (current_comm >> 2) & 0x1);
1175 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 3 (UART RX): %d",
1176 (current_comm >> 3) & 0x1);
1177 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 4 (UART TX): %d",
1178 (current_comm >> 4) & 0x1);
1179 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bits 5-6 (UART TXEN): %d",
1180 (current_comm >> 5) & 0x3);
1181 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bits 7-9 (UART BAUD): %d",
1182 (current_comm >> 7) & 0x7);
1183 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " Current Bit 10 (SPI0 SCK): %d",
1184 (current_comm >> 10) & 0x1);
1185 } else {
1186 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1187 "⚠️ Failed to read current COMM_CONFIG (continuing)");
1188 }
1189
1190 // Build the shared register value (offset 6) - 16-bit write (highest bit is 10)
1191 uint16_t comm = 0;
1192
1193 // UART Configuration
1194 comm |= (cfg.uart.disable_uart ? 1u : 0u) << 0; // BL_DISABLE_UART (bit 0)
1195 comm |= (static_cast<uint16_t>(cfg.uart.rx_pin) & 0x1)
1196 << 3; // BL_UART_RX (bit 3): 0=GPIO7, 1=GPIO1
1197 comm |= (static_cast<uint16_t>(cfg.uart.tx_pin) & 0x1)
1198 << 4; // BL_UART_TX (bit 4): 0=GPIO6, 1=GPIO0
1199 comm |= (static_cast<uint16_t>(cfg.rs485.txen_pin) & 0x3) << 5; // BL_UART_TXEN (bits 5-6)
1200 comm |= (static_cast<uint16_t>(cfg.uart.baud_rate) & 0x7) << 7; // BL_UART_BAUDRATE (bits 7-9)
1201
1202 // SPI Configuration
1203 comm |= (cfg.spiComm.disable_spi ? 1u : 0u) << 1; // BL_DISABLE_SPI (bit 1)
1204
1205 // ============================================================================
1206 // CRITICAL: Handle shared BL_SPI_SELECT (bit 2) and BL_SPI0_SCK (bit 10)
1207 // ============================================================================
1208 // These bits are SHARED between bootloader comm (offset 6) and flash (offset 10).
1209 // Per datasheet: "If both flash and bootloader SPI communication are used,
1210 // they must always be on separate SPI interfaces."
1211 //
1212 // Priority rules:
1213 // 1. If bootloader SPI enabled: bootloader config controls BL_SPI_SELECT
1214 // 2. If bootloader SPI disabled but flash enabled: flash config controls it
1215 // 3. Bootloader communication ALWAYS takes priority over flash
1216 // ============================================================================
1217
1218 // Determine which physical SPI interface each component wants to use
1219 bool bootloader_uses_spi0 = !cfg.spiComm.disable_spi &&
1220 (cfg.spiComm.boot_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0);
1221 bool flash_uses_spi0 = cfg.spiFlash.enable_flash &&
1222 (cfg.spiFlash.flash_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0);
1223
1224 // Validate configuration: both cannot use the same SPI interface
1225 if (!cfg.spiComm.disable_spi && cfg.spiFlash.enable_flash) {
1227 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "⚠️ CONFIGURATION ERROR:");
1228 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1229 " Both bootloader and flash are configured for %s!",
1230 bootloader_uses_spi0 ? "SPI0" : "SPI1");
1231 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1232 " Per datasheet: they MUST be on separate interfaces");
1233 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", " Current config:");
1235 comm_, 0, "TMC9660Bootloader", " - Bootloader SPI: %s (disable_spi=%d)",
1236 cfg.spiComm.boot_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0 ? "SPI0" : "SPI1",
1237 cfg.spiComm.disable_spi ? 1 : 0);
1239 comm_, 0, "TMC9660Bootloader", " - Flash SPI: %s (enable_flash=%d)",
1240 cfg.spiFlash.flash_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0 ? "SPI0" : "SPI1",
1241 cfg.spiFlash.enable_flash ? 1 : 0);
1242 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1243 " Fix: Set one to SPI0 and the other to SPI1");
1244 return false;
1245 }
1246 }
1247
1248 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "SPI interface allocation:");
1249 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - Bootloader: %s (enabled=%d)",
1250 bootloader_uses_spi0 ? "SPI0" : "SPI1", !cfg.spiComm.disable_spi ? 1 : 0);
1251 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - Flash: %s (enabled=%d)",
1252 flash_uses_spi0 ? "SPI0" : "SPI1", cfg.spiFlash.enable_flash ? 1 : 0);
1253
1254 // ============================================================================
1255 // Set BL_SPI_SELECT (bit 2) with correct bit inversion logic
1256 // ============================================================================
1257 // CRITICAL: BL_SPI_SELECT has OPPOSITE meanings for bootloader vs flash!
1258 // For Bootloader: 0=SPI0, 1=SPI1
1259 // For Flash: 0=SPI1, 1=SPI0 (inverted!)
1260 //
1261 // Priority: Bootloader config takes precedence over flash config
1262 // ============================================================================
1264 if (!cfg.spiComm.disable_spi) {
1265 // Bootloader SPI enabled - use bootloader's interface selection directly
1267 (cfg.spiComm.boot_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0) ? 0 : 1;
1268 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - BL_SPI_SELECT=%d (bootloader on %s)",
1269 bl_spi_select_bit, bootloader_uses_spi0 ? "SPI0" : "SPI1");
1270 } else if (cfg.spiFlash.enable_flash) {
1271 // Only flash enabled - use INVERTED flash interface selection
1273 (cfg.spiFlash.flash_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0) ? 1 : 0;
1274 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1275 " - BL_SPI_SELECT=%d (flash on %s, inverted bit)", bl_spi_select_bit,
1276 flash_uses_spi0 ? "SPI0" : "SPI1");
1277 } else {
1278 // Neither enabled - default to 0
1280 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1281 " - BL_SPI_SELECT=%d (default, neither enabled)", bl_spi_select_bit);
1282 }
1283 comm |= (bl_spi_select_bit & 0x1) << 2;
1284
1285 // Handle shared BL_SPI0_SCK bit (bit 10) - only relevant if SPI0 is being used
1287 comm |= (static_cast<uint16_t>(cfg.spiComm.spi0_sck_pin) & 0x1)
1288 << 10; // BL_SPI0_SCK (bit 10): 0=GPIO6, 1=GPIO11
1289 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1290 "BL_SPI0_SCK set from bootloader config (SPI0_SCK pin: %d)",
1291 static_cast<int>(cfg.spiComm.spi0_sck_pin));
1292 } else if (flash_uses_spi0) {
1293 // Flash uses SPI0, so set SCK pin based on flash config
1294 comm |= (static_cast<uint32_t>(cfg.spiFlash.spi0_sck_pin) & 0x1) << 10;
1295 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1296 "BL_SPI0_SCK set from flash config (SPI0_SCK pin: %d)",
1297 static_cast<int>(cfg.spiFlash.spi0_sck_pin));
1298 }
1299 // If neither uses SPI0, leave BL_SPI0_SCK as 0 (default)
1300
1301 // Debug: Show detailed bit breakdown of NEW configuration
1302 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "NEW COMM_CONFIG to be written: 0x%04X", comm);
1303 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 0 (UART disabled): %d",
1304 (comm >> 0) & 0x1);
1305 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 1 (SPI disabled): %d",
1306 (comm >> 1) & 0x1);
1307 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 2 (SPI SELECT): %d",
1308 (comm >> 2) & 0x1);
1309 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 3 (UART RX): %d", (comm >> 3) & 0x1);
1310 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 4 (UART TX): %d", (comm >> 4) & 0x1);
1311 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bits 5-6 (UART TXEN): %d",
1312 (comm >> 5) & 0x3);
1313 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bits 7-9 (UART BAUD): %d (expected: %d)",
1314 (comm >> 7) & 0x7, static_cast<int>(cfg.uart.baud_rate));
1315 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", " New Bit 10 (SPI0 SCK): %d",
1316 (comm >> 10) & 0x1);
1317
1318 if (!write16(comm)) {
1319 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1320 "Failed to write communication config (0x%04X)", comm);
1321 return false;
1322 }
1323 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Communication config written (0x%04X):", comm);
1324 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1325 " UART: disabled=%s, RX=GPIO%d, TX=GPIO%d, TXEN=%s, baud=%d",
1326 cfg.uart.disable_uart ? "yes" : "no",
1327 cfg.uart.rx_pin == tmc9660::bootcfg::UartRxPin::GPIO7 ? 7 : 1,
1328 cfg.uart.tx_pin == tmc9660::bootcfg::UartTxPin::GPIO6 ? 6 : 0,
1329 cfg.rs485.txen_pin == tmc9660::bootcfg::RS485TxEnPin::GPIO8 ? "GPIO8"
1330 : cfg.rs485.txen_pin == tmc9660::bootcfg::RS485TxEnPin::GPIO2 ? "GPIO2"
1331 : "None",
1332 static_cast<int>(cfg.uart.baud_rate));
1334 comm_, 3, "TMC9660Bootloader",
1335 " SPI: disabled=%s, bootloader_SPI0=%s, flash_SPI0=%s, SPI0_SCK=GPIO%d",
1336 cfg.spiComm.disable_spi ? "yes" : "no", bootloader_uses_spi0 ? "yes" : "no",
1337 flash_uses_spi0 ? "yes" : "no",
1339 ? (cfg.spiComm.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6 ? 6 : 11)
1341 ? (cfg.spiFlash.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6 ? 6 : 11)
1342 : 0);
1343
1344 // Verify communication config was written correctly
1345 if (!readAndVerify16(comm, "Communication config")) {
1346 if (failOnVerifyError) {
1347 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Communication config verification failed");
1348 return false;
1349 } else {
1350 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1351 "⚠️ Communication config verification failed (continuing)");
1352 }
1353 }
1354
1355 // ============================================================================
1356 // SPI Flash Configuration (offset 10)
1357 // ============================================================================
1358 // NOTE: This register mirrors some bits from COMM_CONFIG (offset 6):
1359 // - Bit 1: SPI_IFACE_SELECT (mirrors BL_SPI_SELECT from COMM_CONFIG bit 2)
1360 // - Bit 2: SPI0_SCK_SELECT (mirrors BL_SPI0_SCK from COMM_CONFIG bit 10)
1361 //
1362 // These bits were determined above based on priority:
1363 // - If bootloader SPI enabled: bootloader config takes priority
1364 // - If bootloader SPI disabled but flash enabled: flash config is used
1365 //
1366 // This register (offset 10) contains:
1367 // - Bit 0: SPI_FLASH_EN
1368 // - Bit 1: SPI_IFACE_SELECT (mirrors COMM_CONFIG bit 2)
1369 // - Bit 2: SPI0_SCK_SELECT (mirrors COMM_CONFIG bit 10)
1370 // - Bits 3-7: SPI_FLASH_CS (chip select pin)
1371 // - Bits 8-11: SPI_FLASH_FREQ (frequency divider)
1372 // ============================================================================
1373
1374 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Configuring SPI flash settings");
1375 if (!setAddress(bootaddr::SPI_FLASH)) {
1376 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set SPI flash register");
1377 return false;
1378 }
1379
1380 // Determine SCK pin bit value (mirrors BL_SPI0_SCK from COMM_CONFIG bit 10)
1381 uint8_t sck_pin_bit = 0;
1383 sck_pin_bit = (cfg.spiComm.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6) ? 0 : 1;
1384 } else if (flash_uses_spi0) {
1385 sck_pin_bit = (cfg.spiFlash.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6) ? 0 : 1;
1386 }
1387 // If neither uses SPI0, sck_pin_bit remains 0 (default)
1388
1389 uint16_t flash = (cfg.spiFlash.enable_flash ? 1u : 0u); // SPI_FLASH_EN (bit 0)
1390 flash |= (static_cast<uint16_t>(bl_spi_select_bit & 0x1))
1391 << 1; // SPI_IFACE_SELECT (bit 1, mirrors COMM_CONFIG bit 2)
1392 flash |= (static_cast<uint16_t>(sck_pin_bit & 0x1))
1393 << 2; // SPI0_SCK_SELECT (bit 2, mirrors COMM_CONFIG bit 10)
1394 flash |= (static_cast<uint16_t>(cfg.spiFlash.cs_pin & 0x1F)) << 3; // SPI_FLASH_CS (bits 3-7)
1395 flash |= (static_cast<uint16_t>(cfg.spiFlash.freq_div) & 0xF) << 8; // SPI_FLASH_FREQ (bits 8-11)
1396
1397 if (!write16(flash)) {
1398 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write SPI flash config (0x%04X)",
1399 flash);
1400 return false;
1401 }
1402
1403 // Determine which interface and SCK pin are actually being used (from COMM_CONFIG)
1404 const char* actual_iface = flash_uses_spi0 ? "SPI0" : "SPI1";
1405 const char* actual_sck =
1407 ? (cfg.spiFlash.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6 ? "GPIO6" : "GPIO11")
1408 : "N/A";
1409
1410 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "SPI flash config written: 0x%04X", flash);
1411 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - SPI_FLASH_EN (bit 0): %d",
1412 cfg.spiFlash.enable_flash ? 1 : 0);
1413 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1414 " - SPI_IFACE_SELECT (bit 1): %d (mirrors COMM_CONFIG bit 2, %s)",
1416 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1417 " - SPI0_SCK_SELECT (bit 2): %d (mirrors COMM_CONFIG bit 10, %s)", sck_pin_bit,
1418 actual_sck);
1419 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - CS_PIN (bits 3-7): GPIO%d",
1420 cfg.spiFlash.cs_pin);
1421 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - FREQ_DIV (bits 8-11): Div%d",
1422 1 << static_cast<int>(cfg.spiFlash.freq_div));
1423
1424 // Verify SPI flash config was written correctly
1425 // UART mode: has an issue if address not reset
1426 if (comm_.mode() == CommMode::UART) {
1427 if (!setAddress(bootaddr::SPI_FLASH)) {
1428 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1429 "Failed to reset SPI flash register address for verification");
1431 return false;
1432 }
1433 }
1434
1435 if (!readAndVerify16(flash, "SPI flash config")) {
1436 if (failOnVerifyError) {
1437 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "SPI flash config verification failed");
1438 return false;
1439 } else {
1440 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1441 "⚠️ SPI flash config verification failed (continuing)");
1442 }
1443 }
1444
1445 // I2C EEPROM configuration
1446 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring I2C EEPROM settings");
1447 if (!setAddress(bootaddr::I2C_CONFIG)) {
1448 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set I2C config register");
1449 return false;
1450 }
1451 uint16_t i2c = (cfg.i2c.enable_eeprom ? 1u : 0u); // I2C_EN (bit 0)
1452 i2c |= (static_cast<uint16_t>(cfg.i2c.sda_pin) & 0x3) << 1; // I2C_SDA (bits 1-2)
1453 i2c |= (static_cast<uint16_t>(cfg.i2c.scl_pin) & 0x3) << 3; // I2C_SCL (bits 3-4)
1454 i2c |= (static_cast<uint16_t>(cfg.i2c.address_bits & 0x7)) << 5; // I2C_ADDR_BITS (bits 5-7)
1455 i2c |= (static_cast<uint16_t>(cfg.i2c.freq_code) & 0x7) << 8; // I2C_FREQ (bits 8-10)
1456 if (!write16(i2c)) {
1457 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write I2C config (0x%04X)", i2c);
1458 return false;
1459 }
1460 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1461 "I2C EEPROM configured (enabled: %s, SDA: %d, SCL: %d, addr_bits: %d)",
1462 cfg.i2c.enable_eeprom ? "yes" : "no", static_cast<int>(cfg.i2c.sda_pin),
1463 static_cast<int>(cfg.i2c.scl_pin), cfg.i2c.address_bits);
1464
1465 // Verify I2C config was written correctly
1466 // UART mode: address has an issue if not reset
1467 if (comm_.mode() == CommMode::UART) {
1468 if (!setAddress(bootaddr::I2C_CONFIG)) {
1469 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1470 "Failed to reset I2C config register address for verification");
1472 return false;
1473 }
1474 }
1475
1476 if (!readAndVerify16(i2c, "I2C config")) {
1477 if (failOnVerifyError) {
1478 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "I2C config verification failed");
1479 return false;
1480 } else {
1481 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1482 "⚠️ I2C config verification failed (continuing)");
1483 }
1484 }
1485
1486 // GPIO configuration (according to datasheet specification)
1487 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring GPIO settings");
1488
1489 // GPIO output levels for GPIOs 0-15 (offset 14)
1490 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring GPIO output levels (GPIOs 0-15)");
1491 if (!setAddress(bootaddr::GPIO_OUT)) {
1492 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set GPIO output register");
1493 return false;
1494 }
1495 if (!write16(cfg.gpio.outputMask_0_15)) {
1496 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write GPIO output mask (0x%04X)",
1497 cfg.gpio.outputMask_0_15);
1498 return false;
1499 }
1500 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1501 "GPIO output levels configured (GPIOs 0-15: 0x%04X)", cfg.gpio.outputMask_0_15);
1502
1503 // Verify GPIO output levels were written correctly
1504 if (!readAndVerify16(cfg.gpio.outputMask_0_15, "GPIO output levels")) {
1505 if (failOnVerifyError) {
1506 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "GPIO output levels verification failed");
1507 return false;
1508 } else {
1509 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1510 "⚠️ GPIO output levels verification failed (continuing)");
1511 }
1512 }
1513
1514 // GPIO direction (output enable) for GPIOs 0-15 (offset 16)
1515 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring GPIO direction (GPIOs 0-15)");
1516 if (!setAddress(bootaddr::GPIO_DIR)) {
1517 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set GPIO direction register");
1518 return false;
1519 }
1520 if (!write16(cfg.gpio.directionMask_0_15)) {
1521 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write GPIO direction mask (0x%04X)",
1522 cfg.gpio.directionMask_0_15);
1523 return false;
1524 }
1525 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "GPIO direction configured (GPIOs 0-15: 0x%04X)",
1526 cfg.gpio.directionMask_0_15);
1527
1528 // Verify GPIO direction was written correctly
1529 if (!readAndVerify16(cfg.gpio.directionMask_0_15, "GPIO direction")) {
1530 if (failOnVerifyError) {
1531 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "GPIO direction verification failed");
1532 return false;
1533 } else {
1534 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1535 "⚠️ GPIO direction verification failed (continuing)");
1536 }
1537 }
1538
1539 // GPIO pull-up enable for GPIOs 0-15 (offset 18)
1540 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring GPIO pull-up (GPIOs 0-15)");
1541 if (!setAddress(bootaddr::GPIO_PU)) {
1542 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set GPIO pull-up register");
1543 return false;
1544 }
1545 if (!write16(cfg.gpio.pullUpMask_0_15)) {
1546 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write GPIO pull-up mask (0x%04X)",
1547 cfg.gpio.pullUpMask_0_15);
1548 return false;
1549 }
1550 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "GPIO pull-up configured (GPIOs 0-15: 0x%04X)",
1551 cfg.gpio.pullUpMask_0_15);
1552
1553 // Verify GPIO pull-up was written correctly
1554 if (!readAndVerify16(cfg.gpio.pullUpMask_0_15, "GPIO pull-up")) {
1555 if (failOnVerifyError) {
1556 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "GPIO pull-up verification failed");
1557 return false;
1558 } else {
1559 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1560 "⚠️ GPIO pull-up verification failed (continuing)");
1561 }
1562 }
1563
1564 // GPIO pull-down enable for GPIOs 0-15 (offset 20)
1565 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring GPIO pull-down (GPIOs 0-15)");
1566 if (!setAddress(bootaddr::GPIO_PD)) {
1567 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set GPIO pull-down register");
1568 return false;
1569 }
1570 if (!write16(cfg.gpio.pullDownMask_0_15)) {
1571 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write GPIO pull-down mask (0x%04X)",
1572 cfg.gpio.pullDownMask_0_15);
1573 return false;
1574 }
1575 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "GPIO pull-down configured (GPIOs 0-15: 0x%04X)",
1576 cfg.gpio.pullDownMask_0_15);
1577
1578 // Verify GPIO pull-down was written correctly
1579 if (!readAndVerify16(cfg.gpio.pullDownMask_0_15, "GPIO pull-down")) {
1580 if (failOnVerifyError) {
1581 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "GPIO pull-down verification failed");
1582 return false;
1583 } else {
1584 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1585 "⚠️ GPIO pull-down verification failed (continuing)");
1586 }
1587 }
1588
1589 // GPIO extended configuration (offset 22) - GPIOs 16-18 + analog GPIOs 2-5
1590 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1591 "Configuring GPIO extended settings (GPIOs 16-18 + analog 2-5)");
1592 if (!setAddress(bootaddr::GPIO_EXT)) {
1593 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set GPIO extended register");
1594 return false;
1595 }
1596 uint16_t gpio_ext = 0;
1597 gpio_ext |= (cfg.gpio.outputMask_16_18 & 0x7); // Bits 0-2: GPIO16-18 output levels
1598 gpio_ext |= (cfg.gpio.directionMask_16_18 & 0x7) << 3; // Bits 3-5: GPIO16-18 direction
1599 gpio_ext |= (cfg.gpio.pullDownMask_16_18 & 0x7) << 6; // Bits 6-8: GPIO16-18 pull-down
1600 gpio_ext |= (cfg.gpio.pullUpMask_16_18 & 0x7) << 9; // Bits 9-11: GPIO16-18 pull-up
1601 gpio_ext |= (cfg.gpio.analogMask_2_5 & 0xF) << 12; // Bits 12-15: GPIO2-5 analog enable
1602 if (!write16(gpio_ext)) {
1603 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1604 "Failed to write GPIO extended config (0x%04X)", gpio_ext);
1605 return false;
1606 }
1607 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1608 "GPIO extended configured (output_16_18:0x%X, dir_16_18:0x%X, pd_16_18:0x%X, "
1609 "pu_16_18:0x%X, analog_2_5:0x%X)",
1610 cfg.gpio.outputMask_16_18, cfg.gpio.directionMask_16_18,
1611 cfg.gpio.pullDownMask_16_18, cfg.gpio.pullUpMask_16_18,
1612 cfg.gpio.analogMask_2_5);
1613
1614 // Verify GPIO extended config was written correctly
1615 if (!readAndVerify16(gpio_ext, "GPIO extended config")) {
1616 if (failOnVerifyError) {
1617 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "GPIO extended config verification failed");
1618 return false;
1619 } else {
1620 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1621 "⚠️ GPIO extended config verification failed (continuing)");
1622 }
1623 }
1624
1625 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "GPIO configuration completed");
1626
1627 // Hall encoder and ABN encoder 1 configuration (offset 0x20 - shared register)
1628 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1629 "Configuring Hall encoder and ABN encoder 1 settings");
1630 if (!setAddress(bootaddr::HALL_CONFIG)) {
1631 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set Hall/ABN1 config register");
1632 return false;
1633 }
1634 uint16_t hall_abn1 = 0;
1635 // Hall encoder configuration
1636 hall_abn1 |= (cfg.hall.enable ? 1u : 0u); // Bit 0: HALL_ENABLE
1637 hall_abn1 |= (static_cast<uint16_t>(cfg.hall.u_pin) & 0x3) << 4; // Bits 4-5: HALL_U
1638 hall_abn1 |= (static_cast<uint16_t>(cfg.hall.v_pin) & 0x3) << 6; // Bits 6-7: HALL_V
1639 hall_abn1 |= (static_cast<uint16_t>(cfg.hall.w_pin) & 0x3) << 8; // Bits 8-9: HALL_W
1640 // ABN encoder 1 configuration
1641 hall_abn1 |= (cfg.abn1.enable ? 1u : 0u) << 1; // Bit 1: ABN1_ENABLE
1642 hall_abn1 |= (static_cast<uint16_t>(cfg.abn1.a_pin) & 0x3) << 10; // Bits 10-11: ABN1_A
1643 hall_abn1 |= (static_cast<uint16_t>(cfg.abn1.b_pin) & 0x3) << 12; // Bits 12-13: ABN1_B
1644 hall_abn1 |= (static_cast<uint16_t>(cfg.abn1.n_pin) & 0x3) << 14; // Bits 14-15: ABN1_N
1645 if (!write16(hall_abn1)) {
1646 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write Hall/ABN1 config (0x%08X)",
1647 hall_abn1);
1648 return false;
1649 }
1650 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1651 "Hall/ABN1 configured - Hall(enabled:%s, U:%d, V:%d, W:%d), ABN1(enabled:%s, "
1652 "A:%d, B:%d, N:%d)",
1653 cfg.hall.enable ? "yes" : "no", static_cast<int>(cfg.hall.u_pin),
1654 static_cast<int>(cfg.hall.v_pin), static_cast<int>(cfg.hall.w_pin),
1655 cfg.abn1.enable ? "yes" : "no", static_cast<int>(cfg.abn1.a_pin),
1656 static_cast<int>(cfg.abn1.b_pin), static_cast<int>(cfg.abn1.n_pin));
1657
1658 // Verify Hall/ABN1 config was written correctly
1659 if (!readAndVerify16(hall_abn1, "Hall/ABN1 config")) {
1660 if (failOnVerifyError) {
1661 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Hall/ABN1 config verification failed");
1662 return false;
1663 } else {
1664 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1665 "⚠️ Hall/ABN1 config verification failed (continuing)");
1666 }
1667 }
1668
1669 // ABN encoder 2, Reference switches, and Step/Direction configuration (offset 0x22)
1670 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1671 "Configuring ABN2, REF switches, and Step/Dir settings");
1672 if (!setAddress(bootaddr::ABN2_CONFIG)) {
1673 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1674 "Failed to set ABN2/REF/StepDir config register");
1675 return false;
1676 }
1678 // Reference switches (bits 0-6)
1679 abn2_ref_stepdir |= static_cast<uint16_t>(cfg.ref.ref_l_pin) & 0x3; // Bits 0-1: REF_L_PIN
1680 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.ref.ref_r_pin) & 0x3) << 2; // Bits 2-3: REF_R_PIN
1681 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.ref.ref_h_pin) & 0x7) << 4; // Bits 4-6: REF_H_PIN
1682 // Step/Direction (bits 8-11)
1683 abn2_ref_stepdir |= (cfg.stepDir.enable ? 1u : 0u) << 8; // Bit 8: STEPDIR_ENABLE
1684 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.stepDir.step_pin) & 0x3)
1685 << 9; // Bits 9-10: STEP_PIN
1686 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.stepDir.dir_pin) & 0x1) << 11; // Bit 11: DIR_PIN
1687 // ABN encoder 2 (bits 12-15)
1688 abn2_ref_stepdir |= (cfg.abn2.enable ? 1u : 0u) << 12; // Bit 12: ABN2_ENABLE
1689 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.abn2.a_pin) & 0x1) << 13; // Bit 13: ABN2_A
1690 abn2_ref_stepdir |= (static_cast<uint16_t>(cfg.abn2.b_pin) & 0x3) << 14; // Bits 14-15: ABN2_B
1691 if (!write16(abn2_ref_stepdir)) {
1692 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1693 "Failed to write ABN2/REF/StepDir config (0x%08X)", abn2_ref_stepdir);
1694 return false;
1695 }
1697 comm_, 3, "TMC9660Bootloader",
1698 "ABN2/REF/StepDir configured (ABN2:%s, REF_L:%d, REF_R:%d, REF_H:%d, StepDir:%s)",
1699 cfg.abn2.enable ? "enabled" : "disabled", static_cast<int>(cfg.ref.ref_l_pin),
1700 static_cast<int>(cfg.ref.ref_r_pin), static_cast<int>(cfg.ref.ref_h_pin),
1701 cfg.stepDir.enable ? "enabled" : "disabled");
1702
1703 // Verify ABN2/REF/StepDir config was written correctly
1704 if (!readAndVerify16(abn2_ref_stepdir, "ABN2/REF/StepDir config")) {
1705 if (failOnVerifyError) {
1706 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1707 "ABN2/REF/StepDir config verification failed");
1708 return false;
1709 } else {
1710 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1711 "⚠️ ABN2/REF/StepDir config verification failed (continuing)");
1712 }
1713 }
1714
1715 // Mechanical brake and Brake chopper configuration (offset 0x24)
1716 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1717 "Configuring mechanical brake and brake chopper settings");
1718 if (!setAddress(bootaddr::MECH_BRAKE_CONFIG)) {
1719 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set brake config register");
1720 return false;
1721 }
1722 uint16_t brake = 0;
1723 // Brake chopper (bits 4-9)
1724 brake |= (cfg.brakeChopper.enable ? 1u : 0u) << 4; // Bit 4: BRAKECHOPPER_ENABLE
1725 brake |= (static_cast<uint16_t>(cfg.brakeChopper.output_pin) & 0x1F)
1726 << 5; // Bits 5-9: BRAKECHOPPER_OUTPUT
1727 // Mechanical brake (bits 12-14)
1728 brake |= (cfg.mechBrake.enable ? 1u : 0u) << 12; // Bit 12: MECH_BRAKE_ENABLE
1729 brake |= (static_cast<uint16_t>(cfg.mechBrake.output_pin) & 0x3)
1730 << 13; // Bits 13-14: MECH_BRAKE_OUTPUT
1731 if (!write16(brake)) {
1732 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write brake config (0x%04X)",
1733 brake);
1734 return false;
1735 }
1736 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1737 "Brake configuration completed (mech_brake:%s, brake_chopper:%s)",
1738 cfg.mechBrake.enable ? "enabled" : "disabled",
1739 cfg.brakeChopper.enable ? "enabled" : "disabled");
1740
1741 // Verify brake config was written correctly
1742 if (!readAndVerify16(brake, "Brake config")) {
1743 if (failOnVerifyError) {
1744 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Brake config verification failed");
1745 return false;
1746 } else {
1747 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1748 "⚠️ Brake config verification failed (continuing)");
1749 }
1750 }
1751
1752 // SPI encoder configuration (offset 0x26)
1753 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring SPI encoder settings");
1754 if (!setAddress(bootaddr::SPI_ENC_CONFIG)) {
1755 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set SPI encoder config register");
1756 return false;
1757 }
1758 uint16_t spiEnc = (cfg.spiEnc.enable ? 1u : 0u); // Bit 0: SPI_ENC_ENABLE
1759 spiEnc |= (static_cast<uint16_t>(cfg.spiEnc.spi_block) & 0x1) << 1; // Bit 1: SPI_ENC_BLOCK
1760 spiEnc |= (static_cast<uint16_t>(cfg.spiEnc.spi_mode) & 0x3) << 2; // Bits 2-3: SPI_ENC_MODE
1761 spiEnc |= (static_cast<uint16_t>(cfg.spiEnc.spi_freq) & 0xF) << 4; // Bits 4-7: SPI_ENC_FREQ
1762 spiEnc |= (static_cast<uint16_t>(cfg.spiEnc.cs_pin) & 0x3) << 8; // Bits 8-9: SPI_ENC_CS_PIN
1763 spiEnc |= (static_cast<uint16_t>(cfg.spiEnc.cs_polarity) & 0x1) << 10; // Bit 10: SPI_ENC_CS_POL
1764 if (!write16(spiEnc)) {
1765 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write SPI encoder config (0x%04X)",
1766 spiEnc);
1767 return false;
1768 }
1769 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
1770 "SPI encoder configured (enabled: %s, block:%d, mode:%d, freq:%d)",
1771 cfg.spiEnc.enable ? "yes" : "no", static_cast<int>(cfg.spiEnc.spi_block),
1772 static_cast<int>(cfg.spiEnc.spi_mode), static_cast<int>(cfg.spiEnc.spi_freq));
1773
1774 // Verify SPI encoder config was written correctly
1775 if (!readAndVerify16(spiEnc, "SPI encoder config")) {
1776 if (failOnVerifyError) {
1777 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "SPI encoder config verification failed");
1778 return false;
1779 } else {
1780 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1781 "⚠️ SPI encoder config verification failed (continuing)");
1782 }
1783 }
1784
1785 // External memory storage configuration (offset 0x28)
1786 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring external memory storage settings");
1787 if (!setAddress(bootaddr::MEM_STORAGE_CONFIG)) {
1788 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1789 "Failed to set memory storage config register");
1790 return false;
1791 }
1793 static_cast<uint8_t>(cfg.memStorage.tmcl_script) & 0x3; // Bits 0-1: MEM_TMCL_SCRIPT
1794 mem_storage |= (static_cast<uint8_t>(cfg.memStorage.parameters) & 0x3)
1795 << 2; // Bits 2-3: MEM_PARAMETERS
1796 if (!write8(mem_storage)) {
1797 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
1798 "Failed to write memory storage config (0x%02X)", mem_storage);
1799 return false;
1800 }
1802 comm_, 3, "TMC9660Bootloader", "Memory storage configured (TMCL_script:%d, parameters:%d)",
1803 static_cast<int>(cfg.memStorage.tmcl_script), static_cast<int>(cfg.memStorage.parameters));
1804
1805 // Verify memory storage config was written correctly
1806 if (!readAndVerify8(mem_storage, "Memory storage config")) {
1807 if (failOnVerifyError) {
1808 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Memory storage config verification failed");
1809 return false;
1810 } else {
1811 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1812 "⚠️ Memory storage config verification failed (continuing)");
1813 }
1814 }
1815
1816 // ========================================================================
1817 // External Memory Verification (if enabled)
1818 // ========================================================================
1819
1820 // Check SPI Flash if enabled
1821 if (cfg.spiFlash.enable_flash) {
1822 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "========================================");
1823 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Verifying SPI Flash Memory Configuration");
1824 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "========================================");
1825 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Expected config:");
1826 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - SPI_FLASH_EN: %s (bit 0)",
1827 cfg.spiFlash.enable_flash ? "ENABLED" : "DISABLED");
1828 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - CS Pin: GPIO%d (bits 3-7)",
1829 cfg.spiFlash.cs_pin);
1830 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", " - Frequency: Div%d (bits 8-11)",
1831 1 << static_cast<int>(cfg.spiFlash.freq_div));
1833 comm_, 2, "TMC9660Bootloader", " - SPI Interface: %s (bit 12)",
1834 cfg.spiFlash.flash_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0 ? "SPI0" : "SPI1");
1836 comm_, 2, "TMC9660Bootloader", " - SCK Pin: %s (bit 13)",
1837 cfg.spiFlash.spi0_sck_pin == tmc9660::bootcfg::SPI0SckPin::GPIO6 ? "GPIO6" : "GPIO11");
1838
1839 // Check if SPI Flash is configured
1841 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1842 "Checking MEM_IS_CONFIGURED (bank=1, SPI Flash)...");
1843 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONFIGURED),
1844 static_cast<uint32_t>(MemoryBank::SPI_FLASH), &is_configured)) {
1845 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "MEM_IS_CONFIGURED reply: %u", is_configured);
1846 if (is_configured == 1) {
1847 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1848 "✅ SPI Flash is CONFIGURED (bootloader sees SPI_FLASH_EN=1)");
1849
1850 // Check if SPI Flash is connected
1852 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1853 "Checking MEM_IS_CONNECTED (probing flash chip)...");
1854 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONNECTED),
1855 static_cast<uint32_t>(MemoryBank::SPI_FLASH), &is_connected)) {
1856 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "MEM_IS_CONNECTED reply: %u",
1857 is_connected);
1858 if (is_connected == 1) {
1859 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1860 "✅ SPI Flash is CONNECTED (chip responded to probe)");
1861
1862 // Check if SPI Flash is busy
1863 uint32_t is_busy = 0;
1864 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Checking MEM_IS_BUSY...");
1865 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_BUSY),
1866 static_cast<uint32_t>(MemoryBank::SPI_FLASH), &is_busy)) {
1867 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "MEM_IS_BUSY reply: %u", is_busy);
1868 if (is_busy == 0) {
1869 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1870 "✅ SPI Flash is READY (not busy)");
1871 } else {
1872 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1873 "⚠️ SPI Flash is BUSY (write in progress)");
1874 }
1875 } else {
1876 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1877 "⚠️ Failed to check SPI Flash busy status");
1878 }
1879
1880 // Query partition count
1882 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1883 "Querying partition count (GET_INFO SPI_MEM_PARTITIONS)...");
1884 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::GET_INFO),
1885 static_cast<uint32_t>(InfoQuery::SPI_MEM_PARTITIONS),
1886 &partition_count)) {
1887 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "SPI Flash partition count: %u",
1889 if (partition_count == 0) {
1890 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ SPI Flash has NO partitions");
1892 comm_, 1, "TMC9660Bootloader",
1893 " Flash is connected but not partitioned (no partition table)");
1894 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1895 " See datasheet Table 21 for partition header format");
1896 } else {
1897 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "✅ SPI Flash has %u partition(s)",
1899 }
1900 } else {
1901 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1902 "⚠️ Failed to query SPI Flash partition count");
1903 }
1904 } else {
1905 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ SPI Flash is NOT CONNECTED");
1906 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " Possible causes:");
1907 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1908 " 1. No flash chip physically connected");
1909 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1910 " 2. Wrong CS pin configured (expected GPIO%d)",
1911 cfg.spiFlash.cs_pin);
1913 comm_, 1, "TMC9660Bootloader", " 3. Wrong SPI interface (configured: %s)",
1914 cfg.spiFlash.flash_spi_iface == tmc9660::bootcfg::SPIInterface::SPI0 ? "SPI0"
1915 : "SPI1");
1916 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1917 " 4. Flash chip not responding to probe (0xAB, 0x90, 0x9F)");
1918 }
1919 } else {
1920 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1921 "⚠️ Failed to send MEM_IS_CONNECTED command");
1922 }
1923 } else {
1924 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ SPI Flash is NOT CONFIGURED");
1925 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1926 " Bootloader reports SPI_FLASH_EN=0 (bit 0 of flash config)");
1927 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1928 " This means the config write may have failed or been overwritten");
1929 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1930 " Check that cfg.spiFlash.enable_flash = true");
1931 }
1932 } else {
1933 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1934 "⚠️ Failed to send MEM_IS_CONFIGURED command");
1935 }
1936 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "========================================");
1937 }
1938
1939 // Check I2C EEPROM if enabled
1940 if (cfg.i2c.enable_eeprom) {
1941 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Verifying I2C EEPROM memory...");
1942
1943 // Check if I2C EEPROM is configured
1945 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONFIGURED),
1946 static_cast<uint32_t>(MemoryBank::I2C_EEPROM), &is_configured)) {
1947 if (is_configured == 1) {
1948 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "✅ I2C EEPROM is CONFIGURED");
1949
1950 // Check if I2C EEPROM is connected
1952 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_CONNECTED),
1953 static_cast<uint32_t>(MemoryBank::I2C_EEPROM), &is_connected)) {
1954 if (is_connected == 1) {
1955 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "✅ I2C EEPROM is CONNECTED");
1956
1957 // Check if I2C EEPROM is busy
1958 uint32_t is_busy = 0;
1959 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::MEM_IS_BUSY),
1960 static_cast<uint32_t>(MemoryBank::I2C_EEPROM), &is_busy)) {
1961 if (is_busy == 0) {
1962 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
1963 "✅ I2C EEPROM is READY (not busy)");
1964 } else {
1965 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ I2C EEPROM is BUSY");
1966 }
1967 } else {
1968 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1969 "⚠️ Failed to check I2C EEPROM busy status");
1970 }
1971
1972 // Query partition count
1974 if (sendCommand(static_cast<uint8_t>(BootloaderCommand::GET_INFO),
1975 static_cast<uint32_t>(InfoQuery::I2C_MEM_PARTITIONS),
1976 &partition_count)) {
1977 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "I2C EEPROM partition count: %u",
1979 if (partition_count == 0) {
1980 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1981 "⚠️ I2C EEPROM has NO partitions (not partitioned)");
1982 }
1983 } else {
1984 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1985 "⚠️ Failed to query I2C EEPROM partition count");
1986 }
1987 } else {
1988 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1989 "⚠️ I2C EEPROM is NOT CONNECTED (check wiring)");
1990 }
1991 } else {
1992 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1993 "⚠️ Failed to check I2C EEPROM connection status");
1994 }
1995 } else {
1996 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
1997 "⚠️ I2C EEPROM is NOT CONFIGURED (check config settings)");
1998 }
1999 } else {
2000 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2001 "⚠️ Failed to check I2C EEPROM configuration status");
2002 }
2003 }
2004
2005 // ⚠️ CRITICAL: Boot configuration (offset 0x08) - WRITE THIS LAST!
2006 // When START_MOTOR_CTRL=1 is written, the bootloader IMMEDIATELY exits and starts motor control.
2007 // Any commands sent after this will fail because the bootloader is no longer running.
2008 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "Configuring boot settings (FINAL STEP)");
2009 if (!setAddress(bootaddr::BOOT_CONFIG)) {
2010 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set boot config register");
2011 return false;
2012 }
2013 uint16_t boot = 0;
2014 boot |= static_cast<uint16_t>(cfg.boot.boot_mode) & 0x3; // Bits 0-1: BOOT_MODE
2015 boot |= (cfg.boot.bl_ready_fault ? 1u : 0u) << 2; // Bit 2: BL_READY_FAULT
2016 boot |= (cfg.boot.bl_exit_fault ? 1u : 0u) << 3; // Bit 3: BL_EXIT_FAULT
2017 boot |= (cfg.boot.disable_selftest ? 1u : 0u) << 8; // Bit 8: DISABLE_SELFTEST
2018 boot |= (cfg.boot.bl_config_fault ? 1u : 0u) << 9; // Bit 9: BL_CONFIG_FAULT
2019 boot |= (cfg.boot.start_motor_control ? 1u : 0u) << 12; // Bit 12: START_MOTOR_CTRL
2020 if (!write16(boot)) {
2021 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write boot config (0x%04X)", boot);
2022 return false;
2023 }
2024 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
2025 "Boot config written: mode=%d, start_motor_ctrl=%s, bl_exit_fault=%s",
2026 static_cast<int>(cfg.boot.boot_mode),
2027 cfg.boot.start_motor_control ? "TRUE" : "FALSE",
2028 cfg.boot.bl_exit_fault ? "TRUE" : "FALSE");
2029
2030 // CAN'T VERIFY BOOT CONFIG BECAUSE THE BOOTLOADER WILL EXIT IMMEDIATELY AFTER START_MOTOR_CTRL=1
2031 // IS WRITTEN
2032 // // Verify boot config was written correctly
2033 // if (!readAndVerify16(boot, "Boot config")) {
2034 // if (failOnVerifyError) {
2035 // TMC9660_LOG_DEBUG(comm_,0, "TMC9660Bootloader", "Boot config verification failed");
2036 // return false;
2037 // } else {
2038 // TMC9660_LOG_DEBUG(comm_,1, "TMC9660Bootloader", "⚠️ Boot config verification failed
2039 // (continuing)");
2040 // }
2041 // }
2042
2043 if (cfg.boot.start_motor_control) {
2044 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2045 "⚠️ START_MOTOR_CTRL=1 written - Bootloader will hopefully EXIT and motor "
2046 "control will START!");
2047 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2048 "⚠️ No further bootloader commands will be accepted after this point!");
2049 } else {
2050 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
2051 "START_MOTOR_CTRL=0 - Bootloader remains active, motor control NOT started");
2052 }
2053
2054 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader",
2055 "Bootloader configuration application completed successfully");
2056 return true;
2057}
2058
2059//==================================================
2060// MOTOR CONTROL STARTUP
2061//==================================================
2062
2093template <typename CommType>
2095 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader", "Starting motor control system (mode: %d)...",
2096 static_cast<int>(bootMode));
2097
2098 // Set bank to CONFIG (bank 5)
2099 if (!setBank(5)) {
2100 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
2101 "Failed to set CONFIG bank before starting motor control");
2102 return false;
2103 }
2104
2105 // Set address to BOOT_CONFIG register (offset 0x08)
2106 if (!setAddress(bootaddr::BOOT_CONFIG)) {
2107 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to set BOOT_CONFIG address");
2108 return false;
2109 }
2110
2111 // Read current BOOT_CONFIG to preserve other settings
2112 // We'll use a dummy write to get the current value via SPI reply mechanism
2113 // Note: This is optional - if we don't care about preserving other bits, we can skip this
2114
2115 // Construct BOOT_CONFIG value with START_MOTOR_CTRL=1
2117 boot_config |= static_cast<uint32_t>(bootMode) & 0x3; // Bits 0-1: BOOT_MODE
2118 boot_config |= (1u << 12); // Bit 12: START_MOTOR_CTRL = 1
2119
2120 // Optional: Set BL_EXIT_FAULT to get FAULTN signal when exiting (helps debugging)
2121 boot_config |= (1u << 3); // Bit 3: BL_EXIT_FAULT = 1
2122
2123 TMC9660_LOG_DEBUG(comm_, 2, "TMC9660Bootloader",
2124 "Writing BOOT_CONFIG=0x%08X (mode=%d, START_MOTOR_CTRL=1)", boot_config,
2125 static_cast<int>(bootMode));
2126
2127 // Write the boot configuration
2128 // ⚠️ CRITICAL: After this write completes, the bootloader will EXIT!
2129 if (!write32(boot_config)) {
2130 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to write BOOT_CONFIG");
2131 return false;
2132 }
2133
2134 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2135 "⚠️ START_MOTOR_CTRL command sent - Bootloader is EXITING NOW!");
2136 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "⚠️ Motor control system starting in %s mode",
2137 bootMode == bootcfg::BootMode::Parameter ? "PARAMETER" : "REGISTER");
2138 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2139 "⚠️ Allow 100-150ms for motor control to fully initialize");
2140 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2141 "⚠️ Bootloader commands will NO LONGER work after this point");
2142
2143 return true;
2144}
2145
2146//==================================================
2147// READ OPERATIONS
2148//==================================================
2149
2159template <typename CommType>
2161 if (!value) {
2162 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read8: null pointer");
2163 return false;
2164 }
2165
2167 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_8), 0, &reply)) {
2168 return false;
2169 }
2170
2171 *value = static_cast<uint8_t>(reply);
2172 return true;
2173}
2174
2184template <typename CommType>
2186 if (!value) {
2187 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read16: null pointer");
2188 return false;
2189 }
2190
2192 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_16), 0, &reply)) {
2193 return false;
2194 }
2195
2196 *value = static_cast<uint16_t>(reply);
2197 return true;
2198}
2199
2209template <typename CommType>
2211 if (!value) {
2212 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read32: null pointer");
2213 return false;
2214 }
2215
2216 return sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_32), 0, value);
2217}
2218
2219template <typename CommType>
2220
2222 if (!value) {
2223 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read8Inc: null pointer");
2224 return false;
2225 }
2226
2228 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_8_INC), 0, &reply)) {
2229 return false;
2230 }
2231
2232 *value = static_cast<uint8_t>(reply);
2233 return true;
2234}
2235
2236template <typename CommType>
2237
2239 if (!value) {
2240 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read16Inc: null pointer");
2241 return false;
2242 }
2243
2245 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_16_INC), 0, &reply)) {
2246 return false;
2247 }
2248
2249 *value = static_cast<uint16_t>(reply);
2250 return true;
2251}
2252
2253template <typename CommType>
2254
2256 if (!value) {
2257 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "read32Inc: null pointer");
2258 return false;
2259 }
2260
2261 return sendCommand(static_cast<uint8_t>(BootloaderCommand::READ_32_INC), 0, value);
2262}
2263
2264template <typename CommType>
2265
2267 if (!bank) {
2268 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getBank: null pointer");
2269 return false;
2270 }
2271
2273 if (!sendCommand(static_cast<uint8_t>(BootloaderCommand::GET_BANK), 0, &reply)) {
2274 return false;
2275 }
2276
2277 *bank = static_cast<uint8_t>(reply);
2278 return true;
2279}
2280
2281template <typename CommType>
2282
2285 if (!read8(&actual)) {
2286 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read back %s", configName);
2287 return false;
2288 }
2289
2290 if (actual != expected) {
2291 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
2292 "❌ %s verification failed: expected=0x%02X, actual=0x%02X", configName,
2293 expected, actual);
2294 return false;
2295 }
2296
2297 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "✅ %s verified: 0x%02X", configName, actual);
2298 return true;
2299}
2300
2301template <typename CommType>
2302
2305 if (!read16(&actual)) {
2306 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read back %s", configName);
2307 return false;
2308 }
2309
2310 if (actual != expected) {
2311 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
2312 "❌ %s verification failed: expected=0x%04X, actual=0x%04X", configName,
2313 expected, actual);
2314 return false;
2315 }
2316
2317 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "✅ %s verified: 0x%04X", configName, actual);
2318 return true;
2319}
2320
2321template <typename CommType>
2322
2325 if (!read32(&actual)) {
2326 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Failed to read back %s", configName);
2327 return false;
2328 }
2329
2330 if (actual != expected) {
2331 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader",
2332 "❌ %s verification failed: expected=0x%08X, actual=0x%08X", configName,
2333 expected, actual);
2334 return false;
2335 }
2336
2337 TMC9660_LOG_DEBUG(comm_, 3, "TMC9660Bootloader", "✅ %s verified: 0x%08X", configName, actual);
2338 return true;
2339}
2340
2341template <typename CommType>
2342
2344 if (!address) {
2345 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "getAddress: null pointer");
2346 return false;
2347 }
2348
2349 return sendCommand(static_cast<uint8_t>(BootloaderCommand::GET_ADDRESS), 0, address);
2350}
2351
2352template <typename CommType>
2353
2355 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "");
2356 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2357 "╔══════════════════════════════════════════════════════════════╗");
2358 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2359 "║ TMC9660 BOOTLOADER INFORMATION ║");
2360 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2361 "╚══════════════════════════════════════════════════════════════╝");
2362
2363 bool success = true;
2364 uint32_t value = 0;
2365
2366 // 1. Chip Type
2367 if (getChipType(&value)) {
2368 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Chip Type: 0x%08X %s", value,
2369 (value == 0x544D0001) ? "(TMC9660 ✓)" : "(Unknown!)");
2370 } else {
2371 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Chip Type: FAILED TO READ");
2372 success = false;
2373 }
2374
2375 // 2. Bootloader Version
2377 if (getBootloaderVersion(&blVer)) {
2378 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Bootloader Version: v%u.%u", blVer.major,
2379 blVer.minor);
2380 } else {
2381 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Bootloader Version: FAILED TO READ");
2382 }
2383
2384 // 3. Chip Version (Silicon Revision)
2385 if (getChipVersion(&value)) {
2386 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Silicon Revision: %u", value);
2387 } else {
2388 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Silicon Revision: FAILED TO READ");
2389 }
2390
2391 // 4. Chip Variant
2392 if (getChipVariant(&value)) {
2393 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Chip Variant: %u %s", value,
2394 (value == 2) ? "(TMC9660 ✓)" : "(Unexpected!)");
2395 } else {
2396 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Chip Variant: FAILED TO READ");
2397 }
2398
2399 // 5. System Frequency
2400 if (getChipFrequency(&value)) {
2401 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "System Frequency: %u MHz", value);
2402 } else {
2403 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "System Frequency: FAILED TO READ");
2404 }
2405
2406 // 6. Git Info
2408 if (getGitInfo(&gitInfo)) {
2409 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Git Commit: %07X%s",
2410 gitInfo.commit_hash, gitInfo.dirty ? " (dirty)" : "");
2411 } else {
2412 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Git Commit: FAILED TO READ");
2413 }
2414
2415 // 7. Features
2417 if (getFeatures(&features)) {
2418 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Features:");
2419 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " - SRAM Support: %s",
2420 features.sram_support ? "YES" : "NO");
2421 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " - ROM: %s",
2422 features.rom ? "YES" : "NO");
2423 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " - OTP: %s",
2424 features.otp ? "YES" : "NO");
2425 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " - SPI Flash: %s",
2426 features.spi_flash ? "YES" : "NO");
2427 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", " - I2C EEPROM: %s",
2428 features.i2c_eeprom ? "YES" : "NO");
2429 } else {
2430 TMC9660_LOG_DEBUG(comm_, 0, "TMC9660Bootloader", "Features: FAILED TO READ");
2431 }
2432
2433 // 8. CONFIG Memory Info
2435 if (getConfigMemStart(&config_start) && getConfigMemSize(&config_size)) {
2436 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2437 "CONFIG Memory: Start=0x%08X, Size=%u bytes", config_start, config_size);
2438 } else {
2439 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "CONFIG Memory: Info not available");
2440 }
2441
2442 // 9. OTP Memory Size
2443 if (getOtpMemSize(&value)) {
2444 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "OTP Page Size: %u bytes", value);
2445 } else {
2446 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "OTP Page Size: Not available");
2447 }
2448
2449 // 10. External Memory Partition Version
2451 if (getPartitionVersion(&partVer)) {
2452 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Partition Version: v%u.%u", partVer.major,
2453 partVer.minor);
2454 } else {
2455 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "Partition Version: Not available");
2456 }
2457
2458 // 11. SPI Flash Info (may fail if no flash connected)
2460 bool has_spi_flash = getSpiMemSize(&spi_size);
2461 if (has_spi_flash) {
2462 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "SPI Flash Size: %u bytes (%.2f KB)",
2463 spi_size, spi_size / 1024.0f);
2464
2465 // Try to get partition count (requires SPI bank to be selected)
2467 getBank(&current_bank);
2468 setBank(MemoryBank::SPI_FLASH);
2469
2470 if (getSpiMemPartitions(&spi_partitions)) {
2471 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "SPI Partitions: %u", spi_partitions);
2472 } else {
2473 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2474 "SPI Partitions: Not available (may need partitioning)");
2475 }
2476
2477 // Restore original bank
2478 setBank(current_bank);
2479 } else {
2480 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2481 "SPI Flash: Not connected or not configured");
2482 }
2483
2484 // 12. I2C EEPROM Info (may fail if no EEPROM connected)
2486 bool has_i2c_eeprom = getI2cMemSize(&i2c_size);
2487 if (has_i2c_eeprom) {
2488 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "I2C EEPROM Size: %u bytes (%.2f KB)",
2489 i2c_size, i2c_size / 1024.0f);
2490
2491 // Try to get partition count (requires I2C bank to be selected)
2493 getBank(&current_bank);
2494 setBank(MemoryBank::I2C_EEPROM);
2495
2496 if (getI2cMemPartitions(&i2c_partitions)) {
2497 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "I2C Partitions: %u", i2c_partitions);
2498 } else {
2499 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2500 "I2C Partitions: Not available (may need partitioning)");
2501 }
2502
2503 // Restore original bank
2504 setBank(current_bank);
2505 } else {
2506 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2507 "I2C EEPROM: Not connected or not configured");
2508 }
2509
2510 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader",
2511 "╚══════════════════════════════════════════════════════════════╝");
2512 TMC9660_LOG_DEBUG(comm_, 1, "TMC9660Bootloader", "");
2513
2514 return success;
2515}
2516
2517// tmc9660_bootloader.tpp - Template implementation ends here
2518
2519#endif // TMC9660_BOOTLOADER_IMPL
SPI status codes as per TMC9660 Parameter Mode.
Definition tmc9660_comm_interface.hpp:514
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
bool setAddress(uint32_t addr) noexcept
Set the address within the current memory bank.
Definition tmc9660_bootloader.cpp:278
bool flashReadBuffer(uint8_t offset, uint32_t *data) noexcept
Read data from internal 6-byte flash command buffer.
Definition tmc9660_bootloader.cpp:732
bool getInfo(InfoQuery query, uint32_t *value) noexcept
Get bootloader information.
Definition tmc9660_bootloader.cpp:822
bool getAddress(uint32_t *address) noexcept
Get the currently selected memory address.
Definition tmc9660_bootloader.cpp:2343
bool readAndVerify8(uint8_t expected, const char *config_name) noexcept
Read back and verify an 8-bit value matches the expected value.
Definition tmc9660_bootloader.cpp:2283
bool readAndVerify32(uint32_t expected, const char *config_name) noexcept
Read back and verify a 32-bit value matches the expected value.
Definition tmc9660_bootloader.cpp:2323
bool memIsBusy(MemoryBank bank, bool *is_busy) noexcept
Check if external memory is busy.
Definition tmc9660_bootloader.cpp:706
bool sendCommandSPI(uint8_t cmd, uint32_t value, uint32_t *reply) noexcept
Send a bootloader command via SPI interface.
Definition tmc9660_bootloader.cpp:101
bool sendCommand(uint8_t cmd, uint32_t value, uint32_t *reply=nullptr) noexcept
Send a bootloader command and optionally receive a reply.
Definition tmc9660_bootloader.cpp:69
bool flashEraseSector(uint32_t address) noexcept
Erase a sector on external SPI flash.
Definition tmc9660_bootloader.cpp:755
bool read16Inc(uint16_t *value) noexcept
Read a 16-bit word and increment address by 2.
Definition tmc9660_bootloader.cpp:2238
bool bootstrapRS485(uint8_t tx_en_pin, uint8_t pre_delay, uint8_t host_addr, uint8_t device_addr) noexcept
Configure RS485 communication (must be first command for RS485).
Definition tmc9660_bootloader.cpp:788
bool noOp(uint32_t *reply=nullptr) noexcept
No operation - retrieve reply from previous command (SPI only).
Definition tmc9660_bootloader.cpp:364
bool sendCommandUART(uint8_t cmd, uint32_t value, uint32_t *reply) noexcept
Send a bootloader command via UART interface.
Definition tmc9660_bootloader.cpp:191
bool startMotorControl(bootcfg::BootMode boot_mode=bootcfg::BootMode::Parameter) noexcept
Start the motor control system and exit bootloader mode.
Definition tmc9660_bootloader.cpp:2094
bool otpBurnWithWorkaround(uint8_t page, uint8_t page_addr, OtpBurnResult *result, uint32_t vdrv_wait_ms=1000) noexcept
Burn OTP page with Erratum 1 workaround for reliable operation.
Definition tmc9660_bootloader.cpp:498
bool read32Inc(uint32_t *value) noexcept
Read a 32-bit word and increment address by 4.
Definition tmc9660_bootloader.cpp:2255
bool readAndVerify16(uint16_t expected, const char *config_name) noexcept
Read back and verify a 16-bit value matches the expected value.
Definition tmc9660_bootloader.cpp:2303
bool getBank(uint8_t *bank) noexcept
Get the currently selected memory bank.
Definition tmc9660_bootloader.cpp:2266
bool write8Inc(uint8_t v) noexcept
Write a single byte and increment address by 1.
Definition tmc9660_bootloader.cpp:326
bool applyConfiguration(const BootloaderConfig &cfg, bool fail_on_verify_error=true) noexcept
Apply all fields of a ::BootloaderConfig.
Definition tmc9660_bootloader.cpp:956
bool memIsConfigured(MemoryBank bank, bool *is_configured) noexcept
Check if external memory is configured.
Definition tmc9660_bootloader.cpp:678
bool memIsConnected(MemoryBank bank, bool *is_connected) noexcept
Check if external memory is connected.
Definition tmc9660_bootloader.cpp:692
bool getGitInfo(GitInfo *git_info) noexcept
Get Git version control information.
Definition tmc9660_bootloader.cpp:871
bool write32(uint32_t v) noexcept
Write a 32-bit word to the previously selected address.
Definition tmc9660_bootloader.cpp:320
bool write16(uint16_t v) noexcept
Write a 16-bit word to the previously selected address.
Definition tmc9660_bootloader.cpp:306
bool checkOtpBurnStatus(bool *result) noexcept
Check OTP burn status using Erratum 1 workaround verification.
Definition tmc9660_bootloader.cpp:580
bool read8(uint8_t *value) noexcept
Read a single byte from the previously selected address.
Definition tmc9660_bootloader.cpp:2160
bool getFeatures(BootloaderFeatures *features) noexcept
Get available feature flags.
Definition tmc9660_bootloader.cpp:854
bool read32(uint32_t *value) noexcept
Read a 32-bit word from the previously selected address.
Definition tmc9660_bootloader.cpp:2210
TMC9660Bootloader(CommType &comm) noexcept
Construct a TMC9660Bootloader instance.
Definition tmc9660_bootloader.cpp:51
bool otpLoad(uint8_t page, OtpLoadResult *result) noexcept
Load an OTP (One-Time Programmable) page into the OTP memory bank.
Definition tmc9660_bootloader.cpp:385
bool flashLoadBuffer(uint8_t offset, uint32_t data) noexcept
Load data into internal 6-byte flash command buffer.
Definition tmc9660_bootloader.cpp:724
bool flashReadJedecId(uint8_t *manufacturer_id) noexcept
Read JEDEC manufacturer ID from SPI flash.
Definition tmc9660_bootloader.cpp:762
bool setBank(uint8_t bank) noexcept
Select the target memory bank for subsequent operations.
Definition tmc9660_bootloader.cpp:264
bool write32Inc(uint32_t v) noexcept
Write a 32-bit word and increment address by 4.
Definition tmc9660_bootloader.cpp:338
bool read16(uint16_t *value) noexcept
Read a 16-bit word from the previously selected address.
Definition tmc9660_bootloader.cpp:2185
bool write32IncMultiple(const uint32_t *values, size_t count) noexcept
Write multiple 32-bit words starting at the current address.
Definition tmc9660_bootloader.cpp:354
bool write8(uint8_t v) noexcept
Write a single byte to the previously selected address.
Definition tmc9660_bootloader.cpp:292
bool flashSendDatagram(uint8_t num_bytes) noexcept
Send datagram to SPI flash and receive reply.
Definition tmc9660_bootloader.cpp:747
bool read8Inc(uint8_t *value) noexcept
Read a single byte and increment address by 1.
Definition tmc9660_bootloader.cpp:2221
bool getAllBootloaderInfo() noexcept
Retrieve and log all available bootloader information.
Definition tmc9660_bootloader.cpp:2354
bool write16Inc(uint16_t v) noexcept
Write a 16-bit word and increment address by 2.
Definition tmc9660_bootloader.cpp:332
bool getBootloaderVersion(BootloaderVersion *version) noexcept
Get bootloader version information.
Definition tmc9660_bootloader.cpp:837
bool otpBurn(uint8_t page, uint8_t page_addr, OtpBurnResult *result) noexcept
Permanently burn data to OTP (One-Time Programmable) memory.
Definition tmc9660_bootloader.cpp:431
bool getPartitionVersion(PartitionVersion *version) noexcept
Get partition version information.
Definition tmc9660_bootloader.cpp:888
constexpr uint32_t MEM_STORAGE_CONFIG
External memory storage selection register.
Definition bootloader_config.hpp:734
constexpr uint32_t UART_ADDR
UART device/host address register.
Definition bootloader_config.hpp:572
constexpr uint32_t MECH_BRAKE_CONFIG
Mechanical brake configuration register.
Definition bootloader_config.hpp:718
constexpr uint32_t GPIO_DIR
GPIO direction register.
Definition bootloader_config.hpp:630
constexpr uint32_t SPI_FLASH
SPI flash configuration register.
Definition bootloader_config.hpp:606
constexpr uint32_t HALL_CONFIG
Hall encoder configuration register.
Definition bootloader_config.hpp:670
constexpr uint32_t CLOCK_CONFIG
Clock configuration register.
Definition bootloader_config.hpp:662
constexpr uint32_t BOOT_CONFIG
Boot configuration register.
Definition bootloader_config.hpp:598
constexpr uint32_t GPIO_PU
GPIO pull-up enable register.
Definition bootloader_config.hpp:638
constexpr uint32_t ABN2_CONFIG
ABN encoder 2 configuration register.
Definition bootloader_config.hpp:686
constexpr uint32_t GPIO_EXT
GPIO extended configuration register.
Definition bootloader_config.hpp:654
constexpr uint32_t I2C_CONFIG
I2C EEPROM configuration register.
Definition bootloader_config.hpp:614
constexpr uint32_t COMM_CONFIG
Communication interface selection register.
Definition bootloader_config.hpp:588
constexpr uint32_t SPI_ENC_CONFIG
SPI encoder configuration register.
Definition bootloader_config.hpp:710
constexpr uint32_t RS485_DELAY
RS485 TXEN delay configuration register.
Definition bootloader_config.hpp:580
constexpr uint32_t GPIO_PD
GPIO pull-down enable register.
Definition bootloader_config.hpp:646
constexpr uint32_t LDO_CONFIG
LDO configuration register address.
Definition bootloader_config.hpp:564
constexpr uint32_t GPIO_OUT
GPIO output level register.
Definition bootloader_config.hpp:622
BootMode
Boot mode selection for motor control system startup.
Definition bootloader_config.hpp:63
@ GPIO7
Use GPIO7 for UART RX (default)
@ GPIO6
Use GPIO6 for SPI0 SCK (default)
@ GPIO6
Use GPIO6 for UART TX (default)
@ GPIO8
Use GPIO8 for RS485 TX enable.
@ GPIO2
Use GPIO2 for RS485 TX enable.
@ SPI0
Physical SPI0 interface.
Definition bootloader_config.hpp:9
OtpBurnError
OTP burn operation error codes.
Definition bootloader_protocol.hpp:239
MemoryBank
Memory bank identifiers for bootloader operations.
Definition bootloader_protocol.hpp:86
@ SPI_FLASH
External SPI Flash.
@ I2C_EEPROM
External I2C EEPROM.
InfoQuery
GET_INFO query types for retrieving system information.
Definition bootloader_protocol.hpp:105
Definition tmc9660_bootloader.cpp:22
Definition tmc9660_bootloader.cpp:25
Bootloader command structure for SPI (40-bit / 5-byte protocol).
Definition bootloader_protocol.hpp:312
Bootloader command structure for UART (64-bit / 8-byte protocol).
Definition bootloader_protocol.hpp:335
Complete bootloader configuration structure.
Definition bootloader_config.hpp:998
Feature flags indicating available bootloader capabilities.
Definition bootloader_protocol.hpp:151
static BootloaderFeatures fromValue(uint32_t value) noexcept
Parse from 32-bit value.
Definition bootloader_protocol.hpp:159
Bootloader reply structure for SPI (40-bit / 5-byte protocol).
Definition bootloader_protocol.hpp:362
static BootloaderReplySPI fromBuffer(const std::array< uint8_t, 5 > &in) noexcept
Deserialize from 5-byte SPI buffer.
Definition bootloader_protocol.hpp:367
Bootloader reply structure for UART (64-bit / 8-byte protocol).
Definition bootloader_protocol.hpp:397
static BootloaderReplyUART fromBuffer(const std::array< uint8_t, 8 > &in) noexcept
Deserialize from 8-byte UART buffer.
Definition bootloader_protocol.hpp:404
Bootloader version information structure.
Definition bootloader_protocol.hpp:132
static BootloaderVersion fromValue(uint32_t value) noexcept
Parse from 32-bit value (major in upper 16 bits, minor in lower 16 bits)
Definition bootloader_protocol.hpp:137
Git version control information from bootloader firmware.
Definition bootloader_protocol.hpp:177
static GitInfo fromValue(uint32_t value) noexcept
Parse from 32-bit value.
Definition bootloader_protocol.hpp:182
OTP burn operation result information.
Definition bootloader_protocol.hpp:255
static OtpBurnResult createSuccess() noexcept
Create success result.
Definition bootloader_protocol.hpp:261
static OtpBurnResult createError(OtpBurnError code) noexcept
Create error result.
Definition bootloader_protocol.hpp:270
OTP load operation result information.
Definition bootloader_protocol.hpp:217
static OtpLoadResult fromValue(uint32_t value) noexcept
Parse from 32-bit value.
Definition bootloader_protocol.hpp:222
External memory partition version information.
Definition bootloader_protocol.hpp:197
static PartitionVersion fromValue(uint32_t value) noexcept
Parse from 32-bit value.
Definition bootloader_protocol.hpp:202
Main TMC9660 bootloader interface class.
Communication interfaces for TMC9660 Parameter Mode devices using TMCL protocol over SPI.
#define TMC9660_LOG_DEBUG(comm_obj, level, tag,...)
Compile-time debug logging control for TMC9660 library.
Definition tmc9660_comm_interface.hpp:101