2

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:

Picture of the screen of an oscilloscope showing that the frequency at which the digital pin toggles is close to 410kHz

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 ?
2
  • I realize this is peanuts compared to the greater issues which are outlined in Majenko's answer, but if you do want to toggle PB1, you can PINB = 1 << 1; rather than PORTB ^= 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.
    – timemage
    Apr 7, 2021 at 21:34
  • Thanks, I didn't know you could do it more efficiently that way; this would be impactful in some situations indeed.
    – Ramanewbie
    Apr 8, 2021 at 16:46

1 Answer 1

3

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.

8
  • 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
    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
    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
    Apr 8, 2021 at 17:40
  • 1
    @Tri I used avr-objdump -S <filename.elf> on the compiled elf file from the Arduino IDE (actually arduino-cli as I hate the IDE).
    – Majenko
    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
    Apr 8, 2021 at 18:07

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.