/* 10/13/15 - fixed an error in buildSiWords() that caused some bands to be considerably off in frequency. Uploading new version to web site 10/15/2015. A little more info about what this program is and how a user might apply it: It's sort of like a library in that everything needed to accept a desired frequency value and have the Si570 go to that frequency is contained. But it's a simple program running in demo mode - it starts up on 14.025 MHz and steps up and down in 10 Hz steps. As a user, you'd get rid of the code in the main loop that does the stepping and add your own depending on your program logic. The main thing to know is that all you have to do to have the Si570 go to MY_FREQUENCY (which should be a 32 bit integer) is to make these calls: UpdateFrequency(MY_FREQUENCY); send_new_freq(); There are a couple other things you might change. The calibrate() routine in the setup() function gets called every startup. You could make it optional or not done at all. And be aware that the start-up frequency my Si570 was factory programmed for (56.32 MHz) might not be the same as yours. So you'd change this line: " #define STARTUP_FREQ 56320000UL" to whatever your Si570 comes up on. Also, there are some serial port outputs that are for troubleshooting and user information. You can remove those if desired. If you want to learn something about your Si570's internal register startup values, press control-shift-M in your Arduino IDE before running (or restarting) this program and the serial monitor will pop up and show you the numbers. */ /* *************************************************************************** * Si570_New Rev 1.0 * * * * A controller for the Si570 frequency synthesizer * * * * by Nick Kennedy WA5BDU * * * * Started 7/9/2015 Basic version functional 7/17/2015 * * * * Methods for quickly calculating register information for specific * * frequency adapted from the PIC program by Craig Johnson AA0ZZ, * * which is described in QEX July / August 2011 * * * * This version is not intended to be a stand-alone controller, but instead * * to be a functional program with all of the routines needed to take a * * frequency, convert it to the 6 data registers needed by the Si570, and * * send the data to the Si570 via the I2C bus on the Arduino. It will also * * perform band limit checking and local tuning limit checking and perform * * freeze routines and/or band calculation constant generation as needed. * * A calibration routine is included, which takes the vendor's calibration * * constants and uses them to convert the nominal crystal frequency to the * * actual frequency based on the vendor's initial calibration. * * * * Developed using the Arduino Nano with an ATMega328 MCU. * * ************************************************************************** */ // HARDWARE: /* * Only two connections plus ground are needed between a typical * Si570 card and the Arduino. Connections aren't usually direct but * require a bidirectional 5 V to 3.3 V logic shifter. See the AA0ZZ article * for a simple shifter using two MOSFETs and two resistors. SDA of * the Si570 goes to A4 of the Nano and SCL goes to A5. * * A hardware related caution: I found that RF out can actually * interfere with the I2C communicaitons. It works much better if * the RF output is terminated in a 50 ohm load. So beware and use * good practice regarding noise, etc. */ // Be aware that some Nano documention gives the wrong pin numbers for the // I2C SDA and SCL pins #include #include // NOTE: The frequency given below is the start-up frequency for the // Si570. It is used in the calibration routine so it should be the actual // start-up frequency of the user's chip. Many chips bought for Softrock // designs were purchased to start on 56.320 MHz, which is four times // the target 14.080 MHz startup frequency. Others bought with a // default frequency may start up on 10.000 MHz. Edit the line below // as required: #define STARTUP_FREQ 56320000UL #define SI570_ADDRESS (0x55) #define LLIMIT 10000000UL #define ULIMIT 157000000UL // uint8_t band_index; // Index into tables used for register value calcs boolean band_changed = false; // true if Fout change caused band change boolean NeedFreeze = true; // if latest increment causes > +/- 0.35% change uint32_t Fout; // Variable used in calculating reg. values for desired freq unsigned long Fxtal = 114285000; // nominal - calc routine gets actual uint64_t RFREQ; uint64_t centerRFREQ; // RFREQ at last freeze: center for +/- 3500 ppm limit uint8_t band_index; // ************* TABLES ... ADAPTED FROM AA0ZZ PIC VERSION ************* // Below are band edge limits. Routines find the band for requested Fout // and from that develop an index into pre-calculated constants for the band uint32_t SiBandTable[24] = {10000000, 11000000, 12000000, 13000000, 15000000, 17000000, 19000000, 21000000, 23000000, 25000000, 28000000, 32000000, 36000000, 41000000, 47000000, 54000000, 61000000, 70000000, 81000000, 90000000, 101000000, 111000000, 128000000, 135000000}; // Below tables are predetermined values for Si570 values appropriate for each // of the 24 bands ... uint8_t BandN1Minus1[24] = {46, 50, 38, 34, 30, 26, 24, 22, 20, 18, 16, 22, 34, 20, 26, 10, 16, 10, 10, 6, 8, 4, 8, 4}; uint8_t BandHSDIVIndex[24] = {11, 9, 11, 11, 11, 11, 11, 11, 11, 11, 11, 7, 4, 6, 4, 9, 5, 7, 6, 9, 6, 11, 5, 9}; uint8_t BandHSN1D2[24] = {0xFD, 0xE1, 0XD1, 0xBB, 0xA5, 0x8F, 0x84, 0x79, 0x6E, 0x63, 0x58, 0x4D, 0x44, 0x3C, 0x34, 0x2D, 0x28, 0x23, 0x1E, 0x1B, 0x18, 0x16, 0x14, 0x12}; // Step sizes for use when incrementing & decrement Fout uint32_t TuningStepSize[7] = {1, 10, 100, 1000, 10000, 100000, 1000000}; char step_index = 1; // start with 100 Hz steps // Below, the array of FxFactors, one per band is the coefficient that is // custom calculated based on the determination of the actual Fxtal in the Si570 // The value is FxFactor = Fxtal * 256 / (HS_DIV * N1) // The '* 256' factor is to minimize truncation of digits in the division // operation and is cancelled out when we do the RFRREQ calculation. FxFactor // values are calculated at run time based on the actual value of Fxtal and // stored in this table for quick access. uint32_t FxFactor[24]; // ****** Si570 Registers ************************************************* // Defined in a byte array, 0 through 5 are registers 7 through 12 // [6] is register 135 and [7] is register 137 uint8_t SiRegs[8]; // ************************* S E T U P **************************************** void setup() { // The Serial library is used for development and troubleshooting and can be // commented out of the final product. To open the serial monitor from the // Arduino IDE, press control-shift-M. Serial.begin(9600); Wire.begin(); // Initialize I2C, no argument means as master freeze_dco(); // Make it RF quiet during reading of registers delay(1000); // Let the Si570 power up calibrate(); // Retrive start-up regs and calc Fxtal from them unfreeze(); calc_FxFactors(); // Complete table of FxFactors based on Fxtal } // *************************** M A I N L O O P ****************************** void loop() { // while(1) // Removed this test. Function still exists though // { // ScanBands(); // } byte I2Cerror; // for troubleshooting I2C problems Fout = 14025000; UpdateFrequency(); // Calculates regs for current Fout, check limits send_new_freq(); // Sends current regs to Si570 uint8_t x; // After calibration, this program's function is to step frequency up // ten 10 Hz steps and then down ten 10 Hz steps and repeat. while(1) { for (x = 10; x != 0; x--) { step_up(); UpdateFrequency(); I2Cerror = send_new_freq(); //Serial.print("Error: "); // Uncomment to see any I2C error if desired //Serial.println(I2Cerror); // (4 places. 0 means no error) delay(1000); } for (x = 10; x != 0; x--) { step_down(); UpdateFrequency(); I2Cerror = send_new_freq(); //Serial.print("Error: "); //Serial.println(I2Cerror); delay(1000); } } } // *********** UPDATE FREQUENCY ********************************************** void UpdateFrequency() { if (Fout < LLIMIT) Fout = LLIMIT; if (Fout > ULIMIT) Fout = ULIMIT; detect_band_change(); // change band_index if needed, flag change calcRFREQ(); calcSpan(); // see if freeze needed, flag if so buildSiWords(); } // ******************* DETECT BAND CHANGE ************************************ void detect_band_change() { uint8_t x; band_changed = false; for (x=0; x<23; x++) { if (!(Fout > SiBandTable[x+1])) // if not greater than new one, current one is right { if (x != band_index) { band_changed = true; band_index = x; } break; } } } // ********************** CALCULATE FX FACTORS ***************************** // // These factors are calculated at run time rather than hard coded because // Fxtal varies a bit from part to part. The actual Fxtal might be determined // by this program in a calibration routine or determined by the user and har // coded into the source code. // The number Fout * 2^28 * 256 will be divided by the band-appropriate // FxFactor to produce RFREQ void calc_FxFactors() { uint8_t x; for (x=0; x<24; x++) { FxFactor[x] = ((uint64_t)Fxtal * 0x100) / (BandHSN1D2[x]*2); } } // *********************** CALCULATE RFREQ ************************************** void calcRFREQ() { RFREQ = ((uint64_t)Fout * 0x1000000000ULL)/FxFactor[band_index]; } // ************** CALC SPAN CHECKS NEED FOR FREEZE WITH > 3500 PPM MOVEMENT ******* // Verified sets flag correctly at +/- 0.3% (false) and +/- 0.37% (true) void calcSpan() { uint32_t diff; NeedFreeze = false; if (RFREQ > centerRFREQ) {diff = RFREQ - centerRFREQ;} else {diff = centerRFREQ - RFREQ;} if (diff > 39873403UL) NeedFreeze = true; } // ************** BUILD Si WORDS - ASSEMBLE Si570 REGISTERS ************************* void buildSiWords() { uint64_t tempRFREQ; char x; // (a char is a signed byte) uint8_t y; tempRFREQ = RFREQ; for (x=5; x > 0; x--) { SiRegs[x] =(uint8_t)tempRFREQ; tempRFREQ = tempRFREQ >> 8; } // Next I'll need to fix register 8 which is SiRegs[1] to include N1[1:0] y = (BandN1Minus1[band_index]-1) << 6; // move (1:0) up to (8:7) spot SiRegs[1] |= y; // assumed high bits were already clear due to RFREQ size SiRegs[0] = (BandN1Minus1[band_index] -1)>> 2; // Put (6:2) in (4:0) // Revised 10/13/2015 - in the line above, I forgot to subtract 1 from the value // most bands weren't affected but for some of the 24 bands, the output frequency was // seriously off. // Now I need to find the index value for HS_DIV based on the actual // HS_DIV for the current band, and put in (7:5) of SiReg #7 or SiRegs[0] SiRegs[0] |= (BandHSDIVIndex[band_index]-4) << 5; // assumed high 3 bits were clear ... } // *********************** STEP UP AND STEP DOWN FREQUENCY ****************** void step_up() { Fout += TuningStepSize[step_index]; } void step_down() { Fout -= TuningStepSize[step_index]; } // ****************** INCREASE AND DECREASE STEP SIZE *********************** // There are 7 step sizes, 0 through 6 step_index void stepSize_up() { step_index++; if (step_index == 7) step_index = 0; // roll over } void stepSize_down() { step_index--; if (step_index < 0) step_index = 6; // roll under } // ****************** I2C COMMUNICATIONS ************************************* byte send_new_freq() { if (NeedFreeze || band_changed) { freeze_dco(); } // In my array SiRegs[], 0 through 5 are registers 7 through 12 byte errorbyte = 1; char x; Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(7); // send lowest register address to slave for (x=0; x < 6; x++) Wire.write(SiRegs[x]); errorbyte = Wire.endTransmission(); if (NeedFreeze || band_changed) { unfreeze(); NeedFreeze = false; band_changed = false; centerRFREQ = RFREQ; // we've re-centered by freezing } return errorbyte; } void freeze_dco() { Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(137); // Freeze DCO register. Set bit 4 to freeze ... Wire.write(B00010000); Wire.endTransmission(); } void unfreeze() { Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(137); // unfreeze DCO register. Clear bit 4 to freeze ... Wire.write(B00000000); Wire.endTransmission(); Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(135); // register to set new frequency bit b6 ... Wire.write(B01000000); Wire.endTransmission(); } // ********************* CALIBRATE **************************************** // Calibrate will read the registers and extract N1, HSDIV and RFREQ and // use them to calculate the actual Fxtal // Actual N1 used in calculation is stored value plus 1 // Actual HSDIV used in calculation is stored value plus 4 // RFREQ value as extracted is 2^28 times value used in calculation, but it // is retained to keep resolution and offset by another 2^28 in the numerator // // Fxtal is calculated as: // Fout * HSDIV * N1 // Fxtal = ------------------------- // RFREQ // With my 56.32 MHz startup frequency, the numerator above is in the range // of 2^33 and the denominator is about 2^34 (includes the 2^28 factor) // I need to get the numerator as close as possible to 2^63 to use full // resolution of a long long unsigned integer without overflowing. So first, I // already needed to multiply by 2^28 to cancel the factor in RFREQ. That // means I can stand 2^2 additional multiplier. So I multiply all that out and // do my division, then divide the result by 4 to get rid of that extra factor // and I should have my 1 Hz resolution Fxtal. // The user might want to remove all of the Serial calls in the function, // which tell the user the register values and calculated values via // the serial port. To see them, press control-shift-M from the Arduino // IDE just after uploading completes. This opens the serial port // screen. Or open the screen and press RESET on the Arduino. void calibrate() { uint8_t data = 0; uint8_t b7, b8, b9, b10, b11, b12; uint8_t N1c, HSDIVc; uint64_t RFREQc; uint32_t top_part; uint32_t lower_part; Fout = STARTUP_FREQ; // Startup frequency for my specific part # of Si570 // Here I'm going to set the RECALL bit, telling the Si570 to reload // its data from NVM to RAM Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(135); Wire.write(B00000001); // RECALL bit Wire.endTransmission(); Wire.beginTransmission(SI570_ADDRESS); // tell slave something's coming Wire.write(7); // send lowest register address to slave Wire.endTransmission(); Wire.beginTransmission(SI570_ADDRESS); data = Wire.requestFrom(SI570_ADDRESS, 6, false); // getting 6 bytes starting at address 7 Wire.endTransmission(); Serial.println("\nAll six control bytes in hex, 7 to 12:"); delay(50); b7=Wire.read(); Serial.print(b7, HEX); b8=Wire.read(); Serial.print(b8, HEX); b9=Wire.read(); Serial.print(b9, HEX); b10=Wire.read(); Serial.print(b10, HEX); b11=Wire.read(); Serial.print(b11, HEX); b12=Wire.read(); Serial.println(b12, HEX); Serial.println("\nNext extract values for HS_DIV & N1 (binary):\n"); Serial.print ("HS_DIV = "); Serial.println (b7 >> 5, BIN); // HS_DIV is top 3 bits of b7 Serial.print("N1 = "); Serial.println (((b7&B00011111) << 2) | (b8 >> 6), BIN); // N1 is 7 bit number, lower 6 of b7 MSDs and upper 2 of b8 LSDs // Calculation uses stored value of N1, + 1 // Calculation uses stored value of HSDIV, +4 N1c = (((b7&B00011111) << 2) | (b8 >> 6)) +1; HSDIVc = (b7 >> 5) + 4; Serial.print("Reg 7 = "); Serial.println(b7, HEX); Serial.print("Reg 8 = "); Serial.println(b8, HEX); Serial.print("Reg 9 = "); Serial.println(b9, HEX); Serial.print("reg 10 = "); Serial.println(b10, HEX); Serial.print("Reg 11 = "); Serial.println(b11, HEX); Serial.print("Reg 12 = "); Serial.println(b12, HEX); RFREQc =(((uint64_t) b8 & B00111111) << 32) + ((uint64_t) b9 << 24) +( (uint32_t)b10 << 16) +( (uint16_t) b11 << 8) + b12; // Now time to do the math: Serial.println("Below are adjusted N1 HSDIV, RFREQ, Fout, in decimal: "); Serial.print("N1 = "); Serial.println(N1c); Serial.print("HSDIV = "); Serial.println(HSDIVc); Serial.print("RFREQ = "); Serial.println((float) (RFREQc/1000), 0); Serial.print("Fout = "); Serial.println((float) Fout, 0); // the 0x40000000 factor below is 2^30 Fxtal =( (uint64_t) Fout * HSDIVc * N1c * 0x40000000UL) / RFREQc; Fxtal = Fxtal / 4; // get rid of extra x4 factor Serial.print("Fxtal = "); Serial.println((double)Fxtal, 0); } // Test on 1 MHz increments. This routine will start at 10.5 MHz and add // 1 MHz until we hit 70 MHz or so. That's not all bands but will give a good // idea that the logic is working OK. void ScanBands() { Fout = 10500000UL; for(uint8_t x=1; x<61; x++) { UpdateFrequency(); send_new_freq(); Fout += 1000000; delay(3000); } }