Синтезатор на si5351a на arduino

Oddities

The Si5351 datasheet specifies an I2C address of 0b1100000 (0x60), but this has not been the correct address on the samples used at the NT7S lab. Using the Bus Pirate’s I2C address scan macro, we have determined that the address that the Si5351A wants to see is 0x6F (0xDE in 8-bit format), so that is what we use in the library. If you have trouble communicating with your Si5351, you may want to adjust this value in the si5351.h file back to the specified value (0xC0 in 8-bit format). Given the high number of errors we have found in the datasheet so far, this is unsurprising.

Update: It turns out that we were sent defective parts. Another batch was ordered from a different vendor and they work on the proper I2C address of 0x60. The code has been updated to reflect the correct address.

Right now, this code is focused solely on the 3-output 10-MSOP variant (Si5351A3). Since some of the code was derived from the Si5351 driver in the Linux kernel, it may be useable on with the other variants, but certainly many features won’t work yet. With any luck, we will get the library to work with the other variants as well, or even better, maybe someone will take the initiative, write the code, and send me a pull request.

Общие принципы работы проекта

В данном проекте рассматривается генератор перестраиваемой частоты (variable-frequency oscillator, VFO), пригодный для использования в «домашних» (Do It Yourself , DIY) условиях. Этот генератор может пригодиться в синтезаторах частоты, супергетеродинных радиоприемниках, SDR-приемопередатчиках и т.д. Генератор имеет шкальный индикатор (Bargraph indicator) для отображения мощности сигнала (S-Meter) и 20 заранее установленных диапазонов частот.

Основные особенности проекта:

  • рабочий диапазон от 10 кГц до 225 МГц;
  • шаг настройки: 1 Гц, 10 Гц, 1 кГц, 5 кГц, 10 кГц и 1 МГц;
  • регулируемое смещение (+ или -) промежуточной частоты (ПЧ);
  • 20 заранее установленных диапазонов частот (с быстрым доступом) в полосах частот АМ-вещания (BCB) и радиолюбительских диапазонах (HAM frequencies);
  • режим генерации сигналов (функциональный генератор);
  • для использования в качестве местного генератора на самодельных супергетеродинных радиоприемниках или радиоприемниках с прямым преобразованием;
  • для использования в качестве генератора переменной частоты для радиолюбителей;
  • для использования в качестве простого тактового генератора для калибровки или генерации тактовых импульсов;
  • шкальный индикатор для отображения мощности сигнала через вход АЦП (аналого-цифрового преобразователя);
  • возможность работы с платами Arduino Uno, Nano и Pro Mini;
  • использует стандартный дисплей 128×64 I2C OLED SSD1306 и модуль Si5351;
  • передача данных по интерфейсу I2C, необходимо всего 2 провода для подключения дисплея и модуля Si5351 к плате Arduino;
  • высокая стабильность и точность генерации частоты;
  • хорошая эффективность, невысокая стоимость, можно собрать в домашних условиях.

Внесение изменений в настройки проекта (User Preferences)

Вы можете изменить следующие строки в скетче:

#define IF 455 //введите вашу IF (промежуточную) частоту, ex: 455 = 455kHz, 10700 = 10.7MHz, 0 = прямое преобразование частоты приемника или радиочастоты генератора, «+» будет добавляться, а «-» будет вычитаться сдвиг промежуточной частоты.#define BAND_INIT 7 // введите ваш начальный диапазон (Band) (1-21) в начале работы проекта, ex: 1 = Freq Generator, 2 = 800kHz (MW – средние волны), 7 = 7.2MHz (40m), 11 = 14.1MHz (20m).#define XT_CAL_F 33000 // коэффициент калибровки модуля Si5351, можно настроить чтобы получить точно 10MHz. Увеличение этого значения будет уменьшать частоту и наоборот.#define S_GAIN 303 //настройка чувствительности входа измерителя мощности (Signal Meter A/D input): 101 = 500mv; 202 = 1v; 303 = 1.5v; 404 = 2v; 505 = 2.5v; 1010 = 5v (max).#define tunestep A0 //контакт, к которому подключена кнопка для настройки шага настройки.#define band A1 //контакт, к которому подключена кнопка для выбора частотного диапазона.#define rx_tx A2 // контакт, к которому подключена кнопка для выбора режима RX / TX, RX = switch open (переключатель открыт), TX = switch closed to GND (переключатель замкнут на землю). В режиме TX частота IF (промежуточная) не учитывается.#define adc A3 //контакт, используемый как вход измерителя мощности (Signal Meter A/D input).

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. 

