Controlling the Si5351A programmable clock generator might at first glance seem rather complex or perhaps even scary. Therefore you might revert to using the RFzero code as it is or some of the third party libraries available. But with a little help from your friends from RFzero, you will be in a much better position to tweak the examples, add functionality, or perhaps even write your own functions to control the Si5351A.

# Theory behind the Si5351A

The Si5351A consists of two synthesizers, three output dividers, one per output channel, and one output stage per channel that are all controlled via the I2C/Wire bus. A common oscillator circuit, operating at either 25 MHz or 27 MHz, is also built in, but it needs an external crystal.

### The synthesizer

If you are not familiar with RF synthesizers, and how they work, you can see them as RF generators, that generate frequencies derived from another frequency, i.e. the reference frequency. In the RFzero this is what the 27 MHz oscillator is used for.

A synthesizer consists of three main electronic circuits: a phase comparator, a VCO and a feedback divider. Silicon Laboratories (SiLabs) names the feedback divider the Feedback Multisynth Divider (FMD). For each different programmed setting of the FMD, the VCO will generate a different frequency.

In many RF applications this is all what is needed, if the wanted output frequencies are in wide steps, e.g. in 25 kHz steps used for FM channels.

### The output multisynth divider

To improve the frequency resolution, from wide steps to finer steps, the synthesizer in the Si5351A is followed by a divider circuit, which SiLabs names the Output Multisynth Divider (OMD). In the Si5351A SiLabs has added a second divider stage, called R, that can divide the frequency generated by the synthesizer even further.

The drawback of an output divider is that the output frequency (fout) will be lower than the VCO frequency (fvco). In the Si5351A there is a minimum division of four. This means that the VCO frequency is always at least four times higher than the output frequency.

But instead of having wide frequency steps, e.g. 25 kHz, the steps can be much lower, e.g. 1 Hz. It all depends on how the FMD and OMD ratios are setup.

### The math

If you are still with us please stay a bit longer, and we will take you by the hand and walk you through the math, and how to generate the wanted output frequency (fout) derived from the reference frequency (fref). In math lingo the output frequency can be expressed as:

The Si5351A FMD and OMD ratios can also be expressed:

Please note that SiLabs also names the OMD variables, a, b and c, but, they are NOT THE SAME as those for the FMD. This is an obvious place to get the math wrong. So to avoid the possible misunderstanding in the RFzero world they are named d, e and f respectively instead. Please remember this if you dive into the RFzero code or Si5351A programming guide. But now we can write the complete relationship between the reference frequency and the output frequency:

That’s easy – right? Hmmm, not quite. We now have one equation with seven variables to get from the reference frequency to the output frequency. But the complexity doesn’t stop here. There are some constraints to the different variables and the electronic circuit too:

- The VCO frequency can only be from 600 MHz to 900 MHz. Yes, over-clocking is possible, and so in under-clocking, to extend the output frequency range beyond 2,5 kHz to 200 MHz. But let’s leave this for now.
- The FMD ratio can be from 15 + 0/1 048 575 and to 90 + 0/1 048 575, i.e. a = 15, b = 0 and c = 1 048 575 to a = 90, b = 0 and c = 1 048 575. Please remember that c can never be zero (0), as this will violate fundamental math principles.
- The OMD ratio can be 4 and from 6 to 1800. Please remember that f can never be zero (0), as this will violate fundamental math principles.
- The R divider can only be 1, 2, 4, 8, 16, 32, 64 or 128.
- If the output frequency is above 150 MHz d is always 4.
- If the output frequency is below 500 kHz R should be used, i.e. higher than 1.

You also need to understand a bit more about synthesizer and divider circuits. The FMD and OMD rations are what is called fractional, e.g. 10 + 23 / 4567. But synthesizer and divider circuits produce more spurious when operated in fractional mode than in integer mode, e.g. 10 + 0 / 4567 = 10. So a clever RF designer will assess the situation, and decide if fractional modes are need for both the FMD and the OMD, a mix or use both in integer mode. If a mix is sufficient the OMD should be operated in integer mode if possible. Furthermore, are even numbers for a and d preferred again due to spurious. The software will have take all this into account.

Agreed, this doesn’t help a lot.

But here comes the first trick to get started. Do a calculation using a test VCO frequency of 600 MHz, if the wanted output frequency is below 150 MHz.

So let’s continue with the above design advises and chose a test VCO frequency of 600 MHz, and a output frequency of 28,567 MHz which means that R = 1.

The second trick is then to work backwards from the output frequency by first calculating the R · OMD radio:

This means that d is 21 and the e/f ratio is 0,0032555046032…

