Author Topic: 2 clocks from one oscillator.  (Read 1471 times)

0 Members and 1 Guest are viewing this topic.

Offline DaSkippyTopic starter

  • Newbie
  • Posts: 2
  • Country: us
2 clocks from one oscillator.
« on: July 31, 2019, 03:54:07 am »
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 sync

Now 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.
 

Online fourfathom

  • Super Contributor
  • ***
  • Posts: 1884
  • Country: us
Re: 2 clocks from one oscillator.
« Reply #1 on: July 31, 2019, 05:57:19 am »
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.pdf

A cheap eval board with this chip and an xtal: https://learn.adafruit.com/adafruit-si5351-clock-generator-breakout
We'll search out every place a sick, twisted, solitary misfit might run to! -- I'll start with Radio Shack.
 

Offline DaSkippyTopic starter

  • Newbie
  • Posts: 2
  • Country: us
Re: 2 clocks from one oscillator.
« Reply #2 on: July 31, 2019, 08:41:50 am »
Thanks, this chip seems to be what I need! I will order the eval board from Adafruit.
 

Offline awallin

  • Frequent Contributor
  • **
  • Posts: 694
Re: 2 clocks from one oscillator.
« Reply #3 on: July 31, 2019, 03:24:44 pm »
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...
 

Offline awallin

  • Frequent Contributor
  • **
  • Posts: 694
Re: 2 clocks from one oscillator.
« Reply #4 on: July 31, 2019, 03:45:48 pm »
This page talks about the audio chip
https://en.wikipedia.org/wiki/Super_Nintendo_Entertainment_System#Technical_specifications

mentions 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?
 
 

Online radiolistener

  • Super Contributor
  • ***
  • Posts: 3389
  • Country: ua
Re: 2 clocks from one oscillator.
« Reply #5 on: August 01, 2019, 10:20:58 pm »
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/2045
https://www.aliexpress.com/item/32817431908.html

Here is also more cheap module, also works ok: https://www.aliexpress.com/item/32972854971.html

Original (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 Hz
24'607'104 Hz = actual 24'607'104 Hz

It 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 = 64081


Here is a code example:
Code: [Select]
    // PLL = XTAL * [36+(0/1)] = 900'000'000 MHz
    struct 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 Hz
    struct 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 Hz
    struct 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: [Select]
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..90
    ASSERT( pi->b <= 0xFFFFF );             // 20-bit limit
    ASSERT( pi->c <= 0xFFFFF );             // 20-bit limit
    ASSERT( 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] = c
    if (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 4
    ASSERT( mi->a <= 1800 );        // max 900
    ASSERT( mi->b <= 0xFFFFF );     // 20-bit limit
    ASSERT( mi->c <= 0xFFFFF );     // 20-bit limit
    ASSERT( mi->c > 0 );            // avoid divide by zero
    ASSERT( (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] = c
    if (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[8] =
    {
        (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[8] =
    {
        (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;
    else
        reg_val |= SI5351_CLK_POWERDOWN;

    if (isInteger)
        reg_val |= SI5351_CLK_INTEGER_MODE;
    else
        reg_val &= ~SI5351_CLK_INTEGER_MODE;
   
if (pll == SI5351_PLLA)
reg_val &= ~SI5351_CLK_PLL_SELECT;
else
reg_val |= SI5351_CLK_PLL_SELECT;

  if (isInvert)
reg_val |= SI5351_CLK_INVERT;
else
reg_val &= ~SI5351_CLK_INVERT;

    // set input clock source
    reg_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 Strength
    reg_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: [Select]
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          58



other definitions for header you can find here: https://github.com/etherkit/Si5351Arduino/blob/master/src/si5351.h
« Last Edit: August 09, 2019, 04:00:24 am by radiolistener »
 

Offline FenTiger

  • Regular Contributor
  • *
  • Posts: 88
  • Country: gb
Re: 2 clocks from one oscillator.
« Reply #6 on: August 01, 2019, 10:33:37 pm »
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.
 

Online fourfathom

  • Super Contributor
  • ***
  • Posts: 1884
  • Country: us
Re: 2 clocks from one oscillator.
« Reply #7 on: August 02, 2019, 07:02:18 am »
// Actual freq = PLL  / [(a+b)/c]

You 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.
We'll search out every place a sick, twisted, solitary misfit might run to! -- I'll start with Radio Shack.
 
The following users thanked this post: radiolistener

Online radiolistener

  • Super Contributor
  • ***
  • Posts: 3389
  • Country: ua
Re: 2 clocks from one oscillator.
« Reply #8 on: August 09, 2019, 03:57:04 am »
You 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.

yeah, you're right, I wrote comment when prepared code for message, and didn't noted mistyping. Fixed. The code is tested and works ok :)
« Last Edit: August 09, 2019, 04:03:04 am by radiolistener »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf