2 clocks from one oscillator. - Page 1
Forum section: electronics / beginners

author: DaSkippy
author_rank: Newbie
timestamp: July 31, 2019, 03:54:07 am
content: Hey everyone, I have a question regarding clock generation.Description of the problem:In the SNES (yes, the game console) there are currently 2 clocks, one for CPU and one for the APU, which are both highly unstable and varies a lot, for the purpose of emulation, these clocks are 21477272Hz and 24607104Hz respectfully. In our community we try to validate emulators with real hardware by syncing controller inputs and verify that the end result is the same, on the NES this isn't a problem, since there is only one clock, but for the snes it's a huge problem.The greatest common divider between these clocks is 8Hz, which is a bad thing, since you will need to divide and multiply weirdly. And a small diversion initially will result in a huge difference afterwards, we can live with a certain difference between the original clock, but it's more important that the clocks stay in syncNow the question we have is, what would be the best way to get these clocks, and keep them in sync? Would it be best to divide.a 10MHz reference to 1Hz, and start multiplying? And what would be the best method to multiply the signals, since the clock will be I the 24ish MHz range, and each multiplication stage needs to add the same delay, and then at the end we would need to multiply one clock probably by 1 a certain amount of times to keep in sync.

author: fourfathom
author_rank: Super Contributor
timestamp: July 31, 2019, 05:57:19 am
content: Look at the Si5351, an extremely inexpensive and incredibly flexible triple-output clock generator. Put a cheap crystal on it and get two fractional-resolution PLLs driving three fractional divider outputs. Sub-Hz frequency resolution and output range from 8KHz to 150MHz.Datasheet:https://www.silabs.com/documents/public/data-sheets/Si5351-B.pdfA cheap eval board with this chip and an xtal:https://learn.adafruit.com/adafruit-si5351-clock-generator-breakout

author: DaSkippy
author_rank: Newbie
timestamp: July 31, 2019, 08:41:50 am
content: Thanks, this chip seems to be what I need! I will order the eval board from Adafruit.

author: awallin
author_rank: Frequent Contributor
timestamp: July 31, 2019, 03:24:44 pm
content: FWIW the first number is an exact (6x) multiple of some (by now) slightly obscure NTSC analog TV frequency of 3.579545 MHz. Crystals on that frequency should be available.one could now guess that the second one could be related through multiply/divide also to NTSC, or to PAL (europe?) or to some Japanese analog TV frequency...

author: awallin
author_rank: Frequent Contributor
timestamp: July 31, 2019, 03:45:48 pm
content: This page talks about the audio chiphttps://en.wikipedia.org/wiki/Super_Nintendo_Entertainment_System#Technical_specificationsmentions a clock of 24.576 MHz, which is 750-times the usual watch quartz-crystal of 32768 Hz. (or 24-times 1.024 MHz)was that what you meant by APU?