This is neither an integer nor an even one if rounded. So the next trick is to round d up to an even integer, i.e. 22, and check if the resulting VCO frequency is still within the valid range from 600 MHz to 900 MHz. If not then increase the value of d by two. Since we want to use the OMD in integer mode the e/f ratio doesn’t matter, and the FMD will have to take care of this instead. So 22 · 28,567 MHz = 628,474 MHz which is a fine fvco.

We now know that d = 22, e = 0 and f = 1 or any value as long as it is less than or equal to 1 048 575. So we can continue to find the FMD ratio, i.e. a, b and c:

where a = 23 is the integer part of the FMD and b/c = 0,2768148148148… is the remainder.

Hang in there we are almost done. But how do we solve the b/c = 0,2768148148148… equation? The easiest way to do it is to set c to 1 048 575. This results in b equals 290 261.

That’s it! We now have the following values:

- a = 23
- b = 290 261
- c = 1 048 575
- d = 22
- e = 0 (static)
- f = 1 (static)
- R = 1

But will the output frequency be correct then? Well, let’s check:

OK, not spot on but -111 mHz away.

What if the output frequency is less than 500 kHz? In this case the R divider comes into play. Fortunately the procedure is simple. Multiply the wanted output frequency below 500 kHz with a valid R value (1, 2, 4, 8 … 128) so the frequency becomes greater than 500 kHz. Then do the math using this new intermediate frequency as fout in the above equations, e.g. if you want 100 kHz an R = 8 will bring fout up to 800 kHz. The datasheet says that the R divider can be used below 500 kHz but to get to the lowest frequency the Si5351A can generate this border frequency should be set to 333 334 Hz instead coming from fvco / dmax = 600 MHz / 1800.

# Frequency code for the Si5351A

Below is a function that calculates the FMD, OMD and R variables and returns the register values for the Si5351A. In this code the OMD is set to operate in integer mode, thus e = 0 and f = 1.

