Creating an encoder model using PWM and interrupts on DUE

Hi guys, I am trying to simulate the function of an incremental encoder using the DUE. The idea is for it to work in the following way:-

  • Use a Timer Interrupt to change the duty cycle of a PWM signal (high 75% & low 25%)

  • Read in a supply voltage

  • Based on the level of the supply voltage, change the frequency of the interrupt timer.

Lets take an example:

IF at max voltage, max PWM freq is 100,000 Hz, max Timer frequency is 1000 Hz (meaning duty cycle of PWM changes from 75% to 25% with a frequency of 1000 Hz)

At half the voltage, Timer Interrupt frequency drops to half. The PWM frequency remains the same, however, the frequency of the change of the duty cycle of the PWM drops to half.

Here is the code I am using for it (Ive missed out the variable initialisations in order to meet the character limit):

#define motorFrequency1 1000


void setup() {

  Serial.begin(115200);
  pinMode(pinPWM,OUTPUT);
  pinMode(pinDuty, INPUT);

/************************************************************************************************
 * Output PWM thru TIOA0
 ************************************************************************************************/
 
  PMC->PMC_PCER0 |= PMC_PCER0_PID27;                      // Timer Counter 0 channel 0 IS TC0, TCO power ON
  PMC->PMC_PCER0 |= PMC_PCER0_PID12;                      // PIOB power ON, page 38
  
  PIOB->PIO_PDR |= PIO_PDR_P25;
  PIOB->PIO_ABSR |= PIO_ABSR_P25;                        // PB25 is driven by the TC, peripheral type B, page 858

  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_ACPA_CLEAR          // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA0 on RC compare match


  TC0->TC_CHANNEL[0].TC_RC = 420;  //<*********************  Frequency = (Mck/2)/TC_RC
  

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



/************************************************************************************************
 * Generate pulses thru TIOA1 and use it as Interrupt
 ************************************************************************************************/
 
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)ID_TC1);

  TC0->TC_CHANNEL[1].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_ACPA_CLEAR          // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA0 on RC compare match


  TC0->TC_CHANNEL[1].TC_IER = TC_IER_CPCS       // Interrupt on RC compare match
                              | TC_IER_CPAS;    // Interrupt on RA compare match
                              
  NVIC_EnableIRQ(TC1_IRQn);                     // Interrupt enable
                           
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC0 counter and enable

}

/************************************************************************************************
 * Interrupt Handler for Timer Counter 0 Channel 1 (TC1)
 ************************************************************************************************/
 
void TC1_Handler()
{
 TC_GetStatus(TC0, 1);
 if (counter == 0)
 {
  TC0->TC_CHANNEL[0].TC_RA = 336;  //<********************   Duty cycle = (TC_RA/TC_RC) * 100 = 75 %
  counter = 1;
 }
 else
 {
  TC0->TC_CHANNEL[0].TC_RA = 110;  //<********************   Duty cycle = (TC_RA/TC_RC) * 100 = 25 %
  counter = 0;
 }
 }

/************************************************************************************************
 * Main Loop
 ************************************************************************************************/
 
void loop() {

  /**********************************************************************************************
   * Loop conditions for Standard PWM voltage output and reading that voltage filtered to AC back in at A0
   **********************************************************************************************/

  analogWrite(pinPWM, 255);
  
  //take a number of analog samples and add them up
  //continues to iterate until two successive values greater than 200 are seen
  //or if the time elapsed reaches a greater value than the interval
  
  while (sample_count < NUM_SAMPLES) {
    //introduce interval comparison
    unsigned long currentMillis = millis();
    check = 0;
    check2 = 0;
    ;
    check = analogRead(pinVoltage);
    if (check > 200)
    {
      //Serial.print("1st measurement: ");
      //Serial.println(check);
      check2 = analogRead(pinVoltage);
      if (check2 > 200){
        //Serial.print("2nd measurement: ");
        //Serial.println(check2);
        sample_count = 2;
      }
    }
    //Check if the time elapsed is greater than the interval (2ms)
    //If not, continue while loop, if yes, end while loop
    else if (currentMillis - previousMillis < interval)
    {
      previousMillis = currentMillis;
      sample_count = 2;
    }
    
    sum = check + check2;
    delay(10);
  }

  voltage = ((float)sum / 2 * 3.3)/1024.0;
  originalVoltage = voltage * 4;

  sample_count = 0;
  sum = 0;
  
  /*******************************
   * 10000 Hz == 4200 Counts @ 13V
   * Gradient = 10000/13 = 769.23
   * frequency = 769.23 * Vin
   * freq = 42000000/frequency
   *******************************/

  int freq = 42000000/(originalVoltage * (motorFrequency1/13.2)); 
  
  TC0->TC_CHANNEL[1].TC_RC = freq;
  
}

