Inexplicable DUE PWM behavior

I'm currently trying to control fans via PWM. My first attempt was with the Nano 33 IoT and it worked on the first attempts by configuring the registers accordingly.

Now I wanted to implement the same feature on the DUE but got really strange results for which I have no explanation.

I've tried two different approaches.

Here's the first one where I measured 25kHz on the output but experienced the following behavior:
Duty 0 : Meter reports 0% dutycycle. Fan spins quite fast at this stage. When running this cycle on the Nano 33 IoT the fan runs very slow at its absolute minimum capability.

Duty 1680: Meter reports 50% dutycycle. Fan spins on around half the speed
Duty 3360: Meter reports 0%. Fan spins as fast as on duty 0.

I've selected Pins 40 and 41 as they are configurable on PWM channel 3 which shouldn't come in conflict with other stuff.

I need to control a display LED driver at 1kHz on another pin so this is why I need to select pins on other channels where possible.

void setup() {
  Serial.begin(115200);
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;
  PWM->PWM_DIS = PWM_DIS_CHID3;         // Disable PWM channel 3
  //PC8 = D40 = PIOC, P8
  PMC->PMC_PCER0 |= PMC_PCER0_PID11;
  PIOC->PIO_PDR |= PIO_PDR_P8;
  PIOC->PIO_ABSR |= PIO_ABSR_P8;
  //PC9 = D41 = PIOC, P9
  PIOC->PIO_PDR |= PIO_PDR_P9;                         
  PIOC->PIO_ABSR |= PIO_ABSR_P9;                       
  // Set registers for PWM channel 3
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);    // Set the PWM clock rate to 84MHz (84MHz/1). Divide by 1
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_CPRE_CLKA;
  PWM->PWM_CH_NUM[3].PWM_CPRD = 3360; //84MHz / 3360 = 25kHz
  PWM->PWM_CH_NUM[3].PWM_CDTY = 1680; //50% 
  PWM->PWM_ENA = PWM_ENA_CHID3; // Enable PWM channel 3 again
}

void loop() 
{
  PWM->PWM_CH_NUM[3].PWM_CDTY = 0;
  Serial.println(0);
  delay(5000);
  PWM->PWM_CH_NUM[3].PWM_CDTY = 1680;
  Serial.println(1680);
  delay(5000);
  PWM->PWM_CH_NUM[3].PWM_CDTY = 3360;
  Serial.println(3360);
  delay(5000);
}

Alright so this didn't work and so I hopped on the net to find a different approach which utilizes the same methods as the core framework. This time I tried pins 6 and 7 and also pin 5 with different frequencies to understand what goes wrong.

The following example is an edited version of "Bob Cousins" approach.
This time I wanted to know when the dutycycle produces a 0% output.

// --------------------------------------------
//
// Hardware PWM with different frequencies demo
//
// Bob Cousins, August 2014
// --------------------------------------------
int dutyCycle = 0;
void setup() {
  Serial.begin(115200);
  // put your setup code here, to run once:

  pinMode (6, OUTPUT); 
  pinMode (7, OUTPUT); 
  pinMode (5, OUTPUT); 

  // call analogWrite first to allow Arduino to initialise
  analogWrite (6, 128);
  analogWrite (7, 128);
  analogWrite (5, 128);

  // set clka to 1kHz, clkb to 25kHz
  PWMC_ConfigureClocks (255 * 1000, 255 * 25000, VARIANT_MCK);

  // set channel 6 to use clkb
  PWMC_ConfigureChannel (PWM, 6, PWM_CMR_CPRE_CLKB, 0, 0);
  PWMC_ConfigureChannel (PWM, 7, PWM_CMR_CPRE_CLKB, 0, 0);
  // Note: need to re-enable channel after doing Configure
  PWMC_EnableChannel (PWM, 6);
  PWMC_EnableChannel (PWM, 7);

  // set channel 5 to use clka
  PWMC_ConfigureChannel (PWM, 5, PWM_CMR_CPRE_CLKA, 0, 0);
  PWMC_EnableChannel (PWM, 5);
}

