The firmware has drastically changed between this post and the next. I'm not updating what I've changed here, and left the firmware attached. For those interested, it can show the evolution of a design in progress.

The biggest change is that when I was reading the examples on the website and the logs  of the IDE, I thought I could send TMCL commands to the motor.

But that's not the case. You have to implement the TMLC datagram to TMC2300 datagram translation in your firmware. It's not difficult - Trinamic has a full-fledged example on GitHub.

 

I'm road testing Trinamic TMC2300 EVAL KIT (2-PH Stepper Motor). It's their latest design that targets battery powered devices.

In this post: a custom program to control the driver over UART.

This series of posts is an attempt to use the TMC2300 in a real design. I'm using a Hercules microcontroller in stead of the landungsbrücke to control a motor. Firmware is in C++

 

Several things are happening at the same time here. I'm splitting it up in chunks to keep it concise.

 

 

The Objects:

 

  • a main class called CPPMain. It's role is to bridge from the functional style HALCoGen framework to C++, and to run the program.
  • an abstract TMCLDatagram base class, that knows Trinamic's TMC Language definitions, the datagram structure and CRC logic. it does not know anything Hercules (or even the physical layer used).
  • the HerculesTMCLDatagram class, child of the one above. This one knows were using UART, and knows the HALCoGen API.
  • an abstract CommInit class. You pass a child of that class to the init()  function.
  • The HerculesSciInit class, based on the one above, a structure to hold UART config parameters.

 

At the begin of the program, a HerculesTMCLDatagram class is instantiated, then initialised and (in a later post) used.

 

 

The CPPMain class

 

Header:

class CPPMain {
public:
  CPPMain();
  virtual ~CPPMain();
  static void main();
};

 

Implementation:

#include <cpp/CPPMain.h>

#include "gio.h"
#include "sci.h"

#include "trinamic/HerculesTMCLDatagram.h"

extern "C" void CPPMain() {
  CPPMain::main();
}

HerculesTMCLDatagram datagram(1U);


CPPMain::CPPMain() {
}

CPPMain::~CPPMain() {
}

void CPPMain::main() {
  gioInit();
  sciInit();

  HerculesSciInit initStruct;
  initStruct._sci = scilinREG;
  datagram.init(&initStruct);

  // put test code here

  while(1) {
  }
}

 

The TMCL Base Classes

 

Header:

struct CommInit {
};

class TMCLDatagram {
public:

  //Opcodes of all TMCL commands that can be used in direct mode
  const uint32_t TMCL_ROR   = 1;
  const uint32_t TMCL_ROL   = 2;
  const uint32_t TMCL_MST   = 3;
  const uint32_t TMCL_MVP   = 4;
  const uint32_t TMCL_SAP   = 5;
  const uint32_t TMCL_GAP   = 6;
  const uint32_t TMCL_STAP  = 7;
  const uint32_t TMCL_RSAP  = 8;
  const uint32_t TMCL_SGP   = 9;
  const uint32_t TMCL_GGP   = 10;
  const uint32_t TMCL_STGP  = 11;
  const uint32_t TMCL_RSGP  = 12;
  const uint32_t TMCL_RFS   = 13;
  const uint32_t TMCL_SIO   = 14;
  const uint32_t TMCL_GIO   = 15;
  const uint32_t TMCL_SCO   = 30;
  const uint32_t TMCL_GCO   = 31;
  const uint32_t TMCL_CCO   = 32;

  //Options for MVP commandds
  const uint32_t MVP_ABS    = 0;
  const uint32_t MVP_REL    = 1;
  const uint32_t MVP_COORD  = 2;

  //Options for RFS command
  const uint32_t RFS_START  = 0;
  const uint32_t RFS_STOP   = 1;
  const uint32_t RFS_STATUS = 2;

  //Result codes for GetResult
  const uint32_t TMCL_RESULT_OK             = 0;
  const uint32_t TMCL_RESULT_NOT_READY      = 1;
  const uint32_t TMCL_RESULT_CHECKSUM_ERROR = 2;

  TMCLDatagram(uint8_t address);
  virtual ~TMCLDatagram();
  virtual void init(CommInit *commInit) = 0;
  virtual void open() = 0;
  virtual void close() = 0;

  //Send a binary TMCL command
  //e.g.  SendCmd( TMCLDatagram::TMCL_MVP, TMCLDatagram::MVP_ABS, 1, 50000);   will be MVP ABS, 1, 50000
  //Parameters: Command: the TMCL command (see the constants at the begiining of this file)
  //            Type:    the "Type" parameter of the TMCL command (set to 0 if unused)
  //            Motor:   the motor number (set to 0 if unused)
  //            Value:   the "Value" parameter (depending on the command, set to 0 if unused)
  virtual void sendCmd(uint32_t command, uint32_t Type, uint32_t motor, int32_t value);

  //Read the result that is returned by the module
  //Parameters: Status: variable to hold the status returned by the module (100 means okay)
  //            Value:  variable to hold the value returned by the module
  //Return value: TMCLDatagram::TMCL_RESULT_OK: result has been read without errors
  //              TMCLDatagram::TMCL_RESULT_NOT_READY: not enough bytes read so far (try again)
  //              TMCLDatagram::TMCL_RESULT_CHECKSUM_ERROR: checksum of reply packet wrong
  virtual uint32_t getResult(uint8_t &status, int32_t &value);

private:
  uint8_t _address;
protected:
  virtual void physicalSendCmd(uint8_t *txBuffer) = 0;
  virtual uint32_t physicalGetResult(uint8_t *txBuffer) = 0;
};

 

