Useful documents
Some theoretical discussion
Simple example code
Example 1: using bit-banged I2C
Example 2: using AVR's TWI (I2C) peripheral
Example 3: Arduino sketch example
Example 4: OE1CGS Arduino sketch
PIC example code by Russ G4SAQ
A note on Low Frequency operation
This page contains some example code for the Si5351A synthesiser module chip. There are a couple of other Si5351A libraries on the internet. However they are either incomplete (don't go all the way from specifying a frequency, to generating and setting the configuration registers), or quite over-complex. Some are designed to cater for all the variants of the Si5351 (A/B/C) and all the features. There's a Linux Si5351 driver but the code is very confusing and anyway it does not seem well-suited to a limited-space microcontroller environment.
So, with these C examples I have tried to keep it very simple, just to show what needs to be done to get some output out of the Si5351A (only), and only the 10-pin MSOP version with 3 outputs. From here, you can build on it, piece by piece, to customise it for your application. So these examples generate a 10MHz signal on the Clk0 output, that's all. You can choose any frequency in the range 1-150MHz, in the function call.
For frequencies under 1MHz, you need to employ the final divider stage in the Si5351A, which can divide by powers of 2 from 1 to 128. So for example, if you want 100kHz, then one way to do it is to configure the chip for a frequency of 1.6MHz, then configure the final division stage as divide-by-16 to get to 100kHz output. Using the division ratios, according to the datasheet, the Si5351A can then generate low output frequencies from 8kHz to 1MHz.
For frequencies above 150MHz, and to the maximum specified frequency of 160MHz according to the datasheet, some special configuration is required. I haven't attempted to tackle that in this example code.
Useful documents
The Si5351A datasheet: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351-B.pdf
Manually generating an Si5351A register map: https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/application-notes/AN619.pdf
Application note AN619 "Manually generating an Si5351A register map" is much more useful than the actual Si5351A datasheet. I refer to it often. There are quite a lot of mistakes and confusions in both the datasheet and AN619 but all of the information is actually in there and it can be decyphered.
Some theoretical discussion
The Si5351A is confugured using two signals, an I2C bus protocol. The QRP Labs Si5351A module kit includes all the necessary pullups and level conversion to run on a 5V microcontroller system. You only need to connect these two wires to the AVR processor. It is important to understand the basic synthesis mechanism of the Si5351A. There are really three stages to it:
1) A digital PLL multiplies the 27MHz crystal oscillator frequency up, to an internal frequency; somewhere (that you configure) that must be in the range 600-900MHz. The multiplication factor is fractional, not an integer. The code must configure all the registers that specify the integer part, and the numerator/denominator of the fractional part. The integer part must be in the range 15..90; the numerator 0..1048575 (20 bits), and the denominator 1..1048575 (also 20 bits). There are TWO PLLs available, PLL A and PLL B.
2) A divider stage divides this internal UHF frequency back down to the desired output frequency. The division ratio is also fractional with 20-bit fractional parts. However for best jitter performance the datasheet recommends using even integers for the division factor. There are THREE "multisynth" divider stages, one for each of the three Clk outputs of the Si5351A (other variants of the Si5351A have 8 outputs, and various other input arrangements; we aren't considering those here, we're keeping it simple, remember). Each Multisynth divider can be fed by either of the two PLL outputs, you choose.
3) Finally there is also a final division stage for each output, which divides by a power-of-2 factor of 1..128, this is needed for low frequency outputs in the range 8kHz to 1MHz.
Since there are 2 PLLs and 3 outputs, and each output "multisynth" divider can be fed from either of the two PLLs, if you want to set this up for multiple outputs you have to do some tough thinking. If you stick to the "even integer" divider, you don't really have THREE independent flexible output frequencies, you have only two. Because two of those outputs are going to have to feed off the same PLL. If there is not a convenient integral relationship between two of the output frequencies, then for one of them you have to abandon the "even integer division" rule for low jitter.
Anyway, I'm just saying. All of these things are considerations to take into account. But not part of my simple simple example here.
Simple example code
The example code is written in C for AVR microcontrollers, I use the GCC compiler in AVR Studio. It should be easy to use this on any AVR, or on the Arduino platform. Only minor changes would be needed for other microcontrollers.
There are TWO examples here. The first example allows you to use ANY I/O pins of the processor to bit-bang as I2C. That gives a lot of flexibility, if you happen to be using I/O pins other than the AVR's onboard I2C peripheral (they call it the Two Wire interface in the Atmel datasheets and application notes), or if you are using an AVR device that doesn't have an I2C peripheral onboard: not all support I2C. In this example we use a very nice assembly language library by Peter Fleury to take care of the I2C bit-banging. It works perfectly!
The second example uses the AVR's internal I2C peripheral (which they call the Two Wire interface, TWI, in the datasheets and application notes). That assumes that in your application, your chosen AVR does have a TWI peripheral, and that you aren't using those particular I2C pins of the processor for something else, so you can afford to dedicate them to I2C purposes. This is the ideal situation.
Example 1: using bit-banged I2C
Click to download files:
main.c | contains main() function - just a call to si5351aSetFrequency(10000000); which sets Clk0 to 10MHz |
si5351a.c | my si5351a demo library that calculates and sets the Si5351a registers |
si5351a.h | header file for the si5351a demo library |
i2cmaster.s | Peter Fleury's assembly-language bit-banged I2C library, slightly modified (see text below) |
i2cmaster.h | header file for Peter Fleury's bit-banged I2C library |
My example here is actually set up so that it runs on the ATmega328 in my Ultimate3S QRSS/WSPR transmiter kit. In that kit the AVR's actual I2C (TWI) peripheral pins are connected to other stuff. The kit is designed for backward compatibility with the earlier AD9850 DDS module, and other I/O pins are used to program the AD9850 DDS tuning word. It's a good example of using the bit-banged approach.
main.c just contains a call to si5351aSetFrequency(10000000); which sets the frequency to 10MHz. You can specify any value from 1MHz to 150MHz.
si5351a.c is where all the hard work goes on, to determine the correct register entries to configure the Si5351A to get the desired output frequency. There are some comments in the code to explain it. The comments are nicely tab-indented to character column 37 in my AVR Studio, but they might not line up so nicely in your editor or your internet browser.
si5351a.h is just the header file for si5351a.c - note that I kept it minimal. I have not created a #define for every register the Si5351A has, or tried to cater for all the functionality or variants of the Si5351 chip. Unlike other Si5351A demo libraries, I want to keep this one as simple and easy to understand as possible. So I only created #defines for things I needed. You do have #defines for the clock configuration registers, for the base address for PLL A and B, and for the base addresses for multisynth 0, 1 and 2; as well as the R-Divider definitions for division ratios 1..128. There is also XTAL_FREQ which is defined as 27,000,000 to match the 27MHz crystal on the Si5351A Synth module kit. The actual oscillation frequency will be different. I have one here which measures 27,004,417 for example. If precision is important to your application, you will want to measure and put in the actual oscillation frequency here.
i2cmaster.s is Peter Fleury's assembly-language bit-banged I2C library. I made three changes for this application: I installed the Si53451A Synth module in my Ultimate3S QRSS/WSPR transmitter kit, as a convenient hardware platform to use for this demo.
Firstly, the i2cmaster.s as it came, has its i2c_delay_T2 set up to generate a 5us timing delay with a 4MHz clock. My system had a 20MHz clock, so I just added a bunch more "rjmp" instructions to add more delay, to make it 5us even with the 20MHz system clock. You might need to configure this for your system. This all assumes a 100kHz I2C bus but in reality this doesn't need to be that precise.
Secondly, I adapted the section that specifies what I/O pins to use for the bit-banged I2C:
;***** Adapt these SCA and SCL port and pin definition to your target !!
;
#define SDA_PORT PORTD // SDA Port D
#define SDA 2 // SDA Port D, Pin 2
#define SCL_PORT PORTB // SCL Port B
#define SCL 1 // SCL Port B, Pin 1
This needs to be altered to suit your hardware configuration. In my case I am using the Ultimate3S QRSS/WSPR transmitter kit as a test/demo hardware platform, so I chose the Port and Pins which happen to be connected to the Si5351A module's SDA and SCL pins.
Thirdly, I also added an i2c_exit() function. The documentation for this library includes an i2c_init() function which it says needs to be called only once. However, that assumes you want to use the 2 I/O signals of the processor ONLY for behaving as an I2C bus to control the Si5351A. But, maybe your system is more complex than that, and you want to dual-purpose I/O pins to control other things too (like in the Ultimate3S QRSS/WSPR transmitter kit). So I added the i2c_exit() function; then in the examples above I call i2c_init() at the beginning of the functions, and i2c_exit() at the end. This way, the pins can be used for other things (carefully), and are only owned by the I2C Si5351A communication while actually in use.
i2cmaster.h is the header file for i2cmaster.s and I didn't need to modify it, apart from the definition of the i2c_exit function.
Example 2: using AVR's TWI (I2C) peripheral
This is the simpler example, using the AVR's TWI (I2C) peripheral, which is dedicated to the purpose of I2C communication only.
Click to download files:
main.c | contains main() function - an I2C init and a call to si5351aSetFrequency(10000000); which sets Clk0 to 10MHz |
si5351a.c | my si5351a demo library that calculates and sets the si5351a registers |
si5351a.h | header file for the si5351a demo library |
i2c.c | my simple I2C library, based on the ATmega datasheets |
i2c.h | header file for the I2C library |
My example was tested on an ATmega328 AVR running with 20MHz system clock (crystal), with the Si5351A SDA and SCL pins connected to the AVR's SDA and SCL pins.
main.c just contains a call to i2cInit() to set up the AVR's TWI interface; and a call to si5351aSetFrequency(10000000); which sets the frequency to 10MHz. You can specify any value from 1MHz to 150MHz.
si5351a.c is where all the hard work goes on, to determine the correct register entries to configure the Si5351A to get the desired output frequency. There are some comments in the code to explain it. The comments are nicely tab-indented to character column 37 in my AVR Studio, but they might not line up so nicely in your editor or your internet browser.
si5351a.h is just the header file for si5351a.c - note that I kept it minimal. I have not created a #define for every register the Si5351A has, or tried to cater for all the functionality or variants of the Si5351 chip. Unlike other Si5351A demo libraries, I want to keep this one as simple and easy to understand as possible. So I only created #defines for things I needed. You do have #defines for the clock configuration registers, for the base address for PLL A and B, and for the base addresses for multisynth 0, 1 and 2; as well as the R-Divider definitions for division ratios 1..128. There is also XTAL_FREQ which is defined as 27,000,000 to match the 27MHz crystal on the Si5351A Synth module kit. The actual oscillation frequency will be different. I have one here which measures 27,004,417 for example. If precision is important to your application, you will want to measure and put in the actual oscillation frequency here.
i2c.c is my simple I2C library, based mainly on the ATmega datasheet description.
i2c.h is the header file for i2c.c
Example 3: Arduino sketch example
This example is very similar to Example 2 above, just re-organised a bit as an Arduino sketch. It was sent in by Alf VK2YAC (thanks Alf!).
Click to download files. Save them all in the same directory as the .ino sketch:
si5351a.ino | the si5351a demo sketch, that calculates and sets the si5351a registers, and sets the Clk- frequency to 10MHz |
si5351a.h | header file for the si5351a demo sketch |
i2c.c | my simple I2C library, based on the ATmega datasheets |
i2c.h | header file for the I2C library |
The example should run on an Arduino with the Si5351A SDA and SCL pins connected to the AVR's SDA and SCL pins.
si5351a.ino is the Arduino sketch file. It contains a call to i2cInit() to set up the AVR's TWI interface; and a call to si5351aSetFrequency(10000000); which sets the frequency to 10MHz. You can specify any value from 1MHz to 150MHz. It also includes te former example's si5351a.c file, where all the hard work goes on. It determines the correct register entries to configure the Si5351A to get the desired output frequency. There are some comments in the code to explain it. The comments are nicely tab-indented to character column 37 in my AVR Studio, but they might not line up so nicely in your editor or your internet browser.
si5351a.h is just the header file for the sketch, it is the same as the one in the previous example. Note that I kept it minimal. I have not created a #define for every register the Si5351A has, or tried to cater for all the functionality or variants of the Si5351 chip. Unlike other Si5351A demo libraries, I want to keep this one as simple and easy to understand as possible. So I only created #defines for things I needed. You do have #defines for the clock configuration registers, for the base address for PLL A and B, and for the base addresses for multisynth 0, 1 and 2; as well as the R-Divider definitions for division ratios 1..128. There is also XTAL_FREQ which is defined as 27,000,000 to match the 27MHz crystal on the Si5351A Synth module kit. The actual oscillation frequency will be different. I have one here which measures 27,004,417 for example. If precision is important to your application, you will want to measure and put in the actual oscillation frequency here.
i2c.c is my simple I2C library, based mainly on the ATmega datasheet description.
i2c.h is the header file for i2c.c
NOTE: Some people have reported compilation errors, which appear to come from using multiple files in the Arduino IDE. There is a tutorial on this topic here: https://arduino.land/FAQ/content/7/43/en/breaking-a-sketch-into-multiple-files.html (thanks Alf VK2YAC).
Alf VK2YAC also provided a single file sketch, it is the same as the above collection of four files but all in one. Konstantinos SV1ONW has tested this and found it necessary to alter some formatting. The final file is made available here, and should make it all easy:
si5351a-test.ino | All above four files, but arranged into a single Arduino sketch file to make it easy |
Example 4: Arduino sketch by Christoph OE1CGS
Please see this page for a nice simple single-file Arduino sketch by Christoph OE1CGS.
PIC example code by Russ G4SAQ
Please see this page for PIC code contributed by Russ G4SAQ.
A note on Low Frequency operation
The limits of the Si5351A configuration registers for the fractional PLL multiplier and the Multi-Synth divider means that you need to make some small changes if you wish to generate lower frequencies, in the range 8kHz to 1MHz. In order to generate lower frequencies you must use the final division stage, which is configurable to divide by 8 powers of 2 from 0 (divide-by-1) which is the default, to 7 (divide-by-128).
In the example code above you will see the call to the function setupMultisynth passes a parameter "SI_R_DIV_1". This is a constant defined in the .h file to set the final state to divide-by-1, the default state. You can change this to SI_R_DIV_2, or SI_R_DIV_4 etc up to SI_R_DIV_128. There are 8 possible division ratios: 1, 2, 4, 8, 16, 32, 64, 128. In this case you setup a frequency the multiple higher than you really want, and then divide it back again using this division ratio.
For example, suppose you want to generate an output frequency of 136kHz. This is a lot lower than 1MHz and the Si5351A cannot reach it directly. So you use the final division stage, by passing the parameter SI_R_DIV_8 into the function call setupMultisynth instead of SI_R_DIV_1. Then you call the function si5351ASetFrequency with 1088000Hz (1.088MHz), which is 8x the desired 136kHz output.
It should be noted that although the datasheet specifies that the lowest Si5351A output frequency is 8kHz, the register configurations do allow you to configure lower values, all the way down to 3.5kHz; and it DOES work.