This repository has been archived on 2026-06-17. You can view files and clone it, but cannot push or open issues or pull requests.
FireWatchTower_2axis/firmware/lib/tmc/ic/TMC4671/TMC4671.c

511 lines
18 KiB
C
Executable File

/*******************************************************************************
* Copyright © 2016 TRINAMIC Motion Control GmbH & Co. KG
* (now owned by Analog Devices Inc.),
*
* Copyright © 2024 Analog Devices Inc. All Rights Reserved.
* This software is proprietary to Analog Devices, Inc. and its licensors.
*******************************************************************************/
#include "TMC4671.h"
#define TMC4671_WRITE_BIT 0x80
#define TMC4671_ADDRESS_MASK 0x7F
#define STATE_NOTHING_TO_DO 0
#define STATE_START_INIT 1
#define STATE_WAIT_INIT_TIME 2
#define STATE_ESTIMATE_OFFSET 3
// => SPI wrapper
extern void tmc4671_readWriteSPI(uint16_t icID, uint8_t *data, size_t dataLength);
// <= SPI wrapper
// spi access
int32_t tmc4671_readRegister(uint16_t icID, uint8_t address)
{
uint8_t data[5] = { 0 };
// clear write bit
data[0] = address & TMC4671_ADDRESS_MASK;
// Send the read request
tmc4671_readWriteSPI(icID, &data[0], sizeof(data));
return ((int32_t)data[1] << 24) | ((int32_t) data[2] << 16) | ((int32_t) data[3] << 8) | ((int32_t) data[4]);
}
void tmc4671_writeRegister(uint16_t icID, uint8_t address, int32_t value)
{
uint8_t data[5] = { 0 };
data[0] = address | TMC4671_WRITE_BIT;
data[1] = 0xFF & (value>>24);
data[2] = 0xFF & (value>>16);
data[3] = 0xFF & (value>>8);
data[4] = 0xFF & (value>>0);
// Send the write request
tmc4671_readWriteSPI(icID, &data[0], sizeof(data));
}
void tmc4671_switchToMotionMode(uint16_t icID, uint8_t mode)
{
// switch motion mode
tmc4671_fieldWrite(icID, TMC4671_MODE_MOTION_FIELD, mode);
}
void tmc4671_setTargetTorque_raw(uint16_t icID, int32_t targetTorque)
{
tmc4671_switchToMotionMode(icID, TMC4671_MOTION_MODE_TORQUE);
tmc4671_fieldWrite(icID, TMC4671_PID_TORQUE_TARGET_FIELD, targetTorque);
}
int32_t tmc4671_getTargetTorque_raw(uint16_t icID)
{
// remember last set index
uint32_t lastIndex = tmc4671_readRegister(icID, TMC4671_INTERIM_ADDR);
// get value
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, 0);
int32_t value = (int32_t)tmc4671_readRegister(icID, TMC4671_INTERIM_DATA);
// reset last set index
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, lastIndex);
return value;
}
int32_t tmc4671_getActualTorque_raw(uint16_t icID)
{
return tmc4671_fieldRead(icID, TMC4671_PID_TORQUE_ACTUAL_FIELD);
}
void tmc4671_setTargetTorque_mA(uint16_t icID, uint16_t torqueMeasurementFactor, int32_t targetTorque)
{
tmc4671_switchToMotionMode(icID, TMC4671_MOTION_MODE_TORQUE);
tmc4671_fieldWrite(icID, TMC4671_PID_TORQUE_TARGET_FIELD, (targetTorque * 256) / (int32_t)torqueMeasurementFactor);
}
int32_t tmc4671_getTargetTorque_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
return (tmc4671_getTargetTorque_raw(icID) * (int32_t)torqueMeasurementFactor) / 256;
}
int32_t tmc4671_getActualTorque_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
return (tmc4671_getActualTorque_raw(icID) * (int32_t)torqueMeasurementFactor) / 256;
}
int32_t tmc4671_getTargetTorqueFluxSum_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
// remember last set index
uint32_t lastIndex = tmc4671_readRegister(icID, TMC4671_INTERIM_ADDR);
// get target torque value
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, 0);
int32_t torque = (int32_t)tmc4671_readRegister(icID, TMC4671_INTERIM_DATA);
// get target flux value
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, 1);
int32_t flux = (int32_t)tmc4671_readRegister(icID, TMC4671_INTERIM_DATA);
// reset last set index
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, lastIndex);
return (((int32_t)flux+(int32_t)torque) * (int32_t)torqueMeasurementFactor) / 256;
}
int32_t tmc4671_getActualTorqueFluxSum_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
int32_t registerValue = tmc4671_readRegister(icID, TMC4671_PID_TORQUE_FLUX_ACTUAL);
int16_t flux = (registerValue & 0xFFFF);
int16_t torque = ((registerValue >> 16) & 0xFFFF);
return (((int32_t)flux+(int32_t)torque) * (int32_t)torqueMeasurementFactor) / 256;
}
void tmc4671_setTargetFlux_raw(uint16_t icID, int32_t targetFlux)
{
// do not change the MOTION_MODE here! target flux can also be used during velocity and position modes
tmc4671_fieldWrite(icID, TMC4671_PID_FLUX_TARGET_FIELD, targetFlux);
}
int32_t tmc4671_getTargetFlux_raw(uint16_t icID)
{
// remember last set index
uint32_t lastIndex = tmc4671_readRegister(icID, TMC4671_INTERIM_ADDR);
// get value
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, 1);
int32_t value = (int32_t)tmc4671_readRegister(icID, TMC4671_INTERIM_DATA);
// reset last set index
tmc4671_writeRegister(icID, TMC4671_INTERIM_ADDR, lastIndex);
return value;
}
int32_t tmc4671_getActualFlux_raw(uint16_t icID)
{
return tmc4671_fieldRead(icID, TMC4671_PID_FLUX_ACTUAL_FIELD);
}
void tmc4671_setTargetFlux_mA(uint16_t icID, uint16_t torqueMeasurementFactor, int32_t targetFlux)
{
// do not change the MOTION_MODE here! target flux can also be used during velocity and position modes
tmc4671_fieldWrite(icID, TMC4671_PID_FLUX_TARGET_FIELD, (targetFlux * 256) / (int32_t)torqueMeasurementFactor);
}
int32_t tmc4671_getTargetFlux_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
return (tmc4671_getTargetFlux_raw(icID) * (int32_t)torqueMeasurementFactor) / 256;
}
int32_t tmc4671_getActualFlux_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
return (tmc4671_getActualFlux_raw(icID) * (int32_t)torqueMeasurementFactor) / 256;
}
void tmc4671_setTorqueFluxLimit_mA(uint16_t icID, uint16_t torqueMeasurementFactor, int32_t max)
{
tmc4671_fieldWrite(icID, TMC4671_PID_TORQUE_FLUX_LIMITS_FIELD, (max * 256) / (int32_t)torqueMeasurementFactor);
}
int32_t tmc4671_getTorqueFluxLimit_mA(uint16_t icID, uint16_t torqueMeasurementFactor)
{
return ((int32_t)tmc4671_fieldRead(icID, TMC4671_PID_TORQUE_FLUX_LIMITS_FIELD) * (int32_t)torqueMeasurementFactor) / 256;
}
void tmc4671_setTargetVelocity(uint16_t icID, int32_t targetVelocity)
{
tmc4671_switchToMotionMode(icID, TMC4671_MOTION_MODE_VELOCITY);
tmc4671_writeRegister(icID, TMC4671_PID_VELOCITY_TARGET, targetVelocity);
}
int32_t tmc4671_getTargetVelocity(uint16_t icID)
{
return (int32_t)tmc4671_readRegister(icID, TMC4671_PID_VELOCITY_TARGET);
}
int32_t tmc4671_getActualVelocity(uint16_t icID)
{
return (int32_t)tmc4671_readRegister(icID, TMC4671_PID_VELOCITY_ACTUAL);
}
void tmc4671_setAbsolutTargetPosition(uint16_t icID, int32_t targetPosition)
{
tmc4671_switchToMotionMode(icID, TMC4671_MOTION_MODE_POSITION);
tmc4671_writeRegister(icID, TMC4671_PID_POSITION_TARGET, targetPosition);
}
void tmc4671_setRelativeTargetPosition(uint16_t icID, int32_t relativePosition)
{
tmc4671_switchToMotionMode(icID, TMC4671_MOTION_MODE_POSITION);
// determine actual position and add relative position ticks
tmc4671_writeRegister(icID, TMC4671_PID_POSITION_TARGET, (int32_t)tmc4671_readRegister(icID, TMC4671_PID_POSITION_ACTUAL) + relativePosition);
}
int32_t tmc4671_getTargetPosition(uint16_t icID)
{
return (int32_t)tmc4671_readRegister(icID, TMC4671_PID_POSITION_TARGET);
}
void tmc4671_setActualPosition(uint16_t icID, int32_t actualPosition)
{
tmc4671_writeRegister(icID, TMC4671_PID_POSITION_ACTUAL, actualPosition);
}
int32_t tmc4671_getActualPosition(uint16_t icID)
{
return (int32_t)tmc4671_readRegister(icID, TMC4671_PID_POSITION_ACTUAL);
}
// ABN encoder initialization
void tmc4671_doEncoderInitializationMode0(uint16_t icID, uint8_t *initState, uint16_t initWaitTime, uint16_t *actualInitWaitTime, uint16_t startVoltage,
uint16_t *last_Phi_E_Selection, uint32_t *last_UQ_UD_EXT, int16_t *last_PHI_E_EXT)
{
switch (*initState)
{
case STATE_NOTHING_TO_DO:
*actualInitWaitTime = 0;
break;
case STATE_START_INIT: // started by writing 1 to initState
// save actual set values for PHI_E_SELECTION, UQ_UD_EXT, and PHI_E_EXT
*last_Phi_E_Selection = (uint16_t)tmc4671_fieldRead(icID, TMC4671_PHI_E_SELECTION_FIELD);
*last_UQ_UD_EXT = (uint32_t)tmc4671_readRegister(icID, TMC4671_UQ_UD_EXT);
*last_PHI_E_EXT = (int16_t)tmc4671_fieldRead(icID, TMC4671_PHI_E_EXT_FIELD);
//switch motion mode for running icID in open loop
tmc4671_writeRegister(icID, TMC4671_MODE_RAMP_MODE_MOTION, TMC4671_MOTION_MODE_UQ_UD_EXT);
// set ABN_DECODER_PHI_E_OFFSET to zero
tmc4671_fieldWrite(icID, TMC4671_ABN_DECODER_PHI_E_OFFSET_FIELD, 0);
// select phi_e_ext
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, 1);
// set an initialization voltage on UD_EXT (to the flux, not the torque!)
tmc4671_fieldWrite(icID, TMC4671_UQ_EXT_FIELD, 0);
tmc4671_fieldWrite(icID, TMC4671_UD_EXT_FIELD, startVoltage);
// set the "zero" angle
tmc4671_fieldWrite(icID, TMC4671_PHI_E_EXT_FIELD, 0);
*initState = STATE_WAIT_INIT_TIME;
break;
case STATE_WAIT_INIT_TIME:
// wait until initialization time is over (until no more vibration on the icID)
(*actualInitWaitTime)++;
if(*actualInitWaitTime >= initWaitTime)
{
// set internal encoder value to zero
tmc4671_writeRegister(icID, TMC4671_ABN_DECODER_COUNT, 0);
// switch back to last used UQ_UD_EXT setting
tmc4671_writeRegister(icID, TMC4671_UQ_UD_EXT, *last_UQ_UD_EXT);
// set PHI_E_EXT back to last value
tmc4671_fieldWrite(icID, TMC4671_PHI_E_EXT_FIELD, *last_PHI_E_EXT);
// switch back to last used PHI_E_SELECTION setting
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, *last_Phi_E_Selection);
// go to next state
*initState = STATE_ESTIMATE_OFFSET;
}
break;
case STATE_ESTIMATE_OFFSET:
// you can do offset estimation here (wait for N-Channel if available and save encoder value)
// go to ready state
*initState = 0;
break;
default:
*initState = 0;
break;
}
}
int16_t tmc4671_getS16CircleDifference(int16_t newValue, int16_t oldValue)
{
return (newValue - oldValue);
}
void tmc4671_doEncoderInitializationMode2(uint16_t icID, uint8_t *initState, uint16_t *actualInitWaitTime,
int16_t *hall_phi_e_old, int16_t *hall_phi_e_new, int16_t *hall_actual_coarse_offset, uint16_t *last_Phi_E_Selection)
{
switch (*initState)
{
case STATE_NOTHING_TO_DO:
*actualInitWaitTime = 0;
break;
case STATE_START_INIT: // started by writing 1 to initState
// save actual set value for PHI_E_SELECTION
*last_Phi_E_Selection = (uint16_t)tmc4671_fieldRead(icID, TMC4671_PHI_E_SELECTION_FIELD);
// turn hall_mode interpolation off (read, clear bit 8, write back)
tmc4671_writeRegister(icID, TMC4671_HALL_MODE, tmc4671_readRegister(icID, TMC4671_HALL_MODE) & 0xFFFFFEFF);
// set ABN_DECODER_PHI_E_OFFSET to zero
tmc4671_fieldWrite(icID, TMC4671_ABN_DECODER_PHI_E_OFFSET_FIELD, 0);
// read actual hall angle
*hall_phi_e_old = tmc4671_fieldRead(icID, TMC4671_HALL_PHI_E_FIELD);
// read actual abn_decoder angle and compute difference to actual hall angle
*hall_actual_coarse_offset = tmc4671_getS16CircleDifference(*hall_phi_e_old, (int16_t)tmc4671_fieldRead(icID, TMC4671_ABN_DECODER_PHI_E_FIELD));
// set ABN_DECODER_PHI_E_OFFSET to actual hall-abn-difference, to use the actual hall angle for coarse initialization
tmc4671_fieldWrite(icID, TMC4671_ABN_DECODER_PHI_E_OFFSET_FIELD, *hall_actual_coarse_offset);
// normally MOTION_MODE_UQ_UD_EXT is only used by e.g. a wizard, not in normal operation
if (tmc4671_fieldRead(icID, TMC4671_MODE_MOTION_FIELD) != TMC4671_MOTION_MODE_UQ_UD_EXT)
{
// select the use of phi_e_hall to start icID with hall signals
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, TMC4671_PHI_E_HALL);
}
*initState = STATE_WAIT_INIT_TIME;
break;
case STATE_WAIT_INIT_TIME:
// read actual hall angle
*hall_phi_e_new = tmc4671_fieldRead(icID, TMC4671_HALL_PHI_E_FIELD);
// wait until hall angle changed
if(*hall_phi_e_old != *hall_phi_e_new)
{
// estimated value = old value + diff between old and new (handle int16_t overrun)
int16_t hall_phi_e_estimated = *hall_phi_e_old + tmc4671_getS16CircleDifference(*hall_phi_e_new, *hall_phi_e_old)/2;
// read actual abn_decoder angle and consider last set abn_decoder_offset
int16_t abn_phi_e_actual = (int16_t)tmc4671_fieldRead(icID, TMC4671_ABN_DECODER_PHI_E_FIELD) - *hall_actual_coarse_offset;
// set ABN_DECODER_PHI_E_OFFSET to actual estimated angle - abn_phi_e_actual difference
tmc4671_fieldWrite(icID, TMC4671_ABN_DECODER_PHI_E_OFFSET_FIELD, tmc4671_getS16CircleDifference(hall_phi_e_estimated, abn_phi_e_actual));
// switch back to last used PHI_E_SELECTION setting
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, *last_Phi_E_Selection);
// go to ready state
*initState = 0;
}
break;
default:
*initState = 0;
break;
}
}
// analog encoder initialization
void tmc4671_doEncoderInitializationMode3(uint16_t icID, uint8_t *initState, uint16_t initWaitTime, uint16_t *actualInitWaitTime, uint16_t startVoltage,
uint16_t *last_Phi_E_Selection, uint32_t *last_UQ_UD_EXT, int16_t *last_PHI_E_EXT)
{
switch (*initState)
{
case STATE_NOTHING_TO_DO:
*actualInitWaitTime = 0;
break;
case STATE_START_INIT: // started by writing 1 to initState
// save actual set values for PHI_E_SELECTION, UQ_UD_EXT, and PHI_E_EXT
*last_Phi_E_Selection = (uint16_t)tmc4671_fieldRead(icID, TMC4671_PHI_E_SELECTION_FIELD);
*last_UQ_UD_EXT = (uint32_t)tmc4671_readRegister(icID, TMC4671_UQ_UD_EXT);
*last_PHI_E_EXT = (int16_t)tmc4671_fieldRead(icID, TMC4671_PHI_E_EXT_FIELD);
// switch motion mode for running motor in open loop
tmc4671_writeRegister(icID, TMC4671_MODE_RAMP_MODE_MOTION, TMC4671_MOTION_MODE_UQ_UD_EXT);
// set AENC_DECODER_PHI_E_PHI_M_OFFSET and AENC_DECODER_PHI_A_OFFSET to zero
tmc4671_writeRegister(icID, TMC4671_AENC_DECODER_PHI_E_PHI_M_OFFSET, 0);
tmc4671_fieldWrite(icID, TMC4671_AENC_DECODER_PHI_A_OFFSET_FIELD, 0);
// select phi_e_ext
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, 1);
// set an initialization voltage on UD_EXT (to the flux, not the torque!)
tmc4671_fieldWrite(icID, TMC4671_UQ_EXT_FIELD, 0);
tmc4671_fieldWrite(icID, TMC4671_UD_EXT_FIELD, startVoltage);
// set the "zero" angle
tmc4671_fieldWrite(icID, TMC4671_PHI_E_EXT_FIELD, 0);
*initState = STATE_WAIT_INIT_TIME;
break;
case STATE_WAIT_INIT_TIME:
// wait until initialization time is over (until no more vibration on the motor)
(*actualInitWaitTime)++;
if(*actualInitWaitTime >= initWaitTime)
{
// save actual DECODER_PHI_M as -DECODER_PHI_M_OFFSET
int16_t offset_raw = tmc4671_fieldRead(icID, TMC4671_AENC_DECODER_PHI_M_FIELD);
// update PHI_M and keep PHI_E and PH_A at zero
tmc4671_fieldWrite(icID, TMC4671_AENC_DECODER_PHI_E_OFFSET_FIELD, 0);
tmc4671_fieldWrite(icID, TMC4671_AENC_DECODER_PHI_M_OFFSET_FIELD, -offset_raw);
tmc4671_fieldWrite(icID, TMC4671_AENC_DECODER_PHI_A_OFFSET_FIELD, 0);
// switch back to last used UQ_UD_EXT setting
tmc4671_writeRegister(icID, TMC4671_UQ_UD_EXT, *last_UQ_UD_EXT);
// set PHI_E_EXT back to last value
tmc4671_fieldWrite(icID, TMC4671_PHI_E_EXT_FIELD, *last_PHI_E_EXT);
// switch back to last used PHI_E_SELECTION setting
tmc4671_fieldWrite(icID, TMC4671_PHI_E_SELECTION_FIELD, *last_Phi_E_Selection);
// go to next state
*initState = STATE_ESTIMATE_OFFSET;
}
break;
case STATE_ESTIMATE_OFFSET:
// you can do offset estimation here (wait for N-Channel if available and save encoder value)
// go to ready state
*initState = 0;
break;
default:
*initState = 0;
break;
}
}
void tmc4671_checkEncderInitialization(uint16_t icID, uint32_t actualSystick, uint8_t initMode, uint8_t *initState, uint16_t initWaitTime, uint16_t *actualInitWaitTime, uint16_t startVoltage,
int16_t *hall_phi_e_old, int16_t *hall_phi_e_new, int16_t *hall_actual_coarse_offset,
uint16_t *last_Phi_E_Selection, uint32_t *last_UQ_UD_EXT, int16_t *last_PHI_E_EXT)
{
// use the systick as 1ms timer for encoder initialization
static uint32_t lastSystick = 0;
if(actualSystick != lastSystick)
{
// needs timer to use the wait time
if(initMode == 0)
{
tmc4671_doEncoderInitializationMode0(icID, initState, initWaitTime, actualInitWaitTime, startVoltage, last_Phi_E_Selection, last_UQ_UD_EXT, last_PHI_E_EXT);
}
else if (initMode == 3) // analog encoder initialization
{
tmc4671_doEncoderInitializationMode3(icID, initState, initWaitTime, actualInitWaitTime, startVoltage, last_Phi_E_Selection, last_UQ_UD_EXT, last_PHI_E_EXT);
}
lastSystick = actualSystick;
}
// needs no timer
if(initMode == 2)
{
tmc4671_doEncoderInitializationMode2(icID, initState, actualInitWaitTime, hall_phi_e_old, hall_phi_e_new, hall_actual_coarse_offset, last_Phi_E_Selection);
}
}
void tmc4671_periodicJob(uint16_t icID, uint32_t actualSystick, uint8_t initMode, uint8_t *initState, uint16_t initWaitTime, uint16_t *actualInitWaitTime, uint16_t startVoltage,
int16_t *hall_phi_e_old, int16_t *hall_phi_e_new, int16_t *hall_actual_coarse_offset,
uint16_t *last_Phi_E_Selection, uint32_t *last_UQ_UD_EXT, int16_t *last_PHI_E_EXT)
{
tmc4671_checkEncderInitialization(icID, actualSystick, initMode, initState, initWaitTime, actualInitWaitTime, startVoltage,
hall_phi_e_old, hall_phi_e_new, hall_actual_coarse_offset, last_Phi_E_Selection, last_UQ_UD_EXT, last_PHI_E_EXT);
}
void tmc4671_startEncoderInitialization(uint8_t mode, uint8_t *initMode, uint8_t *initState)
{
// allow only a new initialization if no actual initialization is running
if(*initState == STATE_NOTHING_TO_DO)
{
if(mode == 0) // estimate offset
{
// set mode
*initMode = 0;
// start initialization
*initState = 1;
}
else if(mode == 2) // use hall sensor signals
{
// set mode
*initMode = 2;
// start initialization
*initState = 1;
}
else if (mode == 3) // use analog encoder initialization
{
// set mode
*initMode = 3;
// start initialization
*initState = 1;
}
}
}
int32_t tmc4671_readFieldWithDependency(uint16_t icID, RegisterField field, uint8_t dependsReg, uint32_t dependsValue)
{
// remember old depends value
uint32_t lastDependsValue = tmc4671_readRegister(icID, dependsReg);
// set needed depends value
tmc4671_writeRegister(icID, dependsReg, dependsValue);
uint32_t value = tmc4671_fieldRead(icID, field);
// set old depends value
tmc4671_writeRegister(icID, dependsReg, lastDependsValue);
return value;
}