Library to control Silicon Labs SI570 10 MHZ TO 1.4 GHZ I2C PROGRAMMABLE XO/VCXO.
This is the page for the Silicon Laboratories Si570 frequency synthesizer, with I2C interface.
The Si570 XO/Si571 VCXO utilizes Silicon Laboratories’ advanced DSPLL® circuitry to provide a low-jitter clock at any frequency. The Si570/Si571 are user-programmable to any output frequency from 10 to 945 MHz and select frequencies to 1400 MHz with <1 ppb resolution. The device is programmed via an I2C serial interface. Unlike traditional XO/VCXOs where a different crystal is required for each output frequency, the Si57x uses one fixed- frequency crystal and a DSPLL clock synthesis IC to provide any-frequency operation. This IC-based approach allows the crystal resonator to provide exceptional frequency stability and reliability. In addition, DSPLL clock synthesis provides superior supply noise rejection, simplifying the task of generating low-jitter clocks in noisy environments typically found in communication systems.
The Si570 is very popular for amateur radio use. It can be used as the local oscillator in a superheterodyne receiver, or it can be the local oscillator in a direct conversion quadrature receiver for software defined radio (SDR) such as the Softrock. In addition to its use inside a receiver, the Si570 kit can be used as a stand-alone signal source for test and measurement and for other purposes, such as a VFO for your old Heathkit DX40 for that matter. Just keep in mind that the Si570's output is a square wave and may require additional filtering for some purposes.
Image of the SI570 in action
Hello World!
#include "mbed.h" #include "TextLCD.h" #include "SI570.h" #include "QEI.h" TextLCD lcd(p11, p12, p15, p16, p29, p30); // rs, e, d0-d3 SI570 si570(p9, p10, 0xAA); QEI wheel (p5, p6, NC, 360); int main() { int wp,swp=0; float startfreq=7.0; float freq; while (1) { wp = wheel.getPulses(); freq=startfreq+wp*0.00001; if (swp != wp) { si570.set_frequency(freq); swp = wp; } lcd.locate(0,0); lcd.printf("%f MHz", si570.get_frequency()); } }
Links
- NXP mbed design challenge entry: Si570-Based Universal HAM Radio VFO
- www.agri-vision.nl (home of this project)
- Experiments with software defined radio (SDR) using Softrock, the Si570 VCXO, and a hacked Linux based ADM5120 router to control the Si570, using a web browser.
- Review of Si570 Frequency Synthesizer Kit from K5JHF and K5BCQ
- DG8SAQ - Si570 control with AVR
Reference
SI570.cpp
- Committer:
- soldeerridder
- Date:
- 2010-11-09
- Revision:
- 0:dae1bf95c49e
File content as of revision 0:dae1bf95c49e:
/* mbed SI570 Library, for driving the SI570 programable VCXO * Copyright (c) 2010, Gerrit Polder, PA3BYA * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "SI570.h" #include "mbed.h" SI570::SI570(PinName sda, PinName scl, int address) : _i2c(sda, scl) { _address = address; si570reset(); } void SI570::si570reset(void) { _i2c.frequency(100000); cmd[0] = 135; // reset cmd[1] = 0x01; // _i2c.write(_address, cmd, 2); // Send command string get_registers(); fxtal_device = (FOUT_START_UP * n1 * hsdiv) / rfreq; //MHz currentFreq = FOUT_START_UP; } void SI570::get_registers() { // Set pointer to location 7 (first echo) cmd[0] = 0x7; _i2c.write(_address, cmd, 1); _i2c.read(_address, buf, 6); // read the six-byte result // HS_DIV conversion hsdiv = ((buf[0] & 0xE0) >> 5) + 4; // get reg 7 bits 5, 6, 7 // hsdiv's value could be verified here to ensure that it is one // of the valid HS_DIV values from the datasheet. // n1 conversion n1 = (( buf[0] & 0x1F ) << 2 ) + // get reg 7 bits 0 to 4 (( buf[1] & 0xC0 ) >> 6 ); // add with reg 8 bits 7 and 8 if (n1 == 0) { n1 = 1; } else if (n1 & 1 != 0) { // add one to an odd number n1 = n1 + 1; } frac_bits = (( buf[2] & 0xF ) * POW_2_24 ); frac_bits = frac_bits + (buf[3] * POW_2_16); frac_bits = frac_bits + (buf[4] * 256); frac_bits = frac_bits + buf[5]; rfreq = frac_bits; rfreq = rfreq / POW_2_28; rfreq = rfreq + ( (( buf[1] & 0x3F ) << 4 ) + (( buf[2] & 0xF0 ) >> 4 ) ); } double SI570::get_frequency(void) { get_registers(); return (rfreq*fxtal_device)/(hsdiv*n1); } double SI570::get_rfreq(void) { get_registers(); return rfreq; } int SI570::get_n1(void) { get_registers(); return n1; } int SI570::get_hsdiv(void) { get_registers(); return hsdiv; } int SI570::set_frequency(double frequency) { int err; float diff = 1000000 * (abs(frequency - currentFreq) / currentFreq); if (diff < PPM) { err = set_frequency_small_change(frequency); } else { err = set_frequency_large_change(frequency); } return err; } int SI570::set_frequency_small_change(double frequency) { unsigned char reg135; unsigned int whole; unsigned char counter; int i; char reg[6]; rfreq = currentRfreq * frequency / currentFreq; cmd[0] = 0x8; _i2c.write(_address, cmd, 1); _i2c.read(_address, buf, 1); // read register 0x8 reg[1] = buf[0]; reg[2] = 0; // convert new RFREQ to the binary representation // separate the integer part whole = floor(rfreq); // get the binary representation of the fractional part frac_bits = floor((rfreq - whole) * POW_2_28); // set reg 12 to 10 making frac_bits smaller by // shifting off the last 8 bits everytime for (counter=5; counter >=3; counter--) { reg[counter] = frac_bits & 0xFF; frac_bits = frac_bits >> 8; } // set the last 4 bits of the fractional portion in reg 9 reg[2] = SetBits(reg[2], 0xF0, (frac_bits & 0xF)); // set the integer portion of RFREQ across reg 8 and 9 reg[2] = SetBits(reg[2], 0x0F, (whole & 0xF) << 4); reg[1] = SetBits(reg[1], 0xC0, (whole >> 4) & 0x3F); // Load the new frequency // get the current state of register 137 buf[0]=135; _i2c.write(_address, buf, 1); _i2c.read(_address, buf, 1); reg135 = buf[0]; // set the Freeze M bit in that register buf[0]=135; buf[1]=reg135 | 0x20; _i2c.write(_address, buf, 2); // load the new values into the device at registers 8 to 12; buf[0]=8; for (i=1;i<6;i++) { buf[i]=reg[i]; } _i2c.write(_address, buf, 6); // get the current state of register 135 buf[0]=135; _i2c.write(_address, buf, 1); _i2c.read(_address, buf, 1); reg135 = buf[0]; // clear the M bit in that register buf[0]=135; buf[1]= reg135 & 0xDF; _i2c.write(_address, buf, 2); return 0; } int SI570::set_frequency_large_change(double frequency) { const unsigned char HS_DIV[6] = {11, 9, 7, 6, 5, 4}; int i; // float ratio = 0; unsigned char counter; unsigned char reg137; char buf[7]; char reg[6]; unsigned int divider_max; unsigned int curr_div; unsigned int whole; unsigned char validCombo; float curr_n1; float n1_tmp; // find dividers (get the max and min divider range for the HS_DIV and N1 combo) divider_max = floor(FDCO_MAX / frequency); //floorf for SDCC curr_div = ceil(FDCO_MIN / frequency); //ceilf for SDCC validCombo = 0; while (curr_div <= divider_max) { //check all the HS_DIV values with the next curr_div for (counter=0; counter<6; counter++) { // get the next possible n1 value hsdiv = HS_DIV[counter]; curr_n1 = (curr_div * 1.0) / (hsdiv * 1.0); // determine if curr_n1 is an integer and an even number or one // then it will be a valid divider option for the new frequency n1_tmp = floor(curr_n1); n1_tmp = curr_n1 - n1_tmp; if (n1_tmp == 0.0) { //then curr_n1 is an integer n1 = (unsigned char) curr_n1; if ( (n1 == 1) || ((n1 & 1) == 0) ) { // then the calculated N1 is either 1 or an even number validCombo = 1; } } if (validCombo == 1) break; } if (validCombo == 1) break; //increment curr_div to find the next divider //since the current one was not valid curr_div = curr_div + 1; } // if(validCombo == 0) at this point then there's an error // in the calculation. Check if the provided frequencies // are valid. if (validCombo == 0) return -1; rfreq = (frequency * n1 * hsdiv) / fxtal_device; //using float for (counter = 0; counter < 6; counter++) { reg[counter] = 0; //clear registers } // new HS_DIV conversion hsdiv = hsdiv - 4; //reset this memory reg[0] = 0; //set the top 3 bits of reg 13 reg[0] = (hsdiv << 5); // convert new N1 to the binary representation if (n1 == 1) n1 = 0; else if ((n1 & 1) == 0) n1 = n1 - 1; //if n1 is even, subtract one // set reg 7 bits 0 to 4 reg[0] = SetBits(reg[0], 0xE0, n1 >> 2); // set reg 8 bits 6 and 7 reg[1] = (n1 & 3) << 6; // convert new RFREQ to the binary representation // separate the integer part whole = floor(rfreq); // get the binary representation of the fractional part frac_bits = floor((rfreq - whole) * POW_2_28); // set reg 12 to 10 making frac_bits smaller by // shifting off the last 8 bits everytime for (counter=5; counter >=3; counter--) { reg[counter] = frac_bits & 0xFF; frac_bits = frac_bits >> 8; } // set the last 4 bits of the fractional portion in reg 9 reg[2] = SetBits(reg[2], 0xF0, (frac_bits & 0xF)); // set the integer portion of RFREQ across reg 8 and 9 reg[2] = SetBits(reg[2], 0x0F, (whole & 0xF) << 4); reg[1] = SetBits(reg[1], 0xC0, (whole >> 4) & 0x3F); // Load the new frequency // get the current state of register 137 buf[0]=137; _i2c.write(_address, buf, 1); _i2c.read(_address, buf, 1); reg137 = buf[0]; // set the Freeze DCO bit in that register buf[0]=137; buf[1]=reg137 | 0x10; _i2c.write(_address, buf, 2); // load the new values into the device at registers 7 to 12; buf[0]=7; for (i=1;i<7;i++) { buf[i]=reg[i-1]; } _i2c.write(_address, buf, 7); // get the current state of register 137 buf[0]=137; _i2c.write(_address, buf, 1); _i2c.read(_address, buf, 1); reg137 = buf[0]; // clear the FZ_DCO bit in that register buf[0]=137; buf[1]= reg137 & 0xEF; _i2c.write(_address, buf, 2); // set the NewFreq bit, bit will clear itself once the device is ready buf[0]=135; buf[1]= 0x40; _i2c.write(_address, buf, 2); currentFreq = frequency; currentRfreq = rfreq; return 0; } unsigned char SI570::SetBits(unsigned char original, unsigned char reset_mask, unsigned char new_val) { return (( original & reset_mask ) | new_val ); }