-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature_rework_tmc2660'
- Loading branch information
Showing
11 changed files
with
626 additions
and
533 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
 | ||
|
||
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 | ||
 | ||
|
||
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. | ||
 | ||
|
||
### 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). | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.