8
\$\begingroup\$

I need to generate three square waves: 40kHz, 60kHz, and 77.5kHz.

40 and 60 are easy enough, but the only common multipliers with 77.5 don't seem to be widely available as oscillators. About the closest I could find was 26.041MHz, which is slightly off the ideal 26.04MHz.

These are going to be used for radio transmissions over a very short distance, so the 0.001MHz deviation may not be an issue as I've had it working reasonably well with a relatively poor frequency lock. I'd like to be more precise if possible though.

Is there any way to do this with a single micro? My backup plan is to use two, each with a different crystal.

\$\endgroup\$
3
  • \$\begingroup\$ That's a good idea. I can calibrate it slightly. \$\endgroup\$ Commented Nov 24 at 20:48
  • 8
    \$\begingroup\$ In case this helps, it appears the LCM of those frequencies is 3.72MHz, with dividers of 93 (40kHz), 62 (60kHz) and 48 (77.5kHz). eg, the OP's 26.04MHz is the 7 x 3.72MHz. \$\endgroup\$ Commented Nov 24 at 21:27
  • \$\begingroup\$ 0.001MHz is 1kHz which is actually quite a big error on 40kHz. With a standard 1$ STM32 MCU and an external crystal you can drive multiple internal timers at about 48MHz with no problems at all, which should get you way better accuracy. I don't see the issue here, or am I missing something? What accuracy/jitter do you actually need? \$\endgroup\$ Commented Nov 26 at 8:17

7 Answers 7

13
\$\begingroup\$

Here's another solution using the MCU hardware (STM32F411 used in inexpensive 'Black Pill' hobbyist modules):

enter image description here

By using a 16MHz crystal or oscillator and dividing by 10 and multiplying by 93 (then divide by 2) we can get an exact 74.4MHz HCLK, which is:

77.5kHz * 960

60.0kHz * 1240

40.0kHz * 1860

\$\endgroup\$
5
  • \$\begingroup\$ How to know the accuracy of this though? Are there external PLL filter passives or is all of it on-chip? \$\endgroup\$ Commented Nov 25 at 8:02
  • 2
    \$\begingroup\$ @Lundlin There are no external parts for the PLL to work, and it is verified to be within the range that the PLL designed for. The accuracy should be as good as the 16MHz input signal but there will be some phase noise and if the 16MHz changes suddenly it will take a bit of time to catch up. A PLL can clean up a dirty input signal since it's effectively low-pass filtering the frequency. \$\endgroup\$ Commented Nov 25 at 8:11
  • \$\begingroup\$ Ok. Some MCUs offer the ability to add external passives for PLL tuning so you can tweak between faster locking or higher accuracy. \$\endgroup\$ Commented Nov 25 at 8:34
  • 1
    \$\begingroup\$ Thanks. This seems like the best answer so far, as it produces precisely the required frequencies. Highly accurate and stable 16MHz and 8MHz oscillators are widely and cheaply available. \$\endgroup\$ Commented Nov 25 at 10:21
  • \$\begingroup\$ @user Yep, go for TCXO or better. \$\endgroup\$ Commented Nov 26 at 8:58
15
\$\begingroup\$

These are going to be used for radio transmissions over a very short distance, so the 0.001MHz deviation may not be an issue as I've had it working reasonably well with a relatively poor frequency lock.

The distance doesn't matter; it's also not the absolute frequency, but how much of an error factor it is:

$$\frac{26.041}{26.040} \approx 38\cdot 10^{-6}$$

A frequency error 38 ppm – not sure your quartz oscillators are even that accurate!

And: every full oscillation, your period is off 38 of a million – if your receiver's clock recovery can't deal with that, I'm not sure what it could deal with, at all. Think about it: how many 77.5 kHz-oscillations would have had to pass before an error of 38·10⁻⁶ becomes a problem? Probably on the order of 10⁷ oscillations! That will take multiple minutes to happen. How long is a single transmission? Probably: On the order of milliseconds! So, the clock offset won't meaningfully affect your receiver.

So, I wouldn't sweat it; if you can get a 26.041 MHz oscillator, that's going to be more than good enough.

However, there's also an easy way out here that only needs a much easier to get multiple: generate

  • 40.0 kHz,
  • 60.0 kHz, and a
  • (77.5 - 40.0) kHz = 37.5 kHz

square wave, and combine the 40.0 kHz with 37.5 kHz using an XOR gate; analog passive band-pass filter around 77.5 kHz gives you a 77.5 kHz tone.


