Go Down

Topic: attachInterrupt for TCx_Handlers (Read 2568 times) previous topic - next topic

Yubashiri

Hi guys,

I have programmed a class, which allows me to individually generate PWM signals (frequency, duty, resolution). It is also possible to really modulate a signal. I.e. an array with samples of a sinus.
I achieved this trough the TCx_Handler. Every completed period causes an interrupt and in the Handler the next dutycycle will be written.

BUT my problem now ist, that i want to add a method, which works like attachInterrupt().
Basicly the user shall be able to write and name his/her own ISR without thinking about the needed Handler.
I do know that i need to use callbacks, but I do not know how to implement this in my case, since it's all depending on the chosen output pin. 
I thought i could help myself by looking attachInterrupt() and WInterrupts.c but i don't get what was done in the PIO handlers.

I.e.:
Quote
void PIOA_Handler(void) {
  uint32_t isr = PIOA->PIO_ISR;
  uint8_t leading_zeros;
  while((leading_zeros=__CLZ(isr))<32)
  {
    uint8_t pin=32-leading_zeros-1;
    if(callbacksPioA[pin]) callbacksPioA[pin]();
    isr=isr&(~(1<<pin));
  }
}
What are the leading zeros for? What does __CLZ do? From where comes the 32?

Any help would be greatly appreciated :).

Greetings, Yuba.


Oh. nearly forgot: I plan to release my class after i finishing touch and documentation. So no worries about that.

ard_newbie


Well well well...... a uint32_t is a 32-bit variable, hence the 32. __CLZ counts the leading zeroes to detect the first bit set in a uint32_t variable.__CLZ is an intrinsic ARM function using the assembler instruction CLZ (super fast).

To figure which pin is concerned by which PWM channel, I guess a matching table could do the job (Sam3x datasheet, page 973), although all pins are not broken out (see Graynomad pinout diagram).

Did you see the PWM library from antodom ?

Yubashiri

#2
Dec 05, 2019, 10:15 pm Last Edit: Dec 05, 2019, 10:16 pm by Yubashiri
Quote
__CLZ counts the leading zeroes to detect the first bit set in a uint32_t variable.
Alright. Thanks for that already. I didn't think about an Assembler command. Thought it would we a define or something that my IDE couldn't link properly.

I did see his library but it doesn't really cover my intentions and general thoughts so I attached my library so it may be a bit more clear what I'm doing.

At the end of pwmWrite.cpp you can see my poor tryouts for an first callback and also some more in the comments.

Yubashiri

#3
Dec 05, 2019, 10:20 pm Last Edit: Dec 05, 2019, 10:24 pm by Yubashiri
Missclicked on my keyboard. >:(  Here are the codes.
The serial monitor prints 5 times and then stops. But at least he prints something.

ard_newbie

A few thoughts:

The PWM peripheral is better designed for PWM than the TC peripheral when you need to update frequency and/or duty cycle on the fly. That's because the PWM peripheral will automatically update the new frequency or duty cycle right at the end of the previous period with the previous frequency/duty cycle that you set with PWM_CPRDUPD and PWM_DTYUPD in loop().

Furthermore, eventhough some TC pins are not broken out, it doesn't mean that they can't be used. E.g. you can trigger ADC or DAC conversions with TIOA2 pulses because these pulses are internally routed to the ADC or DAC peripheral. You can also chain timers by routing internally some TC pulses.

There are numerous example sketches using direct register programming with PWM or TC in the DUE sub forum.

For PIO interrupts, if you need a faster process than the one provided by attachinterrupt(), you just have to alter Winterrupts.c to be able to write your own PIO_Handler():

Code: [Select]

/*******************************************************************/
/*                       Test PIO Interrupts                       */
/*    Hook a jumper between pin 2 (PB25) and pin 24 (PA15)         */
/*    Do the below modifications in winterrupts.c                  */
/*******************************************************************/

// Winterrupts.c has to be modified since attachinterrupts is too slow
// ...package/arduino/hardware/sam/1.6.6/cores/arduino/Winterrupts
/*
  void PIOA_Handler(void) __attribute((weak));  // <***** Add this attribute before each PIO Handler
  void PIOA_Handler(void) {
  uint32_t isr = PIOA->PIO_ISR;
  uint32_t i;
  for (i=0; i<32; i++, isr>>=1) {
    if ((isr & 0x1) == 0)
      continue;
    if (callbacksPioA[i])
      callbacksPioA[i]();
  }
  }

*/
#define INT_MASK  (PIO_PA15)

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

  pio_setup();
  tc_setup();
}
void loop() {
}
/*****************************************************************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID27;                      // TC0 power ON : Timer Counter 0 channel 0 IS TC0

  PIOB->PIO_PDR = PIO_PDR_P25;                            // Set the GPIO to the peripheral
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0;

  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 = 42;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz = 1 MHz
  TC0->TC_CHANNEL[0].TC_RA = 4;  //<********************   Any Duty cycle in between 1 and TC_RC

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

}

/*******************************************************************/
void pio_setup(void)
{
  PMC->PMC_PCER0 = PMC_PCER0_PID11;   // PIOA power ON

  PIOA->PIO_PER = INT_MASK;         // enable parallel input - output
  PIOA->PIO_PUER = INT_MASK;        // enable light pull up
  PIOA->PIO_IFER = INT_MASK;        // enable glitch filter (1/2 clock cycle glitches discarted)
  PIOA->PIO_AIMER = INT_MASK;       // The interrupt source is described in PIO_ELSR
  PIOA->PIO_ELSR = INT_MASK;        // enable low level detection

  PIOA->PIO_IER = INT_MASK;         // enable interrupt
  NVIC_EnableIRQ(PIOA_IRQn);
}

