Arduino DUE - Using TC with simplefoc Lib

Hello Guys,
I hope I am correct with my question in this category, I am new in this forum :slight_smile:

First of all thank you for this Lib and Shield, Ive been working with it for a project and I didnt have any problems until now, everything was well explained.
I was testing your example Code Steer-by-Wire and it worked out of the box. Now Ive tried to run this script by using two arduino due, each for one motor and sending the motor position between them.
I was testing it with I2C but I didnt get a stable connection or a stable behavior of the motors when turning one of the motors.
Then I tried something different, I used a PWM to send the motor position via duty cycle. And on the other arduino uses a hardware interrupt pin to capture the duty cycle. This was working pretty well but with this methode I only got the resolution of 0 … 1000 caused by the frequency of the pwm - the standard pwm of the pwm of 1kHz (measured with micros()). This resolution isnt enough for a smooth behaviour of the motors.
So I came up with something different I knew I can change the frequency by changing some registers. While searching through the datasheet I found a implementation of a waveform and capture mode on the arduino due. So I tried this to send some values and it worked great.
But after merging this into the steer-by-wire code I got problems with the lib working as intended.
Is it possible to change the TC without changing the behaviour of the lib?
I got the receive function working which is at pin 11 and so at TC2. Maybe the problem is TC0 where I send my PWM.

My Code:

#include <SimpleFOC.h>

float motor1_shaft_angle = 0.0F;                  //shaft angle motor here 
float motor2_shaft_angle = 0.0F;                  //shaft angle motor at slave

// Variables for COM
uint32_t last_dutyCycle = 0xFFFFFFFF;

void setPWMcfg()
{
  /*************  PIN13 - Timer Counter 0 Channel 0 to generate PWM pulses through TIOB0  ************/
  PMC->PMC_PCER0 |= PMC_PCER0_PID27;                          // Timer Counter 0 channel 0 IS TC0 -> PID27
  PIOB->PIO_PDR |= PIO_PDR_P27;                               // disable PB8 default output (B Pin)
  PIOB->PIO_ABSR |= PIO_ABSR_P27;                             // TC0 signal: TIOB0 (Periperal type B)

  TC0->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1      // MCK/2, clk on rising edge
                              | TC_CMR_WAVE                   // Waveform mode 
                              | TC_CMR_WAVSEL_UP_RC           // UP mode with automatic trigger on RC Compare
                              | TC_CMR_EEVT_XC0               // disable external trigger mode (external trigger to XCO)
                              | TC_CMR_BCPB_CLEAR             // Clear TIOB0 on RB compare match
                              | TC_CMR_BCPC_SET;              // Set TIOB0 on RC compare match

  TC0->TC_CHANNEL[0].TC_RC = 10499;                           // Frequency = (Mck/2)/TC_RC = X Hz
  TC0->TC_CHANNEL[0].TC_RB = 5249;                            // Duty cycle = (TC_RA/TC_RC) * 100  % = X % || Initial Value for Com PWM

  TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;    // Software trigger TC0 counter and enable

  /*************  PIN11 - Timer Counter 2 Channel 2 to capture PWM pulses through TIOA8  ************/
  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                          // Timer Counter 2 channel 2 IS TC2 -> PID35

  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1      // MCK/1, clk on rising edge
                              | TC_CMR_ABETRG                 // TIOA8 is used as the external trigger
                              | TC_CMR_LDRA_FALLING           // load RA on falling edge of trigger input
                              | TC_CMR_ETRGEDG_RISING;        // External Trigger Edge Selection to rising

  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;    // Software trigger TC2 counter and enable

  return;
}

void txPWM(uint32_t txVal)
{
  TC0->TC_CHANNEL[0].TC_RB = txVal;  

  return;
}

uint32_t rxPWM(void)
{
  // Variables for COM
  uint32_t dutyCycle = 0xFFFFFFFF;
  uint32_t status;
  uint32_t rxVal;

  status = TC2->TC_CHANNEL[2].TC_SR;

  if (status & TC_SR_LDRAS) {                                 // If ISR is triggered by LDRAS then ....
    dutyCycle = (uint32_t) TC2->TC_CHANNEL[2].TC_RA;               // get data from capture register A of TC0 channel 1

    if (dutyCycle > 200 && dutyCycle < 10200)                           // overrange detection
    {
      //printf("DutyCycle = %d\n", dutyCycle);
      last_dutyCycle = dutyCycle;
      rxVal = dutyCycle;
    }
    else
    {
      //dutyCycle = 0xFFFFFFFF;
      rxVal = last_dutyCycle; 
      //printf("ERROR - OVERFLOW!\n"); // receive wrong Value
    }
  }
  else
  {
    //dutyCycle = 0xFFFFFFFF;
    rxVal = last_dutyCycle;   
    //printf("ERROR - CONNECTION DISCONNECTED!\n");
  }

  return rxVal;
}

void sendValue(float mVal)
{
  uint32_t mapVal;

  // Range Check
  if (mVal < -50.0f)
  {
    mVal = -50.0f;
    //send error message
  } 
  if (mVal > 50.0f)
  {
    mVal = 50.0f;
    //send error message
  }

  mapVal = (mVal * 100) + 5200;                 // Conversion in Com Value
  
  txPWM(mapVal);

  return;
}


