4

I have this code:

void setup() {
  pinMode(11, OUTPUT);
  TCCR2B = 0;
  TCNT2 = 0;
  TCCR2A = _BV(WGM21);
  TCCR2A |= _BV(COM2A0);
  OCR2A = 249;
}

void loop() {
  soundBuzzer();
}

void soundBuzzer() {
  TCCR2B |= _BV(CS21);
}

void silenceBuzzer() {
  TCCR2B &= ~_BV(CS21);
  TCNT2 = 0;
}

(Got it from here).

And I have an ultrasonic water atomizer that works in a frequency of 1.7 Mhz. Writing another number instead of 249 in the line "OCR2A = 249;", the frequency generated changes. With 249 it generates a frequency of 4 Khz. So, my question is: Which number do I have to write to get approximately 1.7 Mhz?

5
  • You can only divide the clock by an integer, so with a 16 MHz clock the closest you can achieve is 2 MHz. The data sheet will show you how to find the divider ratio register setting (specifically, if the division will be one more than the loaded count, and if there is a prescaler upstream of what you get to divide). If there is no perscaler it would seem like switching the board to a 12 MHz crystal and dividing by 7 might be a fit, but you'll need a custom bootloader. Commented Apr 27, 2017 at 19:13
  • More realistically, to operate this usefully you'll need the power and voltage amplifier from the intended driver circuit, so why not just take the entire original driver circuit, frequency generator and all, and figure out a way to enable/disable it from the Arduino? Do take appropriate precautions for the voltages which it may generate! Commented Apr 27, 2017 at 19:14
  • 5
    You cannot generate 1.7 MHz on an Uno. The closest you can get is 16 MHz / 9 = 1.778 MHz. Commented Apr 27, 2017 at 19:21
  • You need to set the CS20 bit, not CS21. That way you have no prescaler. Then have it count to 9, like Edgar calculated. So set OCR2A to 8.
    – Gerben
    Commented Apr 27, 2017 at 19:43
  • 1
    Wow, how did I miss 16/9? That's not much worse than 12/7, for a lot less trouble. Commented Apr 28, 2017 at 2:00

1 Answer 1

3

Expanding from my comment... any signal level generated by this kind of microcontroller can only last for an integer number of CPU cycles. In order to generate it, you first have to compute the timing characteristics of the desired signal in terms of CPU cycles:

period = 16 MHz ÷ 1.7 MHz = 9.412 cycles

The closest you can get is 9 cycles, which gives the frequency

frequency = 16 MHz ÷ 9 = 1.778 MHz

Assuming this is acceptably close to your target, you can configure a timer to do it. The example code you provided uses Timer 2 with the OC2A output set to toggle mode. This approach can only give periods which are an even number of cycles, thus I am using the regular “non-inverting fast PWM” mode instead in the solution below. In this mode, the pin OC2A cannot be used with a controlled frequency, then I changed it to OC2B (pin 3 on the Uno). Here is the code:

const uint8_t OUTPUT_PIN = 3;  // = OC2B
const uint8_t PERIOD = 9;      // 9 CPU cycles ~ 1.778 MHz

void setup()
{
    pinMode(OUTPUT_PIN, OUTPUT);
    TCCR2B = 0;           // stop timer
    TCNT2  = 0;           // reset timer
    TCCR2A = _BV(COM2B1)  // non-inverting PWM on OC2B
           | _BV(WGM20)   // fast PWM mode, TOP = OCR2A
           | _BV(WGM21);  // ...ditto
    TCCR2B = _BV(WGM22);  // ...ditto
    OCR2A = PERIOD - 1;
    OCR2B = PERIOD/2 - 1;
}

void soundBuzzer() {
    TCCR2B |= _BV(CS20);  // F_CPU / 1
}

void silenceBuzzer() {
    TCCR2B &= ~_BV(CS20);
    TCNT2 = 0;
}

The generated signal is slightly asymmetric: it stays HIGH during 4 cycles and LOW for 5 cycles. If you want a symmetric signal (50% duty cycle), you can set PERIOD to 10 (5 cycles HIGH and 5 cycles LOW), but then the frequency drops to 1.6 MHz.

Edit: You could also replace your Arduino Uno by a cheap ATtiny85 microcontroller, or maybe an Arduino-compatible dev board based on it, like the Trinket or the Digispark.

This microcontroller has a built-in PLL which allows it to run one of its timers at 64 MHz, even though the CPU is running much slower. A 64 MHz timer allows you to hit the target 1.7 MHz frequency to within 1%:

64 MHz ÷ 38 = 1.684 MHz
3
  • Ditto for SAMD21-based Arduinos; a built-in PLL lets you run a timer at 96MHz.
    – ocrdu
    Commented Oct 16, 2020 at 14:10
  • That’s a great answer, thank you, and I could follow the process in the ATmega328P datasheet. However, I don’t understand why it’s not possible to output the frequency on the OC2A pin. Indeed, there seems to be the same mode for OC2A in table 17-3: Clear OC2A on compare match, set OC2A at BOTTOM, (non-inverting mode). I tried and it doesn’t work, as you said, but I can’t find the part of the docs that say why. Commented Apr 26, 2022 at 13:18
  • 1
    @Olivier'Ölbaum'Scherler: In this mode (Waveform Generation Mode 7) the register OCR2A is used to set the pulse period (via the “TOP” value). You cannot use the same register to set the pulse length. If you set the pulse length with OCR2B, then the PWM signal can only be output on pin OC2B. Commented Apr 26, 2022 at 19:12

Your Answer

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

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