Added TMC5160 support (#725)

TMC5160: Added TMC5160 support

* Added example code for tmc5160 and smoke test
* Update go.mod
* Updated mod file
* Cleaned up commented out code and updated readme.
* ran go fmt
* Fixed setrampspeed func
* Removed spi1 pin setup in example.go. Renamed SPIComm to spicomm.go
* Removed unused test file
* Removed commented line
This commit is contained in:
Amken USA
2025-01-18 01:49:00 -05:00
committed by GitHub
parent 00578a3a81
commit 486949686c
13 changed files with 3050 additions and 5 deletions
+61
View File
@@ -0,0 +1,61 @@
// Connects to SPI1 on a RP2040 (Pico)
package main
import (
"machine"
"tinygo.org/x/drivers/tmc5160"
)
func main() {
// Step 1. Setup your protocol. SPI setup shown below
spi := machine.SPI1
spi.Configure(machine.SPIConfig{
Frequency: 12000000, // Upto 12 MHZ is pretty stable. Reduce to 5 or 6 Mhz if you are experiencing issues
Mode: 3,
LSBFirst: false,
})
// Step 2. Set up all associated Pins
csPin0 := machine.GPIO13
csPin0.Configure(machine.PinConfig{Mode: machine.PinOutput})
enn0 := machine.GPIO18
enn0.Configure(machine.PinConfig{Mode: machine.PinOutput})
// csPins is a map of all chip select pins in a multi driver setup.
//Only one pin csPin0 mapped to "0"is shown in this example, but add more mappings as required
csPins := map[uint8]machine.Pin{0: csPin0}
//bind csPin to driverAdddress
driverAddress := uint8(0) // Let's assume we are working with driver at address 0x01
// Step 3. Bind the communication interface to the protocol
comm := tmc5160.NewSPIComm(*spi, csPins)
// Step 4. Define your stepper like this below
//stepper := tmc5160.NewStepper(angle , gearRatio vSupply rCoil , lCoil , iPeak , rSense , mSteps, fclk )
stepper := tmc5160.NewDefaultStepper() // Default Stepper should be used only for testing.
// Step 5. Instantiate your driver
driver := tmc5160.NewDriver(
comm,
driverAddress,
enn0,
stepper)
// Setting and getting mode
rampMode := tmc5160.NewRAMPMODE(comm, driverAddress)
err := rampMode.SetMode(tmc5160.PositioningMode)
if err != nil {
return
}
mode, err := rampMode.GetMode()
if err != nil {
println("Error getting mode:", err)
} else {
println("Current Mode:", mode)
}
// Read GCONF register
GCONF := tmc5160.NewGCONF()
gconfVal, err := driver.ReadRegister(tmc5160.GCONF)
// Uppack the register to get all the bits and bytes of the register
GCONF.Unpack(gconfVal)
//E.g. MultiStepFlit is retrieved from the GCONF register
println("GCONF:MultiStepFlit:", GCONF.MultistepFilt)
}
+6 -3
View File
@@ -1,20 +1,23 @@
module tinygo.org/x/drivers
go 1.18
go 1.22.1
toolchain go1.23.1
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/orsinium-labs/tinymath v1.1.0
github.com/soypat/natiu-mqtt v0.5.1
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/net v0.7.0
tinygo.org/x/tinyfont v0.3.0
tinygo.org/x/tinyterm v0.1.0
)
require (
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
)
+6 -2
View File
@@ -3,8 +3,9 @@ github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@@ -12,12 +13,15 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTeV1vS5UA=
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
+1
View File
@@ -136,6 +136,7 @@ tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/mai
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+211
View File
@@ -0,0 +1,211 @@
# TMC5160 Driver for Go (TinyGo)
This repository provides a Go-based driver for the **TMC5160** stepper motor driver, implemented for both **SPI** and **UART** communication modes. The driver allows you to easily interface with the TMC5160 to configure and control stepper motors.
## Table of Contents
- [Installation](#installation)
- [Communication Modes](#communication-modes)
- [SPI Mode](#spi-mode)
- [UART Mode](#uart-mode)
- [Usage Example](#usage-example)
- [Setting and Getting Modes](#setting-and-getting-modes)
- [Reading and Writing Registers](#reading-and-writing-registers)
- [API Reference](#api-reference)
- [License](#license)
## Installation
To use the TMC5160 driver, you'll need to have **TinyGo** installed. You can install TinyGo by following the [official installation guide](https://tinygo.org/getting-started/).
### Dependencies
- **machine**: To interface with hardware on platforms like Raspberry Pi, STM32, etc.
- **TinyGo**: A Go compiler for embedded systems.
Add the module
```bash
import "tinygo.org/x/drivers/tmc5160"
```
### Communication Modes
The TMC5160 supports two communication modes for controlling the motor:
**SPI Mode**
To communicate with the TMC5160 in SPI mode, you'll need to configure the SPI bus and the chip-select (CS) pin. This allows full-speed communication between your microcontroller and the TMC5160.
SPI Setup
In SPI mode, you must configure the SPI interface on your microcontroller. Here's how to set up SPI communication for the TMC5160.
```go
spi := machine.SPI1
csPin := machine.GPIO13
spi.Configure(machine.SPIConfig{
SCK: machine.GPIO10,
SDI: machine.GPIO11,
SDO: machine.GPIO12,
Frequency: 5000000,
Mode: 3,
LSBFirst: false,
})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
```
**Sending Commands via SPI**
The driver supports reading and writing registers using the SPIComm interface, which is initialized with the configured SPI bus and CS pins
```go
comm := tmc5160.NewSPIComm(*spi, csPins)
driver := tmc5160.NewTMC5160(comm, driverIndex)
driver.WriteRegister(tmc5160.GCONF, value)
```
**UART Mode**
Alternatively, you can use UART mode to communicate with the TMC5160. UART mode is useful for cases where SPI is not available or when the TMC5160 is used in multi-driver configurations with limited SPI pins.
UART Setup
In UART mode, you will need to configure the UART interface with the appropriate baud rate and settings:
```go
uart := machine.UART0
uart.Configure(machine.UARTConfig{
BaudRate: 115200,
})
```
#### Sending Commands via UART
The UART communication is handled through the UARTComm struct, which wraps the UART interface.
```go
comm := tmc5160.NewUARTComm(uart, 0x01)
driver := tmc5160.NewTMC5160(comm, 0)
driver.WriteRegister(tmc5160.GCONF, 0x01)
```
## Usage Example
Heres a simple example of how to use the TMC5160 driver with SPI and UART modes:
```aiignore
// Connects to SPI1 on a RP2040 (Pico)
package main
import (
"machine"
"tinygo.org/x/drivers/tmc5160"
)
func main() {
// Step 1. Setup your protocol. SPI setup shown below
spi := machine.SPI1
spi.Configure(machine.SPIConfig{
SCK: machine.GPIO10,
SDI: machine.GPIO11,
SDO: machine.GPIO12,
Frequency: 12000000, // Upto 12 MHZ is pretty stable. Reduce to 5 or 6 Mhz if you are experiencing issues
Mode: 3,
LSBFirst: false,
})
// Step 2. Set up all associated Pins
csPin0 := machine.GPIO13
csPin0.Configure(machine.PinConfig{Mode: machine.PinOutput})
enn0 := machine.GPIO18
enn0.Configure(machine.PinConfig{Mode: machine.PinOutput})
// csPins is a map of all chip select pins in a multi driver setup.
//Only one pin csPin0 mapped to "0"is shown in this example, but add more mappings as required
csPins := map[uint8]machine.Pin{0: csPin0}
//bind csPin to driverAdddress
driverAddress := uint8(0) // Let's assume we are working with driver at address 0x01
// Step 3. Bind the communication interface to the protocol
comm := tmc5160.NewSPIComm(*spi, csPins)
// Step 4. Define your stepper like this below
//stepper := tmc5160.NewStepper(angle , gearRatio vSupply rCoil , lCoil , iPeak , rSense , mSteps, fclk )
stepper := tmc5160.NewDefaultStepper() // Default Stepper should be used only for testing.
// Step 5. Instantiate your driver
driver := tmc5160.NewDriver(
comm,
driverAddress,
enn0,
stepper)
// Setting and getting mode
rampMode := tmc5160.NewRAMPMODE(comm, driverAddress)
err := rampMode.SetMode(tmc5160.PositioningMode)
if err != nil {
return
}
mode, err := rampMode.GetMode()
if err != nil {
println("Error getting mode:", err)
} else {
println("Current Mode:", mode)
}
// Read GCONF register
GCONF := tmc5160.NewGCONF()
gconfVal, err := driver.ReadRegister(tmc5160.GCONF)
// Uppack the register to get all the bits and bytes of the register
GCONF.Unpack(gconfVal)
//E.g. MultiStepFlit is retrieved from the GCONF register
println("GCONF:MultiStepFlit:", GCONF.MultistepFilt)
}
```
## Reading and Writing Registers
You can easily read and write registers using the WriteRegister and ReadRegister methods:
```aiignore
// Write a value to a register
err := driver.WriteRegister(tmc5160.GCONF, 0x01)
if err != nil {
fmt.Println("Error writing register:", err)
}
// Read a register
value, err := driver.ReadRegister(tmc5160.GCONF)
if err != nil {
fmt.Println("Error reading register:", err)
} else {
fmt.Println("Read value from GCONF:", value)
}
```
## API Reference
NewSPIComm(spi machine.SPI, csPins map[uint8]machine.Pin) *SPIComm
Creates a new SPI communication interface for the TMC5160.
NewUARTComm(uart machine.UART, address uint8) *UARTComm
Creates a new UART communication interface for the TMC5160.
NewTMC5160(comm RegisterComm, address uint8) *TMC5160
Creates a new instance of the TMC5160 driver.
WriteRegister(register uint8, value uint32) error
Writes a value to the specified register.
ReadRegister(register uint8) (uint32, error)
Reads a value from the specified register.
## License
This project is licensed under the MIT License
+82
View File
@@ -0,0 +1,82 @@
package tmc5160
// Driver Register addresses
const (
GCONF uint8 = 0x00 // Global configuration flags
GSTAT uint8 = 0x01 // Global status flags
IFCNT = 0x02 // UART transmission counter
SLAVECONF = 0x03 // UART slave configuration
IOIN = 0x04 // Read input / write output pins
X_COMPARE = 0x05 // Position comparison register
OTP_PROG = 0x06 // OTP programming register
OTP_READ = 0x07 // OTP read register
FACTORY_CONF = 0x08 // Factory configuration (clock trim)
SHORT_CONF = 0x09 // Short detector configuration
DRV_CONF = 0x0A // Driver configuration
GLOBAL_SCALER = 0x0B // Global scaling of motor current
OFFSET_READ = 0x0C // Offset calibration results
/* Velocity dependent driver feature control registers */
IHOLD_IRUN = 0x10 // Driver current control
TPOWERDOWN = 0x11 // Delay before power down
TSTEP = 0x12 // Actual time between microsteps
TPWMTHRS = 0x13 // Upper velocity for stealthChop voltage PWM mode
TCOOLTHRS = 0x14 // Lower threshold velocity for switching on smart energy coolStep and stallGuard feature
THIGH = 0x15 // Velocity threshold for switching into a different chopper mode and fullstepping
/* Ramp generator motion control registers */
RAMPMODE = 0x20 // Driving mode (Velocity, Positioning, Hold)
XACTUAL = 0x21 // Actual motor position
VACTUAL = 0x22 // Actual motor velocity from ramp generator
VSTART = 0x23 // Motor start velocity
A_1 = 0x24 // First acceleration between VSTART and V1
V_1 = 0x25 // First acceleration/deceleration phase target velocity
AMAX = 0x26 // Second acceleration between V1 and VMAX
VMAX = 0x27 // Target velocity in velocity mode
DMAX = 0x28 // Deceleration between VMAX and V1
D_1 = 0x2A // Deceleration between V1 and VSTOP
//Attention: Do not set 0 in positioning mode, even if V1=0!
VSTOP = 0x2B // Motor stop velocity
//Attention: Set VSTOP > VSTART!
//Attention: Do not set 0 in positioning mode, minimum 10 recommend!
TZEROWAIT = 0x2C // Waiting time after ramping down to zero velocity before next movement or direction inversion can start.
XTARGET = 0x2D // Target position for ramp mode
/* Ramp generator driver feature control registers */
VDCMIN = 0x33 // Velocity threshold for enabling automatic commutation dcStep
SW_MODE = 0x34 // Switch mode configuration
RAMP_STAT = 0x35 // Ramp status and switch event status
XLATCH = 0x36 // Ramp generator latch position upon programmable switch event
/* Encoder registers */
ENCMODE = 0x38 // Encoder configuration and use of N channel
X_ENC = 0x39 // Actual encoder position
ENC_CONST = 0x3A // Accumulation constant
ENC_STATUS = 0x3B // Encoder status information
ENC_LATCH = 0x3C // Encoder position latched on N event
ENC_DEVIATION = 0x3D // Maximum number of steps deviation between encoder counter and XACTUAL for deviation warning
/* Motor driver registers */
MSLUT0 uint8 = 0x60 // 32 bits
MSLUT1 uint8 = 0x61 // 32 bits
MSLUT2 uint8 = 0x62 // 32 bits
MSLUT3 uint8 = 0x63 // 32 bits
MSLUT4 uint8 = 0x64 // 32 bits
MSLUT5 uint8 = 0x65 // 32 bits
MSLUT6 uint8 = 0x66 // 32 bits
MSLUT7 uint8 = 0x67 // 32 bits
MSLUTSEL = 0x68 // Look up table segmentation definition
MSLUTSTART = 0x69 // Absolute current at microstep table entries 0 and 256
MSCNT = 0x6A // Actual position in the microstep table
MSCURACT = 0x6B // Actual microstep current
CHOPCONF = 0x6C // Chopper and driver configuration
COOLCONF = 0x6D // coolStep smart current control register and stallGuard2 configuration
DCCTRL = 0x6E // dcStep automatic commutation configuration register
DRV_STATUS = 0x6F // stallGuard2 value and driver error flags
PWMCONF = 0x70 // stealthChop voltage PWM mode chopper configuration
PWM_SCALE = 0x71 // Results of stealthChop amplitude regulator.
PWM_AUTO = 0x72 // Automatically determined PWM config values
LOST_STEPS = 0x73 // Number of input steps skipped due to dcStep. only with SD_MODE = 1
expectedVersion = 0x03
DEFAULT_F_CLK = 12000000
)
+56
View File
@@ -0,0 +1,56 @@
package tmc5160
import (
"github.com/orsinium-labs/tinymath"
"golang.org/x/exp/constraints"
)
// VelocityToVMAX calculates the VMAX register value from the current stepper velocity which is in microsteps per tRef (i.e 1/clock speed)
func (stepper *Stepper) CurrentVelocityToVMAX() uint32 {
tref := float32(16777216) / (float32(stepper.Fclk) * 1000000)
r := stepper.VelocitySPS * stepper.GearRatio * float32(tref)
return constrain(uint32(r), 0, maxVMAX) // VMAX register value cannot exceed maxVMAX
}
func (stepper *Stepper) DesiredVelocityToVMAX(v float32) uint32 {
tref := 16777216 / (float32(stepper.Fclk) * 1000000)
r := tinymath.Round(v * stepper.GearRatio * tref)
return constrain(uint32(r), 0, maxVMAX) // VMAX register value cannot exceed maxVMAX
}
func (stepper *Stepper) DesiredAccelToAMAX(dacc float32, dVel float32) uint32 {
dVelToVMAX := stepper.DesiredVelocityToVMAX(dVel)
_a := uint64(dVelToVMAX) * 131072
_b := float32(_a) / dacc
_c := _b / float32(uint32(stepper.Fclk)*1000000)
return uint32(_c)
}
// Convert threshold speed (Hz) to internal TSTEP value
func (stepper *Stepper) DesiredSpeedToTSTEP(thrsSpeed uint32) uint32 {
if thrsSpeed < 0 {
return 0
}
_a := stepper.DesiredVelocityToVMAX(float32(thrsSpeed))
_b := float32(16777216 / _a)
_c := float32(stepper.MSteps) / float32(256)
_d := uint32(_b * _c)
return constrain(_d, 0, 1048575)
}
func (stepper *Stepper) VMAXToTSTEP(vmax uint32) uint32 {
_b := float32(16777216 / vmax)
_c := float32(stepper.MSteps) / float32(256)
_d := tinymath.Round(_b * _c)
return constrain(uint32(_d), 0, 1048575)
}
// Constrain function to limit values to a specific range (supports multiple types).
func constrain[T constraints.Ordered](value, min, max T) T {
if value < min {
return min
} else if value > max {
return max
}
return value
}
+2080
View File
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
//go:build tinygo
package tmc5160
import (
"machine"
"time"
)
// CustomError is a lightweight error type used for TinyGo compatibility.
type CustomError string
func (e CustomError) Error() string {
return string(e)
}
// SPIComm implements RegisterComm for SPI-based communication
type SPIComm struct {
spi machine.SPI
CsPins map[uint8]machine.Pin // Map to store CS pin for each Driver by its address
}
// NewSPIComm creates a new SPIComm instance.
func NewSPIComm(spi machine.SPI, csPins map[uint8]machine.Pin) *SPIComm {
return &SPIComm{
spi: spi,
CsPins: csPins,
}
}
// Setup initializes the SPI communication with the Driver and configures all CS pins.
func (comm *SPIComm) Setup() error {
// Check if SPI is initialized
if comm.spi == (machine.SPI{}) {
return CustomError("SPI not initialized")
}
// Configure all CS pins (make them output and set them high)
for _, csPin := range comm.CsPins {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.High() // Set all CS pins high initially
}
// Configure the SPI interface with the desired settings
err := comm.spi.Configure(machine.SPIConfig{
LSBFirst: false,
Mode: 3,
})
if err != nil {
return CustomError("Failed to configure SPI")
}
return nil
}
// WriteRegister sends a register write command to the TMC5160.
func (comm *SPIComm) WriteRegister(register uint8, value uint32, driverAddress uint8) error {
// Assert the chip select pin (set CS low to start communication)
csPin, exists := comm.CsPins[driverAddress]
if !exists {
return CustomError("Invalid driver address")
}
csPin.Low()
// Set the register address with WRITE_ACCESS (0x80)
addressWithWriteAccess := register | 0x80
// Send the address and the data to write (split into 4 bytes)
_, err := spiTransfer40(&comm.spi, addressWithWriteAccess, value)
if err != nil {
csPin.High()
return CustomError("Failed to write register")
}
// Deassert the chip select pin (set CS high to end communication)
csPin.High()
return nil
}
// ReadRegister sends a register read command to the TMC5160.
func (comm *SPIComm) ReadRegister(register uint8, driverAddress uint8) (uint32, error) {
// Assert the chip select pin (set CS low to start communication)
csPin, exists := comm.CsPins[driverAddress]
if !exists {
return 0, CustomError("Invalid driver address")
}
csPin.Low()
// Step 1: Send a dummy write operation to begin the read sequence
_, err := spiTransfer40(&comm.spi, register, 0x00) // Send dummy data
if err != nil {
csPin.High()
return 0, CustomError("Failed to send dummy write")
}
csPin.High()
time.Sleep(176 * time.Nanosecond)
csPin.Low()
// Step 2: Send the register read request again to get the actual value
response, err := spiTransfer40(&comm.spi, register, 0x00) // Send again to get actual register data
if err != nil {
csPin.High()
return 0, CustomError("Failed to read register")
}
// Deassert the chip select pin (set CS high to end communication)
csPin.High()
return response, nil
}
func spiTransfer40(spi *machine.SPI, register uint8, txData uint32) (uint32, error) {
// Prepare the 5-byte buffer for transmission (1 byte address + 4 bytes data)
tx := []byte{
register, // Address byte
byte(txData >> 24), // Upper 8 bits of data
byte(txData >> 16), // Middle 8 bits of data
byte(txData >> 8), // Next 8 bits of data
byte(txData), // Lower 8 bits of data
}
rx := make([]byte, 5)
// Perform the SPI transaction
err := spi.Tx(tx, rx)
if err != nil {
return 0, err
}
// Combine the received bytes into a 32-bit response, ignore the address byte
rxData := uint32(rx[1])<<24 | uint32(rx[2])<<16 | uint32(rx[3])<<8 | uint32(rx[4])
return rxData, nil
}
+105
View File
@@ -0,0 +1,105 @@
package tmc5160
const maxVMAX = 8388096
// PowerStageParameters represents the power stage parameters
type PowerStageParameters struct {
drvStrength uint8
bbmTime uint8
bbmClks uint8
}
// MotorParameters represents the motor parameters
type MotorParameters struct {
globalScaler uint16
ihold uint8
irun uint8
iholddelay uint8
pwmGradInitial uint16
pwmOfsInitial uint16
freewheeling uint8
}
// MotorDirection defines motor direction constants
type MotorDirection uint8
const (
Clockwise MotorDirection = iota
CounterClockwise
)
const (
// Common stepper motor angles
StepAngle_1_8 = 1.8
StepAngle_0_9 = 0.9
StepAngle_0_72 = 0.72
StepAngle_1_2 = 1.2
StepAngle_0_48 = 0.48
// Common microstepping options
Step_1 uint8 = 1
Step_2 uint8 = 2
Step_4 uint8 = 4
Step_8 uint8 = 8
Step_16 uint8 = 16
Step_32 uint8 = 32
Step_64 uint8 = 64
Step_128 uint8 = 128
)
const (
DefaultAngle float32 = StepAngle_1_8
DefaultGearRatio float32 = 1.0
DefaultVSupply float32 = 12.0
DefaultRCoil float32 = 1.2
DefaultLCoil float32 = 0.005
DefaultIPeak float32 = 2.0
DefaultRSense float32 = 0.1
DefaultFclk uint8 = 12
DefaultStep_256 = 256
)
type Stepper struct {
Angle float32
GearRatio float32
VelocitySPS float32 // Velocity in Steps per sec
VSupply float32
RCoil float32
LCoil float32
IPeak float32
RSense float32
MSteps uint8
Fclk uint8 //Clock in Mhz
}
// NewStepper function initializes a Stepper with default values used for testing
func NewDefaultStepper() Stepper {
return Stepper{
Angle: StepAngle_1_8, // Default to 1.8 degrees
GearRatio: 1.0, // Default to no reduction (1:1)
VSupply: 12.0, // Default 12V supply
RCoil: 1.2, // Default coil resistance (1.2 ohms)
LCoil: 0.005, // Default coil inductance (5 mH)
IPeak: 2.0, // Default peak current (2A)
RSense: 0.1, // Default sense resistance (0.1 ohms)
MSteps: Step_16, // Default 16 Microsteps
Fclk: DefaultFclk,
}
}
// NewStepper initializes a Stepper with user-defined values
func NewStepper(angle float32, gearRatio, vSupply, rCoil, lCoil, iPeak, rSense float32, mSteps uint8, fclk uint8) Stepper {
return Stepper{
Angle: angle, // User-defined stepper angle (e.g., StepAngle_1_8)
GearRatio: gearRatio, // User-defined gear ratio
VSupply: vSupply, // User-defined supply voltage
RCoil: rCoil, // User-defined coil resistance
LCoil: lCoil, // User-defined coil inductance
IPeak: iPeak, // User-defined peak current
RSense: rSense, // User-defined sense resistance
MSteps: mSteps, // User-defined microstepping setting
Fclk: fclk, // User-defined clock frequency in MHz
}
}
+194
View File
@@ -0,0 +1,194 @@
//go:build tinygo
package tmc5160
import (
"github.com/orsinium-labs/tinymath"
"machine"
)
type Driver struct {
comm RegisterComm
address uint8
enablePin machine.Pin
stepper Stepper
}
func NewDriver(comm RegisterComm, address uint8, enablePin machine.Pin, stepper Stepper) *Driver {
return &Driver{
comm: comm,
address: address,
enablePin: enablePin,
stepper: stepper,
}
}
// WriteRegister sends a register write command to the Driver.
func (driver *Driver) WriteRegister(reg uint8, value uint32) error {
if driver.comm == nil {
return CustomError("communication interface not set")
}
// Use the communication interface (RegisterComm) to write the register
return driver.comm.WriteRegister(reg, value, driver.address)
}
// ReadRegister sends a register read command to the Driver and returns the read value.
func (driver *Driver) ReadRegister(reg uint8) (uint32, error) {
if driver.comm == nil {
return 0, CustomError("communication interface not set")
}
// Use the communication interface (RegisterComm) to read the register
return driver.comm.ReadRegister(reg, driver.address)
}
// Begin initializes the Driver driver with power and motor parameters
func (driver *Driver) Begin(powerParams PowerStageParameters, motorParams MotorParameters, stepperDirection MotorDirection) bool {
// Clear the reset and charge pump undervoltage flags
gstat := NewGSTAT()
gstat.Reset = true
gstat.UvCp = true
err := driver.WriteRegister(GSTAT, gstat.Pack())
if err != nil {
return false
}
// Configure driver settings
drvConf := NewDRV_CONF()
drvConf.DrvStrength = constrain(powerParams.drvStrength, 0, 3)
drvConf.BBMTime = constrain(powerParams.bbmTime, 0, 24)
drvConf.BBMClks = constrain(powerParams.bbmClks, 0, 15)
err = driver.WriteRegister(DRV_CONF, drvConf.Pack())
if err != nil {
return false
}
// Set global scaler
err = driver.WriteRegister(GLOBAL_SCALER, uint32(constrain(motorParams.globalScaler, 32, 256)))
if err != nil {
return false
}
// Set initial currents and delay
iholdrun := NewIHOLD_IRUN()
iholdrun.Ihold = constrain(motorParams.ihold, 0, 31)
iholdrun.Ihold = constrain(motorParams.irun, 0, 31)
iholdrun.IholdDelay = 7
err = driver.WriteRegister(IHOLD_IRUN, iholdrun.Pack())
if err != nil {
return false
}
// Set PWM configuration values
pwmconf := NewPWMCONF()
err = driver.WriteRegister(PWMCONF, 0xC40C001E)
if err != nil {
return false
} // Reset default value pwm_ofs = 196,pwm_grad = 12,pwm_freq = 0, pwm_autoscale = false, pwm_autograd = false,freewheel = 3
pwmconf.PwmAutoscale = false // Temporarily set to false for setting OFS and GRAD values
_fclk := int(driver.stepper.Fclk) * 1000000
if _fclk > DEFAULT_F_CLK {
pwmconf.PwmFreq = 0
} else {
pwmconf.PwmFreq = 0b01 // Recommended: 35kHz with internal 12MHz clock
}
pwmconf.PwmGrad = uint8(motorParams.pwmGradInitial)
pwmconf.PwmOfs = uint8(motorParams.pwmOfsInitial)
pwmconf.Freewheel = motorParams.freewheeling
err = driver.WriteRegister(PWMCONF, pwmconf.Pack())
if err != nil {
return false
}
// Enable PWM auto-scaling and gradient adjustment
pwmconf.PwmAutoscale = true
pwmconf.PwmAutograd = true
err = driver.WriteRegister(PWMCONF, pwmconf.Pack())
if err != nil {
return false
}
// Recommended chop configuration settings
_chopConf := NewCHOPCONF()
_chopConf.Toff = 5
_chopConf.Tbl = 2
_chopConf.HstrtTfd = 4
_chopConf.HendOffset = 0
err = driver.WriteRegister(CHOPCONF, _chopConf.Pack())
if err != nil {
return false
}
rampMode := NewRAMPMODE(driver.comm, driver.address)
rampMode.SetMode(PositioningMode)
gconf := NewGCONF()
gconf.EnPwmMode = true // Enable stealthChop PWM mode
gconf.Shaft = stepperDirection == Clockwise
err = driver.WriteRegister(GCONF, gconf.Pack())
if err != nil {
return false
}
// Set default start, stop, threshold speeds
driver.setRampSpeeds(0.0, 0.1, 0.0) // Start, stop, threshold speeds
// Set default D1 (must not be = 0 in positioning mode even with V1=0)
err = driver.WriteRegister(D_1, 100)
if err != nil {
return false
}
return false
}
func (driver *Driver) setRampSpeeds(startSpeed float32, stopSpeed float32, transitionSpeed float32) {
str := driver.stepper.DesiredSpeedToTSTEP(uint32(startSpeed))
stp := driver.stepper.DesiredSpeedToTSTEP(uint32(stopSpeed))
ts := driver.stepper.DesiredSpeedToTSTEP(uint32(transitionSpeed))
driver.WriteRegister(VSTART, uint32(tinymath.Min(0x3FFFF, float32(str))))
driver.WriteRegister(VSTOP, uint32(tinymath.Min(0x3FFFF, float32(stp))))
driver.WriteRegister(V_1, uint32(tinymath.Min(0xFFFFF, float32(ts))))
println("Ramp set to: startSpeed:", startSpeed, "stopSpeed:", stopSpeed, "transitionSpeed:", transitionSpeed)
}
// setMaxSpeed sets the maximum speed to 0 (placeholder function)
func setMaxSpeed(speed uint32) {
// This is a placeholder function that sets the speed
// Implement the actual logic to set the maximum speed register value
println("Max Speed set to:", speed)
}
// Dump_TMC reads multiple registers from the Driver and logs their values with their names.
func (driver *Driver) Dump_TMC() error {
registers := []uint8{
GCONF, CHOPCONF, GSTAT, DRV_STATUS, FACTORY_CONF, IOIN, LOST_STEPS, MSCNT,
MSCURACT, OTP_READ, PWM_SCALE, PWM_AUTO, TSTEP,
}
registerNames := map[uint8]string{
GCONF: "GCONF",
CHOPCONF: "CHOPCONF",
GSTAT: "GSTAT",
DRV_STATUS: "DRV_STATUS",
FACTORY_CONF: "FACTORY_CONF",
IOIN: "IOIN",
LOST_STEPS: "LOST_STEPS",
MSCNT: "MSCNT",
MSCURACT: "MSCURACT",
OTP_READ: "OTP_READ",
PWM_SCALE: "PWM_SCALE",
PWM_AUTO: "PWM_AUTO",
TSTEP: "TSTEP",
}
for _, reg := range registers {
// Fetch the register name from the map
regName, exists := registerNames[reg]
if !exists {
regName = "Unknown Register"
}
val, err := driver.ReadRegister(reg)
if err != nil {
println("Error reading register", regName, err)
return err
}
println("Register", regName, "Value:", val)
}
return nil
}
+101
View File
@@ -0,0 +1,101 @@
//go:build uart
package tmc5160
import (
"machine"
"time"
)
// UARTComm implements RegisterComm for UART-based communication with Driver.
type UARTComm struct {
uart machine.UART
address uint8
}
// NewUARTComm creates a new UARTComm instance.
func NewUARTComm(uart machine.UART, address uint8) *UARTComm {
return &UARTComm{
uart: uart,
address: address,
}
}
// Setup initializes the UART communication with the Driver.
func (comm *UARTComm) Setup() error {
if comm.uart == (machine.UART{}) {
return CustomError("UART not initialized")
}
err := comm.uart.Configure(machine.UARTConfig{
BaudRate: 115200,
})
if err != nil {
return CustomError("Failed to configure UART")
}
return nil
}
// WriteRegister sends a register write command to the Driver.
// Prepare the data packet (sync byte + address + register + data + checksum)
func (comm *UARTComm) WriteRegister(register uint8, value uint32, driverIndex uint8) error {
buffer := []byte{
0x05, // Sync byte
comm.address, // Slave address
register | 0x80, // Write command (MSB set to 1 for write)
byte((value >> 24) & 0xFF), // MSB of value
byte((value >> 16) & 0xFF), // Middle byte
byte((value >> 8) & 0xFF), // Next byte
byte(value & 0xFF), // LSB of value
}
checksum := byte(0)
for _, b := range buffer[:7] {
checksum ^= b
}
buffer[7] = checksum // Set checksum byte
// Write the data to the Driver
done := make(chan error, 1)
go func() {
comm.uart.Write(buffer)
done <- nil
}()
select {
case err := <-done:
return err
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
return CustomError("write timeout")
}
}
// ReadRegister sends a register read command to the Driver.
func (comm *UARTComm) ReadRegister(register uint8, driverIndex uint8) (uint32, error) {
// Prepare the read command (sync byte + address + register + checksum)
var writeBuffer [4]byte
writeBuffer[0] = 0x05 // Sync byte
writeBuffer[1] = comm.address // Slave address
writeBuffer[2] = register & 0x7F // Read command (MSB clear for read)
writeBuffer[3] = writeBuffer[0] ^ writeBuffer[1] ^ writeBuffer[2] // Checksum
done := make(chan []byte, 1)
go func() {
comm.uart.Write(writeBuffer[:])
readBuffer := make([]byte, 8) // Prepare the buffer to read 8 bytes
comm.uart.Read(readBuffer)
done <- readBuffer
}()
select {
case readBuffer := <-done:
checksum := byte(0)
for i := 0; i < 7; i++ {
checksum ^= readBuffer[i]
}
if checksum != readBuffer[7] {
return 0, CustomError("checksum error")
}
return uint32(readBuffer[3])<<24 | uint32(readBuffer[4])<<16 | uint32(readBuffer[5])<<8 | uint32(readBuffer[6]), nil
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
return 0, CustomError("read timeout")
}
}
+13
View File
@@ -0,0 +1,13 @@
package tmc5160
func ToHex(value uint32) string {
hexChars := "0123456789ABCDEF"
result := ""
for i := 0; i < 8; i++ { // 8 nibbles for a 32-bit number
nibble := (value >> (28 - i*4)) & 0xF
result += string(hexChars[nibble])
}
return "0x" + result
}