// // Author: Hans Summers, 2015 // Website: http://www.hanssummers.com // // A very demonstration, a simple QRSS/DFCW/CW/FSKCW beacon QRP transmitter using Si5351a synthesiser // It uses the Si5351a module kit http://www.hanssummers.com/synth // #include #define MODE_NONE 0 // Mode NONE means the Power Amplifier is disabled #define MODE_QRSS 1 // QRSS mode (i.e. ordinary CW) #define MODE_FSKCW 2 // FSK CW mode #define MODE_DFCW 3 // DFCW mode // ****************************** // Edit these parameters for your situation! // #define Message "CALLSIGN " // Your callsign (or message) here! Remember to put a space at the end! #define Frequency 10000000 // Transmission frequency, in Hz #define Fsk 5 // FSK amount for FKSCW and DFCW, in Hz #define Mode MODE_QRSS // Transmission mode: MODE_QRSS is used for QRSS or CW #define Speed 0 // Dit speed: 0, 1, 2, 3 index into the speeds[] array, see below. 0 is 12wpm CW #define XTAL_FREQ 27000000 // Crystal frequency for Si5351A board - needs to be adjusted specific to your board's crystal const unsigned int speeds[] = {1, 30, 60, 100}; // Speeds for: 12wpm, QRSS3, QRSS6, QRSS10, can be adjusted as per your wishes const char msg[] = Message; // Message #define LED 13 // Standard Arduino LED output on pin 13, indicates key state in this sketch void i2cInit(); uint8_t i2cSendRegister(uint8_t reg, uint8_t data); uint8_t i2cReadRegister(uint8_t reg, uint8_t *data); #define I2C_START 0x08 // I2C interface constants #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 SI_CLK0_CONTROL 16 // Si5351A 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 void si5351aOutputOff(uint8_t clk); void si5351aSetFrequency(uint32_t frequency); // // Arduino setup function // void setup() { pinMode(LED, OUTPUT); // Define pin 13 (LED) as output i2cInit(); // Initialise I2C interface } // // Arduino loop function // void loop() { static unsigned long milliPrev; // Static variable stores previous millisecond count unsigned long milliNow; milliNow = millis(); // Get millisecond counter value if (milliNow != milliPrev) // If one millisecond has elapsed, call the beacon() function { milliPrev = milliNow; beacon(); } } // // function returns the encoded CW pattern for the character passed in // byte charCode(char c) { switch (c) { case 'A': return B11111001; break; // A .- case 'B': return B11101000; break; // B -... case 'C': return B11101010; break; // C -.-. case 'D': return B11110100; break; // D -.. case 'E': return B11111100; break; // E . case 'F': return B11100010; break; // F ..-. case 'G': return B11110110; break; // G --. case 'H': return B11100000; break; // H .... case 'I': return B11111000; break; // I .. case 'J': return B11100111; break; // J .--- case 'K': return B11110101; break; // K -.- case 'L': return B11100100; break; // L .-.. case 'M': return B11111011; break; // M -- case 'N': return B11111010; break; // N -. case 'O': return B11110111; break; // O --- case 'P': return B11100110; break; // P .--. case 'Q': return B11101101; break; // Q --.- case 'R': return B11110010; break; // R .-. case 'S': return B11110000; break; // S ... case 'T': return B11111101; break; // T - case 'U': return B11110001; break; // U ..- case 'V': return B11100001; break; // V ...- case 'W': return B11110011; break; // W .-- case 'X': return B11101001; break; // X -..- case 'Y': return B11101011; break; // Y -.-- case 'Z': return B11101100; break; // Z --.. case '0': return B11011111; break; // 0 ----- case '1': return B11001111; break; // 1 .---- case '2': return B11000111; break; // 2 ..--- case '3': return B11000011; break; // 3 ...-- case '4': return B11000001; break; // 4 ....- case '5': return B11000000; break; // 5 ..... case '6': return B11010000; break; // 6 -.... case '7': return B11011000; break; // 7 --... case '8': return B11011100; break; // 8 ---.. case '9': return B11011110; break; // 9 ----. case ' ': return B11101111; break; // Space case '/': return B11010010; break; // / -..-. default: return charCode(' '); } } // // Sets the FSK value (shift of the RF carrier) and key on/off // void setOut(boolean doKey, boolean doFsk) { if (!doKey) si5351aOutputOff(SI_CLK0_CONTROL); else if (doFsk) si5351aSetFrequency(Frequency + Fsk); else si5351aSetFrequency(Frequency); } // // This function is called 1000 times per second // void beacon() { static byte timerCounter; // Counter to get to divide by 100 to get 10Hz static int ditCounter; // Counter to time the length of each dit static byte pause; // Generates the pause between characters static byte msgIndex = 255; // Index into the message static byte character; // Bit pattern for the character being sent static byte key; // State of the key static byte charBit; // Which bit of the bit pattern is being sent static boolean dah; // True when a dah is being sent byte divisor; // Divide 1kHz by 100 normally, but by 33 when sending DFCW) if (Mode == MODE_DFCW) // Divisor is 33 for DFCW, to get the correct timing divisor = 33; // (inter-symbol gap is 1/3 of a dit) else divisor = 100; timerCounter++; // 1000Hz at this point if (timerCounter == divisor) // Divides by 100 (or 33 for DFCW) { timerCounter = 0; // 10 Hz here (30Hz for DFCW) ditCounter++; // Generates the correct dit-length if (ditCounter >= speeds[Speed]) { ditCounter = 0; if (!pause) { // Pause is set to 2 after the last symbol of the character has been sent key--; // This generates the correct pause between characters (3 dits) if ((!key) && (!charBit)) { if (Mode == MODE_DFCW) pause = 3; // DFCW needs an extra delay to make it 4/3 dit-length else pause = 2; } } else pause--; // Key becomes 255 when the current symbol (dit or dah) has been sent if (key == 255) { // If the last symbol of the character has been sent, get the next character if (!charBit) { // Increment the message character index msgIndex++; // Reset to the start of the message when the end is reached if (!msg[msgIndex]) msgIndex = 0; // Get the encoded bit pattern for the morse character character = charCode(msg[msgIndex]); // Start at the 7'th (leftmost) bit of the bit pattern charBit = 7; // Look for 0 signifying start of coding bits while (character & (1<> 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 - for best jitter performance 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 1MHz to 150MHz // Example: si5351aSetFrequency(10000000); // will set output CLK0 to 10MHz // // This example sets up PLL A // and MultiSynth 0 // and produces the output on CLK0 // void si5351aSetFrequency(uint32_t frequency) { uint32_t pllFreq; uint32_t xtalFreq = XTAL_FREQ; uint32_t l; float f; uint8_t mult; uint32_t num; uint32_t denom; uint32_t divider; divider = 900000000 / frequency; // Calculate the division ratio. 900,000,000 is the maximum internal PLL frequency: 900MHz if (divider % 2) divider--; // Ensure an even integer division ratio pllFreq = divider * frequency; // Calculate the pllFrequency: the divider * desired output frequency mult = pllFreq / xtalFreq; // Determine the multiplier to get to the required pllFrequency l = pllFreq % xtalFreq; // 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 /= xtalFreq; // 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 // 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, SI_R_DIV_1); // Reset the PLL. This causes a glitch in the output. For small changes to // the parameters, you don't need to reset the PLL, and there is no glitch 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); }