diff --git a/WSPR-RX_HwVersion2_SoftwareVersion1.01/WSPR-RX_V2_1.02.ino b/WSPR-RX_HwVersion2_SoftwareVersion1.01/WSPR-RX_V2_1.02.ino new file mode 100644 index 0000000..bbd984c --- /dev/null +++ b/WSPR-RX_HwVersion2_SoftwareVersion1.01/WSPR-RX_V2_1.02.ino @@ -0,0 +1,617 @@ + +//Software for Zachtek for the WSPR-RX Hardware Version2 product with product number 1010. +//The Version 2 of the hardware has been simplified so there are no buttons or jumpers to set the frequency. +//As a consequence the calibration and Frequency selection is done using serial commands on the serial port. +//At boot time the Arduino checks the EEPROM and if it finds calibration data for the crystal and LO Frequency it will initialize with this data. +// +//The Version of this software is stored in the constant "softwareversion" and is displayed on the Serialport att startup +// +//To compile you need to install several libraries, se below in the #include section what they are and download them using The Library manager in the Arduino IDE or use the direct URL +//For Arduino Pro Mini 3.3V 8MHz. +// +// Version History +// 1.01 initial Release +// 1.02 Added 15m, 12m and 10m band +// +//The Si5351 programming is based on Hans Summer Demo code. https://www.qrp-labs.com/synth/si5351ademo.html + +#include // Arduino EEPROM lib. +#include //EtherKit +#include //https://github.com/neomilium/arduino-CmdParser +#include //https://github.com/neomilium/arduino-CmdParser +#include //https://github.com/neomilium/arduino-CmdParser + +CmdCallback<7> cmdCallback; // Number of commands listed below + +char strSetBand[] ="SETBAND"; +char strSetRef[] ="SETREF"; +char strIncRef[] ="INCREF"; +char strDecRef[] ="DECREF"; +char strCalOut[] ="CALOUT"; +char strSave[] ="SAVE"; +char strHelp[] ="HELP"; + +// Data structures +struct S_GadgetData +{ + uint32_t RefFreq; //The exact frequency in Hertz of the Crystal Reference as determined by calibration + uint32_t LOFreq; //The local oscillator Frequency in Hertz +}; + +//Constants +#define I2C_START 0x08 +#define I2C_START_RPT 0x10 +#define I2C_SLA_W_ACK 0x18 +#define I2C_SLA_R_ACK 0x40 +#define I2C_DATA_ACK 0x28 +#define I2C_WRITE 0b11000000 +#define I2C_READ 0b11000001 +#define SI5351A_H +#define SI_CLK0_CONTROL 16 // Register definitions +#define SI_CLK1_CONTROL 17 +#define SI_CLK2_CONTROL 18 +#define SI_SYNTH_PLL_A 26 +#define SI_SYNTH_PLL_B 34 +#define SI_SYNTH_MS_0 42 +#define SI_SYNTH_MS_1 50 +#define SI_SYNTH_MS_2 58 +#define SI_PLL_RESET 177 +#define SI_R_DIV_1 0b00000000 // R-division ratio definitions +#define SI_R_DIV_2 0b00010000 +#define SI_R_DIV_4 0b00100000 +#define SI_R_DIV_8 0b00110000 +#define SI_R_DIV_16 0b01000000 +#define SI_R_DIV_32 0b01010000 +#define SI_R_DIV_64 0b01100000 +#define SI_R_DIV_128 0b01110000 +#define SI_CLK_SRC_PLL_A 0b00000000 +#define SI_CLK_SRC_PLL_B 0b00100000 + +#define RXFREQ10m 28124600UL //10m 28.124,600MHz +#define RXFREQ12m 24924600UL //12m 24.924,600MHz +#define RXFREQ15m 21094600UL //15m 21.094,600MHz +#define RXFREQ17m 18104600UL //17m 18.104,600MHz +#define RXFREQ20m 14095600UL //20m 14.095,600MHz +#define RXFREQ30m 10138700UL //30m 10.138,700MHz +#define RXFREQ40m 7038600UL //40m 7.038,600MHz +#define RXFREQ80m 3568600UL //80m 3.568,600MHz +#define RXFREQ160m 1836600UL //160m 1.836,600MHz +#define RXFREQ630m 474200UL //630m 474.200kHz +#define RXFREQ2190m 136000UL //2190m 136.000kHz + +//Global Variables +S_GadgetData GadgetData; //A datastructure that holds all relevant data for the reciver like LO frequency etc +const char softwareversion[] = "1.02" ; //Version of this program, sent to serialport at startup + + + +void setup() { + Serial.begin (9600); + while (!Serial);//Wait for Serialport to be initialized properly + Serial.print(F("Zachtek HF WSPR-RX Hardware version 2, Software version: ")); + Serial.println(softwareversion); + Serial.println(F("Initializing..")); + //Tie Serial commands to Functions + cmdCallback.addCmd(strSetBand, &functSetBand); + cmdCallback.addCmd(strSetRef, &functSetRef); + cmdCallback.addCmd(strIncRef, &functIncRef); + cmdCallback.addCmd(strDecRef, &functDecRef); + cmdCallback.addCmd(strCalOut, &functCalOut); + cmdCallback.addCmd(strSave, &functSave); + cmdCallback.addCmd(strHelp, &functHelp); + + i2cInit(); + if (LoadFromEPROM()==false) //Read configuration from EEPROM + { //if Configuration and Calibration has not been run set some default values. + GadgetData.RefFreq=27000000UL; //Set Crystal Frequency to 25MHz + GadgetData.LOFreq=10000000UL; //Set LO to 10MHz + Serial.println (F("The receiver is not configured !")); + Serial.println (F("")); + PrintRefData(); + } + SetLO(); + PrintRXData(); + Serial.println (F("Type HELP for information on commands")); +} + +void loop() +{ +//Do nothing but check for Serial commands as everything else was done in the Setup routine + CmdBuffer<26> myBuffer; + CmdParser myParser; + + // Automatic handling of incoming comands on the serial port. + cmdCallback.loopCmdProcessing(&myParser, &myBuffer, &Serial); +} + +//Serial Command SetBand +void functSetBand(CmdParser *myParser) { +boolean ValidInput=true; +int Band; + Band = StrTouint64_t(myParser->getCmdParam(1)); + switch (Band) { + case 2190: + GadgetData.LOFreq=RXFREQ2190m; + break; + case 630: + GadgetData.LOFreq=RXFREQ630m; + break; + case 160: + GadgetData.LOFreq=RXFREQ160m; + break; + case 80: + GadgetData.LOFreq=RXFREQ80m; + break; + case 40: + GadgetData.LOFreq=RXFREQ40m; + break; + case 30: + GadgetData.LOFreq=RXFREQ30m; + break; + case 20: + GadgetData.LOFreq=RXFREQ20m; + break; + case 17: + GadgetData.LOFreq=RXFREQ17m; + break; + case 15: + GadgetData.LOFreq=RXFREQ15m; + break; + case 12: + GadgetData.LOFreq=RXFREQ12m; + break; + case 10: + GadgetData.LOFreq=RXFREQ10m; + break; + default: + Serial.println(F("Invalid input!")); + ValidInput=false; + break; + } + if (ValidInput) { + PrintRXData(); + SetLO(); + } +} + +//Serial Command SetRef +void functSetRef(CmdParser *myParser) { +boolean ValidInput=true; +int Ref; + Ref = StrTouint64_t(myParser->getCmdParam(1)); + switch (Ref) { + case 25: + GadgetData.RefFreq=25000000UL; + break; + case 26: + GadgetData.RefFreq=26000000UL; + break; + case 27: + GadgetData.RefFreq=27000000UL; + break; + default: + Serial.println(F("Invalid input!")); + ValidInput=false; + break; + } + if (ValidInput) { + PrintRefData (); + SetLO(); + } +} + +//Serial Command IncRef +void functIncRef(CmdParser *myParser) { +int IncValue; + IncValue =StrTouint64_t(myParser->getCmdParam(1)); + if (IncValue >0 && IncValue <1000001){ + GadgetData.RefFreq=GadgetData.RefFreq + IncValue; + PrintRefData (); + SetLO(); + } + else + { + Serial.println(F("Invalid input!")); + } +} + +//Serial Command DecRef +void functDecRef(CmdParser *myParser) { +int DecValue; + DecValue=StrTouint64_t(myParser->getCmdParam(1)); + if (DecValue >0 && DecValue <1000001){ + GadgetData.RefFreq=GadgetData.RefFreq - DecValue; + PrintRefData (); + SetLO(); + } + else + { + Serial.println(F("Invalid input!")); + } +} + +//Serial Command CalOut +void functCalOut(CmdParser *myParser) { + GadgetData.LOFreq=10000000UL; + PrintRXData(); + SetLO(); +} + +void functSave(CmdParser *myParser) { + SaveToEPROM (); + PrintRefData(); + PrintRXData(); + Serial.println(F("Saved to EEPROM")); +} + + +void functHelp (CmdParser *myparser) { + HelpText (); +} + + + +void softReset(){ + asm volatile (" jmp 0"); +} + +void SetLO() +{ + si5351aSetFrequency(GadgetData.LOFreq*100UL);//Set LO freq, convert to centiHertz as used by Si5351 routine +} + +void PrintRXData() +{ + Serial.print (F("Receive frequency set to is set to ")); + Serial.print (GadgetData.LOFreq); + Serial.println (F("Hz")); + +} + +void PrintRefData() +{ + Serial.print(F("Reference Ocillator is set to ")); + Serial.print(GadgetData.RefFreq); + Serial.println(F("Hz")); +} + +unsigned long GetEEPROM_CRC(void) { + + const unsigned long crc_table[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c + }; + + unsigned long crc = ~0L; + + for (int index = 0 ; index < sizeof(GadgetData) ; ++index) { + crc = crc_table[(crc ^ EEPROM[index]) & 0x0f] ^ (crc >> 4); + crc = crc_table[(crc ^ (EEPROM[index] >> 4)) & 0x0f] ^ (crc >> 4); + crc = ~crc; + } + return crc; +} + +bool LoadFromEPROM (void) +{ + unsigned long CRCFromEEPROM, CalculatedCRC; + EEPROM.get(0, GadgetData); //Load all the data from EEPROM + EEPROM.get(sizeof(GadgetData), CRCFromEEPROM); //Load the saved CRC + CalculatedCRC = GetEEPROM_CRC(); //Calculate the CRC of the saved data + return (CRCFromEEPROM == CalculatedCRC); //If Stored and Calculated CRC are the same return true +} + + +void SaveToEPROM () +{ + unsigned long CRCFromEEPROM; + EEPROM.put(0, GadgetData); //Save all the data to EEPROM at adress0 + CRCFromEEPROM = GetEEPROM_CRC (); //Calculate CRC on the saved data + EEPROM.put(sizeof(GadgetData), CRCFromEEPROM); //Save the CRC after the data +} + + + +void HelpText () { + + Serial.println(F("Type one of the following serial commands to configure the Receiver: ")); + Serial.println(F(" : SETBAND ... ,sets the receiver to receive on a specific band. Valid values are the following:")); + Serial.println(F(" : 2190=2190m band (136.000kHz)")); + Serial.println(F(" : 630= 630m band (474.200kHz)")); + Serial.println(F(" : 160= 160 band (1.836,600MHz)")); + Serial.println(F(" : 80= 80m band (3.568,600MHz)")); + Serial.println(F(" : 40= 40m band (7.038,600MHz)")); + Serial.println(F(" : 30= 30m band (10.138,700MHz)")); + Serial.println(F(" : 20= 20m band (14.095,600MHz)")); + Serial.println(F(" : 17= 17m band (18.104,600MHz)")); + Serial.println(F(" : 15= 15m band (21.094,600MHz)")); + Serial.println(F(" : 12= 12m band (24.924,600MHz)")); + Serial.println(F(" : E.g 'Setband 20' sets receiver to the 20m band")); + Serial.println(F(" : SETREF ... , set the reference crystal frequency in MHz, valid values are 25, 26 or 27 for 25 to 27MHz crystals")); + Serial.println(F(" : INCREF ... , Increments the reference frequency in Hz, valid values are 1 to 1000.000")); + Serial.println(F(" : DECREF ... , Decrements the reference frequency in Hz, valid values are 1 to 1000.000")); + Serial.println(F(" : CALOUT , Sets the LO to 10MHz for calibration purposes")); + Serial.println(F(" : SAVE ,(SAVE) Saves the current settings to EEPROM")); + Serial.println(F(" : HELP ,(HELP) prints this information")); + Serial.println(F(" : Dont forget to end your command line with New Line and Carriage Return characters.")); + } + + +uint64_t StrTouint64_t (String InString) +{ + uint64_t y = 0; + + for (int i = 0; i < InString.length(); i++) { + char c = InString.charAt(i); + if (c < '0' || c > '9') break; + y *= 10; + y += (c - '0'); + } + return y; +} + +String uint64ToStr (uint64_t p_InNumber, boolean p_LeadingZeros) +{ + char l_HighBuffer[7]; //6 digits + null terminator char + char l_LowBuffer[7]; //6 digits + null terminator char + char l_ResultBuffer [13]; //12 digits + null terminator char + String l_ResultString = ""; + uint8_t l_Digit; + + sprintf(l_HighBuffer, "%06lu", p_InNumber / 1000000L); //Convert high part of 64bit unsigned integer to char array + sprintf(l_LowBuffer, "%06lu", p_InNumber % 1000000L); //Convert low part of 64bit unsigned integer to char array + l_ResultString = l_HighBuffer; + l_ResultString = l_ResultString + l_LowBuffer; //Copy the 2 part result to a string + + if (!p_LeadingZeros) //If leading zeros should be removed + { + l_ResultString.toCharArray(l_ResultBuffer, 13); + for (l_Digit = 0; l_Digit < 12; l_Digit++ ) + { + if (l_ResultBuffer[l_Digit] == '0') + { + l_ResultBuffer[l_Digit] = ' '; // replace zero with a space character + } + else + { + break; //We have found all the leading Zeros, exit loop + } + } + l_ResultString = l_ResultBuffer; + l_ResultString.trim();//Remove all leading spaces + } + return l_ResultString; +} + + +uint8_t i2cStart() +{ + TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); + + while (!(TWCR & (1 << TWINT))) ; + + return (TWSR & 0xF8); +} + +void i2cStop() +{ + TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO); + + while ((TWCR & (1 << TWSTO))) ; +} + +uint8_t i2cByteSend(uint8_t data) +{ + TWDR = data; + + TWCR = (1 << TWINT) | (1 << TWEN); + + while (!(TWCR & (1 << TWINT))) ; + + return (TWSR & 0xF8); +} + +uint8_t i2cByteRead() +{ + TWCR = (1 << TWINT) | (1 << TWEN); + + while (!(TWCR & (1 << TWINT))) ; + + return (TWDR); +} + +uint8_t i2cSendRegister(uint8_t reg, uint8_t data) +{ + uint8_t stts; + + stts = i2cStart(); + if (stts != I2C_START) return 1; + + stts = i2cByteSend(I2C_WRITE); + if (stts != I2C_SLA_W_ACK) return 2; + + stts = i2cByteSend(reg); + if (stts != I2C_DATA_ACK) return 3; + + stts = i2cByteSend(data); + if (stts != I2C_DATA_ACK) return 4; + + i2cStop(); + + return 0; +} + +uint8_t i2cReadRegister(uint8_t reg, uint8_t *data) +{ + uint8_t stts; + + stts = i2cStart(); + if (stts != I2C_START) return 1; + + stts = i2cByteSend(I2C_WRITE); + if (stts != I2C_SLA_W_ACK) return 2; + + stts = i2cByteSend(reg); + if (stts != I2C_DATA_ACK) return 3; + + stts = i2cStart(); + if (stts != I2C_START_RPT) return 4; + + stts = i2cByteSend(I2C_READ); + if (stts != I2C_SLA_R_ACK) return 5; + + *data = i2cByteRead(); + + i2cStop(); + + return 0; +} + +// Init TWI (I2C) +// +void i2cInit() +{ + TWBR = 92; + TWSR = 0; + TWDR = 0xFF; + PRR = 0; +} + +// +// Set up specified PLL with mult, num and denom +// mult is 15..90 +// num is 0..1,048,575 (0xFFFFF) +// denom is 0..1,048,575 (0xFFFFF) +// +void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom) +{ + uint32_t P1; // PLL config register P1 + uint32_t P2; // PLL config register P2 + uint32_t P3; // PLL config register P3 + + P1 = (uint32_t)(128 * ((float)num / (float)denom)); + P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512); + P2 = (uint32_t)(128 * ((float)num / (float)denom)); + P2 = (uint32_t)(128 * num - denom * P2); + P3 = denom; + + i2cSendRegister(pll + 0, (P3 & 0x0000FF00) >> 8); + i2cSendRegister(pll + 1, (P3 & 0x000000FF)); + i2cSendRegister(pll + 2, (P1 & 0x00030000) >> 16); + i2cSendRegister(pll + 3, (P1 & 0x0000FF00) >> 8); + i2cSendRegister(pll + 4, (P1 & 0x000000FF)); + i2cSendRegister(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & + 0x000F0000) >> 16)); + i2cSendRegister(pll + 6, (P2 & 0x0000FF00) >> 8); + i2cSendRegister(pll + 7, (P2 & 0x000000FF)); +} + +// +// Set up MultiSynth with integer Divider and R Divider +// R Divider is the bit value which is OR'ed onto the appropriate +// register, it is a #define in si5351a.h +// +void setupMultisynth(uint8_t synth, uint32_t Divider, uint8_t rDiv) +{ + uint32_t P1; // Synth config register P1 + uint32_t P2; // Synth config register P2 + uint32_t P3; // Synth config register P3 + + P1 = 128 * Divider - 512; + P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the Divider + P3 = 1; + + i2cSendRegister(synth + 0, (P3 & 0x0000FF00) >> 8); + i2cSendRegister(synth + 1, (P3 & 0x000000FF)); + i2cSendRegister(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv); + i2cSendRegister(synth + 3, (P1 & 0x0000FF00) >> 8); + i2cSendRegister(synth + 4, (P1 & 0x000000FF)); + i2cSendRegister(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & + 0x000F0000) >> 16)); + i2cSendRegister(synth + 6, (P2 & 0x0000FF00) >> 8); + i2cSendRegister(synth + 7, (P2 & 0x000000FF)); +} + +// +// Switches off Si5351a output +// Example: si5351aOutputOff(SI_CLK0_CONTROL); +// will switch off output CLK0 +// +void si5351aOutputOff(uint8_t clk) +{ + i2cSendRegister(clk, 0x80); // Refer to SiLabs AN619 to see + //bit values - 0x80 turns off the output stage +} + + + + +// +// Set CLK0 output ON and to the specified frequency +// Frequency is in the range 10kHz to 150MHz and given in centiHertz (hundreds of Hertz) +// Example: si5351aSetFrequency(1000000200); +// will set output CLK0 to 10.000,002MHz +// +// This example sets up PLL A +// and MultiSynth 0 +// and produces the output on CLK0 +void si5351aSetFrequency(uint64_t frequency) //Frequency is in centiHz +{ + + int32_t FreqChange; + uint64_t pllFreq; + uint32_t l; + float f; + uint8_t mult; + uint32_t num; + uint32_t denom; + uint32_t Divider; + uint8_t rDiv; + + + if (frequency > 100000000) { //If higher than 1MHz then set output divider to 1 + rDiv = SI_R_DIV_1; + Divider = 90000000000ULL / frequency;// Calculate the division ratio. 900MHz is the maximum internal (expressed as deciHz) + pllFreq = Divider * frequency; // Calculate the pllFrequency: + mult = pllFreq / (GadgetData.RefFreq * 100UL); // Determine the multiplier to + l = pllFreq % (GadgetData.RefFreq * 100UL); // It has three parts: + f = l; // mult is an integer that must be in the range 15..90 + f *= 1048575; // num and denom are the fractional parts, the numerator and denominator + f /= GadgetData.RefFreq; // each is 20 bits (range 0..1048575) + num = f; // the actual multiplier is mult + num / denom + denom = 1048575; // For simplicity we set the denominator to the maximum 1048575 + num = num / 100; + } + else // lower freq than 1MHz - set output Divider to 128 + { + rDiv = SI_R_DIV_128; + frequency = frequency * 128ULL; //Set base freq 128 times higher as we are dividing with 128 in the last output stage + Divider = 90000000000ULL / frequency;// Calculate the division ratio. 900,000,000 is the maximum internal + pllFreq = Divider * frequency; // Calculate the pllFrequency: + mult = pllFreq / (GadgetData.RefFreq * 100UL); // Determine the multiplier to + l = pllFreq % (GadgetData.RefFreq * 100UL); // It has three parts: + f = l; // mult is an integer that must be in the range 15..90 + f *= 1048575; // num and denom are the fractional parts, the numerator and denominator + f /= GadgetData.RefFreq; // each is 20 bits (range 0..1048575) + num = f; // the actual multiplier is mult + num / denom + denom = 1048575; // For simplicity we set the denominator to the maximum 1048575 + num = num / 100; + } + + // Set up PLL A with the calculated multiplication ratio + setupPLL(SI_SYNTH_PLL_A, mult, num, denom); + + // Set up MultiSynth Divider 0, with the calculated Divider. + // The final R division stage can divide by a power of two, from 1..128. + // reprented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file) + // If you want to output frequencies below 1MHz, you have to use the + // final R division stage + setupMultisynth(SI_SYNTH_MS_0, Divider, rDiv); + // Reset the PLL. + i2cSendRegister(SI_PLL_RESET, 0xA0); + // Finally switch on the CLK0 output (0x4F) + // and set the MultiSynth0 input to be PLL A + i2cSendRegister(SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_A); +}