3

I want to generate a 1khz square signal with an Arduino Mega.

I have read the MCU contains several timers. What i want to do is to configure one of this timers.

My first question is: Is there a way to automatically control an output pin from the timer, without having to execute any line of code in my arduino program ? Look at my code: i need to write a line in ISR(TIMER1_OVF_vect). Some MCU can do that but i did not find any information about it on documentation. (Some pins are connected on timer outputs, by hardware conception).

My second question is: What is wrong on my calculation on my code ? I want 1khz and i get 990Hz

Here is what i've done:

  • Arduino Mega runs at 16Mhz = 16000Khz
  • I have set a /8 prescaler, so timer's frequency is 8000Khz
  • The frequency should by divide by two because i have 2 states (HIGH and LOW): 4000Khz
  • The interupt is fired when the counter reach 65536. So if i load 65536-1000 in TCNT1, the timer should count 1000 times in order to fire the interupt. So my frequency is 4000khz/1000 = 1khz.

What is wrong ?

Thanks a lot

void setup() {
  pinMode(14,OUTPUT);
  
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 65536 - 1000; 
  TCCR1B |= (1 << CS11);    // Prescaler clock / 8
  TIMSK1 |= (1 << TOIE1);
  interrupts();
}

ISR(TIMER1_OVF_vect)
{
  TCNT1 = 65536 - 1000;
  digitalWrite(14, digitalRead(14) ^ 1);
}

void loop() {

}
2
  • 3
    This does not really answer your question, but actually there really is a way to let the timer automatically control an output pin. You can use the timer in output compare mode. You write a value in the corresponding OCR (the register) and let the timer run from 0 to this value. When reaching that, the corresponding pin gets automatically toggled and the timer resets. That way you can do this a lot more accurate
    – chrisl
    Nov 11, 2020 at 15:22
  • You shouldn't set TCNT1, but you need to set the TOP value. This depends on which PWM/timer mode you use (see table in datasheet). I know it's ICR1 or OCR1A on the UNO. Not sure about the Mega.
    – Gerben
    Nov 11, 2020 at 15:43

2 Answers 2

3

As explained in the previous answer, you forgot to account for the time taken by the CPU to handle the interrupt prologue. As a general rule, if you want consistent timing, you should never reset the timer by software after the initialization.

If you want to run the timer in normal mode, and use it to schedule interrupts at a period that is not the timer's own period, it is possible, but you should not use the overflow interrupt for this purpose. You should instead use any of the “compare match” interrupts, and adjust the compare register in the IRS in order to schedule the next interrupt. For example:

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

    TCCR1A = 0;            // normal mode
    TCCR1B = _BV(CS11);    // clock @ F_CPU/8
    TCNT1  = 0;            // clear timer
    OCR1A  = 1000;         // first COMPA match in 1000 cycles
    TIFR1  = _BV(OCF1A);   // clear interrupt flag
    TIMSK1 = _BV(OCIE1A);  // enable TIMER1_COMPA interrupt
}

ISR(TIMER1_COMPA_vect) {
    OCR1A += 1000;      // schedule next interrupt
    PINJ   = _BV(PJ1);  // toggle PJ1 = digital 14
}

Note that the line OCR1A += 1000; can cause a wrap around, but this is no problem, as the addition wraps around in exactly the same way as the timer itself.

The advantage of this technique is that you can schedule up to three different tasks, having different periods, with only one timer. And you can even schedule tasks in a non-periodic fashion.

That being said, if you do not need the timer for anything else other than scheduling a single periodic task (or up to four tasks sharing the same period), then a better solution is to let the timer handle the period all by itself. Configure the timer in mode 12 or 14 (CTC or fast PWM, with TOP = ICR1), and set the period with ICR1 = 999;. Then the ISR is reduced to:

ISR(TIMER1_COMPA_vect) { PINJ = _BV(PJ1); }

and you save a few cycles. This would be my preferred solution if I absolutely had to output the signal on pin 14.

Note that this solution should give you the right average frequency, but you will still have some jitter, because the interrupt can occasionally be delayed by other interrupts. If you are free to choose the output pin, then the best solution is to let the timer handle the PWM generation all by itself. Set the timer to any PWM mode (fast PWM is the simplest) and use one of the waveform generators in order to generate the PWM signal. Only the PWM-capable pins (those marked with a “~” symbol) can be used this way. For example, Timer 1 can output to pins 11 (OC1A), 12 (OC1B) and 13 (OC1C).

Edit answering the question:

what is the goal of TIFR1 = _BV(OCF1A); // clear interrupt flag?

Whenever the timer value matches the contents of the compare register OCR1A, the interrupt flag OCF1A (Output Compare Flag of timer 1, channel A) is raised. This flag controls the interrupt request when the OCIE1A bit (Output Compare Interrupt Enable 1A) is set. The flag is automatically cleared when the ISR runs, and can be manually cleared by writing a logic 1 to it (yes, it is kind of backwards).

It is quite likely that the flag raises early in the start process, while the Arduino library is initializing. If the flag is up when you enable the interrupt, an interrupt request is immediately triggered. In order to avoid that, the common practice is to clear the flag before enabling the interrupt.

7
  • Thanks but what is the goal of "TIFR1 = _BV(OCF1A); // clear interrupt flag" ?
    – Bob5421
    Nov 11, 2020 at 17:08
  • pin 14 is an example, i can do it on any pin
    – Bob5421
    Nov 11, 2020 at 17:09
  • @Bob5421: See amended answer. Nov 11, 2020 at 19:02
  • 1
    @Bob5421: Yes. noInterrupts() does not prevent the interrupt request from being triggered. The interrupt would then fire as soon as you call interrupts(). Nov 11, 2020 at 19:46
  • 1
    @DaveX: OCR1A = -1000; is indeed equivalent to OCR1A = 65536-1000;. That was an error of mine. I was mixing this up with the idea that TCNT1 = -1000; ensures the overflow fires in 1000 cycles. Jan 3, 2022 at 13:59
4

My first question is: Is there a way to automatically control an output pin from the timer, without having to execute any line of code in my arduino program ?

Yes. It's called PWM with a 50% duty cycle.

You need to read up on 19. Output Compare Modulator (OCM1C0A) in the datasheet.

My second question is: What is wrong on my calculation on my code ? I want 1khz and i get 990Hz

You have to take into account the number of CPU cycles it takes to start executing your ISR. There's a short bit of code before your code is run that preserves any used registers on the stack and allocates any room needed for local variables. That takes time, and must be calculated into your timing factor.

1
  • The 19. Output Compare Modulator (OCM1C0A) feature seems like overkill for a 1KHz square wave. That modulates timer 1 and timer 2 together based on OCR1C OCR0A and puts an output on B7. An unmodulated 1kHz square wave could be done without overhead by using Timer 1 in one of the WGM modes with TOP as ICR1 or OCR1A.
    – Dave X
    Jan 3, 2022 at 14:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.