There's a host of other methods.

  • You could use your microcontroller's DAC (or smoothed PWM output) as control for a VCO and simply build a digital PLL. Use a timer to count the number of oscillations that VCO went through in a defined time. If it's above what would be right at 77.5 kHz, increase control voltage, if it's below, reduce
  • Instead of two MCUs, you could also use one that has a DAC. Generate a 77.5 kHz sine wave in your MCU and output it via DAC. Between straightforward approximation of \$\sin\$ in software (e.g. with a Taylor series), classical DDS (i.e., large lookup tables), there's a lot of options to calculates sines. The reconstruction filter any DAC would need anyways (at or below half the sample rate) would "interpolate" the sine wave to be nice and precisely 77.5 kHz at relatively low jitter (essentially, only limited by the bitdepth of your DAC). If you need a square instead of a sine wave, a simple comparator can be used for that conversion.
  • 77.5 kHz is very very low frequency for a microcontroller which usually runs in the dozens of MHz. Set up your timer to toggle an output with a period slightly below 1/(77.5 kHz), at 1/((77.5 - a) kHz). Also make it trigger a function where you calculate whether the next period should instead be slightly above 1/(77.5 kHz), at 1/((77.5 + b) kHz), such that both periods are integer multiples of your available clock. For example, if your timer clock is 48 MHz, then a 77.5 kHz clock would have a halfperiod of approximately 309.677419 clocks. You can set the timer to count up to 309 initially. Once it triggers, you have around 308 clock cycles leeway to set the correct next clock, which would be 310 clock cycles. How often you need to run with 309 cycles and 310 cycles is given by the fractional part of the clock cycles (here, 0.677419). This is effectively dithering the frequency generated.
\$\endgroup\$
2
  • \$\begingroup\$ Thanks. I like the idea of mixing two frequencies. I think some of the STM32 parts might even be able to do that internally with two timers/PWM channels. \$\endgroup\$ Commented Nov 25 at 10:14
  • \$\begingroup\$ if you have an STM32, you can just directly generate the clocks you need with multipliers, so the external mixing is pointless. See the answers by Spehro and pipe. I was assuming you had no such flexible microcontroller. \$\endgroup\$ Commented Nov 25 at 11:08
9
\$\begingroup\$

Microcontroller with a PLL

There isn’t any need to reinvent the wheel. Use a microcontroller with a good clock system and capable timers. There are a number of microcontrollers with a real PLL to set the internal clock frequency. Combined with a fractionally dividing timer, you can likely get the exact frequencies.

My go-to microcontroller now is the Raspberry Pi Pico. You can just program the base frequency for the timers to an exact multiplier of whatever frequency you want. For example, 111.6 MHz works for all three, and it can be had using the standard 12 MHz oscillator.

Fractional divider

If that wouldn't have worked, the PWM timers have a 4-bit fractional divider, and the PIOs each has an 8-bit fractional divider to get really close to the target by trading a small frequency deviation for a small amount of jitter.

I'm sure you can find similar features for your favourite architecture.

\$\endgroup\$
3
\$\begingroup\$

The ESP32 microcontroller has a hardware PCM module driven by an 80MHz clock. This was originally intended for providing PWM signals to control LED brightness and as a result is called LEDC, but it is just a rectangular wave generator and could be used to generate a square wave 77.5kHz signal with an accuracy of about 0.001kHz.

The number of LEDC channels depends on the variant of the ESP32 but all of them have at least three channels and could generate the three frequencies you need. And since this is a hardware module controlled by the processor it is not affected by interrupts or any of the other things the CPU might get up to.

ESP32 dev boards are only a few dollars and are easy to program so this seems a simple solution. Since I found myself with some spare time I put together a quick program to generate the three frequencies you need:

//----------------------------------------------------------------------
// Use LEDC to generate:
// - 40kHz square wave on GPIO 5
// - 60kHz square wave on GPIO 6
// - 77.5kHz square wave on GPIO 7
// This is tested on the C3, C6, S3 and original ESP32
//----------------------------------------------------------------------
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "esp_log.h"

const char* TAG = "LEDC";

// Just to be annoying the original ESP32 works slightly differently
// from the later ones.
#if CONFIG_IDF_TARGET_ESP32
#define XTAL_CLK_FREQ 80000000
#define SPEED_MODE LEDC_HIGH_SPEED_MODE
#define CLK_CFG    LEDC_USE_APB_CLK
#else
#define SPEED_MODE LEDC_LOW_SPEED_MODE
#define CLK_CFG    LEDC_USE_XTAL_CLK
#endif

