Go Down

Topic: Any tutorial on Due PWM interrupt? (Read 981 times) previous topic - next topic


Apr 24, 2016, 08:02 am Last Edit: Apr 24, 2016, 08:15 am by souravg009
Is there any tutorial on PWM Interrupt?
In AVR, the timer corresponding to the PWM pin could generate an interrupt after its time period is elapsed. In timer ISR we used to some code to be executed. is there any option like that in Arduino Due?
Thanks and Regards,


Hey Sourav,

         I'm looking for something similar. Did you find a solution?




I know I talked with you a bit about this already but I think something bears mentioning (even though it is mentioned a lot in many places). It's going to sound kind of preachy but it's really for the best:

The key to life is being willing to figure things out yourself. When I was first setting up PWM I did look for examples and tried to find an example that would be very close to what I needed. I did find some code but I had to tweak it to do what I wanted and I couldn't find an example of how to set up PWM to trigger an ADC interrupt. I had to look at the data sheet and figure it out myself. Sometimes good examples just don't exist.

You will need to learn to read the processor's data sheet. It's well over a thousand pages but you're really only interested in perhaps 30-40 pages of it. Read the whole PWM section, read it again. Then, read the parts that really interest you. Pay special attention to the registers. Then, go look at the PWM library files from LIBSAM and see what functions you can use. They make it simpler. Now, here's the key: try something. Make a guess, take a stab at it. Did it work? Great! Not at all? Try again. Did it kind of work but like like you wanted? Tweak it until it works like you wanted.

The bottom line is that there are people willing to help but usually it pays to figure out as much as you can and then ask very specific questions. A lot of times people don't want to answer "How do I use PWM interrupts? I need examples!" but would be fine with "I've got this code (posts code) that I believe should set up PWM interrupts and do X, Y, and Z but when I run it Y doesn't happen and sometimes the program crashes. I've tried (A) and (B) but they didn't work. I've run out of ideas. Can anyone help?"

To answer your question, though, yes, you can trigger a PWM interrupt based on the time period elapsing. You can kind of see that with the code I posted where I'm triggering an ADC interrupt instead. It's the same basic idea except you want to trigger a PWM interrupt instead of a PWM event line like I did. You see, what you want to do is set up a PWM counter event (comparison unit) and then enable the interrupt for the channel you just set up a counter event for. Then it should interrupt when the PWM counter gets up to the value you requested. If you are in center aligned mode and you want to interrupt dead in the center then put in your max PWM duty number and it'll do what you want. You can see in my code I actually used a number a little less than the max so that I'd trigger the ADC event line just a little early and start ADC conversion right around when I get to the center. So, try still setting up the comparison unit for channel 0 and then enable channel 0's interrupt with

Code: [Select]

PWMC_ConfigureComparisonUnit(PWM_INTERFACE, 0, 1030, PWM_CMPM_CEN);
PWMC_EnableChannelIt(PWM_INTERFACE, 0);


Apr 21, 2018, 05:07 am Last Edit: Apr 21, 2018, 05:13 am by renanfb13
I also had lots of problems to set the PWM and interrupt.
But here some code to do it ! Hope it can help you guys.

Universidade Federal de Ouro Preto
Exemplo de interrupção sincronizado com o PWM
Autor: Dr. Renan Fernandes Bastos
uint32_t pwmPin9 = 9; // canal 1   define o pino do PWM
uint32_t pwmPin8 = 8; // canal 2
uint32_t pwmPin7 = 7; // canal 3
uint32_t pwmPin6 = 6; // canal 4

uint32_t channel_1 = g_APinDescription[pwmPin9].ulPWMChannel; // atribui a porta ao PWM
uint32_t channel_2 = g_APinDescription[pwmPin8].ulPWMChannel;
uint32_t channel_3 = g_APinDescription[pwmPin7].ulPWMChannel;
uint32_t channel_4 = g_APinDescription[pwmPin6].ulPWMChannel;