OVERCLOCK

Yes, you can overclock the Si5351, the datasheet states that the VCO moves from 600 to 900 MHz and that gives us a usable range from ~3 kHz to 225 MHz.

But what if we can move the VCO frequency to a higher values?

The overclock feature does just that, use a higher top limit for the VCO on the calculations. In my test with two batch of the Si5351A I can get safely up to 1.000 GHz without trouble; in one batch the PLL unlocks around 1.1 GHz and in the other about 1.050 GHz; so I recommend not going beyond 1.000 GHz.

With a maximum VCO of 1.000 GHz and a lower division factor of 4 we have jumped from a 225 MHz to 250 MHz top frequency that can be generated with our cheap chip.

Some «must include» WARNINGS:

  • The chip was not intended to go that high, so, use it with caution and test it on your hardware moving the overclock limit in steps of 10 MHz starting with 900 MHz and testing with every change until it does not work; then go down by 10 MHz to be in a safe zone.
  • Moving the upper limit has its penalty on the lower level, your bottom frequency will move from the ~3 kHz to ~10 kHz range.
  • The phase noise of the output if worst as you use a higher frequency, at a fixed 250 MHz it’s noticeable but no so bad for a TTL or CMOS clock application.
  • The phase noise is specially bad if you make a sweep or move action beyond 180 MHz; the phase noise from the unlock state to next lock of the PLL is very noticeable in a spectrum analyzer, even on a cheap RTL-SDR one.
  • I recommend to only use the outputs beyond 150 MHz as fixed value and don’t move them if you cares about phase noise.

How to do it?

You need to declare a macro with the overclock value BEFORE the library include, just like this:

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.

A Brief Word about the Si5351 Architecture

The Si5351 consists of two main stages: two PLLs which are locked to the reference oscillator (a 25/27 MHz crystal) and which can be set from 600 to 900 MHz, and the output (multisynth) clocks which are locked to a PLL of choice and can be set from 500 kHz to 200 MHz (per the datasheet, although it does seem to be possible to set an output up to 225 MHz).

The B variant has an additional VCXO stage with control voltage pin which can be used as a reference synth for a clock output (PLLB must be used as the source for any VCXO output clock).

The C variant is able to take a reference clock input from 10 to 100 MHz separate from the standard crystal reference. If using this reference input, be sure to initialize the library with the correct frequency.

This library makes PLL assignments based on ease of use. They can be changed manually if needed, although that can introduce complications (see Manually Selecting a PLL Frequency below).

Sweep Function

Sweep is accessed from a separate menu. Press down the encoder knob for more than two seconds (a long press) and release to enter sweep parameters for the currently selected port.  A short click of the encoder knob will advance the menu through the sweep choices, currently +/-  0, 1000, 5000, 15000, 50000, or 150000 Hz. The unit sweeps from frequency minus that amount to frequency plus that amount so the total width of sweep is twice the setting. Sweeping is done by reprogramming frequencies in 20 steps between the limits.

A second long press of the encoder knob will return to the frequency menu. The letters “sw” appear on the LCD to indicate that that port is set up to sweep. All three ports can be set up but only the port currently selected in the display will be sweeping at any given time.

A pulse is available at a phono jack on top of the box to trigger an oscilloscope at start of each sweep iteration.

Sweep parameters are not currently saved in EEPROM.

Arduino-Si5351 Signal Generator Sweep Setup Screen

S-meter

The only drawback of this library is that it includes only printable ASCII characters, so drawing the S-meter necessitated a bit of font hacking. There are GLCD font editors that generate code from a drawing of your custom character in an X by Y grid. I grabbed one and designed a font of six 10×15 pixel characters — S1, S3, S5, S7, S9, +x dB. The resultant code didn’t render properly, so I got into a hacking mindset, cycling through a tight trial-and-error loop, adjusting various hex digits in the font character array, and observing the result. After about an hour of fairly mindless trial-and-error, I’d sussed out a set of usable characters. The font is available here.