// Set the LEDC frequency
bool set_freq(uint32_t Frequency, ledc_timer_t Timer, ledc_channel_t Channel, int Pin) {
  // The duty cycle resolution is the number of bits needed for the clock divider, but it cannot exceed 20 bits.
  int clk_divider = (XTAL_CLK_FREQ/Frequency) >> 2;
  int duty_res_bits = 1;
  while (clk_divider > 0 && duty_res_bits < 20) {
    clk_divider = clk_divider >> 1;
    duty_res_bits++;
  }

  // Configure the timer
  esp_err_t e;
  ledc_timer_config_t t = {0};
  t.speed_mode = SPEED_MODE;
  t.duty_resolution = duty_res_bits;
  t.timer_num = Timer;
  t.freq_hz = Frequency;
  t.clk_cfg = CLK_CFG;
  if ((e = ledc_timer_config(&t)) != ESP_OK) {
    ESP_LOGE(TAG, "ledc_timer_config returned %s", esp_err_to_name(e));
    return false;
  }

  // Start the LEDC signal
  ledc_channel_config_t c = {0};
  c.gpio_num   = Pin;
  c.speed_mode = SPEED_MODE;
  c.channel    = Channel;
  c.duty       = 1 << (duty_res_bits-1);
  c.timer_sel  = Timer;
  if ((e = ledc_channel_config(&c)) != ESP_OK) {
    ESP_LOGE(TAG, "ledc_channel_config returned %s", esp_err_to_name(e));
    return false;
  }

  // Return indicating success
  return true;
}

void app_main(void) {
  ESP_LOGI(TAG, "Configuring LEDC outputs");
  set_freq(40000, LEDC_TIMER_0, LEDC_CHANNEL_0, GPIO_NUM_5);
  set_freq(60000, LEDC_TIMER_1, LEDC_CHANNEL_1, GPIO_NUM_6);
  set_freq(77500, LEDC_TIMER_2, LEDC_CHANNEL_2, GPIO_NUM_7);

  // Suspend the task and let LEDC get on with it
  ESP_LOGI(TAG, "Suspending main task");
  vTaskSuspend(0);
}
\$\endgroup\$
2
\$\begingroup\$

If we make the assumption that off the shelf oscillators that are close to exact multiples of 77.5KHz do exist, then this just boils down to a problem of searching distributor inventory.

So, I put in a query into Digikey that said "Give me a list of the 500 cheapest oscillators with values > 10MHz". Doing a bit of spreadsheet math on that list gives the following result.