void loop() {
  //Increase cycle
  dutyCycle += 1;

  analogWrite (6, dutyCycle);
  analogWrite (7, dutyCycle);
  analogWrite (5, dutyCycle);
  Serial.print("Duty:");
  Serial.println(dutyCycle);

  //Start over if max value has been reached
  if (dutyCycle >= 255)
  {
    dutyCycle = 0;
  }
  delay(100);
}

Guess what? I've got the exact same result across all pins - even D5. Around a value of 240 the fan jumps back to normal speed and the output reads 0% duty on the meter.

What am I doing wrong? I can't seem to find an explanation...

There are numerous PWM example sketches in the DUE sub forum.

An example sketch to output a PWM pulse at 641 Hz with 50% Duty cycle and blink an LED at 1 Hz:

/******************************************************************************/
/*                         PWMH0 641  Hz                                      */
/******************************************************************************/

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                   // PWM power ON

  PIOC->PIO_PDR |= PIO_PDR_P3;                         // Set PWM pin to a peripheral
  PIOC->PIO_ABSR |= PIO_PC3B_PWMH0;                    // Set PWM pin peripheral type B for PWMH0 (Arduino pin 35)

  PWM->PWM_CLK = PWM_CLK_PREB(0) | PWM_CLK_DIVB(2);    // select Frequency for clock B: Mck/2 = 42 MHz
  PWM->PWM_CH_NUM[0].PWM_CMR = PWM_CMR_CPRE_CLKB;      // The period is left aligned, clock source as CLKB on channel 0
  PWM->PWM_CH_NUM[0].PWM_CPRD = 65535;                 // Set the PWM frequency 42 MHz/PWM_CPRD = 641 Hz
  PWM->PWM_CH_NUM[0].PWM_CDTY = 32768;                 // Set the PWM duty cycle = (CPRD/CDTY) * 100 %

  PWM->PWM_IER1 = PWM_IER1_CHID0;                      // Interrupt on PWM Channel 0 counter
  NVIC_EnableIRQ(PWM_IRQn);                            // Enable interrupt

  PWM->PWM_ENA = PWM_ENA_CHID0;                        // Enable PWM channel 0
}

void loop() {
  // update PWM frequency with  PWM->PWM_CH_NUM[0].PWM_CPRDUPD = ....;
  // update PWM duty cycle with PWM->PWM_CH_NUM[0].PWM_CDTYUPD = ....;
}

void PWM_Handler() {
  static uint32_t Count;

  PWM->PWM_ISR1;      // Clear status register
  if (Count++ == 641)
  {
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;
    Count = 0;
  }
}

The problem is obviously not the approach.
I’ve tried numerous things and just for the sake of completeness I adapted the example you provided just to find the exact same behavior.

Here’s the code which should put out 25kHz on Pins 40 and 41, which it actually does (24,99kHz) but steps out at between 1640 and 1660 at an actual dutycycle of 84%

Even better: on Pin41 I got 99,9% at around 600.

I get the feeling something is wrong with my board…

int dutyCounter = 0;
int steps = 10;
unsigned long oneSecondTimerMillis = 0;
void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;    // PWM Enable
  PWM->PWM_DIS = PWM_DIS_CHID3;         // Disable PWM channel 3

  //PC8 = D40 = PIOC, P8
  PIOC->PIO_PDR |= PIO_PDR_P8;          // Set Pin Peripheral
  PIOC->PIO_ABSR |= PIO_PC8B_PWML3;     //Pin Peripheral Type B "PIO_PC8B_PWML3" as defined in "pio_sam3x8e.h" is D40 as defined at "variant.cpp"
  //PC9 = D41 = PIOC, P9
  PIOC->PIO_PDR |= PIO_PDR_P9;         // Set Pin Peripheral
  PIOC->PIO_ABSR |= PIO_PC9B_PWMH3;    //Pin Peripheral Type B "PIO_PC9B_PWMH3" as defined in "pio_sam3x8e.h" is D41 as defined at "variant.cpp"
  // Set registers for PWM channel 3
  PWM->PWM_CLK = PWM_CLK_PREB(0) | PWM_CLK_DIVB(2);    // Clock B: 84MHz / 2 = 42MHz
  PWM->PWM_CH_NUM[3].PWM_CMR = PWM_CMR_CPRE_CLKB;
  PWM->PWM_CH_NUM[3].PWM_CPRD = 1680;                 // 42MHz / 1680 = 25kHz
  PWM->PWM_CH_NUM[3].PWM_CDTY = 840;                  // 50%


  PWM->PWM_IER1 = PWM_IER1_CHID3;                      // Interrupt on PWM Channel 0 counter
  NVIC_EnableIRQ(PWM_IRQn);                            // Enable interrupt

  PWM->PWM_ENA = PWM_ENA_CHID3;                       // Enable PWM channel 3 again
}