Example

First, install the Si5351Arduino library into your instance of the Arduino IDE as described above.

There is a simple example named si5351_example.ino that is placed in your examples menu under the Si5351Arduino folder. Open this to see how to initialize the Si5351 and set a couple of the outputs to different frequencies. The commentary below will analyze the sample sketch.

Before you do anything with the Si5351, you will need to include the «si5351.h» and «Wire.h» header files and instantiate the Si5351 class.

Now in the Setup() function, let’s initialize communications with the Si5351, specify the load capacitance of the reference crystal, that we want to use the default reference oscillator frequency of 25 MHz (the second argument of «0» indicates that we want to use the default), and that we will apply no frequency correction at this point (the third argument of «0»):

The init() method returns a bool which indicates whether the Arduino can communicate with a device on the I2C bus at the specified address (it does not verify that the device is an actual Si5351, but this is useful for ensuring that I2C communication is working).

Next, let’s set the CLK0 output to 14 MHz:

Frequencies are indicated in units of 0.01 Hz. Therefore, if you prefer to work in 1 Hz increments in your own code, simply multiply each frequency passed to the library by 100ULL (better yet, use the define called SI5351_FREQ_MULT in the header file for this multiplication).

In the main Loop(), we use the Serial port to monitor the status of the Si5351, using a method to update a public struct which holds the status bits:

When the synthesizers are locked and the Si5351 is working correctly, you’ll see an output similar to this one (the REVID may be different):

The nominal status for each of those flags is a 0. When the program indicates 1, there may be a reference clock problem, tuning problem, or some kind of other issue. (Note that it may take the Si5351 a bit of time to return the proper status flags, so in program initialization issue update_status() and then give the Si5351 a few hundred milliseconds to initialize before querying the status flags again.)

Hardware Requirements and Setup

An 8-bit AVR microcontroller with the TWI peripheral is required for this library. It has currently only been tested with the ATmega328P microcontroller, but should be portable to at least the ATmega88 and ATmega168, although the code size is currently about 8 kB (due to the math in the PLL calculations), so you will most likely need at least 16 kB of program space.

The Si5351 is a +3.3 V only part, so if you are not using a +3.3 V microcontroller, be sure you have some kind of level conversion strategy.

Wire the SDA and SCL pins of the Si5351 to the corresponding pins on the AVR. Since the I2C interface is set to 400 kHz, use 1 kΩ pullup resistors from +3.3 V to the SDA and SCL lines.

Connect a 25 MHz or 27 MHz crystal with a load capacitance of 6, 8, or 10 pF to the Si5351 XA and XB pins. Locate the crystal as close to the Si5351 as possible and keep the traces as short as possible. Please use a SMT crystal. A crystal with leads will have too much stray capacitance.

CLK Output Options

Please see the example sketch si5351_outputs.ino

In most cases, you will most likely end up using the multisynth associated with a CLK output, but the Si5351 has some other options available as well. The reference clocks (both the crystal oscillator and the CLKIN signal) can be mirrored to any CLK output. Also CLK1 through CLK3 can mirror the MS0 (CLK0) output, and likewise the CLK5 through CLK7 outputs can mirror the MS4 (CLK4) output.

If you choose to use one or more of these output options, you first need to enable the fanout option for that particular signal:

Once that is done, you can use the set_clock_source() method to choose the output option you desire. Since the CLK outputs by default are turned off, you may need to turn your CLK output on as well:

Constraints

  • Two multisynths cannot share a PLL with when both outputs are >= 100 MHz. The library will refuse to set another multisynth to a frequency in that range if another multisynth sharing the same PLL is already within that frequency range.
  • Setting phase will be limited in the extreme edges of the output tuning ranges. Because the phase register is 7-bits in size and is denominated in units representing 1/4 the PLL period, not all phases can be set for all output frequencies. For example, if you need a 90° phase shift, the lowest frequency you can set it at is 4.6875 MHz (600 MHz PLL/128).
  • The frequency range of Multisynth 6 and 7 is ~18.45 kHz to 150 MHz. The library assigns PLLB to these two multisynths, so if you choose to use both, then both frequencies must be an even divisor of the PLL frequency (between 6 and 254), so plan accordingly. You can see the current PLLB frequency by accessing the pllb_freq public member.
  • VCXO pull range can be ±30 to ±240 ppm