OSC (Hz)  | COUNT  | F(Hz)       |  ERRROR(PPM) | COST   | PART NO
10000000  |  129   | 77519.38    |  250         | $0.645 | SG-210STF 10.0000ML5
11059200  |  143   | 77337.063   |  2102        | $0.733 | 12.94422
11289600  |  146   | 77326.027   |  2245        | $0.889 | S73305T-11.2896-X-30-R
12000000  |  155   | 77419.355   |  1041        | $0.586 | SG-210STF 12.0000ML5
12272700  |  158   | 77675.316   |  2262        | $0.87  | ASE2-12.2727MHZ-L-C-T
12288000  |  159   | 77283.019   |  2800        | $0.642 | SG-210STF 12.2880ML0
12500000  |  161   | 77639.752   |  1803        | $0.87  | ASE-12.500MHZ-E-T
13000000  |  168   | 77380.952   |  1536        | $0.966 | KC2016Z13.0000C1KX00
13330000  |  172   | 77500       |  0           | $0.909 | ASDDV-13.330MHZ-LC-T
13560000  |  175   | 77485.714   |  184         | $0.966 | KC2520Z13.5600C1KX00
14318180  |  185   | 77395.568   |  1348        | $0.733 | 12.95094
14745600  |  190   | 77608.421   |  1399        | $0.733 | 12.90311
16000000  |  206   | 77669.903   |  2192        | $0.633 | SG-210STF 16.0000ML0
16384000  |  211   | 77649.289   |  1926        | $0.966 | KC2520Z16.3840C1KX00
18432000  |  238   | 77445.378   |  705         | $0.87  | ASE-18.432MHZ-L-C-T
19200000  |  248   | 77419.355   |  1041        | $0.733 | TG2016SMN 19.2000M-MCGNNM0
20000000  |  258   | 77519.38    |  250         | $0.733 | 12.90322
22000000  |  284   | 77464.789   |  454         | $0.87  | ASE-22.000MHZ-E-T
22118400  |  285   | 77608.421   |  1399        | $0.733 | 12.92146
22579200  |  291   | 77591.753   |  1184        | $0.966 | KC2520Z22.5792C1KX00
24000000  |  310   | 77419.355   |  1041        | $0.57  | OSC24M-3.3I/S3
24576000  |  317   | 77526.814   |  346         | $0.831 | CO4305-24.576-EXT-T-TR
25000000  |  323   | 77399.381   |  1298        | $0.573 | SG-210STF 25.0000ML5
26000000  |  335   | 77611.94    |  1444        | $0.638 | SG-210STF 26.0000ML0
27000000  |  348   | 77586.207   |  1112        | $0.69  | OSC27M-3.3I/S5-25T
27120000  |  350   | 77485.714   |  184         | $0.733 | 12.95538
28636300  |  370   | 77395.405   |  1350        | $0.773 | S73305T-28.6363-15-R
28636400  |  370   | 77395.676   |  1346        | $0.966 | KC2016Z28.6364C1KX00
30000000  |  387   | 77519.38    |  250         | $0.634 | TG2520SMN 30.0000M-MCGNNM0
30720000  |  396   | 77575.758   |  978         | $0.966 | KC3225Z30.7200C1KX00
32000000  |  413   | 77481.84    |  234         | $0.644 | SG-210STF 32.0000ML0
32768000  |  423   | 77465.721   |  442         | $0.966 | KC2520Z32.7680C1KX00
33000000  |  426   | 77464.789   |  454         | $0.63  | OSC33M-3.32/S22-25T
33330000  |  430   | 77511.628   |  150         | $0.947 | SG-8018CE 33.3300M-TJHPA3
33333000  |  430   | 77518.605   |  240         | $0.87  | ASE-33.333MHZ-E-T
33333300  |  430   | 77519.302   |  249         | $0.87  | ASE3-33.3333MHZ-L-C-T
38400000  |  495   | 77575.758   |  978         | $0.741 | TG2016SMN 38.4000M-ECGNNM0
40000000  |  516   | 77519.38    |  250         | $0.634 | TG2520SMN 40.0000M-MCGNNM0
44000000  |  568   | 77464.789   |  454         | $0.678 | ASE-44.000MHZ-L-C-T
48000000  |  619   | 77544.426   |  573         | $0.54  | OSC48M-3.3I/S3-20T
49152000  |  634   | 77526.814   |  346         | $0.87  | ECS-3225MV-491.52-CN-TR
50000000  |  645   | 77519.38    |  250         | $0.654 | SG3225CAN 50.0000M-TJGA6
54000000  |  697   | 77474.892   |  324         | $0.966 | CO2520-54.000-2.5-20-T-TR-NS1
60000000  |  774   | 77519.38    |  250         | $0.966 | KC3225Z60.0000C1KX00
65000000  |  839   | 77473.182   |  346         | $0.733 | 12.92106
66666600  |  860   | 77519.302   |  249         | $0.946 | DSC1001CI2-066.6666B
66667000  |  860   | 77519.767   |  255         | $0.966 | KC2520Z66.6667C1KX00
75000000  |  968   | 77479.339   |  267         | $0.966 | KC3225Z75.0000C1KX00
76800000  |  991   | 77497.477   |  33          | $0.966 | KC2520Z76.8000C1KX00
80000000  |  1032  | 77519.38    |  250         | $0.733 | 12.95102
96000000  |  1239  | 77481.84    |  234         | $0.966 | KC3225Z96.0000C1KX00
100000000 |  1290  | 77519.38    |  250         | $0.966 | KC2016Z100.000C1KX00
125000000 |  1613  | 77495.35    |  60          | $0.966 | KC2016Z125.000C1KX00
133000000 |  1716  | 77505.828   |  75          | $0.966 | KC3225Z133.000C1KX00
  • A common 10MHz oscillator (or multiple of it) will get you within 250ppm.
  • A 13.33MHz oscillator can be purchased for under $1 and will get you an exact result (0PPM error) using a period with 172 timer counts.
  • A 76.8MHz oscillator will get you within 33ppm.
  • A 125 MHz oscillator will get you within 60ppm.
  • A 133 MHz oscillator will get you within 75ppm.
  • A 33.33 MHz oscillator will get you within 150ppm.

The 10MHz oscillator option can be used with most cheap micros.

Also note: A 31MHz oscillator with a timer period of 400 counts would also give you an exact value. 31MHz oscillators do exist, but they are not common enough to be cheap. But if you use an MCU with a 1MHz oscillator, and it has an internal PLL that accepts arbitrary multiplier values (like 31X) then you can do it that way. And running at 31MHz would be well within the range for timers on most lower end MCUs.

\$\endgroup\$
1
\$\begingroup\$

If you do not need exactly square waves, you can use fractional-N methods - set timers to pipelined reload mode. Then at the end of each cycle, interrupt and accumulate the difference between the exact period, and the period you have just used: when the error exceeds +1 , the next timer cycle is made one count longer, and the error is reduced by 1.00. So you get a jitter of 1 peripheral clock period in the pulse width. To avoid beat tones, you can add a "dither" to the error, so it changes period with a little randomness.

\$\endgroup\$
0
\$\begingroup\$

At least in Europe, 77.5 kHz is used for radio controlled clocks. You might pick up that signal, but you might as well find cheap+good crystals and receivers on the market.

\$\endgroup\$
1
  • \$\begingroup\$ The idea is to generate DCF77 myself. I know, not supposed to, but for very short range in one room it will be fine. DCF77 clocks don't work here, we are too far from the transmitter. \$\endgroup\$ Commented Nov 26 at 13:47

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.