float recieveValue(void)
{
  uint32_t mapVal;
  float mVal;

  mapVal = rxPWM();                             // mapVal in Range 200...10200 and -1 for Error (maybe future introduction of error codes)
  //if (mapVal == ERROR_VAL)
  mVal = ( ((float) mapVal) - 5200) / 100.0f;     // Conversion in Float Value
  
  return mVal;
}


//InlineCurrentSense current_sense1  = InlineCurrentSense(0.01, 50, A0, A2);

BLDCMotor motor1 = BLDCMotor(11);
BLDCDriver3PWM driver1 = BLDCDriver3PWM(5, 9, 6, 8);
MagneticSensorPWM sensor1 = MagneticSensorPWM(7, 4, 923);
void doPWM1(){sensor1.handlePWM();}
 
void setup() {

  pinMode(LED_BUILTIN, OUTPUT); 
  digitalWrite(LED_BUILTIN, LOW);

  setPWMcfg();

  // initialise magnetic sensor hardware
  sensor1.init();  
  // enable the interrupt and start reading the sensor
  sensor1.enableInterrupt(doPWM1);
  // link the motor to the sensor
  motor1.linkSensor(&sensor1);

  // init driver
  // power supply voltage [V]
  driver1.voltage_power_supply = 12;
  driver1.init();
  // link driver
  motor1.linkDriver(&driver1);

  // current sense init hardware
  //current_sense1.init();
  // link the current sense to the motor
  //motor1.linkCurrentSense(&current_sense1);

  // set control loop type to be used
  motor1.controller = MotionControlType::torque;
  // initialise motor
  motor1.init();
  // align encoder and start FOC
  motor1.initFOC();
  

  Serial.begin(115200);

  _delay(1000);
}

 
void loop() {

  // receive motor postion 
  motor2_shaft_angle = recieveValue();

  // send motor position
  sendValue(motor1.shaft_angle);
    
  // iterative setting FOC phase voltage
  motor1.loopFOC(); 
  // virtual link code
  motor1.move(5*(motor2_shaft_angle - motor1.shaft_angle));

  Serial.print(motor1.shaft_angle);
  Serial.print("\t");
  Serial.println(motor2_shaft_angle);
}

I thought about using different TCx and different channel but it didnt help.

Sorry if something is not the way how to use a forum. Its my first time writing a question about coding.
Thank you in advance.

Liam

Hey @LiamMaxwell,

Welcome to the community, I am very happy to have a question about DUE boards.

So I’ve gone through the arudino code and the pins you’re using (5,9 and 6) should not use TC0 and should not configure it:

Pins 6 and 9 are set by using the DUE’s PWM interface directly which bypasses the timers (TCx), only the pin 5 doesn’t have this interface and use the TC2, the channel 6.

I am not 100% sure why your code does not work.
I would have said that the TC2 would not work and TC0 would :smiley:

In the motor.init() we do try to sync all the timers used and start them at the same time, so what you can try is to call your function setPWMcfg(); afterwards.

Can you give some more info about what does actually happen when you try to run this code, what part of it is working and which parts arent :smiley:

Another approach you could try is to use the low level API for PWM generation that we use for the simplefoc.
We support 2 pwm stepper motors so you need to configure 2 channels even if you need only one but it might be a simpler solution.

You could do something linke this:

long pwm_frequency = 15000; // example 15kHz
int pinA = 13; // pin 13 for example
int pinB = 13; // some other pin you might not need (can be 13 also )

void setup(){

  _configure2PWM(pwm_frequency, pinA, pinB);

}

float dc_a = 0.5; // duty cycle for the pin A - float between 0 and 1
floar dc_b = 0.5; // duty cycle for the pin B - float between 0 and 1
void loop(){
 _writeDutyCycle2PWM(dc_a, dc_b, pinA, pinB);
}

Hallo @Antun_Skuric,

thank you for your fast answer. Sorry I didnt post the behaviour, you’re right.
If I print the motor position of one motor, the value is rising very high and very low while only turning ~15°. Normaly without the pwm config the value behaves like it should: 1 Rotation - 6.3 radians. I have already tested to call the setPWMcfg() function after motor.intit(), but I got the same behavior. It seems like it has something to do with the higher frequency. And I thought I checked every TC Pin but you are right pin 5 is TC2 but as you said its channel 6 so normally no problem.
I mean my code is working and I tested it already with just sending values and it did it pretty well. The interessting thing is that it is only influencing the sensor at the motor I turn and the value which gets transmit stays the same. I tested it with just sending values and printing the motor position and the same happens. The value is rising too fast.
I really like this solution with just reading and writing registers. Its fast and the hardware does the job instead of the software.

Thank you for your approach, this would have safed me some time. If I need something like this again I would use it.

I will test with another TCx and look for a solution. In addition I check the connections between Motor Shield and Arduino if I conected everything correct.

Thank you again for your help.

Liam