The code works for frequencies from 2605 Hz and up. According to SiLabs the Si5351A can work up to 200 MHz. It is actually possible to use the Si5351A beyond 280 MHz but the maximum frequency depends very much of the actual Si5351A device. Once you get closer to the frequency limit of the Si5351A, you will experience, that the synthesizer has difficulties getting into a steady state (locked). Beyond 200 MHz the signal-to-noise ratio also decreases. Another way to use the Si5351A at higher frequencies is to use one of the harmonics. But please keep in mind that higher output frequency also means worse spectral performance.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
void CalcRegisters(const int fout, uint8_t *regs) { int fref = 27000000L; // The reference frequency // Calc Output Multisynth Divider and R with e = 0 and f = 1 => msx_p2 = 0 and msx_p3 = 1 int d = 4; int msx_p1 = 0; // If fout > 150 MHz then MSx_P1 = 0 and MSx_DIVBY4 = 0xC0, see datasheet 4.1.3 int msx_divby4 = 0; int rx_div = 0; int r = 1; if (fout > 150000000L) msx_divby4 = 0x0C; // MSx_DIVBY4[1:0] = 0b11, see datasheet 4.1.3 else if (fout < 333334L) // If fout < 500 kHz then use R divider, see datasheet 4.2.2. { // In reality this means > 333333 1/3 Hz when d = 1800 { int rd = 0; while ((r < 128) && (r * fout < 333334)) { r <<= 1; rd++; } rx_div = rd << 4; d = 600000000L / (r * fout); // Use lowest VCO frequency but handle d minimum if (d % 2) // Make d even to reduce phase noise/jitter, see datasheet 4.1.2.1. d++; if (d * r * fout < 600000000L) // VCO frequency to low check and maintain an even d value d += 2; } else { d = 600000000L / fout; // Use lowest VCO frequency but handle d minimum if (d < 6) d = 6; else if (d % 2) // Make d even to reduce phase noise/jitter, see datasheet 4.1.2.1. d++; if (d * fout < 600000000L) // VCO frequency to low check and maintain an even d value d += 2; } msx_p1 = 128 * d - 512; int fvco = d * r * fout; // Calc Feedback Multisynth Divider double fmd = (double)fvco / fref; // The FMD value has been found int a = fmd; // a is the integer part of the FMD value double b_c = (double)fmd - a; // Get b/c int c = 1048575; int b = (double)b_c * c; if (b > 0) { c = (double)b / b_c + 0.5; // Improves frequency precision in some cases if (c > 1048575) c = 1048575; } int msna_p1 = 128 * a + 128 * b / c - 512; // See datasheet 3.2 int msna_p2 = 128 * b - c * (128 * b / c); int msna_p3 = c; // Feedback Multisynth Divider registers regs[0] = (msna_p3 >> 8) & 0xFF; regs[1] = msna_p3 & 0xFF; regs[2] = (msna_p1 >> 16) & 0x03; regs[3] = (msna_p1 >> 8) & 0xFF; regs[4] = msna_p1 & 0xFF; regs[5] = ((msna_p3 >> 12) & 0xF0) + ((msna_p2 >> 16) & 0x0F); regs[6] = (msna_p2 >> 8) & 0xFF; regs[7] = msna_p2 & 0xFF; // Output Multisynth Divider registers regs[8] = 0; // (msx_p3 >> 8) & 0xFF regs[9] = 1; // msx_p3 & 0xFF regs[10] = rx_div + msx_divby4 + ((msx_p1 >> 16) & 0x03); regs[11] = (msx_p1 >> 8) & 0xFF; regs[12] = msx_p1 & 0xFF; regs[13] = 0; // ((msx_p3 >> 12) & 0xF0) + (msx_p2 >> 16) & 0x0F regs[14] = 0; // (msx_p2 >> 8) & 0xFF regs[15] = 0; // msx_p2 & 0xFF return; } |

Here is an online tool to calculate the FMD, OMD and R register values.

# Putting it all together

We are now almost ready generate a frequency with the Si5351A. But before we can do this a pair of I2C (Wire) read and write functions are needed.

*The I2C (Wire) read function.*

1 2 3 4 5 6 7 8 9 10 11 12 13 |
uint8_t ReadRegister(uint8_t regAddr) { int data = 0xFF; // Set value often not seen Wire.beginTransmission(0x60); // The I2C address of the Si5351A Wire.write((uint8_t)regAddr); Wire.endTransmission(); Wire.requestFrom(0x60, 1); if (Wire.available()) data = Wire.read(); return data; } |

*The I2C (Wire) write function.*

1 2 3 4 5 6 7 |
void WriteRegister(uint8_t regAddr, uint8_t data) { Wire.beginTransmission(0x60); // The I2C address of the Si5351A Wire.write((uint8_t) regAddr); Wire.write((uint8_t) data); Wire.endTransmission(); } |

For the RFzero the Wire instance must be replaced by WireLocal.

### Initializing the Si5351A

In the below example the structure of the initialization of the Si5351A is taken from the flowchart in the datasheet.

The OMD for CLK0 is set to integer mode. Only CLK0 is initialized and with an output stage current of 8 mA. The reference load is set to 6 pF.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void Si5351A_Initialize() { // Initialize Si5351A while (ReadRegister(0) & 0x80); // Wait for Si5351A to initialize WriteRegister(3, 0xFF); // Output Enable Control, disable all for (int i = 16; i < 24; i++) WriteRegister (i, 0x80); // CLKi Control, power down CLKi WriteRegister(15, 0x00); // PLL Input Source, select the XTAL input as the reference clock for PLLA and PLLB WriteRegister(24, 0x00); // CLK3–0 Disable State, unused are low and never disable CLK0 // Output Multisynth0, e = 0, f = 1, MS0_P2 and MSO_P3 WriteRegister(42, 0x00); WriteRegister(43, 0x01); WriteRegister(47, 0x00); WriteRegister(48, 0x00); WriteRegister(49, 0x00); WriteRegister(16, 0x4F); // Power up CLK0, PLLA, MS0 operates in integer mode, Output Clock 0 is not inverted, Select MultiSynth 0 as the source for CLK0 and 8 mA // Reference load configuration WriteRegister(183, 0x12); // Set reference load C: 6 pF = 0x12, 8 pf = 0x92, 10 pF = 0xD2 // Turn CLK0 output on WriteRegister(3, 0xFE); // Output Enable Control. Active low } |

### The program

The final program is now ready. Add each of the above functions to the .ino file and complete the setup() function.

*The setup() and loop() part of the program.*

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Arduino includes #include <Arduino.h> void setup() { uint8_t regs[16]; // Registers holding the FMD and OMD values const int freq = 28567000; // The wanted output frequency Si5351A_Initialize(); CalcRegisters(freq, regs); // Load PLLA Feedback Multisynth NA for (int i = 0; i < 8; i++) WriteRegister(26 + i, regs[i]); // Load Output Multisynth0 with d (e and f already set during init. and never changed) for (int i = 10; i < 13; i++) WriteRegister(34 + i, regs[i]); // Reset PLLA delayMicroseconds(500); // Allow registers to settle before resetting the PLL WriteRegister(177, 0x20); } void loop() { } |