Implementation:

This code has drastically changed after I published this. It's now using the CRC and raw datagram generation/interpretation for the TMC2300. The revised code is attached to the next blog post.

TMCLDatagram::TMCLDatagram(uint8_t address) : _address(address) {
}

TMCLDatagram::~TMCLDatagram() {
}

void TMCLDatagram::sendCmd(uint32_t command, uint32_t type, uint32_t motor, int32_t value) {
  uint8_t txBuffer[9];
  uint32_t i;

  txBuffer[0] =_address;
  txBuffer[1] = command;
  txBuffer[2] = type;
  txBuffer[3] = motor;
  txBuffer[4] = value >> 24;
  txBuffer[5] = value >> 16;
  txBuffer[6] = value >> 8;
  txBuffer[7] = value & 0xff;
  txBuffer[8] =0;
  for(i = 0; i < 8; i++) {
    txBuffer[8]+=txBuffer[i];
  }
  physicalSendCmd(txBuffer);
}

uint32_t TMCLDatagram::getResult( uint8_t &status, int32_t &value) {
  uint8_t rxBuffer[9];
  uint8_t checksum;
  uint32_t i;

  if (physicalGetResult(rxBuffer)) {
    checksum = 0;
    for(i = 0; i < 8; i++) {
      checksum += rxBuffer[i];
     }
    if (checksum != rxBuffer[8]) {
      return TMCL_RESULT_CHECKSUM_ERROR;
    }
    // todo: what if this is not for us?
//    *Address=RxBuffer[0];
    status = rxBuffer[2];
    value = (rxBuffer[4] << 24) | (rxBuffer[5] << 16) | (rxBuffer[6] << 8) | rxBuffer[7];
  } else {
    return TMCL_RESULT_NOT_READY;
  }
  return TMCL_RESULT_OK;
}

 

Hercules UART Specific Classes

 

Header:

struct HerculesSciInit: public CommInit {
  sciBASE_t * _sci;
};

class HerculesTMCLDatagram: public TMCLDatagram {
public:
  HerculesTMCLDatagram(uint8_t address);
  virtual ~HerculesTMCLDatagram();
  virtual void init(CommInit *commInit);
  virtual void open();
  virtual void close();
private:
  sciBASE_t * _sci;
protected:
  virtual void physicalSendCmd(uint8_t *txBuffer);
  virtual uint32_t physicalGetResult(uint8_t *txBuffer);
};

 

Implementation:

HerculesTMCLDatagram::HerculesTMCLDatagram(uint8_t address) : TMCLDatagram(address), _sci((sciBase *) NULL) {
}

HerculesTMCLDatagram::~HerculesTMCLDatagram() {
}

void HerculesTMCLDatagram::init(CommInit *commInit) {
  // Hercules initialisation is done in HALCoGen
  _sci = (static_cast<HerculesSciInit *>(commInit))->_sci;
}

void HerculesTMCLDatagram::open() {
  // Hercules does not have to open SCI
}

void HerculesTMCLDatagram::close() {
  // Hercules does not have to close SCI
}

void HerculesTMCLDatagram::physicalSendCmd(uint8_t *txBuffer) {
  uint32_t i;
  for(i = 0; i < 8; i++) {
    while (!sciIsTxReady(_sci)) {}
    sciSendByte(_sci, txBuffer[i]); // send out text
  }
}

uint32_t HerculesTMCLDatagram::physicalGetResult(uint8_t *txBuffer) {
  uint32_t i;
  // todo: this hangs if no data received
  for(i = 0; i < 9; i++) {
    while (!sciIsRxReady(_sci)) {}
    txBuffer[i] = sciReceiveByte(_sci); // receive text
  }
  return i;
}

 

OO Do's and Dont's

 

Because the HALCoGen framework is intended for C, and C++ static objects are constructed early, there is a watchout.

It's not adviced to dynamically allocate objects on an automotive microcontroller (unless you include an approach in your Misra strategy), so I'm creating objects statically.

The constructor of these objects are called before the main() function and before the HALCoGen init is complete, so hardware isn't initialised yet.

Don't do anything hardware related in the constructors. Use an init() function for late initialisation.

 

I've made the HerculesTMCLDatagram class a child of the TMCLDatagram class.

The TMCLDatagram's physicalRead() and physicalWrite() functions are pure virtual, implemented by HerculesTMCLDatagram.

There are other options, such as putting reads and writes in a separate opbject that's passed to TMCLDatagram, or (ducking for cover) multiple inheritance.

 

Not too happy with the initialisation approach where the base class expects a CommInit object and the derived class a HerculesSciInit. There are pros and cons. Discuss.

... and in this post and the attached project I'm mixing 8 and 32 bit unsigned integers. I'm going to use 8 for those that take a single space n the transmit buffer. It will be updated in the next post.

 

Hercules Peripherals Configuration

 

 

Initialisation and configuration of the peripheral drivers is done in HALCoGen. I did not try to abstract that into objects.

 

 

The Code Composer Studio and HALCoGen project are attached.

In a next post I have to address the I/O pins (I can reuse the classes I ported from MBED for another blog series). Then I can try talking to the driver...

 

related blog
UART Interface
Evaluation Kit and IDE Functionality, basic functionality
C++ Custom Firmware Pt 1: Datagram Classes
C++ Custom Firmware Pt 2: Wire Up, Test and Comms Analysis
C++ Custom Firmware Pt 3: Try to Replicate Evaluation Kit Conversation
Trinamic TMC2300 EVAL KIT (2-PH Stepper Motor) - Review