void setup() {

 SetPin(pwmPin6); // Habilita o canal
 SetPin(pwmPin7); // Habilita o canal
 SetPin(pwmPin8); // Habilita o canal
 SetPin(pwmPin9); // Habilita o canal
 pmc_enable_periph_clk(PWM_INTERFACE_ID); // habilita a utilização do Clock.

 PWMC_ConfigureChannelExt(PWM_INTERFACE,channel_1,PWM_CMR_CPRE_MCK_DIV_2,PWM_CMR_CALG, 0,0,PWM_CMR_DTE,0,0); // Configurando do canal 1.  
 PWMC_ConfigureChannelExt(PWM_INTERFACE,channel_2,PWM_CMR_CPRE_MCK_DIV_2,PWM_CMR_CALG, 0,0,PWM_CMR_DTE,0,0); // Configurando do canal 2.  

 PWMC_ConfigureChannelExt(PWM_INTERFACE,channel_3,PWM_CMR_CPRE_MCK_DIV_2,PWM_CMR_CALG, 0,0,PWM_CMR_DTE,0,0); // Configurando do canal 3.
 PWMC_ConfigureChannelExt(PWM_INTERFACE,channel_4,PWM_CMR_CPRE_MCK_DIV_2,PWM_CMR_CALG, 0,0,PWM_CMR_DTE,0,0); // Configurando do canal 4.

                  Para definir a frequência de Amostragem/chaveamento, basta definir o CPRD
PWMC_SetPeriod(PWM_INTERFACE, channel_1, CPRD);     CPRD define a frequência de chaveamento de acordo com CPRD = 84MHz/(2*X*freq)
para 10kHz com preSCALER 2 TEM-SE  ->>>>      CPRD = 84MHz/(2*X*freq) = 84MHz/(2*2*10000) = 2100
X podendo ser atribuído pelos valores  1, 2, 4, 8, 16, 32, 64, 128, 256, 512, ou 1024)

 PWMC_SetPeriod(PWM_INTERFACE, channel_1, 2100);  // para 10kHz
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_1, 1050); // Setando um duty cycle inicial 50%
 PWMC_EnableChannel(PWM_INTERFACE, channel_1); //Ativar o canal para saída PWM
 PWMC_SetPeriod(PWM_INTERFACE, channel_2, 2100);
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_2, 1050);
 PWMC_EnableChannel(PWM_INTERFACE, channel_2); //Ativar o canal para saída PWM

 PWMC_SetPeriod(PWM_INTERFACE, channel_3, 2100);
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_3, 1050);
 PWMC_EnableChannel(PWM_INTERFACE, channel_3); //Ativar o canal para saída PWM

 PWMC_SetPeriod(PWM_INTERFACE, channel_4, 2100);
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_4, 1050);
 PWMC_EnableChannel(PWM_INTERFACE, channel_4); //Ativar o canal para saída PWM

 PWM_INTERFACE->PWM_IER1 = 0x10; //enable interrupt on channel 4 - 00010000
 PWM_INTERFACE->PWM_IER1 = 0x20; //enable interrupt on channel 5 - 00100000
 NVIC_DisableIRQ(PWM_IRQn); // set up interrupt
 NVIC_SetPriority(PWM_IRQn, 0);
 PWMC_EnableChannel(PWM_INTERFACE, channel_1);
 PWMC_EnableChannel(PWM_INTERFACE, channel_2);
 PWMC_EnableChannel(PWM_INTERFACE, channel_3);
 PWMC_EnableChannel(PWM_INTERFACE, channel_4);


void loop()      
programa geralmente não faz nada no loop. Apenas espera a interrupção ocorrer.
Aqui qualquer função não prioritária pode ser incluida.
Não utilizar nenhuma função que desabilite as interrupções

