Square-wave generator - the basics


No doubt, the most popular Arduino sketch is BLINK.ino which you will find on most Arduinos when shipped. Actually, it can be regarded as a very, very low frequency square-wave generator. The blink program ist something like the "Hello world" programs delivered with all computers that offer any kind of text output. It just proves you managed to make the system working (even without understanding anything).

As generating square-waves is a very important feature we will discuss this matter in detail.

  1. Preliminary notes
  2. Program-controlled
  3. Library routine tone()
  4. With Interrupt-Service-Routine
  5. By using a Hardware-Timer

  1. Preliminary notes
  2. If you don't need high frequencies and precision you may use the delay(milliseconds) function. Keep in mind that the delay function does not respect the operations your program ist performing like switching LEDs on and off.
    You might improve you code by checking the system time millis(). Just compare the system time with the next switching time.

    The examples shown below will use PIN 11 as OUTPUT pin. Except for the examples shown in chapter 5. you may select another pin.

    The oscillograms shown below were produced using an old analog HAMEG HM312 (band width 10 MHz). That is why the signals generated by a 16 MHz processor can only be reproduced in limited quality.

    Take care when producing high frequencies: the might transmit radio signals. Check out example 4. d), you might hear a sound in your radio (long wave 1730 meter). In most countries you need a licence to transmit radio signals. Check out your local laws.

  3. Program controlled
  4. In this mode the controller is totally busy producing the square wave signal. If interrupts are disabled only a reset will stop it. Honestly, an Arduino is too expensive to misuse it in this way. But you can reprogram it after the test.
    You can select any pin for output.

    a) As said above the example shipped with the Arduino IDE → 1. Basics → Blink.ino will work as a very low frequency square-wave generator:

    byte pin = 11;
    
    void loop() {
      digitalWrite(pin, HIGH);   // set pin on
      delay(1000);              // wait for a second
      digitalWrite(pin, LOW);    // set pin off
      delay(1000);              // wait for a second
    }
    

    (no oscillogram, as the shutter speed had to exceed 2 seconds

    This generator will produce a square-wave signal of 0.5 Hz, the duty-cycle will be 1 by 2.

    b) By modifying the parameters of the delay function you may control frequency an duty cycle over a wide range. If you omit the delays you get

      while (true) {
        digitalWrite(pin,HIGH);
        digitalWrite(pin,LOW);
      }
    

    which will give you a frequency of 103,250 Hz (Arduino UNO) respectively 74,982 Hz (Arduino MEGA).

    c) Whenever you use the digitalWrite() function the compiler has to generate code to find out which port and which bit has to be set or reset. If you know which port and bit you want to handle you can skip all this. Keep in mind: if you upload your sketch to a different Arduino board you might have to change port and bit number. But you get much higher frequencies:

      byte a = 0x08;
      asm("cli");
      while (true) {
        PORTB = a; 
    // 118:    80 e2   ldi   r24, 0x08  ; 8
    // 11a:    85 b9   out   0x05, r24  ; 1 cycle
        PORTB = 0; 
    // 11c:    15 b8   out   0x05, r1   ;  1 cycle
    // 11e:    fd cf   rjmp  .-6        ; 0x11a  2 cycles
      }                                                    
    

    The comments show the way the compiler translates the code.
    When checked with a high quality frequency counter the result was 3,999,002.25 Hz which equals 16 MHz divided by 4. The instructions in the loop contain exactly 4 cycles.
    If you do not disable interrupts by omitting the asm("cli") instruction you still get 3,973,672 Hz.

    If you look at the duty cycle you will notice it is only 1 by 4: the output is only HIGH for 1 cycle and will be LOW during the other 3 cycles.

    d) You can obtain a duty cycle of 1 by 2 when you have the same amount of cycles with HIGH and LOW outputs. Just use a variable for setting the output pin. In each cycle, use an exclusive-or instruction to invert the output value.

        asm("cli");
    // 118:    f8 94   cli
      byte a = 0;
    // 11a:    80 e0   ldi   r24, 0x00 ; 0
      byte b = 0x08;
    // 11c:   90 e2    ldi   r25, 0x08 ; 8
      while (true) {
        PORTB = a;
    // 11e:    85 b9   out   0x05, r24 ; 1 cycle
        a = a ^ b;
    // 120:    89 27   eor   r24, r25  ; 1 cycle
    // 122:    fd cf   rjmp  .-6       ; 0x11e  2 cycles
      }
    

    The frequency will be only 1,999,650 Hz = 16 MHz / 8.

    e) You may increase the speed slightly by using two PORT instructions. But now its your job to ensure that the same number of cyles will be performed on HIGH and LOW output.

          asm("cli");
    // 118:    f8 94   cli
        byte a = 0; 
    // no code generated; the compiler knows  
    // register r1 always will contain a zero value   
        byte b = 0x08;
    // 11a:    80 e2   ldi   r24, 0x08 ; 8
        PORTB = b;
      while (true) {
        PORTB = a;
    // 11c:    15 b8   out   0x05, r1  ; 1 cycle
        asm("   rjmp 0f \n\t"
            "0:         \n\t");
    // jump to the next lable   (0:)
    // (equal to 2 NOPs but only 1 instruction)
    // 11e:   00 c0    rjmp   .+0      ; 2 cycles
        PORTB = b;
    // 120:    85 b9   out   0x05, r24 ; 1 cycle
    // 122:    fc cf   rjmp  .-8       ; 2 cycles
      }
    //------------------------------------------------
    //                                     6 cycles 
    

    The resulting frequency will be 2,666,210 Hz = 16 MHz / 6.

  5. Library routine tone()
  6. This is the mollycoddle version of ISR.
    The standard function tone() will produce square-wave signals at any pin you want. The frequency range is 31 Hz to 65535 Hz.
    see: File → Examples → 02.Digital : toneKeyboard, toneMelody, toneMultiple, tonePitchFollower.

    Note: the function tone() is using Timer2 of the ATmega328P. Using this function may interfere with other time-oriented functions.

    If you want to listen to the tones you may connect any commercial earphone with 32 Ω-capsules - of course on your own risk.
    The specification won't allow you to connect any resistance that might pull more than 40 mA from a single pin and never connect any coils.


    With chapters 4. and 5. we leave the Arduino concept. But Arduino does not mind - do it on your own risk. When dealing with timers better beware of touching Timer0 (8-bit). It is used by several Arduino functionsand should not be modified.

    All examples given are restricted to Timer1 (16 Bit) and Timer-2 (8 Bit).

    For chapter 4. and 5. two of the special function registers (SFR) are used.


    Data taken from Atmel Documents for ATmega328 .

    Table 14-3. Port B Pins Alternate Functions

    Port Bit Alternate Functions Prozessor Pin
    ATmega328P
    Arduino Pin
    PB7 XTAL2 (Chip Clock oscillator pin 2)
    TOSC2 (Timer oscillator pin 2)
    PCINT7 (Pin Change Interrupt 7)
    10 -
    PB6 XTAL1 (Chip Clock oscillator pin 1 or External clock input)
    TOSC1 (Timer oscillator pin 1)
    PCINT6 (Pin Change Interrupt 6)
    9 -
    PB5 SCK (SPI Bus Master clock Input)
    PCINT5 (Pin Change Interrupt 5)
    19 13
    PB4 MISO (SPI Bus Master Input/Slave output)
    PCINT4 (Pin Change Interrupt 4)
    18 12
    PB3 MOSI (SPI Bus Master output/Slave Input)
    OC2A (Timer/Counter2 output Compare Match A output)
    PCINT3 (Pin Change Interrupt 3)
    17 11
    PB2 SS (SPI Bus Master Slave select)
    OC1B (Timer/Counter1 output Compare Match B output)
    PCINT2 (Pin Change Interrupt 2)
    16 10
    PB1 OC1A (Timer/Counter1 output Compare Match A output)
    PCINT1 (Pin Change Interrupt 1)
    15 9
    PB0 ICP1 (Timer/Counter1 Input Capture Input)
    CLKO (Divided System Clock output)
    PCINT0 (Pin Change Interrupt 0)
    14 8

    Table 14-9. Port D Pins Alternate Functions

    Port Bit Alternate Function Prozessor Pin
    ATmega328P
    Arduino Pin
    PD7 AIN1 (Analog Comparator Negative Input)
    PCINT23 (Pin Change Interrupt 23)
    13 7
    PD6 AIN0 (Analog Comparator Positive Input)
    OC0A (Timer/Counter0 output Compare Match A output)
    PCINT22 (Pin Change Interrupt 22)
    12 6
    PD5 T1 (Timer/Counter 1 External Counter Input)
    OC0B (Timer/Counter0 output Compare Match B output)
    PCINT21 (Pin Change Interrupt 21)
    11 5
    PD4 XCK (USART External Clock Input/output)
    T0 (Timer/Counter 0 External Counter Input)
    PCINT20 (Pin Change Interrupt 20)
    6 4
    PD3 INT1 (External Interrupt 1 Input)
    OC2B (Timer/Counter2 output Compare Match B output)
    PCINT19 (Pin Change Interrupt 19)
    5 3
    PD2 INT0 (External Interrupt 0 Input)
    PCINT18 (Pin Change Interrupt 18)
    4 2
    PD1 TXD (USART output Pin)
    PCINT17 (Pin Change Interrupt 17)
    3 1
    PD0 RXD (USART Input Pin)
    PCINT16 (Pin Change Interrupt 16)
    2 0

  7. With Interrupt-Service-Routine (ISR)
  8. Inside the interrupt service routine you can do whatever you want - provided you are finished before it is called again. (You surely don't want to write reentrant code.)
    What you should not do: call the Serial.println(); function. Most probably this would kill your program.
    Also you are free to select any pin for output. You also can control more than one pin. That might be useful if you want to control stepper motors.

    Activating and deactivating the routine is done by setting or resetting the bits in the registers.

    If you want a very high precision square wave at high frequencies without a jitter you have to disable the Timer0 interrupts which are used for the Arduino internal clock (by setting TIMSK0 = 0). If you still want to know the time you have to count your own interrupts.

    (table showing the mapping of Ardduino-pin# and PORT/bit# ):

      7 6 5 4 3 2 1 0 Register name
    TCCR1A COM1A1 COM1A0 COM1B1 COM1B0 - - WGM11 WGM10 Timer/Counter1 Control Register A
    TCCR1B ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10 Timer/Counter1 Control Register B
    TCCR2A COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20 Timer/Counter2 Control Register A
    TCCR2B FOC2A FOC2B - - WGM22 CS22 CS21 CS20 Timer/Counter2 Control Register B
    TIMSK1 - - ICIE1 - - OCIE1B OCIE1A TOIE1 Timer/Counter1 Interrupt Mask Register
    TIMSK2 - - - - - OCIE2B OCIE2A TOIE2 Timer/Counter2 Interrupt Mask Register

    Timer/Counter-2 Control Register B:

    Most important are the bits 2, 1, and 0. They are explained in the table below:
    CS22 CS21 CS20 Description
    0 0 0 No clock source (Timer/Counter stopped).
    0 0 1 clkT2S/1 (No prescaling)
    0 1 0 clkT2S/8 (From prescaler)
    0 1 1 clkT2S/32 (From prescaler)
    1 0 0 clkT2S/64 (From prescaler)
    1 0 1 clkT2S/128 (From prescaler)
    1 1 0 clkT2S/256 (From prescaler)
    1 1 1 clkT2S/1024 (From prescaler)

    Timer/Counter-1 Interrupt Mask Register:

    Bit 5 ICIE1: Timer/Counter1, Input Capture Interrupt Enable When this bit is written to one, and the I-flag in the Status Register is set (interrupts globally enabled), the Timer/Counter1 Input Capture interrupt is enabled. The corresponding Interrupt Vector is executed when the ICF1 Flag, located in TIFR1, is set.

    Bit 2 OCIE1B: Timer/Counter1, Output Compare B Match Interrupt Enable When this bit is written to one, and the I-flag in the Status Register is set (interrupts globally enabled), the Timer/Counter1 Output Compare B Match interrupt is enabled. The corresponding Interrupt Vector is executed when the OCF1B Flag, located in TIFR1, is set.

    Bit 1 OCIE1A: Timer/Counter1, Output Compare A Match Interrupt Enable When this bit is written to one, and the I-flag in the Status Register is set (interrupts globally enabled), the Timer/Counter1 Output Compare A Match interrupt is enabled. The corresponding Interrupt Vector is executed when the OCF1A Flag, located in TIFR1, is set.

    Bit 0 TOIE1: Timer/Counter1, Overflow Interrupt Enable When this bit is written to one, and the I-flag in the Status Register is set (interrupts globally enabled), the Timer/Counter1 Overflow interrupt is enabled. The corresponding Interrupt Vector is executed when the TOV1 Flag, located in TIFR1, is set.

    Funny: How to trace the copies of your knowledge ...

    a) Example for Timer-1 (16 bit):

    byte LEDPIN = 11; // gives a frequency of 122 Hz on Pin-11
    // to be exact, 16,000,000 / 2^16 / 2 = 122.0703
     
    void setup() {
      pinMode(LEDPIN, OUTPUT);
      cli();      // disable global interrupts
      TCCR1A = 0; // set TCCR1A to zero
      TCCR1B = 0; // set TCCR1B to zero
      TIMSK1 = 1; // enable Timer1 overflow interrupt
      TCCR1B = 1; // set CS10 bit so Timer1 will run with clock speed
      sei();      // enable global interrupts 
    }
    
    ISR(TIMER1_OVF_vect) {
      static boolean b;
      digitalWrite(LEDPIN, b);
      b = !b; 
    }
    
    void loop() {
    }
    

    b) Example for Timer-2 (8 Bit) with Overflow-Interrupt:

    byte ledPin = 11;  // frequency on Pin-11: 977 Hz. 
    // 16,000,000 / 2^14 = 976.5625 
    void setup() {
      pinMode(ledPin,OUTPUT);
      TCCR2A = 0; // Timer clock = 16MHz/8 = 2Mhz or 0.5 μs
      TCCR2B = 3; // Timer2 Settings: Timer Prescaler /32, mode 0
      TIMSK2 = 1; // Timer2 Overflow Interrupt Enable
    }
    
    ISR(TIMER2_OVF_vect) { // Timer 2 interrupt service routine 
      static boolean b;
      digitalWrite(ledPin,b);
      b = !b;  
    }
    
    void loop() {
    }
    

    Other values for TCCR2B
    (see also above CS22 CS21 CS20):       
    TCCR2B Prescaler frequency / Hz
    1 1 31260
    2 8 3907
    3 32 977
    4 64 489
    5 128 244
    6 256 122
    7 1024 31

    c) same as example b), but frequency 78,134 Hz

    const byte tcnt2 = 176;    // 78,134 Hz 
    // two pins are used in this example
    // this gives you the opportunity to do some amplitude
    // modulation in the loop by modifying their pinModes
    const byte pin1  =  11;      
    const byte pin2  =  12;   
    const byte mask = 0x18;        // Port B, Bits 3 and 4
    volatile byte value;
    
    void setup() {
      pinMode(pin1, OUTPUT);
      pinMode(pin2, OUTPUT); 
      //Setup Timer2
      TCCR2B = 0x00;        // Disable Timer2 while we set it up
      TCNT2  = tcnt2;       // Reset Timer Count 
      TIFR2  = 0x00;        // Timer2 INT Flag Reg: Clear Timer Overflow Flag
      TIMSK2 = 0x01;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
      TCCR2A = 0x00;        // Timer2 Control Reg A: Wave Gen Mode normal
      TCCR2B = 0x01;        // Timer2 Control Reg B: Timer Prescaler set to 1
    }
    
    ISR(TIMER2_OVF_vect) {        
      static int count;
      // the position of the following instruction within the ISR is absolutely critical
      TCNT2 = tcnt2;           // Reset Timer 
      
      // beware of spending too much time in here 
      
      PORTB = value;
      value = value ^ mask;
      TIFR2 = 0x00;            // Timer2 INT Flag Reg: Clear Timer Overflow Flag
    };
    
    void loop() {
      //
    }
    

    d) same as example b), but at highest possible frequency
    (If your microcontroller sits on a breadboard you might easily replace the quartz with a 20 MHz version to obtain even 216,853 Hz.
    But keep in mind that your serial data rate will be affected.)

    // gives a frequency of 173,483 Hz (with 16 MHz quartz)
    const byte pin = 11;    // on the ATmega328P Port B, Bit 3
    const byte tcnt2 = 236; // higher values won't get higher frequencies
    
    ISR(TIMER2_OVF_vect) {               
      static byte value;
      const byte mask = 0x08;
      PORTB = value;
      value = value ^ mask;
      TCNT2 = tcnt2;           // Reset Timer 
    };
    
    void setup() {
      pinMode(pin, OUTPUT);
      //Setup Timer2
      TCCR2B = 0x00;        // Disable Timer2 while we set it up
      TCNT2  = tcnt2;       // Reset Timer Count 
      TIFR2  = 0x00;        // Timer2 INT Flag Reg: Clear Timer Overflow Flag
      TIMSK2 = 0x01;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
      TCCR2A = 0x00;        // Timer2 Control Reg A: Wave Gen Mode normal
      TCCR2B = 0x01;        // Timer2 Control Reg B: Timer Prescaler set to 1
    }
    
    void loop() {
    }
    

    To make this high frequency possible it is absolutely necessary to replace the digitalWrite-command by the appropriate PORT instruction.
    In the Arduino documentation you will find that PIN-11 is mapped to bit#3 of PORT B:
    PORTB = value;
    value = value ^ 0x08;
    (the character "^" means exklusive-or).

    If you really want high frequencies for your timer ISR and set the prescaler to low values like 1, 2 or 3 the ISR cannot work as it is meant to do. Check this out, setting different values for ps:

    const byte mask = B00010000;   // Port B, bit 3
    const byte pin = 11;     
    byte tcnt2; 
    byte ps = 2;          
    
    void setTimer(byte prescaler) {
      TCCR2A = 0;  
      TCCR2B = prescaler;  
      tcnt2 = 256 - 125;  
      TCNT2 = tcnt2;
      TIMSK2 = 1;       
    }
    
    void setup() { 
      DDRB = mask;
      setTimer(ps);
    }
    
    ISR(TIMER2_OVF_vect) {
      TCNT2 = tcnt2;  
      PORTB ^= mask; 
    }
    
    void loop() { } 
    

    As you can see from the results at higher frequencies the time intervals between two successive ISR calls get longer than they should (TCNT2 = 131):

    TCCR2B 1 2 3 4 5 6 7
    prescaler 1 8 32 64 128 256 1024
    expected frequency (Hz) 64000 8000 2000 1000 500 250 62.5
    measured frequency (Hz) 53919 7848 1998 999 500 250 62

    Have a look at the machine code generated by the gcc compiler. When the overflow occurs (and interrupts are enabled) the ISR will be called. In order to start a new timer interval as soon as possible, that means immediately the TCNT2 register should be reloaded. But as you can see, a lot of housekeeping has to be done before: eight instructions are being carried out before the Timer/Counter register is reloaded. So precious time is wasted and the new timer interval is started too late.

    =====> ISR(TIMER2_OVF_vect)
    000000d8 <__vector_9>:
      d8:	1f 92       	push	r1		; what for - it is not needed
      da:	0f 92       	push	r0		; housekeeping: save whatever gets destroyed
      dc:	0f b6       	in	r0, 0x3f	; saving status register, accu flags
      de:	0f 92       	push	r0
      e0:	11 24       	eor	r1, r1		; what for it is not needed
      e2:	8f 93       	push	r24
      e4:	9f 93       	push	r25
      e6:	80 91 02 01 	lds	r24, 0x0102	; tcnt2
      ea:	80 93 b2 00 	sts	0x00B2, r24             TCNT2
      ee:	85 b1       	in	r24, 0x05	; 5      PORTB
      f0:	90 e1       	ldi	r25, 0x08	; 8  =  00001000
      f2:	89 27       	eor	r24, r25                     toggle
      f4:	85 b9       	out	0x05, r24	; 5      PORTB
      f6:	9f 91       	pop	r25                             now restore everything
      f8:	8f 91       	pop	r24
      fa:	0f 90       	pop	r0
      fc:	0f be       	out	0x3f, r0	; restoring status register
      fe:	0f 90       	pop	r0
     100:	1f 90       	pop	r1		; what for - it is not needed
     102:	18 95       	reti
    

    So, if you need higher frequencies, you might go for the Arduino DUE which comes with an 84 MHz CPU but be aware that there are a lot of differences to the "small" Arduinos.


    e) Example for Timer-2 (8 Bit) with Compare-Interrupt:

    
    double freq = 100;  // the desired frequency
    
    byte pin = 11;
    
    ISR(TIMER2_COMPA_vect) {
      static boolean b;
      digitalWrite(pin, b = !b);
    }
    
    void setInterrupt(long f) {
      const int prescaler[] = {0, 1, 8, 32, 64, 128, 256, 1024};
      byte mode = 7;
      byte ocr2a = F_CPU / prescaler[mode] / f / 2; 
      TCNT2  = 0;
      TCCR2A = B00000010;   // Configure timer 2 for CTC mode
      TCCR2B = mode;        // Start timer at Fcpu / prescaler
      TIMSK2 = B00000010;   // Enable CTC interrupt
      OCR2A  = ocr2a;       // Set CTC compare value 
      sei();                // Enable global interrupts
    }
    
    void setup() {
      pinMode(pin, OUTPUT);
      setInterrupt(freq);
    }
    
    void loop() {
    }
    




    f) Another example for Timer-2 (8 Bit) with Overflow-Interrupt:

    This will produce an extremely short needle pulse every 1 millisecond.

    byte a;
    //Timer2 Overflow Interrupt Vector, called every 1ms
    ISR(TIMER2_OVF_vect) {
      PORTB = B00001000;     // Arduino UNO PIN 11 = PORT B, bit# 3
      a++;
      PORTB = B00000000;
      TCNT2 = 131;           //Reset Timer to 131 out of 255
    }
    
    void setup() {
      DDRB = B00001000;
      TCCR2A = 0x00;  // Timer2 Control Reg A: Wave Gen Mode normal
      TCCR2B = 0x05;  // Timer2 Control Reg B: Timer Prescaler set to 128
      TIMSK2 = 0x01;  // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
    }
    
    void loop() {
    }
    

    The a++ was inserted only to make the signal visible on a 10 MHz oscilloscope. With this command, the needle pulses are about 0.3 μs long. Without, it cannot be seen clearly.




    The lowest possible frequency is 30.6 Hz (16,000,000 / 1024 / 2 = 255. If you wanted a lower frequency a value of more than 255 was needed for the OCR2A register what obviously is impossible).

    This table below shows all possible frequencies that can achieved with the prescaler 1024. The value of the OCR2A (shown as a HEX number) register is cut into the high nibble (top row of the table) and the low nibble (left column of the table). To get the OCR2A value, you have to add the high value and the low value. Example on how to read the table: look for the column with "40" on top. Then look for the row with "E" at the left. The value of the OCR2A register will be 0x4E = 78. The resulting frequency will be 100.2 Hz.


    00 10 20 30 40 50 60 70 80 90 A0 B0 C0 D0 E0 F0
    0 -/- 488.3 244.1 162.8 122.1 97.7 81.4 69.8 61.0 54.3 48.8 44.4 40.7 37.6 34.9 32.6
    1 7812.5 459.6 236.7 159.4 120.2 96.5 80.5 69.1 60.6 53.9 48.5 44.1 40.5 37.4 34.7 32.4
    2 3906.3 434.0 229.8 156.3 118.4 95.3 79.7 68.5 60.1 53.5 48.2 43.9 40.3 37.2 34.6 32.3
    3 2604.2 411.2 223.2 153.2 116.6 94.1 78.9 67.9 59.6 53.1 47.9 43.6 40.1 37.0 34.4 32.2
    4 1953.1 390.6 217.0 150.2 114.9 93.0 78.1 67.3 59.2 52.8 47.6 43.4 39.9 36.9 34.3 32.0
    5 1562.5 372.0 211.1 147.4 113.2 91.9 77.4 66.8 58.7 52.4 47.3 43.2 39.7 36.7 34.1 31.9
    6 1302.1 355.1 205.6 144.7 111.6 90.8 76.6 66.2 58.3 52.1 47.1 42.9 39.5 36.5 34.0 31.8
    7 1116.1 339.7 200.3 142.0 110.0 89.8 75.8 65.7 57.9 51.7 46.8 42.7 39.3 36.3 33.8 31.6
    8 976.6 325.5 195.3 139.5 108.5 88.8 75.1 65.1 57.4 51.4 46.5 42.5 39.1 36.2 33.7 31.5
    9 868.1 312.5 190.5 137.1 107.0 87.8 74.4 64.6 57.0 51.1 46.2 42.2 38.9 36.0 33.5 31.4
    A 781.3 300.5 186.0 134.7 105.6 86.8 73.7 64.0 56.6 50.7 46.0 42.0 38.7 35.8 33.4 31.3
    B 710.2 289.4 181.7 132.4 104.2 85.9 73.0 63.5 56.2 50.4 45.7 41.8 38.5 35.7 33.2 31.1
    C 651.0 279.0 177.6 130.2 102.8 84.9 72.3 63.0 55.8 50.1 45.4 41.6 38.3 35.5 33.1 31.0
    D 601.0 269.4 173.6 128.1 101.5 84.0 71.7 62.5 55.4 49.8 45.2 41.3 38.1 35.4 33.0 30.9
    E 558.0 260.4 169.8 126.0 100.2 83.1 71.0 62.0 55.0 49.4 44.9 41.1 37.9 35.2 32.8 30.8
    F 520.8 252.0 166.2 124.0 98.9 82.2 70.4 61.5 54.6 49.1 44.6 40.9 37.7 35.0 32.7 30.6

  9. By using hardware timer
  10. As you find in "14.3.1 Alternate Functions" of the Atmel manual certain bits of PORT B and D can be controlled by timers even without any code - you not even need an interrupt service routine. So, toggling of those bits is performed in no time. You just have to set the appropriate bits in the control registers. Having a very complex controller, this turns out to be a bit complicated. Also the selection of the output pins is restricted.

    Example for Timer-2:

    void setup() {
      Serial.begin(9600);
      Serial.println(__FILE__);
      // generate 1 MHz signal at outpin:
      TCCR2A = B01000010;
      TCCR2B = B00000010; // prescaler: /8
    }
    
    void loop() {
      const byte outpin  = 11;
      // Modulation 500 Hz:
      pinMode(outpin, OUTPUT);
      delay(1);
      pinMode(outpin, INPUT);
      delay(1);
    }
    

    Warning:
    If you upload this sketch and insert some wire in Pin-11 you will hear a clear
    500 Hz tone in a medium wave receiver placed near by and tuned to 1.000 MHz.
    So, if you don't want to end up in prison don't do this.

    Another example for Timer-2:

    // in this example we are using Timer/Counter 2 (TCNT2)
    byte outpin = 11; // this is Pin 17 of the ATMega328P (PB3, MOSI/OC2A/PCINT3)
    
    void setup () {
    
      TCCR2B = B00000001;            
      // Bits 2 1 0 = 0 0 1 : no prescaling, divide by 1 
      // other prescalers: 8, 32, 64, 128, 256, 1024
    
      TCCR2A = B01000010;  
      // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
      // | 0 | 1 | 0 | 0 | - | - | - | - | : Toggle OC2A on Compare Match. see (*)
    
      // | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
      // | - | - | - | - | 0 | 0 | 1 | 0 | : Clear Timer on Compare Match
      
      OCR2A  = 255;
      // each time when Timer2 get 255 the selected output gets inverted
      // frequency = 16,000,000 / 2 / (OCR2A + 1)
      // OCR2A 255 gives 31,250 Hz, smaller values get higher frequencies
     
      pinMode(outpin, OUTPUT);       // set direction to OUTPUT   
    }
    
    void loop () {
    }
    

    (*) if you configure Timer/Counter Control Register A (TCCR2A) to

         | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
         | 0 | 0 | 1 | 0 | - | - | - | - |
    
    the output (PCINT19/OC2B/INT1) PD3, CPU-Pin-5, Arduino-pin-3 will be toggled. In this case, the variable outpin has to be set to 3.

    To make calculations easy the Arduino system provides the constant F_CPU. If you select Arduino UNO in Tools -> Board, its value will be 16,000,000.

    The table below gives some examples of prescale values and OCR2A values and the resulting frequencies.

      Prescale
    OCR2A 1 8 32 64 128 256 1024
    0 8000000.00 1000000.00 250000.00 125000.00 62500.00 31250.00 7812.50
    1 4000000.00 500000.00 125000.00 62500.00 31250.00 15625.00 3906.25
    3 2000000.00 250000.00 62500.00 31250.00 15625.00 7812.50 1953.13
    7 1000000.00 125000.00 31250.00 15625.00 7812.50 3906.25 976.56
    15 500000.00 62500.00 15625.00 7812.50 3906.25 1953.13 488.28
    31 250000.00 31250.00 7812.50 3906.25 1953.13 976.56 244.14
    63 125000.00 15625.00 3906.25 1953.13 976.56 488.28 122.07
    127 62500.00 7812.50 1953.13 976.56 488.28 244.14 61.04
    255 31250.00 3906.25 976.56 488.28 244.14 122.07 30.52

Mapping Pin# - PORT/Bit#, for the Arduino-UNO

(Other Arduinos: see the hardware documentations)




contact: nji(at)gmx.de