Phase

Please see the example sketch si5351_phase.ino

The phase of the output clock signal can be changed by using the set_phase() method. Phase is in relation to (and measured against the period of) the PLL that the output multisynth is referencing. When you change the phase register from its default of 0, you will need to keep a few considerations in mind.

Setting the phase of a clock requires that you manually set the PLL and take the PLL frequency into account when calculation the value to place in the phase register. As shown on page 10 of Silicon Labs Application Note 619 (AN619), the phase register is a 7-bit register, where a bit represents a phase difference of 1/4 the PLL period. Therefore, the best way to get an accurate phase setting is to make the PLL an even multiple of the clock frequency, depending on what phase you need.

If you need a 90 degree phase shift (as in many RF applications), then it is quite easy to determine your parameters. Pick a PLL frequency that is an even multiple of your clock frequency (remember that the PLL needs to be in the range of 600 to 900 MHz). Then to set a 90 degree phase shift, you simply enter that multiple into the phase register. Remember when setting multiple outputs to be phase-related to each other, they each need to be referenced to the same PLL.

You can see this in action in a sketch in the examples folder called si5351phase. It shows how one would set up an I/Q pair of signals at 14.1 MHz.

Calibration

There will be some inherent error in the reference oscillator’s actual frequency, so we can account for this by measuring the difference between the uncalibrated actual and nominal output frequencies, then using that difference as a correction factor in the library. The init() and set_correction() methods use a signed integer calibration constant measured in parts-per-billion. The easiest way to determine this correction factor is to measure a 10 MHz signal from one of the clock outputs (in Hz, or better resolution if you can measure it), scale it to parts-per-billion, then use it in the set_correction() method in future use of this particular reference oscillator. Once this correction factor is determined, it should not need to be measured again for the same reference oscillator/Si5351 pair unless you want to redo the calibration. With an accurate measurement at one frequency, this calibration should be good across the entire tuning range.

The calibration method is called like this:

However, you may use the third argument in the init() method to specify the frequency correction and may not actually need to use the explict set_correction() method in your code.

A handy calibration program is provided with the library in the example folder named si5351_calibration. To use it, simply hook up your Arduino to your Si5351, then connect it to a PC with the Arduino IDE. Connect the CLK0 output of the Si5351 to a frequency counter capable of measuring at 10 MHz (the more resolution, the better). Load the sketch then open the serial terminal window. Follow the prompts in the serial terminal to change the output frequency until your frequency counter reads exactly 10.000 000 00 MHz. The output from the Arduino on your serial terminal will tell you the correction factor you will need for future use of that reference oscillator/Si5351 combination.

One thing to note: the library is set for a 25 MHz reference crystal. If you are using a 27 MHz crystal, use the second parameter in the init() method to specify that as the reference oscillator frequency.

Operation

On power up the unit reads saved frequency settings from EEPROM. The display then shows frequency, current port selected, and the output power setting.

Click one of the three buttons to change the port selected to display on the LCD.

Rotating the encoder knob will change the frequency digit under the flashing cursor. Press click the encoder knob to change the digit under the cursor, you can set the cursor to change digits from 1 Hz to 10 MHz.

Hold one of the port select buttons down and turn the encoder knob to change output power for that port. The chip has choices of 8 milliamps, 6 mA, 4 mA, 2 mA, and OFF. Silicon Labs’ spec for driver impedance is 50-85 ohms.

If any of the above settings are changed, the software waits ten seconds, then copies the current settings into EEPROM. In the event the unit gets confused, it can be restored to last saved settings by cycling the power switch. Also it can be returned to default settings by holding down all three port select buttons, while powering up.

Arduino-Si5351 Signal Generator Frequency/Power Setting

The Box

A thin metal gift card box was cut up to form an enclosure for the generator. It is about a quarter inch larger than the usual Altoids tin in all three dimensions. I needed the extra volume to fit in a battery and charger removed from a cheap phone power pack. These booster packs usually contain a single 18650 cell and it just fits.