void PWM_Handler() // Rotina de processamento da interrupção. A cada ciclo de chaveamento, está função é executada uma única vez.

 volatile long dummy = PWM_INTERFACE->PWM_ISR1; // clear interrupt flag
 dummy = PWM_INTERFACE->PWM_ISR2; // clear interrupt flag

 Usa-se a porta 13 do arduino como saída para verificar se a interrupção ocorreu.
 A saída 13 marca o tempo que o código da interrupção demora a ser executado.
 Equanto a saída 13 está em nível alto, a função da interrupção ainda está sendo executada.
 /*  ========================================================================
   Insira o código aqui !!!!!!!!  A cada ciclo de chaveamento o arduino roda uma vez este loop.
  Faça aqui a amostragem das entradas, cálculo dos controladores e resultado das saídas dos PWMs.

  Fim do código
  Basta agora atualizar os PWMs, EX:
   PWMC_SetDutyCycle(PWM_INTERFACE, channel_1, PWM);  
   onde PWM = Duty * CRPD   = Duty * 2100  para o caso F=10kHz. Onde   0< Duty <1.  

 // Atualização dos PWMs, 1050 representa um Duty de 50% para freq de 10kHz
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_1, 1050);    
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_2, 1050);
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_3, 1050);
 PWMC_SetDutyCycle(PWM_INTERFACE, channel_4, 1050);
 /* Fim da marcação de tempo da interrupção
 Quando a saída 13 vai a nível baixo, significa que a interrupção acabou.
 Note que o tempo de interrupção DEVE ser menor que o período do PWM.
 Ou seja, para um PWM de 1kHz, o tempo de interrupção deve ser menor que 1ms.
}  // fim da interrupção

void SetPin(uint8_t pwmPin){


IMO, it's easier this way:

Code: [Select]

void setup () {

void loop() {

  // Todo : update frequency with PWM_CPRDUPD in loop()
  // update duty with PWM_CDTUPD in loop()

void PWM_Init() {
  // PWM Set-up on pins PC7 and PA20 (Arduino Pins 39(PWMH2) : see Datasheet chap. 38.5.1 page 973
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                    // PWM power ON
  PWM->PWM_DIS = PWM_DIS_CHID2;                         // Disable PWM channel 2

  // Select Instance=PWM; Signal=PWMH2 (channel 2); I/O Line=PC7 (P7, Arduino pin 39, see pinout diagram) ; Peripheral type B
  PIOC->PIO_PDR |= PIO_PDR_P7;                          // Set the pin to the peripheral PWM, not the GPIO
  PIOC->PIO_ABSR |= PIO_PC7B_PWMH2;                     // Set PWM pin perhipheral type B

  PWM->PWM_CLK = PWM_CLK_PREB(0) | PWM_CLK_DIVB(42);    // Set the PWM clock rate to 2MHz (84MHz/42).
  PWM->PWM_CH_NUM[2].PWM_CMR = PWM_CMR_CPRE_CLKB;       // clock source as CLKB on channel 2, waveform left aligned

  PWM->PWM_CH_NUM[2].PWM_CPRD = 2000;                   // Channel 2 : Set the PWM frequency 2MHz/CPRD = 1 KHz ;
  PWM->PWM_CH_NUM[2].PWM_CDTY = 100;                    // Channel 2: Set the PWM duty cycle to x%= (CDTY/ CPRD)  * 100 % ;

  PWM->PWM_IER1 = PWM_IER1_CHID2;                       // Interrupt on Channel 2 Counter match
  NVIC_EnableIRQ(PWM_IRQn);                             // Enable interrupt

  PWM->PWM_ENA = PWM_ENA_CHID2;                         // Enable PWM Channel 2

void PWM_Handler() {
  static uint32_t Count;

  PWM->PWM_ISR1; // Read and clear status register

  if (Count++ == 1000) {
    Count = 0;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle every 1 Hz



IMO, it's easier this way:

Code: [Select]

  if (Count++ == 1000) {
    Count = 0;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle every 1 Hz


Are you sure about digitalRead() an output on DUE? AFAIK doesn't work, I wrote this function in my sketch to read the status of an output :

Code: [Select]

uint8_t read_output(uint8_t pin){
    uint32_t pbm = digitalPinToBitMask(pin);
    uint32_t ret = *portOutputRegister(digitalPinToPort(pin)) & pbm;
    if(ret > 0) return 1;
    else return 0;

Ciao, Ale.


Apr 21, 2018, 05:54 pm Last Edit: Apr 21, 2018, 05:57 pm by ard_newbie
Yes it works. remember, once you set:
pinMode(LED_BUILTIN, OUTPUT) , you power PIOB (LED_BUILTIN is PB27) and you enable output on that pin, BUT you can always read the status of a pin, since you have powered PIOB.

The sketch works nicely on my DUE.

Page 620, Sam3x datasheet:
Note that the Input Change Interrupt, Interrupt Modes on a programmable event and the read of the pin level require the clock to be validated.


Yes it works. remember, once you set:
pinMode(LED_BUILTIN, OUTPUT) , you power PIOB (LED_BUILTIN is PB27) and you enable output on that pin, BUT you can always read the status of a pin, since you have powered PIOB.

The sketch works nicely on my DUE.

Page 620, Sam3x datasheet:
Note that the Input Change Interrupt, Interrupt Modes on a programmable event and the read of the pin level require the clock to be validated.
Yeah, you are  absolutely right, I just test myself and is working, now I would like to understand why this was not working before in my sketch. I just remember that I wrote that function 'cause I needed to read the state of an output, and digitalRead() was always return LOW.

Ciao, Ale.

Go Up