void PIOA_Handler(void)
{
  static uint32_t Count;
  // uint32_t status = PIOA->PIO_ISR;
  PIOA->PIO_ISR;
  // if (status & INT_MASK)
  // {
  if (Count++ == 3000000)
  {
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;
    // do something...
  }
  //}
}


or use a blocking code like this one (no interruption at all):

Code: [Select]

/*******************************************************************/
/*                       Test PIO Reading                          */
/*    Hook a jumper between pin 2 (PB25) and pin 24 (PA15)         */
/*******************************************************************/

#define INT_MASK   (PIO_PA15)

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pio_setup();
  tc_setup();
}
void loop() {
  static uint32_t Count;
  while (true)
  {
    while (!(PIOA->PIO_ISR & INT_MASK));
    if (Count++ == 3000000)
    {
      Count = 0;
      PIOB->PIO_ODSR ^= PIO_ODSR_P27;
    }
  }
}
/*****************************************************************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID27;                      // TC0 power ON : Timer Counter 0 channel 0 IS TC0

  PIOB->PIO_PDR = PIO_PDR_P25;                            // Set the GPIO to the peripheral
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0;

  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 = 14;  //<*********************  Frequency = (Mck/2)/TC_RC = 3 MHz
  TC0->TC_CHANNEL[0].TC_RA = 2;  //<********************   Any Duty cycle in between 1 and TC_RC

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

}

/*******************************************************************/
void pio_setup(void)
{
  PMC->PMC_PCER0 = PMC_PCER0_PID11;   // PIOA power ON

  PIOA->PIO_PER = INT_MASK;         // enable paralel input - output
  PIOA->PIO_PUER = INT_MASK;        // enable light pull up
  PIOA->PIO_IFER = INT_MASK;        // enable glitch filter (1/2 clock cycle glitches discared)
}



Yubashiri

#5
Dec 06, 2019, 12:15 pm Last Edit: Dec 06, 2019, 12:16 pm by Yubashiri
Yea those work pretty fine but I don't aim to get a simple sketch to work. I'm new to C++ and want/ am obliged to write some software in Arduino style.

It's obviously a "over-engineering" path but that's exactly what i want to do.
The whole library shall be able to work like a analogWrite() command with far more options at some point in the future.

Quote
he PWM peripheral is better designed for PWM than the TC peripheral when you need to update frequency and/or duty cycle on the fly. That's because the PWM peripheral will automatically update the new frequency or duty cycle right at the end of the previous period with the previous frequency/duty cycle that you set with PWM_CPRDUPD and PWM_DTYUPD in loop().
Yes i thought so to but my oscilloscope didn't bother the TC pins. With a 10 bit resolution everything works as fine as it gets so far.

It would also be far more easy to just use the DAC but the whole point of the PWM is to show, that PWM is a primitiv but working way to modulate analog signals. It's the basic form of sigma-delta modulation after all which is still used in cheap audio devices.

So yea, the whole thing is pretty ineffective but so are most of the arduino libraries for the cause of abstraction and user friendly environment.
Btw. OBJECT_NAME.duty() needs around 2.5 us to to the job.

Go Up