Skip to content

Commit

Permalink
Merge branch 'feature_rework_tmc2660'
Browse files Browse the repository at this point in the history
  • Loading branch information
trinamic-ASU committed Jan 29, 2025
2 parents 62f07b0 + 7942524 commit 75c4ef8
Show file tree
Hide file tree
Showing 11 changed files with 626 additions and 533 deletions.
52 changes: 52 additions & 0 deletions tmc/ic/TMC2660/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# TMC2660


## How to use

To access the TMC2660's registers, the TMC-API offers two functions: **tmc2660_readRegister** and **tmc2660_writeRegister**.
Each of these functions takes in an **icID**, which is used to identify the IC when multiple ICs are connected. This identifier is passed down to the callback functions (see How to Integrate).

## How to integrate: overview

1. Include all the files of the TMC-API/ic/tmc/TMC2660 folder into the custom project.
2. Include the TMC2660.h file in the custom source code.
3. Implement the necessary callback functions (see below).

## Accessing the TMC2660 via SPI
The following diagram depicts how to access the TMC2660 via SPI using the TMC-API.

### Reading a register
![screenshot](registercall_hierarchy_flowchart_SPI.png)

The description of the functions, in the above flowchart, are as follows:
- The functions tmc2660_readRegister and tmc2660_writeRegister are used to read and write the registers respectively.
- tmc2660_writeRegister function calls the readWrite function which constructs the datagram and further calls the bus specific callback 'tmcXXXX_readWriteSPI'.
- This callback function further calls the hardware specific read/write function for SPI and needs to be implemented externally.
- tmc2660_readRegister function calls readImmediately function if it has to read one of the three responses.
- readImmediately funciton sets the RDSEl bits in DRVCONF register and further calls the readwrite function to receive the response.

### Writing a register
![screenshot](registerwrite_hierarchy_flowchart_SPI.png)

Similarly, a register is written as depicted in the flowchart above.

### How to integrate: Callback functions
In software we use a **continuousModeEnable** variable to distinguish between a normal mode and a continuous mode. In continuous mode we periodically read all the registers to keep them updated incase of brownout. For that, the callback function **tmc2660_getcontinuousModeEnable()** needs to be implemented.
Additionally, implement the following callback functions to access the chip via SPI:
**tmc2660_readWriteSPI()**, which is a HAL wrapper function that provides the necessary hardware access. This function should also set the chip select pin CSN to low before starting the data transfer and set to high upon completion. Please refer to the datasheet of the IC for further details.

### Option to use the cache logic for Write-Only registers
The chip features write-only registers that are unable to be read, necessitating the creation of a shadow copy to cache their contents. This copy is automatically updated whenever data is written to these registers. This cache logic could be enabled by setting the macro **TMC2660_CACHE** to **'1'** or disabled by setting to **'0'** respectively. If this feature is enabled then there comes another option to use **tmc2660_cache** function, which is already implemeted in the API, by defining **TMC2660_ENABLE_TMC_CACHE** macro to **'1** or one can implement their own function. The function **tmc2660_cache** works for both reading from and writing to the shadow array. It first checks whether the register has write-only access and data needs to be read from the hadow copy. On the basis of that, it returns **true** or **false**. The shadowRegisters on the premade cache implementation need to be one per chip. **TMC2660_IC_CACHE_COUNT** is set to '1' by default and is user-overwritable. If multiple chips are being used in the same project, increment its value to the number of chips connected.

## Further info
### Dependency graph for the ICs with new register R/W mechanism
This graph illustrates the relationships between files within the TMC-API library, highlighting dependencies and identifying the files that are essential for integrating the library into the custom projects.
![screenshot](uml-tmc-api.png)

