This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/src/DuetNG/TMC2660.cpp
David Crocker d89ec70dfc Version 1.16 beta 1 (more like an alpha)
Support up to 6 axes in many commands
Allow creation of additional axes in M584
Display delta paremners to 3dp instead of 2dp
Removed M126/M127 error messages
Delta calibration now supports bed tilt parameters
On Duet WiFi, delta calibration is now done in double precision maths
Added support for G4 S parameter
Bed compensation or delta calibration is now aborted if the probe was
triggered at the start or was not triggered by the end of the probing
move
Bug fix: JSON file list returned by M20 S2 is now terminated by \n
Bug fix: stupidly high temperatures were returned immediately after
power up in response to status requests
Bug fix: when using an analog Z probe, probing movements were sometimes
aborted  before the full depth had been probed but the firmware thought
that probing had terminated at the full depth
Bug fix: generic messages were sent to PanelDue in plain text format
2016-10-16 22:07:10 +01:00

476 lines
15 KiB
C++

/*
* TMC2660.cpp
*
* Created on: 23 Jan 2016
* Author: David
*/
#include "RepRapFirmware.h"
#if !defined(PROTOTYPE_1)
static size_t numTmc2660Drivers;
static bool driversPowered = false;
// Pin assignments for the second prototype, using USART1 SPI
const Pin DriversClockPin = 15; // PB15/TIOA1
const Pin DriversMosiPin = 22; // PA13
const Pin DriversMisoPin = 21; // PA22
const Pin DriversSclkPin = 23; // PA23
#define USART_EXT_DRV USART1
#define ID_USART_EXT_DRV ID_USART1
#define TMC_CLOCK_TC TC0
#define TMC_CLOCK_CHAN 1
#define TMC_CLOCK_ID ID_TC1 // this is channel 1 on TC0
const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock for now
// TMC2660 registers
const uint32_t TMC_REG_DRVCTRL = 0;
const uint32_t TMC_REG_CHOPCONF = 0x80000;
const uint32_t TMC_REG_SMARTEN = 0xA0000;
const uint32_t TMC_REG_SGCSCONF = 0xC0000;
const uint32_t TMC_REG_DRVCONF = 0xE0000;
const uint32_t TMC_DATA_MASK = 0x0001FFFF;
// DRVCONF register bits
const uint32_t TMC_DRVCONF_RDSEL_0 = 0 << 4;
const uint32_t TMC_DRVCONF_RDSEL_1 = 1 << 4;
const uint32_t TMC_DRVCONF_RDSEL_2 = 2 << 4;
const uint32_t TMC_DRVCONF_RDSEL_3 = 3 << 4;
const uint32_t TMC_DRVCONF_VSENSE = 1 << 6;
const uint32_t TMC_DRVCONF_SDOFF = 1 << 7;
const uint32_t TMC_DRVCONF_TS2G_3P2 = 0 << 8;
const uint32_t TMC_DRVCONF_TS2G_1P6 = 1 << 8;
const uint32_t TMC_DRVCONF_TS2G_1P2 = 2 << 8;
const uint32_t TMC_DRVCONF_TS2G_0P8 = 3 << 8;
const uint32_t TMC_DRVCONF_DISS2G = 1 << 10;
const uint32_t TMC_DRVCONF_SLPL_MIN = 0 << 12;
const uint32_t TMC_DRVCONF_SLPL_MED = 2 << 12;
const uint32_t TMC_DRVCONF_SLPL_MAX = 3 << 12;
const uint32_t TMC_DRVCONF_SLPH_MIN = 0 << 14;
const uint32_t TMC_DRVCONF_SLPH_MIN_TCOMP = 1 << 14;
const uint32_t TMC_DRVCONF_SLPH_MED_TCOMP = 2 << 14;
const uint32_t TMC_DRVCONF_SLPH_MAX = 3 << 14;
const uint32_t TMC_DRVCONF_TST = 1 << 16;
// Chopper control register bits
const uint32_t TMC_CHOPCONF_TOFF_MASK = 15;
#define TMC_CHOPCONF_TOFF(n) ((((uint32_t)n) & 15) << 0)
#define TMC_CHOPCONF_HSTRT(n) ((((uint32_t)n) & 7) << 4)
#define TMC_CHOPCONF_HEND(n) ((((uint32_t)n) & 15) << 7)
#define TMC_CHOPCONF_HDEC(n) ((((uint32_t)n) & 3) << 11)
const uint32_t TMC_CHOPCONF_RNDTF = 1 << 13;
const uint32_t TMC_CHOPCONF_CHM = 1 << 14;
#define TMC_CHOPCONF_TBL(n) (((uint32_t)n & 3) << 15)
// Driver control register bits, when SDOFF=0
const uint32_t TMC_DRVCTRL_MRES_MASK = 0x0F;
const uint32_t TMC_DRVCTRL_MRES_SHIFT = 0;
const uint32_t TMC_DRVCTRL_MRES_16 = 0x04;
const uint32_t TMC_DRVCTRL_MRES_32 = 0x03;
const uint32_t TMC_DRVCTRL_MRES_64 = 0x02;
const uint32_t TMC_DRVCTRL_MRES_128 = 0x01;
const uint32_t TMC_DRVCTRL_MRES_256 = 0x00;
const uint32_t TMC_DRVCTRL_DEDGE = 1 << 8;
const uint32_t TMC_DRVCTRL_INTPOL = 1 << 9;
// stallGuard2 control register
const uint32_t TMC_SGCSCONF_CS_MASK = 31;
#define TMC_SGCSCONF_CS(n) ((((uint32_t)n) & 31) << 0)
const uint32_t TMC_SGCSCONF_SGT_MASK = 127 << 8;
#define TMC_SGCSCONF_SGT(n) ((((uint32_t)n) & 127) << 8)
const uint32_t TMC_SGCSCONF_SGT_SFILT = 1 << 16;
// coolStep control register
const uint32_t TMC_SMARTEN_SEMIN_MASK = 15;
const uint32_t TMC_SMARTEN_SEMIN_SHIFT = 0;
const uint32_t TMC_SMARTEN_SEUP_1 = 0 << 5;
const uint32_t TMC_SMARTEN_SEUP_2 = 1 << 5;
const uint32_t TMC_SMARTEN_SEUP_4 = 2 << 5;
const uint32_t TMC_SMARTEN_SEUP_8 = 3 << 5;
const uint32_t TMC_SMARTEN_SEMAX_MASK = 15;
const uint32_t TMC_SMARTEN_SEMAX_SHIFT = 8;
const uint32_t TMC_SMARTEN_SEDN_32 = 0 << 13;
const uint32_t TMC_SMARTEN_SEDN_8 = 1 << 13;
const uint32_t TMC_SMARTEN_SEDN_2 = 2 << 13;
const uint32_t TMC_SMARTEN_SEDN_1 = 3 << 13;
const uint32_t TMC_SMARTEN_SEIMIN_HALF = 0 << 15;
const uint32_t TMC_SMARTEN_SEIMIN_QTR = 1 << 15;
// Read response. The microstep counter can also be read, but we don't include that here.
const uint32_t TMC_RR_SG = 1 << 0; // stall detected
const uint32_t TMC_RR_OT = 1 << 1; // over temperature shutdown
const uint32_t TMC_RR_OTPW = 1 << 2; // over temperature warning
const uint32_t TMC_RR_S2G = 3 << 3; // short to ground counter (2 bits)
const uint32_t TMC_RR_OLA = 1 << 5; // open load A
const uint32_t TMC_RR_OLB = 1 << 6; // open load B
const uint32_t TMC_RR_STST = 1 << 7; // standstill detected
const unsigned int NumWriteRegisters = 5;
// Chopper control register defaults
const uint32_t defaultChopConfReg =
TMC_REG_CHOPCONF
| TMC_CHOPCONF_TBL(2)
| TMC_CHOPCONF_HDEC(0)
| TMC_CHOPCONF_HEND(3)
| TMC_CHOPCONF_HSTRT(3)
| TMC_CHOPCONF_TOFF(0); // 0x901B4 as per datasheet example except TOFF set to zero to disable driver
const uint32_t defaultChopConfToff = 4; // default value for TOFF when drive is enabled
// StallGuard configuration register
const uint32_t defaultSgscConfReg =
TMC_REG_SGCSCONF
| 0; // minimum current until user has set it
// Driver configuration register
const uint32_t defaultDrvConfReg =
TMC_REG_DRVCONF
| TMC_DRVCONF_VSENSE // use high sensitivity range
| 0;
// Driver control register
const uint32_t defaultDrvCtrlReg =
TMC_REG_DRVCTRL
| TMC_DRVCTRL_MRES_16
| TMC_DRVCTRL_INTPOL; // x16 microstepping with interpolation
// coolStep control register
const uint32_t defaultSmartEnReg =
TMC_REG_SMARTEN
| 0; // disable coolStep, we already do this in the main firmware
//----------------------------------------------------------------------------------------------------------------------------------
// Private types and methods
struct TmcDriverState
{
uint32_t drvCtrlReg;
uint32_t chopConfReg;
uint32_t smartEnReg;
uint32_t sgcsConfReg;
uint32_t drvConfReg;
uint32_t lastReadValue;
uint32_t pin;
void Init(uint32_t p_pin);
void WriteAll();
void SetChopConf(uint32_t newVal);
void SetMicrostepping(uint32_t shift, bool interpolate);
void SetCurrent(float current);
void Enable(bool en);
void SpiSendWord(uint32_t dataOut);
uint32_t ReadStatus();
};
// Initialise the state of the driver.
void TmcDriverState::Init(uint32_t p_pin)
pre(!driversPowered)
{
drvCtrlReg = defaultDrvCtrlReg;
chopConfReg = defaultChopConfReg;
smartEnReg = defaultSmartEnReg;
sgcsConfReg = defaultSgscConfReg;
drvConfReg = defaultDrvConfReg;
pin = p_pin;
// No point in calling WriteAll() here because the drivers are assumed to be not powered up.
}
// Send an SPI control string. The drivers need 20 bits. We send and receive 24 because the USART only supports 5 to 9 bit transfers.
// If the drivers are not powered up, don't attempt a transaction, and return 0xFFFFFFFF
void TmcDriverState::SpiSendWord( uint32_t dataOut)
{
if (driversPowered)
{
USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX; // reset transmitter and receiver
digitalWrite(pin, LOW); // set CS low
delayMicroseconds(1); // allow some CS low setup time
USART_EXT_DRV->US_CR = US_CR_RXEN | US_CR_TXEN; // enable transmitter and receiver
uint32_t dataIn = 0;
for (int i = 0; i < 3; ++i)
{
USART_EXT_DRV->US_THR = (dataOut >> 16) & 0x000000FFu;
dataOut <<= 8;
dataIn <<= 8;
for (int j = 0; j < 10000 && (USART_EXT_DRV->US_CSR & (US_CSR_RXRDY | US_CSR_TXRDY)) != (US_CSR_RXRDY | US_CSR_TXRDY); ++j)
{
// nothing
}
dataIn |= USART_EXT_DRV->US_RHR & 0x000000FF;
}
delayMicroseconds(1);
digitalWrite(pin, HIGH); // set CS high again
USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS; // reset and disable transmitter and receiver
delayMicroseconds(1); // ensure it stays high for long enough before the next write
lastReadValue = dataIn >> 4;
}
}
// Write all registers, if the drivers are powered up
void TmcDriverState::WriteAll()
{
SpiSendWord(chopConfReg);
SpiSendWord(sgcsConfReg);
SpiSendWord(drvConfReg);
SpiSendWord(drvCtrlReg);
SpiSendWord(smartEnReg);
}
// Set the chopper control register
void TmcDriverState::SetChopConf(uint32_t newVal)
{
chopConfReg = (newVal & 0x0001FFFF) | TMC_REG_CHOPCONF;
SpiSendWord(chopConfReg);
}
// Set the microstepping and microstep interpolation
void TmcDriverState::SetMicrostepping(uint32_t shift, bool interpolate)
{
drvCtrlReg &= ~TMC_DRVCTRL_MRES_MASK;
drvCtrlReg |= (shift << TMC_DRVCTRL_MRES_SHIFT) & TMC_DRVCTRL_MRES_MASK;
if (interpolate)
{
drvCtrlReg |= TMC_DRVCTRL_INTPOL;
}
else
{
drvCtrlReg &= ~TMC_DRVCTRL_INTPOL;
}
SpiSendWord(drvCtrlReg);
}
// Set the motor current
void TmcDriverState::SetCurrent(float current)
{
#if defined(DUET_NG) && !defined(PROTOTYPE_1)
// The current sense resistor on the production Duet WiFi is 0.051 ohms.
// This gives us a range of 101mA to 3.236A in 101mA steps in the high sensitivity range (VSENSE = 1)
drvConfReg |= TMC_DRVCONF_VSENSE; // this should always be set, but send it again just in case
SpiSendWord(drvConfReg);
const uint32_t iCurrent = static_cast<uint32_t>(constrain<float>(current, 100.0, 2000.0));
const uint32_t csBits = (32 * iCurrent - 1600)/3236; // formula checked by simulation on a spreadsheet
sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK;
sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
SpiSendWord(sgcsConfReg);
#else
// The current sense resistor is 0.1 ohms on the evaluation board.
// This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is low (but max allowed RMS current is 2A),
// or 52mA to 1.65A in 52mA steps when VSENSE is high.
if (current > 1650.0)
{
// Need VSENSE = 0, but set up the current first to avoid temporarily exceeding the 2A rating
const uint32_t iCurrent = (current > 2000.0) ? 2000 : (uint32_t)current;
const uint32_t csBits = (32 * iCurrent - 1500)/3050; // formula checked by simulation on a spreadsheet
sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK;
sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
SpiSendWord(sgcsConfReg);
drvConfReg &= ~TMC_DRVCONF_VSENSE;
SpiSendWord(drvConfReg);
}
else
{
// Use VSENSE = 1
drvConfReg |= TMC_DRVCONF_VSENSE;
SpiSendWord(drvConfReg);
const uint32_t iCurrent = (current < 50) ? 50 : (uint32_t)current;
const uint32_t csBits = (32 * iCurrent - 800)/1650; // formula checked by simulation on a spreadsheet
sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK;
sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
SpiSendWord(sgcsConfReg);
}
#endif
}
// Enable or disable the driver
void TmcDriverState::Enable(bool en)
{
chopConfReg &= ~TMC_CHOPCONF_TOFF_MASK;
if (en)
{
chopConfReg |= TMC_CHOPCONF_TOFF(defaultChopConfToff);
}
SpiSendWord(chopConfReg);
}
// Read the status
uint32_t TmcDriverState::ReadStatus()
{
// We need to send a command in order to get up-to-date status
SpiSendWord(smartEnReg);
return lastReadValue & (TMC_RR_SG | TMC_RR_OT | TMC_RR_OTPW | TMC_RR_S2G | TMC_RR_OLA | TMC_RR_OLB | TMC_RR_STST);
}
static TmcDriverState driverStates[DRIVES];
//--------------------------- Public interface ---------------------------------
namespace TMC2660
{
// Initialise the driver interface and the drivers, leaving each drive disabled.
// It is assumed that the drivers are not powered, so driversPowered(true) must be called after calling this before the motors can be moved.
void Init(const Pin driverSelectPins[DRIVES], size_t numTmcDrivers)
{
numTmc2660Drivers = numTmcDrivers;
// Make sure the ENN pins are high
pinMode(GlobalTmcEnablePin, OUTPUT_HIGH);
// Set up the SPI pins
#ifdef DUET_NG
// The pins are already set up for SPI in the pins table
ConfigurePin(GetPinDescription(DriversMosiPin));
ConfigurePin(GetPinDescription(DriversMisoPin));
ConfigurePin(GetPinDescription(DriversSclkPin));
#else
// Pins AD0 and AD7 may have already be set up as an ADC pin by the Arduino core, so undo that here or we won't get a clock output
ADC->ADC_CHDR = (1 << 7);
const PinDescription& pin2 = GetPinDescription(DriversMosiPin);
pio_configure(pin2.pPort, PIO_PERIPH_A, pin2.ulPin, PIO_DEFAULT);
const PinDescription& pin3 = GetPinDescription(DriversMisoPin);
pio_configure(pin3.pPort, PIO_PERIPH_A, pin3.ulPin, PIO_DEFAULT);
const PinDescription& pin1 = GetPinDescription(DriversSclkPin);
pio_configure(pin1.pPort, PIO_PERIPH_A, pin1.ulPin, PIO_DEFAULT);
#endif
// Enable the clock to the USART
pmc_enable_periph_clk(ID_USART_EXT_DRV);
// Set up the CS pins and set them all high
// When this becomes the standard code, we must set up the STEP and DIR pins here too.
for (size_t drive = 0; drive < numTmc2660Drivers; ++drive)
{
pinMode(driverSelectPins[drive], OUTPUT_HIGH);
}
// Set USART_EXT_DRV in SPI mode, with data changing on the falling edge of the clock and captured on the rising edge
USART_EXT_DRV->US_IDR = ~0u;
USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS;
USART_EXT_DRV->US_MR = US_MR_USART_MODE_SPI_MASTER
| US_MR_USCLKS_MCK
| US_MR_CHRL_8_BIT
| US_MR_CHMODE_NORMAL
| US_MR_CPOL
| US_MR_CLKO;
USART_EXT_DRV->US_BRGR = VARIANT_MCK/DriversSpiClockFrequency; // 1MHz SPI clock
USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS | US_CR_RSTSTA;
// We need a few microseconds of delay here for the USART to sort itself out,
// otherwise the processor generates two short reset pulses on its own NRST pin, and resets itself.
// 2016-07-07: removed this delay, because we no longer send commands to the TMC2660 drivers immediately.
//delay(10);
driversPowered = false;
for (size_t drive = 0; drive < numTmc2660Drivers; ++drive)
{
driverStates[drive].Init(driverSelectPins[drive]);
}
}
void SetCurrent(size_t drive, float current)
{
if (drive < numTmc2660Drivers)
{
driverStates[drive].SetCurrent(current);
}
}
void EnableDrive(size_t drive, bool en)
{
if (drive < numTmc2660Drivers)
{
driverStates[drive].Enable(en);
}
}
uint32_t GetStatus(size_t drive)
{
return (drive < numTmc2660Drivers) ? driverStates[drive].ReadStatus() : 0;
}
bool SetMicrostepping(size_t drive, int microsteps, int mode)
{
if (drive < numTmc2660Drivers)
{
if (mode == 999 && microsteps >= 0)
{
driverStates[drive].SetChopConf((uint32_t)microsteps); // set the chopper control register
}
else if (microsteps > 0 && (mode == 0 || mode == 1))
{
// Set the microstepping. We need to determine how many bits left to shift the desired microstepping to reach 256.
unsigned int shift = 0;
unsigned int uSteps = (unsigned int)microsteps;
while (uSteps < 256)
{
uSteps <<= 1;
++shift;
}
if (uSteps == 256)
{
driverStates[drive].SetMicrostepping(shift, mode != 0);
return true;
}
}
}
return false;
}
unsigned int GetMicrostepping(size_t drive, bool& interpolation)
{
if (drive < numTmc2660Drivers)
{
const uint32_t drvCtrl = driverStates[drive].drvCtrlReg;
interpolation = (drvCtrl & TMC_DRVCTRL_INTPOL) != 0;
const uint32_t mresBits = (drvCtrl & TMC_DRVCTRL_MRES_MASK) >> TMC_DRVCTRL_MRES_SHIFT;
return 256 >> mresBits;
}
return 1;
}
// Flag the the drivers have been powered up.
// Important notes:
// 1. Before the first call to this function with powered true, you must call Init().
// 2. This may be called by the tick ISR with powered false, possibly while another call (with powered either true or false) is being executed
void SetDriversPowered(bool powered)
{
const bool wasPowered = driversPowered;
driversPowered = powered;
if (powered && !wasPowered)
{
// Power to the drivers has been provided or restored, so we need to enable and re-initialise them
digitalWrite(GlobalTmcEnablePin, LOW);
delayMicroseconds(10);
for (size_t drive = 0; drive < numTmc2660Drivers; ++drive)
{
driverStates[drive].WriteAll();
}
}
else if (!powered && wasPowered)
{
digitalWrite(GlobalTmcEnablePin, HIGH); // disable the drivers
}
}
}; // end namespace
#endif
// End