I am using the digital output to provide the supply voltage. Ofcourse with lower duty cycle this DC signal is first filtered through Low pass.

The voltage is read back in and set inside the main loop. Depending on the value of the voltage, in the final command of the while loop, the frequency for the interrupt is set (code pasted again below):

int freq = 42000000/(originalVoltage * (motorFrequency1/13.2)); 
  
  TC0->TC_CHANNEL[1].TC_RC = freq;

Then, using this frequency, inside the interrupt handler, the duty cycle of the original PWM is altered (code pasted again below):

void TC1_Handler()
{
 TC_GetStatus(TC0, 1);
 if (counter == 0)
 {
  TC0->TC_CHANNEL[0].TC_RA = 336;  //<********************   Duty cycle = (TC_RA/TC_RC) * 100 = 75 %
  counter = 1;
 }
 else
 {
  TC0->TC_CHANNEL[0].TC_RA = 110;  //<********************   Duty cycle = (TC_RA/TC_RC) * 100 = 25 %
  counter = 0;
 }
 }

The Issues I am facing with this are:

at high Interrupt frequencies, the DUE just keeps going around the while loop for the first few minutes, and then once the interrupt happens, it get stuck there and doesnt iterate through the loop again.

At low frequencies (about 1Hz - 50 Hz) for the interrupt, everything seems to work fine.

Can somebody please explain to me why I am seeing such a behaviour? What can I do to make my program work at higher frequencies as well?

Really appreciate the help. Please let me know if anything is unclear about the project and if I can explain something better.

There is something I don't understand right from the beginning of your post, because:

From wikipedia:

"The pulses emitted from an incremental encoder's A and B outputs are quadrature-encoded, meaning that when the encoder is moving at a constant velocity, the duty cycle of each pulse is 50% (i.e., the waveform is a square wave) and there is a 90 degree phase difference between A and B.[2] "

----> To emulate an incremental encoder, use TC_SMMR (Sam3x datasheet, page 877).

Is that a uni assignment ?

ard_newbie:
There is something I don't understand right from the beginning of your post, because:

From wikipedia:

"The pulses emitted from an incremental encoder's A and B outputs are quadrature-encoded, meaning that when the encoder is moving at a constant velocity, the duty cycle of each pulse is 50% (i.e., the waveform is a square wave) and there is a 90 degree phase difference between A and B.[2] "

----> To emulate an incremental encoder, use TC_SMMR (Sam3x datasheet, page 877).

Is that a uni assignment ?

Hey man, thanks for you reply

Well yes that is how an encoder works. WOW. If it really is that simple with the TC_SMMR I'm in luck! Are there any example scripts of that?

Basically that graph we can see on page 877 of Sam3x datasheet is what I'm trying to reproduce, using one channel to output PWM, and another channel to control an Interrupt.

No, its part of a project I am doing as my thesis.

See that thread from reply #86:

https://forum.arduino.cc/index.php?topic=140205.75

ard_newbie:
See that thread from reply #86:

Using the Hardware Quadrature Encoder Channel on the DUE - Arduino Due - Arduino Forum

Thank you I am trying to make that work now.

However, I would still really like to know why my original solution doesn't work. It could be a learning curve for me.

What I realised is that I was using large integer types and that was slowing down the processing. After changing all the 'int' to 'byte' I am now able to go up to 100 Hz frequencies for the interrupt.

What is going on here?