Arduino-Si5351 Signal Generator Interior

The components are, top to bottom, blue 16×2 LCD board supporting the Teensy-LC. The entire unit can be 5 volt powered either from the Teensy USB jack or from the battery charger, I added a fat diode to isolate the two sources.  Both the Teensy and the Si5351 board are using their on board voltage regulators. The LCD is five volt, but the Teensy-LC does not have 5 volt tolerant inputs. There are 2.2k resistors between the LCD data pins and the Teensy to smooth over that discrepancy, these would not be necessary if the LCD was a 3.3 volt unit. The pot below the Teensy adjusts the LCD contrast.

At left below the LCD is the Adafruit (Bourns) rotary encoder. It just clears the battery. Right of the encoder is a strip of perf board with the three output select pushbuttons mounted. The LCD, encoder and buttons are Adafruit parts.

At the top of the open box bottom is the salvaged 18650 cell. The measured drain of the whole circuit is less than 100 milliamps so the battery should last half a day at least. The gooey glob at the center is a 2.5 amp fuse. To the right of the battery is the on/off switch and isolation diode. The salvaged Lithium charge/boost circuit is the green board at bottom right. It has it’s own separate USB jack because the USB data leads are not accessible. To program the unit you have to plug into the Teensy USB jack.

Finally the blue board in the bottom is the Etherkit breakout. The actual Si5351 chip is at the left end, next to the TCXO. There are three RCA output jacks mounted in the left side of the box, the fourth jack next to the handle is for syncing the scope when sweeping.

Device Description

For a long time I wanted a general purpose signal generator. Now Direct Digital Synthesizer hardware is available on a single programmable chip. The Analog Devices AD9851 is used in many ham radio projects, also widely used is the Silicon Labs Si5351. Either of these can be obtained from many sources such as Adafruit, Sparkfun, or on EBay and there’s lots of information on the internet. Even Amazon has them. A few dollars gets a DDS chip that will tune continuously from audio to VHF mounted on a small breakout board. I purchased an Si5351 board from Etherkit because they offer a version with a TCXO.

Silicon Labs makes the Si5351 in several variations. It’s intended use is as a multiple output clock generator with up to eight individually programmed frequencies. Most commonly available breakout boards though, use the A version with three outputs. Digikey has the bare chips for less than $1. Connectors are optional, most boards are set up for SMA female jacks. The signal generator I built brings out all three outputs but I used good old RCA phono jacks. SMA connectors are wonderful but the cables to use them are pricey. My box will be used to check and align receivers so precision impedance control is less important.

AD9851 chips have a real Digital/Analog converter on board, it uses an amplitude lookup table to produce a fair sine wave. The data sheet says:

“The AD9851 uses an innovative and proprietary angle rotation algorithm that mathematically converts the 14-bit truncated value of the 32-bit phase accumulator to the 10-bit quantized amplitude that is passed to the DAC. This unique algorithm uses a much-reduced ROM look-up table and DSP to perform this function.”

which sounds suspiciously like the quarter wave sine synthesis I experimented with a few years ago. I’ll have to revise my WordPress pages about quarter wave DDS techniques to include the phrase “angle rotation algorithm”. The price for pure sine wave complexity is, the device has only a single output. In contrast, the Si5351 outputs a square wave on all it’s ports – it is intended to serve as a programmable frequency clock source but for my purposes I don’t care and the available multiple outputs are attractive.

I breadboarded the Etherkit breakout along with one of Paul Stoffregen’s Teensy-LCs I had on hand for the controller. A Teensy-LC has an ARM Cortex M0 heart clocked at 48 Mhz. An ordinary 16 Mhz Arduino would probably work in this circuit but would have trouble with sweep. I used basic software from N6QW to get started and my oscilloscope soon showed it was working. Then a series of enhancements to the N6QW sketch followed:

  • expand to controlling all three outputs
  • setting up frequencies using rotary encoder and LCD menus
  • save and restore setup in EEPROM
  • add output power change to the menus
  • add  output OFF to the power menu
  • add sweep capability with menu control