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 Answers 2
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.
-
Thanks but what is the goal of "TIFR1 = _BV(OCF1A); // clear interrupt flag" ?Bob5421– Bob542111/11/2020 17:08:51Commented Nov 11, 2020 at 17:08
-
pin 14 is an example, i can do it on any pinBob5421– Bob542111/11/2020 17:09:59Commented Nov 11, 2020 at 17:09
-
@Bob5421: See amended answer.Edgar Bonet– Edgar Bonet11/11/2020 19:02:01Commented 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 callinterrupts()
.Edgar Bonet– Edgar Bonet11/11/2020 19:46:06Commented Nov 11, 2020 at 19:46 -
1@DaveX:
OCR1A = -1000;
is indeed equivalent toOCR1A = 65536-1000;
. That was an error of mine. I was mixing this up with the idea thatTCNT1 = -1000;
ensures the overflow fires in 1000 cycles.Edgar Bonet– Edgar Bonet01/03/2022 13:59:39Commented Jan 3, 2022 at 13:59
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.
-
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– Dave X01/03/2022 14:54:17Commented Jan 3, 2022 at 14:54
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'sICR1
orOCR1A
on the UNO. Not sure about the Mega.