For anyone who might be trying to use a TMC2130 stepper motor driver with the ESP32, I’ve got a great library for you. It took me a decent amount of time to pull together.
The code is at https://github.com/chilipeppr/robot-actuator-esp32-v2/tree/master/esp32
tmc2130_spi.lua - Main library file
-- TMC2130 driver
-- Communicating with the TMC2130 happens over SPI. Trinamic expects
-- 40 bits sent in (5 bytes) and then 40 bits are sent back. So,
-- even if you want to just update one flag, you have to send all
-- of the other parameters for that register as well. This library
-- makes all of that easy for you.
local m = {}
-- m = {}
bitstr = require("bitstr_v2")
-- for version 1.2
m.pinSdi = 19
m.pinSck = 18
m.pinCs = 5
m.pinSdo = 23
-- for original 1.0
-- m.pinSdi = 12
-- m.pinSck = 13
-- m.pinCs = 15
-- m.pinSdo = 5
-- for version 1.2
m.pinStep = 22 -- GPIO22, pin36 on esp32-wroom-32d, Orig 2
m.pinDir = 21 -- pin33 on esp32-wroom-32d, Orig 14
m.pinSleep = 4 --17 -- ENN pin28 GPIO17 on esp32-wroom-32d, Orig 15 or 0
-- for original 1.0 design
-- m.pinStep = 2
-- m.pinDir = 14
-- m.pinSleep = 0 --15
m.IRUN = 1
m.isDebug = false
m._isInitted = false
-- Pass in a table of settings
-- @param tbl.initStepDirEnPins Defaults to false
-- @param tbl.initAsStealthChop Defaults to false
-- @param tbl.IRUN Defaults to 1 for lowest current. Can go to 31.
-- @param tbl.pinStep Defaults to 22
-- @param tbl.pinDir Defaults to 21
-- @param tbl.pinEn Defaults to 4
-- @param tbl.isDebug Defaults to false. Turn on for extra logging.
-- Example motor.init({initStepDirEnPins=true, initAsStealthChop=true, IRUN=2})
function m.init(tbl)
if m._isInitted then
print("TMC2130 already initted")
return
end
m._isInitted = true
if tbl.IRUN ~= nil then m.IRUN = tbl.IRUN end
if tbl.pinStep ~= nil then m.pinStep = tbl.pinStep end
if tbl.pinDir ~= nil then m.pinDir = tbl.pinDir end
if tbl.pinEn ~= nil then m.pinSleep = tbl.pinEn end
if tbl.isDebug == true then m.isDebug = true end
-- defaults to false
if tbl.initStepDirEnPins == true then
gpio.config({
gpio= {
m.pinStep, m.pinSleep, m.pinDir
},
dir=gpio.IN_OUT,
-- pull=gpio.PULL_UP
})
gpio.write(m.pinStep, 0)
gpio.write(m.pinSleep, 0) -- needs to be low for TMC2130 to go into spi mode
gpio.write(m.pinDir, 1)
-- use pull up approach so the pulse counter can read this
-- port value without chewing up a 2nd input port
-- gpio.config( { gpio=m.pinDir, dir=gpio.IN_OUT, pull=gpio.PULL_UP } )
end
-- Make sure ENABLE pin is set to LOW (not HIGH) correctly for TMC2130 before
-- trying to do SPI to it, or it won't respond
-- Setup spi bus
local busmaster_config = {
sclk = m.pinSck,
mosi = m.pinSdi,
miso = m.pinSdo
}
m.busmaster = spi.master(spi.HSPI, busmaster_config)
local device_config = {
cs = m.pinCs,
mode = 3,
freq = 16000000/8, --, MSBFIRST, SPI_MODE3,
-- command_bits =
}
m.dev1 = m.busmaster:device(device_config)
-- read GSTAT cuz it can nicely reset stuff
m.readGSTAT()
-- See if they want stealthchop init
if tbl.initAsStealthChop then
m.initAsStealthChop()
end
end
-- Sets up motor for stealthChop operation which is the default
function m.initAsStealthChop()
-- enables stealthChop (with default PWM_CONF)
m.writeGCONF({
en_pwm_mode=true, -- 1: stealthChop voltage PWM mode enabled (depending on velocity thresholds).
direct_mode=false,
stop_enable=false,
I_scale_analog=1, -- 1: Use voltage supplied to AIN as current reference, 0: Normal operation, use internal reference voltage
internal_Rsense=0, -- 0: Normal operation, 1: Internal sense resistors. Use current supplied into AIN as reference for internal sense resistor,
})
-- IRUN current to run motors at. 0..31, so 10 is 1/3rd of 2a or 600ma
-- the small steppers expect 150ma, so running it at intense current
-- IHOLD should be 0 for no current at standstill
-- IHOLDDELAY is 0..15 Controls the number of clock cycles for motor
-- power down after a motion as soon as standstill is
-- detected (stst=1) and TPOWERDOWN has expired.
-- The smooth transition avoids a motor jerk upon
-- power down.
-- : 0x9000061F0A; // IHOLD_IRUN: IHOLD=10, IRUN=31 (max. current), IHOLDDELAY=6
m.writeIHOLD_IRUN({
IRUN=m.IRUN, -- 1 to 31
IHOLD=0, -- 0 is no current at standstill
IHOLDDELAY=15
})
-- motor.readCHOPCONF()
-- : 0xEC000100C3; // CHOPCONF: TOFF=3, HSTRT=4, HEND=1, TBL=2, CHM=0 (spreadCycle)
--
m.STEPS = 1
m.writeCHOPCONF({
intpol=1,
MRES=m.STEPFULL,
CHM=1, -- chm Chopper Mode. 0 = Standard mode (spreadCycle). 1 = Constant off time with fast decay time.
TOFF=3,
HSTRT=4,
HEND=1,
TBL=2,
vsense=1 -- vsense=1 allows 55% of current setting for vsense=0
})
-- Delay before power down in stand still. 255 is 4 seconds. 127 is 2 seconds.
-- TPOWERDOWN sets the delay time after stand still (stst) of the
-- motor to motor current power down. Time range is about 0 to
-- 4 seconds.
m.writeTPOWERDOWN(127)
-- turn on freewheeling and pwm_autoscale so we enable automatic current control
-- this also lets us freewheel with IHOLD=0
-- motor.FREEWHEEL_FREEWHEEL or motor.FREEWHEEL_SHORT_LS or motor.FREEWHEEL_SHORT_HS, or FREEWHEEL_NORMAL
m.writePWMCONF({
freewheel=m.FREEWHEEL_FREEWHEEL,
pwm_autoscale=1
})
m.enable()
end
m.DIR_FWD = 1
m.DIR_REV = 0
m._dir = nil
function m.setDir(dir)
if dir == m.DIR_FWD then
if m._dir == m.DIR_FWD then
-- already set. ignore.
if m.isDebug then print("Dir fwd already set. Ignoring.") end
return
end
gpio.write(m.pinDir,1)
-- use pull up approach so the pulse counter can read this
-- port value without chewing up a 2nd input port
-- gpio.config( { gpio=m.pinDir, dir=gpio.IN, pull=gpio.PULL_UP } )
m._dir = m.DIR_FWD
if m.isDebug then print("Set dir fwd") end
else
if m._dir == m.DIR_REV then
-- already set. ignore.
if m.isDebug then print("Dir rev already set. Ignoring.") end
return
end
gpio.write(m.pinDir,0)
-- use pull up approach so the pulse counter can read this
-- port value without chewing up a 2nd input port
-- gpio.config( { gpio=m.pinDir, dir=gpio.IN, pull=gpio.PULL_DOWN } )
m._dir = m.DIR_REV
if m.isDebug then print("Set dir rev") end
end
end
function m.dirFwd()
m.setDir(m.DIR_FWD)
end
function m.dirRev()
m.setDir(m.DIR_REV)
end
function m.dirToggle()
if m._dir == m.DIR_FWD then
m.dirRev()
else
m.dirFwd()
end
end
function m.disable()
gpio.write(m.pinSleep, 1)
end
function m.enable()
gpio.write(m.pinSleep, 0)
end
function m.readGCONF()
-- print("readGCONF")
local rx = m.send2130(0x00)
local r = {}
local b
-- get 5th byte, bits 0 to 7
b = string.byte(rx,5)
r.I_scale_analog = bit.isset(b, 0)
r.internal_Rsense = bit.isset(b, 1)
r.en_pwm_mode = bit.isset(b, 2)
r.enc_commutation = bit.isset(b, 3)
r.shaft = bit.isset(b, 4)
r.diag0_error = bit.isset(b, 5)
r.diag0_otpw = bit.isset(b, 6)
r.diag0_stall = bit.isset(b, 7)
-- get 4th byte, bits 8 to 15
b = string.byte(rx,4)
r.diag1_stall = bit.isset(b, 0)
r.diag1_index = bit.isset(b, 1)
r.diag1_onstate = bit.isset(b, 2)
r.diag1_steps_skipped = bit.isset(b, 3)
r.diag0_int_pushpull = bit.isset(b, 4)
r.diag1_pushpull = bit.isset(b, 5)
r.small_hysteresis = bit.isset(b, 6)
r.stop_enable = bit.isset(b, 7)
-- get 3rd byte, bits 16 to 23
b = string.byte(rx,4)
r.direct_mode = bit.isset(b, 0)
r.test_mode = bit.isset(b, 1)
if m.isDebug then print("GCONF:", sjson.encode(r)) end
return r
end
The rest is at https://github.com/chilipeppr/robot-actuator-esp32-v2/tree/master/esp32 since this forum doesn’t allow more than 32,000 chars to be posted.