I want to achieve the generation of a custom binary signal over a digital pin at the highest possible frequency on Arduino Uno.
Using Timer's output pins (e.g. OC0A), one can get a pin to toggle at a maximum frequency of 16MHz (which corresponds to a sqaure wave of frequency 8MHz), as described here: Maximum frequency of digital signal in Arduino Uno?
However as far as I understand, Timer's output pins are only useful if one needs to generate a repetitive waveform such as a PWM or square wave, so it cannot serve my purpose which requires the generation of a custom binary signal.
Using the following code, I was expecting digital pin 9 to toggle at a frequency close to 16MHz too, since I use no prescaler and set the Compare Match Register value to 0:
void setup()
{
DDRB = 1 << 1; // Set D9 as OUTPUT
cli();
TCNT0 = 0; TCCR0A = 0; TCCR0B = 0;
TCCR0A |= (1 << WGM01); // CTC mode
TCCR0B |= (1 << CS00); // no prescaling
OCR0A = 0; // Highest timer overflow frequency possible
TIMSK0 |= (1 << OCIE0A);
sei();
}
ISR(TIMER0_COMPA_vect)
{
PORTB ^= 1 << 1; // Toggle D9; this allows to measure the toggle frequency, however this could be replaced by some code to generate a custom binary signal
}
void loop(){}
However in fact it appears to toggle at a frequency of only approximately 410kHz (which corresponds to a square wave of frequency 205kHz), as the below image shows:
I used Timer0 but I get the same frequency with Timer1 and Timer2.
- How can one explain that using this code I get this toggle frequency on the digital pin ?
- How can I generate a custom binary signal over a digital pin at the highest possible frequency ?
1 Answer 1
How can one explain that using this code I get this toggle frequency on the digital pin ?
Code takes time to execute. Your ISR, when compiled, looks like:
ISR(TIMER0_COMPA_vect)
{
124: 1f 92 push r1
126: 0f 92 push r0
128: 0f b6 in r0, 0x3f ; 63
12a: 0f 92 push r0
12c: 11 24 eor r1, r1
12e: 8f 93 push r24
130: 9f 93 push r25
PORTB ^= 1 << 1; // Toggle D9; this allows to measure the toggle frequency, however this could be replaced by some code to generate a custom binary signal
132: 85 b1 in r24, 0x05 ; 5
134: 92 e0 ldi r25, 0x02 ; 2
136: 89 27 eor r24, r25
138: 85 b9 out 0x05, r24 ; 5
}
13a: 9f 91 pop r25
13c: 8f 91 pop r24
13e: 0f 90 pop r0
140: 0f be out 0x3f, r0 ; 63
142: 0f 90 pop r0
144: 1f 90 pop r1
146: 18 95 reti
That's 18 assembly instructions, each needing between 1 and 4 clock cycles to execute. Referencing the datasheet you can count the exact cycles needed and sum them up, yielding 31 clock cycles.
Toggling at maximum speed that would be 16MHz / 31 = 516kHz.
And then you have the rest of the code running between each call to the interrupt, so it's slower than that.
How can I generate a custom binary signal over a digital pin at the highest possible frequency ?
By not using interrupts, and writing a tight loop in assembly language.
-
How did you get the exact assembly out? Is it possible to do that with the Arduino IDE or did you have to use the Atmel IDE?Tri– Tri2021年04月08日 07:40:50 +00:00Commented Apr 8, 2021 at 7:40
-
That actually addresses the question quite well. My understanding of what is happening is the following: the first Timer Compare Interrupt occurs. Before the ISR has finished running, a second Timer Compare Interrupt occurs, but is queued because the previous ISR has not finished running. As soon as the first ISR has finished running, the second ISR is allowed to run. And that goes on and on is such a way that the ISR is running at a frequency equal to (or at least close to) the inverse of its duration. What do you say about this interpretation?Ramanewbie– Ramanewbie2021年04月08日 17:38:54 +00:00Commented Apr 8, 2021 at 17:38
-
How can I use assembly language to achieve a higher toggle frequency than with the Timer Compare Interrupt ? In fact, how can I use assembly language to achieve my goal without using a timer interrupt (which as you showed is slow)?Ramanewbie– Ramanewbie2021年04月08日 17:40:09 +00:00Commented Apr 8, 2021 at 17:40
-
1@Tri I used
avr-objdump -S <filename.elf>
on the compiled elf file from the Arduino IDE (actuallyarduino-cli
as I hate the IDE).Majenko– Majenko2021年04月08日 18:05:03 +00:00Commented Apr 8, 2021 at 18:05 -
@Ramanewbie That's a good interpretation, yes. Writing assembly language can be a bit of a black art. You would need to code delay loops to get the timing you want, along with toggling of the IO pin and looking up of the data sequence. Not a fun task. Another option could be to stream the data out using the SPI port, where the speed will be relative to the resolution you desire - maximum 8 MBits/s (4MHz square wave)Majenko– Majenko2021年04月08日 18:07:43 +00:00Commented Apr 8, 2021 at 18:07
PINB = 1 << 1;
rather thanPORTB ^= 1 << 1
to save a couple of cycles. The construction may look weird, but they've designed the AVR GPIO hardware so that you can toggle pin be writing 1 bits to their bit positions in the PINx registers; writing 0-bits have no effect. If you take the writing in a tight loop outside of an interrupt suggestion, this may have some impact.