### Example usage: TMC-Evalsystem
**For a reference usage of the TMC-API**, visit the [TMC-Evalsystem](https://github.com/analogdevicesinc/TMC-EvalSystem)

## Migration status
The TMC2660 has been reworked to the access system described above. For more infos on the status of this and other ICs, check out the [migration page](https://github.com/analogdevicesinc/TMC-API/issues/53).


271 changes: 120 additions & 151 deletions tmc/ic/TMC2660/TMC2660.c
Original file line number Diff line number Diff line change
@@ -1,184 +1,153 @@
/*******************************************************************************
* Copyright © 2017 TRINAMIC Motion Control GmbH & Co. KG
* Copyright © 2019 TRINAMIC Motion Control GmbH & Co. KG
* (now owned by Analog Devices Inc.),
*
* Copyright © 2023 Analog Devices Inc. All Rights Reserved.
* Copyright © 2024 Analog Devices Inc. All Rights Reserved.
* This software is proprietary to Analog Devices, Inc. and its licensors.
*******************************************************************************/


#include "TMC2660.h"

const uint8_t tmc2660_defaultRegisterAccess[TMC2660_REGISTER_COUNT] =
/**************************************************************** Cache Implementation *************************************************************************/

#if TMC2660_CACHE == 0
static inline bool tmc2660_cache(uint16_t icID, TMC2660CacheOp operation, uint8_t address, uint32_t *value)
{
TMC_ACCESS_WRITE, // 0: DRVCTRL
TMC_ACCESS_NONE, // 1: UNUSED
TMC_ACCESS_NONE, // 2: UNUSED
TMC_ACCESS_NONE, // 3: UNUSED
TMC_ACCESS_WRITE, // 4: CHOPCONF
TMC_ACCESS_WRITE, // 5: SMARTEN
TMC_ACCESS_WRITE, // 6: SGCSCONF
TMC_ACCESS_WRITE // 7: DRVCONF
};

const int32_t tmc2660_defaultRegisterResetState[TMC2660_REGISTER_COUNT] =
UNUSED(icID);
UNUSED(address);
UNUSED(operation);
return false;
}
#else
#if TMC2660_ENABLE_TMC_CACHE == 1
int32_t tmc2660_shadowRegister[TMC2660_IC_CACHE_COUNT][TMC2660_REGISTER_COUNT];

/*
* This function is used to cache the value written to the Write-Only registers in the form of shadow array.
* The shadow copy is then used to read these kinds of registers.
*/
bool tmc2660_cache(uint16_t icID, TMC2660CacheOp operation, uint8_t address, uint32_t *value)
{
0x00000000, // 0: DRVCTRL
0x00000000, // 1: UNUSED
0x00000000, // 2: UNUSED
0x00000000, // 3: UNUSED
0x00091935, // 4: CHOPCONF
0x000A0000, // 5: SMARTEN
0x000D0505, // 6: SGCSCONF
0x000EF040 // 7: DRVCONF
};

// => SPI wrapper
extern void tmc2660_writeInt(uint8_t motor, uint8_t address, int32_t value);
extern uint32_t tmc2660_readInt(uint8_t motor, uint8_t address);
extern void tmc2660_readWrite(uint8_t motor, uint32_t value);
//extern void tmc2660_setField(uint8_t motor, uint8_t address, uint32_t clearMask, uint32_t field);
// <= SPI wrapper

static void standStillCurrentLimitation(TMC2660TypeDef *TMC2660)
{ // mark if current should be reduced in stand still if too high
static uint32_t errorTimer = 0;

// check the standstill flag
if(TMC2660_GET_STST(tmc2660_readInt(0, TMC2660_RESPONSE_LATEST)))
{
// check if current reduction is neccessary
if(TMC2660->runCurrentScale > TMC2660->standStillCurrentScale)
{
TMC2660->isStandStillOverCurrent = 1;

// count timeout
if(errorTimer++ > TMC2660->standStillTimeout/10)
{
// set current limitation flag
TMC2660->isStandStillCurrentLimit = 1;
errorTimer = 0;
}
return;
}
}

// No standstill or overcurrent -> reset flags & error timer
TMC2660->isStandStillOverCurrent = 0;
TMC2660->isStandStillCurrentLimit = 0;
errorTimer = 0;
if (operation == TMC2660_CACHE_READ)
{
// Check if the value should come from cache

// Only supported chips have a cache
if (icID >= TMC2660_IC_CACHE_COUNT)
return false;

// Only non-readable registers care about caching
// Note: This could also be used to cache i.e. RW config registers to reduce bus accesses
if (TMC2660_IS_READABLE(tmc2660_registerAccess[address]))
return false;

// Grab the value from the cache
*value = tmc2660_shadowRegister[icID][address];
return true;
}
else if (operation == TMC2660_CACHE_WRITE || operation == TMC2660_CACHE_FILL_DEFAULT)
{
// Fill the cache

// only supported chips have a cache
if (icID >= TMC2660_IC_CACHE_COUNT)
return false;

// Write to the shadow register
tmc2660_shadowRegister[icID][address] = *value;

return true;
}
return false;
}

static void continousSync(ConfigurationTypeDef *TMC2660_config)
{ // refreshes settings to prevent chip from loosing settings on brownout
static uint8_t write = 0;
static uint8_t read = 0;
static uint8_t rdsel = 0;
#else
// User must implement their own cache
extern bool tmc2660_cache(uint16_t icID, TMC2660CacheOp operation, uint8_t address, uint32_t *value);
#endif
#endif

// rotational reading all replys to keep values up to date
uint32_t value, drvConf;
/************************************************************** Register read / write Implementation ******************************************************************/

// additional reading to keep all replies up to date
value = drvConf = tmc2660_readInt(0, TMC2660_WRITE_BIT | TMC2660_DRVCONF); // buffer value amd drvConf to write back later
value &= ~TMC2660_SET_RDSEL(-1); // clear RDSEL bits
value |= TMC2660_SET_RDSEL(rdsel % 3); // clear set rdsel
tmc2660_readWrite(0, value);
tmc2660_readWrite(0, drvConf);
void readWrite(uint8_t icID, uint32_t datagram)
{
uint8_t data[3] = {0};
uint32_t reply;
uint8_t rdsel = TMC2660_GET_RDSEL(datagram);

// determine next read address
read = (read + 1) % 3;
data[0] = 0xFF & (datagram >> 16);
data[1] = 0xFF & (datagram >> 8);
data[2] = 0xFF & (datagram >> 0);

// write settings from shadow register to chip.
//readWrite(TMC2660_config->shadowRegister[TMC2660_WRITE | write]);
tmc2660_readWrite(0, TMC2660_config->shadowRegister[TMC2660_WRITE_BIT | write]);
// Send 24 bytes of data and receive reply
tmc2660_readWriteSPI(icID, &data[0], sizeof(data));

// determine next write address - skip unused addresses
write = (write == TMC2660_DRVCTRL) ? TMC2660_CHOPCONF : ((write + 1) % TMC2660_REGISTER_COUNT);
}
reply = (data[0] << 16 | data[1] << 8 | data[2]) >> 4;

void tmc2660_initConfig(TMC2660TypeDef *tmc2660)
{
tmc2660->velocity = 0;
tmc2660->oldTick = 0;
tmc2660->oldX = 0;
tmc2660->continuousModeEnable = 0;
tmc2660->isStandStillCurrentLimit = 0;
tmc2660->isStandStillOverCurrent = 0;
tmc2660->runCurrentScale = 5;
tmc2660->coolStepActiveValue = 0;
tmc2660->coolStepInactiveValue = 0;
tmc2660->coolStepThreshold = 0;
tmc2660->standStillCurrentScale = 5;
tmc2660->standStillTimeout = 0;

int32_t i;
for(i = 0; i < TMC2660_REGISTER_COUNT; i++)
{
tmc2660->registerAccess[i] = tmc2660_defaultRegisterAccess[i];
tmc2660->registerResetState[i] = tmc2660_defaultRegisterResetState[i];
}
// write value to response shadow register
tmc2660_shadowRegister[icID][rdsel] = reply;

// Store the latest response value to extract status bits in tmc2660_getStatusBits()
tmc2660_shadowRegister[icID][TMC2660_RESPONSE_LATEST] = reply;

// write value to response shadow register
if (TMC2660_GET_ADDRESS(datagram) == TMC2660_DRVCONF)
rdsel = TMC2660_GET_RDSEL(datagram);

// write value to shadow register
tmc2660_shadowRegister[icID][TMC2660_GET_ADDRESS(datagram) | TMC2660_WRITE_BIT] = datagram;
}

// Currently unused, we write the whole configuration as part of the reset/restore functions
void tmc2660_writeConfiguration(TMC2660TypeDef *tmc2660, ConfigurationTypeDef *TMC2660_config)
void readImmediately(uint8_t icID, uint8_t rdsel)
{
// write one writeable register at a time - backwards to hit DRVCONF before DRVCTRL
UNUSED(tmc2660);
UNUSED(TMC2660_config);

//uint8_t *ptr = &TMC2660_config->configIndex;
//const int32_t *settings = (TMC2660_config->state == CONFIG_RESTORE) ? TMC2660_config->shadowRegister : tmc2660->registerResetState;

//while((*ptr >= 0) && !IS_WRITEABLE(tmc2660->registerAccess[*ptr]))
//(*ptr)--;

//if(*ptr >= 0)
//{
//tmc2660_writeInt(0, *ptr, settings[*ptr]);
//(*ptr)--;
//}
//else
//{
//TMC2660_config->state = CONFIG_READY;
//}
// sets desired reply in DRVCONF register, resets it to previous settings whilst reading desired reply
uint32_t value;

// additional reading to keep all replies up to date
value = tmc2660_readRegister(0, TMC2660_DRVCONF); // buffer (value and drvConf) to write back later
value &= ~TMC2660_SET_RDSEL(-1); // clear RDSEL bits
value |= TMC2660_SET_RDSEL(rdsel % 3); // set rdsel
readWrite(icID, value); // write to chip and readout reply
readWrite(icID, value); // write to chip and return desired reply
}

void tmc2660_periodicJob(uint8_t motor, uint32_t tick, TMC2660TypeDef *tmc2660, ConfigurationTypeDef *TMC2660_config)
void tmc2660_writeRegister(uint8_t icID, uint8_t address, uint32_t value)
{
UNUSED(motor);

if(tick - tmc2660->oldTick >= 10)
{
standStillCurrentLimitation(tmc2660);
tmc2660->oldTick = tick;
}

if(tmc2660->continuousModeEnable)
{ // continuously write settings to chip and rotate through all reply types to keep data up to date
continousSync(TMC2660_config);
}
// Don't write to read-only registers
if (TMC2660_IS_READONLY_REGISTER(address))
return;

// Extract 20 bits of valid data
value &= 0x0FFFFF;

//Cache the registers with write-only access
tmc2660_cache(icID, TMC2660_CACHE_WRITE, address, &value);

// 0XF7 to mask the write bit
if (!tmc2660_getcontinuousModeEnable(icID))
readWrite(icID, TMC2660_DATAGRAM((address & 0xF7), value));
}

uint8_t tmc2660_reset(TMC2660TypeDef *TMC2660, ConfigurationTypeDef *TMC2660_config)
uint32_t tmc2660_readRegister(uint8_t icID, uint8_t address)
{
UNUSED(TMC2660_config);
uint32_t value;

// Read from cache for registers with write-only access
if (tmc2660_cache(icID, TMC2660_CACHE_READ, address, &value))
return value;

tmc2660_writeInt(0, TMC2660_DRVCONF, TMC2660->registerResetState[TMC2660_DRVCONF]);
tmc2660_writeInt(0, TMC2660_DRVCTRL, TMC2660->registerResetState[TMC2660_DRVCTRL]);
tmc2660_writeInt(0, TMC2660_CHOPCONF, TMC2660->registerResetState[TMC2660_CHOPCONF]);
tmc2660_writeInt(0, TMC2660_SMARTEN, TMC2660->registerResetState[TMC2660_SMARTEN]);
tmc2660_writeInt(0, TMC2660_SGCSCONF, TMC2660->registerResetState[TMC2660_SGCSCONF]);
if (!tmc2660_getcontinuousModeEnable(icID))
{
// Read the read-only register, refreshing the cache
readImmediately(icID, address);
}

return 1;
// Return the read-only register from cache
return tmc2660_shadowRegister[icID][address];
}

uint8_t tmc2660_restore(ConfigurationTypeDef *TMC2660_config)
uint8_t tmc2660_getStatusBits(uint8_t icID)
{
tmc2660_writeInt(0, TMC2660_DRVCONF, TMC2660_config->shadowRegister[TMC2660_DRVCONF | TMC2660_WRITE_BIT]);
tmc2660_writeInt(0, TMC2660_DRVCTRL, TMC2660_config->shadowRegister[TMC2660_DRVCTRL | TMC2660_WRITE_BIT]);
tmc2660_writeInt(0, TMC2660_CHOPCONF, TMC2660_config->shadowRegister[TMC2660_CHOPCONF | TMC2660_WRITE_BIT]);
tmc2660_writeInt(0, TMC2660_SMARTEN, TMC2660_config->shadowRegister[TMC2660_SMARTEN | TMC2660_WRITE_BIT]);
tmc2660_writeInt(0, TMC2660_SGCSCONF, TMC2660_config->shadowRegister[TMC2660_SGCSCONF | TMC2660_WRITE_BIT]);

return 1;
// Grab the status bits from the last request
return tmc2660_shadowRegister[icID][TMC2660_RESPONSE_LATEST] & TMC2660_STATUS_MASK;
}
Loading

0 comments on commit 75c4ef8

Please sign in to comment.