Library to control Silicon Labs SI570 10 MHZ TO 1.4 GHZ I2C PROGRAMMABLE XO/VCXO.

Dependents:   t2d Thing2Do

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

/media/uploads/soldeerridder/si570_sr.jpg

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

Reference

Revision:
0:dae1bf95c49e
--- /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 );
+}
+