Go Down

Topic: Tone output on Arduino Due (Read 863 times) previous topic - next topic

nicola_fabbri

I'm trying to output a square wave with a variable frequency from 10Hz to 20 KHz that is updated evry 10 ms. I found a library that work  but during frequency update there is some extra-delay and output is not perfect because there some spike during frequency update and from speaker you can ear some noise.
Looking on source code I found this line:

   TC_Stop(chTC, chNo);
   TC_SetRC(chTC, chNo, rc);                      // set frequency
   TC_Start(chTC, chNo);

I suppose that problem is due to stopping timer and then starting again, but if I remove the stop and start call then frequency is not generated.

There is some idea or some workaround, or a library that can solve this?

WesBrooks

Did you get any where with this? I'm currently looking for a way to change the timer parameters on the fly too.

WesBrooks

I've found that you don't have to call the stop timer function to adjust the RC or RA after the timer has been started. Not much help...

MarkT

I'm sure this can be done, I fear it will take some in-depth reading of the datasheet
about how to configure the timers so that the period can be safely updated on the
fly.  The way this would normally be achieved is a mode that only makes the register
update live as the counter resets to 0, which stops the counter escaping from current
range of counting since 0 is always valid no matter how short a period.

It may be that this works or almost works already, I've only glanced at the timers
(the PWM unit I know better - it might be work a look).

Another way to solve the problem is configure one timer to interrupt at a high rate
(perhaps 100kHz or something like that) and drive a DDS loop.  However that
forces your output transitions to always be on an interrupt tick, which might
not be good enough.
[ I won't respond to messages, use the forum please ]

WesBrooks

I did try to have a high frequency interupt which was a counter to generate another interupt at variable frequencies but this bogged down the Due too much.

I've started having a look at the SAM3X/A document and I've spotted something which might be useful. On page 882 Figure 37-10 WAVSEL=10 with trigger it shows the counter counting up to RC and then being reset, or being reset by a trigger. I think there is a compare which can be used for this trigger, but as you say it will take some diging!

My issue is relating to stepper motor drive. Adding the control to the main loop() function with delays isn't the best on timing and at worse if you use the delays method the Due bogs down and becomes only useful for one thing and moinitoring inputs (switches or serial), or presenting outputs (LEDs and TFT), give up.

Thanks for your help. If I get any further I will post back here...

WesBrooks

Ok, spent the day trawling through the docs and internet yesterday and figured out a few things, but alas nothing that is a perfect solution.

I will have a look at the PWM channels at some point but that would require a re-wire of my connections to the shield. Not out of the question but I'm hoping to avoid that. To be fair the standard wiring for the motor shield isn't ideal any way as it's using the LED pin to control the direction of the current - loosing me a simple debug tool of flash if I've got a problem!

The Software Trigger is a little help, isn't ideal. My understanding of if it is that you can use it to raise an interupt from the main code that resets the timer. This might improve my last efforts a little as the code that is changing the motor phases and pwm will be high priority, rather than standard script...

WesBrooks

Feeling silly...

My timer counter was set up to raise an interupt when the counter value matched RC, which is as most of the timer examples on this forum:

Code: [Select]
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq) {
    pmc_set_writeprotect(false);
    pmc_enable_periph_clk((uint32_t)irq);
    tc->TC_WPMR = 0x54494D00;
    TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1);
    TC_SetRA(tc, channel, 500); // Interupt raised at this value.
    TC_SetRC(tc, channel, 400); // Counter resets at this value.
    TC_Start(tc, channel);
    tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
    tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
    NVIC_EnableIRQ(irq);
}

void TC3_Handler() {
    ....code to execute when timer counter value == RA....
}

startTimer(TC1, 0, TC3_IRQn);


As you can see in that example I had started a timer that would never trigger the interrupt, as it counts upto 400 and then resets to 0 before reaching the timer interrupt of 500. My thinking was that this was a stopped state, and I could dynamically change the value of RC to adjust the frequency.

I could change the value of RC, but only by running the code following:

Code: [Select]
TC_SetRC(TC1, 0, 400);
TC_Start(tc, channel);


After looking throught the arduino folders I found that TC_Start in it's rawest form (tc.c) is:

Code: [Select]
TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN  | TC_CCR_SWTRG ;

TC_CCR is the command register for a particular timer counter. As I wasn't starting the timer, merely trying to force it to reload the value of RC I found that this could be cut back to:

Code: [Select]
TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG ;

This is calling the software trigger which resets the timer. This still causes the difficulty of resetting the timer counter back to zero at the same time. This means that if you call this many times on the trot then it can cause a nice smooth chain of pulses to become irratic.

To address this I've added a pulse_period variable in my main code that I update whenever I like and section to the TC3_Handler that checks to see if it has changed, and if so updates RC and forces a reset.

Code: [Select]
void TC3_Handler() {
    ....code to execute when timer counter value == RA....
    if (pulse_period != TC1->TC_CHANNEL[0].TC_RC) {
        TC1->TC_CHANNEL[0].TC_RC = pulse_period;
        TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG;
    }
  }


This isn't quite perfect as this change will take place at RA counts through the timer but it is a lot better than it was before. By making the difference inbetween RA and RC much smaller this effect would be reduced, or I could use the second channel and get that triggered by the RB value.



WesBrooks

#7
Aug 21, 2014, 03:10 pm Last Edit: Aug 21, 2014, 03:45 pm by WesBrooks Reason: 1
This is the current set up of the code now. Since I'm only interested in creating one event per timer cycle I've swaped to a timer that does not use the RC compare reset, and swapped to triggering the interrupt on the RA compare. I was mistaken before, the interrupts were raised on the RC compare.

Code: [Select]
void set_up_timer(Tc *tc, uint32_t channel, IRQn_Type irq) {
   pmc_set_writeprotect(false);
   pmc_enable_periph_clk((uint32_t)irq);
   // Note using  'TC_CMR_WAVSEL_UP' rather than ' TC_CMR_WAVSEL_UP_RC'
   TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
   TC_SetRA(tc, channel, 500);
   // No need to set RC, only interested in creating one event.
   tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPAS;     // Enable interupt on RA compare
   tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPAS;  // Disable all other interupts.
   NVIC_EnableIRQ(irq);
}

void change_speed(uint32_t pulse_period){
 if (pulse_period == 0) {
   // If pulse period set to 0 the timer is checked to see if it is enabled. If so it is disabled.
   if ((TC1->TC_CHANNEL[0].TC_SR & TC_SR_CLKSTA) == TC_SR_CLKSTA){
     TC_Stop(TC1, 0);
   }
 }
 else {
   // If pulse period set to anythin other than 0 the timer is checked to see if it is disabled. If so it is enabled.
   if ((TC1->TC_CHANNEL[0].TC_SR & TC_SR_CLKSTA) != TC_SR_CLKSTA){
     TC_Start(TC1, 0);
   }
 }
}

void TC3_Handler() {
 TC_GetStatus(TC1, 0);
 ...doing stuff here...  
 if (pulse_period != TC1->TC_CHANNEL[0].TC_RA) {
   // If value of RA is not equal to the pulse period it is updated.
   TC1->TC_CHANNEL[0].TC_RA = pulse_period;
 }
 // The following triggers a software reset of the timer.
 TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG;
}

Go Up