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
Revision 0:dae1bf95c49e, committed 2010-11-09
- Comitter:
- soldeerridder
- Date:
- Tue Nov 09 20:56:52 2010 +0000
- Commit message:
- added documentation
Changed in this revision
SI570.cpp | Show annotated file Show diff for this revision Revisions of this file |
SI570.h | Show annotated file Show diff for this revision Revisions of this file |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SI570.cpp Tue Nov 09 20:56:52 2010 +0000 @@ -0,0 +1,310 @@ +/* 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 ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SI570.h Tue Nov 09 20:56:52 2010 +0000 @@ -0,0 +1,150 @@ +/* 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. + */ + +#ifndef MBED_SI570_H +#define MBED_SI570_H + +#include "mbed.h" + +//these must be floating point number especially 2^28 so that +//there is enough memory to use them in the calculation +#define POW_2_16 65536.0 +#define POW_2_24 16777216.0 +#define POW_2_28 268435456.0 +#define FOUT_START_UP 56.320 //MHz +#define PPM 3500 // +/- max ppm from center frequency +#define FDCO_MAX 5670.0 //MHz +#define FDCO_MIN 4850.0 //MHz + + +/** Interface to the popular SI570 VCXO */ +class SI570 { +public: + /** Create an instance of the SI570 connected to specified I2C pins, with the specified address. + * + * Example: + * @code + * #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()); + * } + * } + * @endcode + * + * @param sda The I2C data pin + * @param scl The I2C clock pin + * @param address The I2C address for this SI570 + */ + SI570(PinName sda, PinName scl, int address); + + /** Resets the SI570. + * + * Resets and reads the startup configuration from the Si570 + */ + void si570reset(void); + + /** Read the current frequency. + * + * get the SI570 registers an calculate the current frequency + * + * @returns the current frequency + */ + double get_frequency(void); + + /** Read the current rfreq value. + * + * get the SI570 registers an calculate the current rfreq + * + * @returns the current rfreq + */ + double get_rfreq(void); + + /** Read the current n1. + * + * get the SI570 registers an calculate the current n1 + * + * @returns the current n1 + */ + int get_n1(void); + + /** Read the current hsdiv. + * + * get the SI570 registers an calculate the current hsdiv + * + * @returns the current hsdiv + */ + int get_hsdiv(void); + + /** Set a new frequency. + * + * Set the SI570 registers to output frequency + * When the new frequency is within the 3500 PPM range of the center frequency, hsdiv and n1 needs not to be reprogrammed, resulting in a glitch free transition. + * For changes larger than 3500 PPM, the process of freezing and unfreezing the DCO will cause the output clock to momentarily stop and start at any arbitrary point during a clock cycle. This process can take up to 10 ms. + * + * @param frequency the frequency to set. + * @returns the current frequency + */ + int set_frequency(double frequency); + + +private: + I2C _i2c; + int _address; + char cmd[2]; + char buf[6]; + unsigned char n1; + unsigned char hsdiv; + unsigned long frac_bits; + double rfreq; + double fxtal_device; + double currentFreq; + double currentRfreq; + + void get_registers(void); + int set_frequency_small_change(double currentFrequency); + int set_frequency_large_change(double currentFrequency); + + unsigned char SetBits(unsigned char original, unsigned char reset_mask, unsigned char new_val); +}; + + +#endif \ No newline at end of file