Go Down

Topic: Arduino Due - Emulating Incremental Rotary Encoder Signals (Read 1 time) previous topic - next topic


Hi, for my current project I would like to emulate an incremental rotary encoder signal. Therefore I have to create six different signals A, B, Index (and their 3 inverted signals). I want to do this using 6 PWM signals with a duty cycle of 50%. The frequency of the PWM signals I determine, by continuously reading the voltage of the ADC pin. I succeeded with the A signal to vary freq. depending on the voltage with:

Code: [Select]
//Set clock A to run at PWM_FREQUENCY * MAX_DUTY_CYCLE

For the B signal and inverse A and B I need to create a phase shift of 90 degrees for the B signal, and 180 degrees for inverse A and B. The frequency must remain identical. In the SAM3X datasheet I read about link channels in synchronous mode. Since I'm fairly new to programming, I do not know how I get this done. Is there someone who can help to me how to do this? Thanks!



my answer might be a bit late but I just needed the same thing and found out a nice way how to get the quadrature signals A and B and their inverse with 50% duty cycle.
The approach uses the internal PWM channels of the SAM3X8E that are on different pins than the normal PWM pins defined by the Arduino platform. They can be identified in the pinout diagram by labels like PWMLx and PWMHx.
These PWM channels offer the possibility of generating a 2-bit gray count signal on a given pair of PWM channels. This means that both PWM channels output a signal of given frequency and 50% duty cycle, but shifted by 90° in phase.

The following code generates such a coupled gray count signal on pins 35 (PWMH0) and 37 (PWMH1), corresponding to quadrature signals A and B. Naturally the inverted signals of A and B can be simultaneously found on the PWML0 and PWML1 channels (pins 34 and 36).

Code: [Select]

// pin definitions
const int pwm_pin1 = 35; // PWMH0
const int pwm_pin2 = 37; // PWMH1
const int pwm_pin3 = 34; // PWML0
const int pwm_pin4 = 36; // PWML1
boolean sense = 0;       // 0 up counter, 1 down counter

const unsigned int mask_pwm_pin1 = digitalPinToBitMask(pwm_pin1);
const unsigned int mask_pwm_pin2 = digitalPinToBitMask(pwm_pin2);
const unsigned int mask_pwm_pin3 = digitalPinToBitMask(pwm_pin3);
const unsigned int mask_pwm_pin4 = digitalPinToBitMask(pwm_pin4);

void setup() {
    // to set the frequency of the quadrature signal in programm use REG_PWM_CPRDUPD0 = value, not REG_PWM_CPRD0 = value;

    // activate clock for PWM controller
    REG_PMC_PCER1 = 1<<4;           
    // activate correct peripheral functions for PWM pins, necessary for internal PWM mode
    REG_PIOC_PDR = mask_pwm_pin1;   
    REG_PIOC_ABSR |= mask_pwm_pin1;   
    REG_PIOC_PDR = mask_pwm_pin2;   
    REG_PIOC_ABSR |= mask_pwm_pin2;   
    REG_PIOC_PDR = mask_pwm_pin3;   
    REG_PIOC_ABSR |= mask_pwm_pin3; 
    REG_PIOC_PDR = mask_pwm_pin4;   
    REG_PIOC_ABSR |= mask_pwm_pin4;   

    REG_PWM_CLK = 0;                // choose clock reference, 0 -> full MCLK as reference 84MHz, determines the possible range of PWM freq
    REG_PWM_CPRD0 = 840;            // initialize gray code PWM slope period -> T = value/84MHz (2<value<16bit), value=840 -> 100kHz slope rate
    REG_PWM_CDTY0 = 1;              // initialize duty cycle at 1 (meaningless but necessary for it to work, for values of 0 or >=CPRD0 the gray mode doesn't work)
    REG_PWM_SMMR = (1<<0)|(sense<<16); // enable gray counter for channel 0 and 1
    REG_PWM_ENA = (1<<0)|(1<<1);    // enable PWM channels 0 and 1 (pin 35 = PWMH0, complement on pin 34 = PWML0 and pin 37 = PWMH1, complement on pin 36 = PWML1)

void loop() {
  // ramping down the frequency
  for (int i = 2; i<1000; i++) {
    REG_PWM_CPRDUPD0 = i*10;

The frequency of the PWM signal can be chosen by setting the value in the CPRDUPD register (REG_PWM_CPRDUPD0 = value). Possible working values are from 2 to 2^16. The according slope rate calculates to f = 84MHz/value when the master clock is chosen as a reference (REG_PWM_CLK = 0). This enables values of slope rates between about 1kHz and 42MHz.
If lower frequencies are needed a clock divider needs to be activated in the REG_PWM_CLK register (please look in the datasheet for details). The counter direction can be chosen by setting the sense variable to either 1 or 0.

By this you can create quite simply the quadrature A and B signals as well as their inverse, but you'd still need to do some work to get proper Index signals.

However I hope this might still help you or someone else.


Thanks schwingkopf for your good explanation! I had mine working by using the dead time generator. You had to sync all channels etc (a lot more work). This solution is much better (using it now)!

Things I noticed:
Code: [Select]

REG_PWM_CLK = ...;                                       // changing the MCK doesn't work for me this way
PWM->PWM_CH_NUM[0].PWM_CMR = 0x....        // only like this

When using the full MCK I get a range of 10,5 MHz (CPRD = 2) to 320 Hz (CPRD = 65535) (scoped this)
So this means 84MHz / (CPRD * 4) = freq?


Hi vorkiej,

you are right about the MCK thing, the PWM_CLK register only selects the clock dividers but they have to be configured in the CMR register... good point ;)

Concerning the frequency: you are probably talking about the real frequency on one single PWM channel output (1 / full period). This is correct that they have a frequency of 84MHz / (CPRD * 4) = freq, but I was talking about the frequency at which a slope on either of the two channels appears (which I think is the more relevant quantity when using a quadrature decoder) which happens 4 times during one full period cycle. Thats where the factor of 4 comes from ;)

Go Up