author: radiolistener
author_rank: Super Contributor
timestamp: August 01, 2019, 10:20:58 pm
content: you can do it with si5351, it allows to produce two or three different clocks synced from a single PLL, or from two PLL.You can use this cheap module:https://www.adafruit.com/product/2045https://www.aliexpress.com/item/32817431908.htmlHere is also more cheap module, also works ok:https://www.aliexpress.com/item/32972854971.htmlOriginal (blue pcb) module has a better quality pcb and more precise xtal oscillator with error about 200 Hz. Chinese clone with magenta pcb has a little worse pcb quality and xtal with error about 2-3 kHz. But both versions works ok.With XTAL = 25 MHz, you can get 21 MHz with a little fraction part and exact 24 MHz value:21'477'272 Hz = actual 21'477'272.000005688844389483886704 Hz24'607'104 Hz = actual 24'607'104 HzIt can be done by setting PLL = 900.000000 MHz: a=36, b=0, c=1.And use the following multisynth parameters for CLK0 and CLK1 channels:- CLK0 = 21'477'272 Hz: a = 41, b = 60725, c = 67117- CLK1 = 24'607'104 Hz: a = 36, b = 36834, c = 64081Here is a code example:Code:// PLL = XTAL * [36+(0/1)] = 900'000'000 MHzstruct si5351_pll_info pi = { 36, 0, 1 };si5351_set_pll(SI5351_PLLA, &pi);si5351_pll_reset(SI5351_PLLA);// CLK0 = 21'477'272 Hz// a = 41, b = 60725, c = 67117// Actual freq = PLL / [a+(b/c)] = 21'477'272.000005688844389483886704 Hzstruct si5351_msx_info mi0 = { 41, 60725, 67117, SI5351_OUTPUT_CLK_DIV_1, false };si5351_set_msx(SI5351_CLK0, &mi0);si5351_output_control(SI5351_CLK0,SI5351_PLLA,SI5351_DRIVE_8MA,SI5351_CLK_SRC_MS,true, false, false);// CLK1 = 24'607'104 Hz// a = 36, b = 36834, c = 64081// Actual freq = PLL / [a+(b/c)] = 24'607'104 Hzstruct si5351_msx_info mi1 = { 36, 36834, 64081, SI5351_OUTPUT_CLK_DIV_1, false };si5351_set_msx(SI5351_CLK1, &mi1);si5351_output_control(SI5351_CLK1,SI5351_PLLA,SI5351_DRIVE_8MA,SI5351_CLK_SRC_MS,true, false, false);si5351_output_enable(SI5351_CLK0, true);si5351_output_enable(SI5351_CLK1, true);Here is a functions:Code:void si5351_set_pll(enum si5351_pll pll, struct si5351_pll_info* pi){uint32_t p1;uint32_t p2;uint32_t p3;ASSERT( pi->a >= 15 && pi->a <= 90 ); // mult = 15..90ASSERT( pi->b <= 0xFFFFF ); // 20-bit limitASSERT( pi->c <= 0xFFFFF ); // 20-bit limitASSERT( pi->c > 0 ); // avoid divide by zero// p1[17:0] = 128 * a + floor( 128 * b / c ) - 512// p2[19:0] = 128 * b - c * floor( 128 * b / c)// p3[19:0] = cif (pi->b == 0){p1 = 128 * pi->a - 512;p2 = 0;p3 = pi->c;}else{uint32_t frac = (128 * pi->b) / pi->c;p1 = 128 * pi->a + frac - 512;p2 = 128 * pi->b - pi->c * frac;p3 = pi->c;}si5351_write_pll(pll, p1, p2, p3);}void si5351_set_msx(enum si5351_clock output, struct si5351_msx_info* mi){uint32_t p1;uint32_t p2;uint32_t p3;ASSERT( mi->a >= 4 ); // min 4ASSERT( mi->a <= 1800 ); // max 900ASSERT( mi->b <= 0xFFFFF ); // 20-bit limitASSERT( mi->c <= 0xFFFFF ); // 20-bit limitASSERT( mi->c > 0 ); // avoid divide by zeroASSERT( (mi->b == 0 && mi->a >= 4 && mi->a <= 6) ||((mi->a + mi->b / mi->c) >= 6 && (mi->a + mi->b / mi->c) <= 1800) );// P1[17:0] = 128 * a + floor( 128 * b / c ) - 512// P2[19:0] = 128 * b - c * floor( 128 * b / c )// P3[19:0] = cif (mi->b == 0){p1 = 128 * mi->a - 512;p2 = 0;p3 = mi->c;}else{uint32_t frac = (128 * mi->b) / mi->c;p1 = 128 * mi->a + frac - 512;p2 = 128 * mi->b - mi->c * frac;p3 = mi->c;}si5351_write_msx(output, p1, p2, p3, mi->rdiv, mi->divby4);}void si5351_write_pll(enum si5351_pll pll, uint32_t p1, uint32_t p2, uint32_t p3){uint8_t params ={(p3 & 0x0000FF00) >> 8,(p3 & 0x000000FF),(p1 & 0x00030000) >> 16,(p1 & 0x0000FF00) >> 8,(p1 & 0x000000FF),((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16),(p2 & 0x0000FF00) >> 8,(p2 & 0x000000FF),};switch (pll){case SI5351_PLLA:si5351_write_bulk(SI5351_PLLA_PARAMETERS, params, 8);break;case SI5351_PLLB:si5351_write_bulk(SI5351_PLLB_PARAMETERS, params, 8);break;}}void si5351_write_msx(enum si5351_clock output, uint32_t p1, uint32_t p2, uint32_t p3, uint8_t rdiv, bool divby4){uint8_t params ={(p3 & 0x0000FF00) >> 8,(p3 & 0x000000FF),((p1 & 0x00030000) >> 16) | ((rdiv & 7) << 4) | (divby4 ? 0x0C : 0x00),(p1 & 0x0000FF00) >> 8,(p1 & 0x000000FF),((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16),(p2 & 0x0000FF00) >> 8,(p2 & 0x000000FF),};switch (output){case SI5351_CLK0:si5351_write_bulk(SI5351_CLK0_PARAMETERS, params, 8);break;case SI5351_CLK1:si5351_write_bulk(SI5351_CLK1_PARAMETERS, params, 8);break;case SI5351_CLK2:si5351_write_bulk(SI5351_CLK2_PARAMETERS, params, 8);break;}}void si5351_output_control(enum si5351_clock clk,enum si5351_pll pll,enum si5351_drive drive,enum si5351_clock_source source,bool isPowerUp,bool isInvert,bool isInteger){uint8_t reg_val = 0;if (isPowerUp)reg_val &= ~SI5351_CLK_POWERDOWN;elsereg_val |= SI5351_CLK_POWERDOWN;if (isInteger)reg_val |= SI5351_CLK_INTEGER_MODE;elsereg_val &= ~SI5351_CLK_INTEGER_MODE;if (pll == SI5351_PLLA)reg_val &= ~SI5351_CLK_PLL_SELECT;elsereg_val |= SI5351_CLK_PLL_SELECT;if (isInvert)reg_val |= SI5351_CLK_INVERT;elsereg_val &= ~SI5351_CLK_INVERT;// set input clock sourcereg_val &= ~SI5351_CLK_INPUT_MASK;switch(source){case SI5351_CLK_SRC_XTAL:reg_val |= SI5351_CLK_INPUT_XTAL;break;case SI5351_CLK_SRC_CLKIN:reg_val |= SI5351_CLK_INPUT_CLKIN;break;case SI5351_CLK_SRC_MS0:reg_val |= SI5351_CLK_INPUT_MULTISYNTH_0_4;break;case SI5351_CLK_SRC_MS:reg_val |= SI5351_CLK_INPUT_MULTISYNTH_N;break;}// set drive Drive Strengthreg_val &= ~SI5351_CLK_DRIVE_STRENGTH_MASK;switch(drive){case SI5351_DRIVE_2MA:reg_val |= 0x00;break;case SI5351_DRIVE_4MA:reg_val |= 0x01;break;case SI5351_DRIVE_6MA:reg_val |= 0x02;break;case SI5351_DRIVE_8MA:reg_val |= 0x03;break;}si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);}void si5351_output_enable(enum si5351_clock clk, bool enable){uint8_t reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL);if(enable){reg_val &= ~(1<<(uint8_t)clk);}else{reg_val |= (1<<(uint8_t)clk);}si5351_write(SI5351_OUTPUT_ENABLE_CTRL, reg_val);}void si5351_pll_reset(enum si5351_pll pll){if(pll == SI5351_PLLA){si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_A);}else if(pll == SI5351_PLLB){si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_B);}}void si5351_write_bulk(uint8_t addr, uint8_t *data, uint16_t length){i2c_write(I2C1, si5351_addr, addr, data, length);}void si5351_read_bulk(uint8_t addr, uint8_t *data, uint16_t length){i2c_read(I2C1, si5351_addr, addr, data, length);}void si5351_write(uint8_t addr, uint8_t data){i2c_write(I2C1, si5351_addr, addr, &data, 1);}uint8_t si5351_read(uint8_t addr){uint8_t data;i2c_read(I2C1, si5351_addr, addr, &data, 1);return data;}some values from header:Code:struct si5351_pll_info{uint32_t a;uint32_t b;uint32_t c;};struct si5351_msx_info{uint32_t a;uint32_t b;uint32_t c;uint8_t rdiv;bool divby4;};enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3,SI5351_CLK4, SI5351_CLK5, SI5351_CLK6, SI5351_CLK7};enum si5351_pll {SI5351_PLLA,SI5351_PLLB};enum si5351_drive {SI5351_DRIVE_2MA,SI5351_DRIVE_4MA,SI5351_DRIVE_6MA,SI5351_DRIVE_8MA};enum si5351_clock_source {SI5351_CLK_SRC_XTAL,SI5351_CLK_SRC_CLKIN,SI5351_CLK_SRC_MS0,SI5351_CLK_SRC_MS};#define SI5351_PLLA_PARAMETERS 26#define SI5351_PLLB_PARAMETERS 34#define SI5351_CLK0_PARAMETERS 42#define SI5351_CLK1_PARAMETERS 50#define SI5351_CLK2_PARAMETERS 58other definitions for header you can find here:https://github.com/etherkit/Si5351Arduino/blob/master/src/si5351.h