void loop()
{
  if (millis() - oneSecondTimerMillis >= 1000)
  {
    oneSecondTimerMillis = millis();
    float actDuty = (dutyCounter / 1680.0) * 100.0;
    Serial.println(dutyCounter);
    Serial.print(actDuty);
    Serial.println("%");
    //Update dCycle
    PWM->PWM_CH_NUM[3].PWM_CDTYUPD  = dutyCounter;
    //Increase counter
    dutyCounter += steps;
    //Go up and down from 0 to 1680
    if (dutyCounter <= 0 || dutyCounter >= 1680)
    {
      steps = -steps;
    }
  }
}

void PWM_Handler() {
  static uint32_t Count;

  PWM->PWM_ISR1;      // Clear status register
  if (Count++ == 25000) //25kHz
  {
    PIOB->PIO_ODSR ^= PIO_ODSR_P27; //Builtin LED Toggle
    Count = 0;
  }
}

You are initializing dutyCounter with 0 and steps with 10.

In loop(), the first time you are entering in the if() statement, you set PWM_DTYUPD to 0 !, then you are testing dutyCounter and it is 0, hence you set steps to -10!. The next time you are entering the if() statement you update PWM_DTYUPD with -10 !

Don't you see the isssue ?

Thanks for the hint but unfortunately this isn’t true. You’ve missed the line dutyCounter += steps; which is called right before the check.

void loop()
{
  if (millis() - oneSecondTimerMillis >= 1000)
  {
    oneSecondTimerMillis = millis();
    float actDuty = (dutyCounter / 1680.0) * 100.0;
    Serial.println(dutyCounter);
    Serial.print(actDuty);
    Serial.println("%");
    //Update dCycle

dutycounter eq. 0

    PWM->PWM_CH_NUM[3].PWM_CDTYUPD  = dutyCounter;
    //Increase counter
    dutyCounter += steps;

dutycounter eq. 10
Since dutycounter is now greater than 0 or not greater or equal than 1680, the steps won’t be inverted

    //Go up and down from 0 to 1680
    if (dutyCounter <= 0 || dutyCounter >= 1680)
    {
      steps = -steps;
    }
  }
}

Next iteration:

void loop()
{
  if (millis() - oneSecondTimerMillis >= 1000)
  {
    oneSecondTimerMillis = millis();
    float actDuty = (dutyCounter / 1680.0) * 100.0;
    Serial.println(dutyCounter);
    Serial.print(actDuty);
    Serial.println("%");

dutycounter eq. 10

    //Update dCycle
    PWM->PWM_CH_NUM[3].PWM_CDTYUPD  = dutyCounter;
    //Increase counter
    dutyCounter += steps;

dutycounter eq. 20

    //Go up and down from 0 to 1680
    if (dutyCounter <= 0 || dutyCounter >= 1680)
    {
      steps = -steps;
    }
  }
}

Serial Output:

11:13:10.555 -> 0
11:13:10.555 -> 0.00%
11:13:11.529 -> 10
11:13:11.529 -> 0.60%
11:13:12.539 -> 20
11:13:12.539 -> 1.19%
11:13:13.550 -> 30
11:13:13.550 -> 1.79%
11:13:14.560 -> 40
11:13:14.560 -> 2.38%
11:13:15.534 -> 50
11:13:15.534 -> 2.98%
11:13:16.545 -> 60
11:13:16.545 -> 3.57%
11:13:17.554 -> 70
11:13:17.554 -> 4.17%

So as I’ve said this isn’t related to the kind of approach.

I’m running out of ideas…

Oooops !!

I tried with 2 Leds on pins 40 and 41, and everything seems to be Ok with my board.

Well well.... then the conclusion is the board is faulty.

Thank you for your time