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

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 );
}