author: FenTiger
author_rank: Regular Contributor
timestamp: August 01, 2019, 10:33:37 pm
content: Do they need to be that precise?1 / 21477272 ~= 47 parts per billion. Is the frequency really that stable between different units, across a range of temperatures, etc?Would 21.477 MHz be OK?What about 21.5 MHz?Similarly for the other one.

author: fourfathom
author_rank: Super Contributor
timestamp: August 02, 2019, 07:02:18 am
content: Quote from: radiolistener on August 01, 2019, 10:20:58 pmYou got the parentheses mixed up in the comment. The clock out of the PLL will actually be divided by [a + (b / c)]. The code is correct, at least as far as the a, b, c values are concerned.This device can give you rather astounding frequency resolution, since you have the fractional-resolution dividers in both the PLL feedback loop and the clock output path (plus some additional dividers available in the output path). You usually get better spectral purity if you use integer-only PLL divisors, but in many cases this won't matter. Of course the actual frequency accuracy is only as good as the reference crystal accuracy, but sometimes you want accurate frequency differences even if the frequency itself isn't perfectly precise.

author: radiolistener
author_rank: Super Contributor
timestamp: August 09, 2019, 03:57:04 am
content: Quote from: fourfathom on August 02, 2019, 07:02:18 amyeah, you're right, I wrote comment when prepared code for message, and didn't noted mistyping. Fixed. The code is tested and works ok