Quickstart¶
Spin a BLDC motor from your MCU: wire six signals, write a small port class, call five driver functions.
Before you power the motor
Use a current-limited supply for the first run and keep the motor free to turn. The example commands 1000 RPM as soon as the chip is up.
1. Wire it¶
| MCU | TMC9660 | Notes |
|---|---|---|
| SPI SCLK, MOSI, MISO, CS | SPI0_SCK, SPI0_MOSI, SPI0_MISO, SPI0_CS0 | SPI mode 3, up to 10 MHz; start at 1 MHz |
| GPIO out | RST | Reset; the driver pulses it in bootloaderInit() |
| GPIO out | DRV_EN | Enables the gate driver outputs |
| GPIO out | WAKE | Wakes the chip from hibernation |
| GPIO in | FAULTN | Open drain: pull it up |
| GND | GND | Common ground between the MCU and the motor supply |
The Hardware page has the full pin list, the evaluation-kit headers and the electrical limits.
2. Add the driver¶
Add the repository as a submodule and link the hf_tmc9660 CMake target (or include inc/ and build with
C++20). Installation covers CMake and ESP-IDF.
3. Write the port¶
The port is the only platform code. It moves bytes over SPI, sets and reads the four control pins and waits.
Replace the four platform_* calls with your SDK's.
class MySpi : public tmc9660::SpiCommInterface<MySpi> {
public:
// Physical level of each control pin when ACTIVE: RST, DRV_EN, WAKE, FAULTN.
MySpi() noexcept : SpiCommInterface<MySpi>(true, true, false, false) {}
// One 64-bit TMCL datagram, full duplex, SPI mode 3, CS held low for all 8 bytes.
bool spiTransferTMCL(std::array<uint8_t, 8>& tx, std::array<uint8_t, 8>& rx) noexcept {
return platform_spi_transfer(tx.data(), rx.data(), tx.size());
}
// One 40-bit bootloader datagram (used by bootloaderInit() before parameter mode).
bool spiTransferBootloader(std::array<uint8_t, 5>& tx, std::array<uint8_t, 5>& rx) noexcept {
return platform_spi_transfer(tx.data(), rx.data(), tx.size());
}
// ACTIVE / INACTIVE to a GPIO level, using the active levels given to the constructor.
bool gpioSet(tmc9660::TMC9660CtrlPin pin, tmc9660::GpioSignal signal) noexcept {
return platform_gpio_write(gpioOf(pin), signalToGpioLevel(pin, signal));
}
bool gpioRead(tmc9660::TMC9660CtrlPin pin, tmc9660::GpioSignal& signal) noexcept {
bool level = false;
if (!platform_gpio_read(gpioOf(pin), level)) {
return false;
}
signal = gpioLevelToSignal(pin, level);
return true;
}
void delayMs(uint32_t ms) noexcept { platform_delay_us(ms * 1000U); }
void delayUs(uint32_t us) noexcept { platform_delay_us(us); }
// Optional: route driver logs to your logger (level 0 = error ... 4 = verbose).
void debugLog(int /*level*/, const char* /*tag*/, const char* /*format*/, va_list /*args*/) noexcept {}
private:
static int gpioOf(tmc9660::TMC9660CtrlPin pin) noexcept {
switch (pin) {
case tmc9660::TMC9660CtrlPin::RST: return 22;
case tmc9660::TMC9660CtrlPin::DRV_EN: return 20;
case tmc9660::TMC9660CtrlPin::FAULTN: return 19;
case tmc9660::TMC9660CtrlPin::WAKE: return 21;
}
return -1;
}
};
The constructor arguments are the physical level of RST, DRV_EN, WAKE and FAULTN when each is active. Get them from your schematic: inverters and level shifters change them. Porting explains each method and shows the UART variant.
4. Spin the motor¶
#include "tmc9660.hpp"
int spin_bldc_at_1000_rpm(MySpi& spi) {
tmc9660::TMC9660<MySpi> driver(spi);
tmc9660::BootloaderConfig cfg{}; // 1. boot into parameter mode
cfg.boot.boot_mode = tmc9660::bootcfg::BootMode::Parameter;
cfg.boot.start_motor_control = true;
if (driver.bootloaderInit(&cfg) != tmc9660::TMC9660<MySpi>::BootloaderInitResult::Success) {
return -1; // wiring, power or SPI mode
}
driver.motorConfig.setType(tmc9660::tmcl::MotorType::BLDC_MOTOR, 7); // 2. motor: 7 pole pairs
driver.motorConfig.setPWMFrequency(25000);
const auto ctx = driver.getMotorContext(); // 3. unit conversions from the chip
driver.velocityControl.setTargetVelocityRpm(1000.0, ctx); // 4. spin
return 0;
}
What each step does:
bootloaderInit()resets the chip, sends the bootloader configuration and starts the motor-control firmware in parameter mode. Nothing else works until it returnsSuccess.setType()andsetPWMFrequency()tell the chip what it drives. Use the pole-pair count from the motor's datasheet.getMotorContext()reads the settings the unit conversions need (pole pairs, sensor, scaling) from the chip.setTargetVelocityRpm()converts RPM to the chip's internal unit and writes the target velocity.
This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
bootloaderInit() returns Failure |
SPI mode 3, CS held low for the whole 8-byte frame, RST active level |
Every reply is 0x00 or 0xFF |
MISO wiring, a common ground, the chip's supply |
| Motor hums but does not turn | Pole pairs, phase wiring, commutation mode and feedback (API guide) |
| FAULTN stays active | Supply under-voltage, gate-driver fault (Troubleshooting) |
Next¶
- API guide: configure feedback and control loops, read telemetry, handle errors.
- Examples: complete ESP32 test applications for BLDC, stepper, DC and telemetry.
- Bootloader: every boot option, including encoders and step/dir.