/********************************************************************************************* 10kHz to 120MHz VFO / RF Generator with Si5351 and Arduino Nano, with Zwischenfrquenz (ZF) offset + . See the schematics for wiring details. By J. CesarSound - ver 1.0 - Dec/2020. Originallösung verwendet PINCHANGE Interrupt etwas geändert auf Timer2 Interrupt Timer0 wird in Arduino für millis() verwendet Funktionen Wobbeln, Calibration via DCF-Frequenzabgleich mit Oszi, ev mit Lissajoufigur. und aus hist. Gründen 9 MHz an clk0 und clk2 90° phasenverschoben etwas FM-Modulation mit 2,5kHz Jan 2022 adapted by eRDe mit Danke an CesarSound s.o. 328 auf BluePill portiert. Wobbeln geht nur mit 1,5 Hz bei 128 Schritten. Hoffe auf BluePill ***********************************************************************************************/ //Libraries #include //IDE Standard //#include //Ben Buxton https://github.com/brianlow/Rotary #include //Etherkit https://github.com/etherkit/Si5351Arduino #include //Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library #include //Adafruit SSD1306 https://github.com/adafruit/Adafruit_SSD1306 //#include //------------User preferences---------------------------------------------------------------- #define ZF 0 //Enter your ZF frequency, ex: 455000 = 455kHz, 10700 = 10.7MHz, // direct convert receiver or RF generator, + will add ZF offfset. #define FREQ_INIT 1000000 //Enter your initial frequency at startup, ex: 7000000 = 7MHz, #define XT_CAL_F 90000 //Si5351 calibration factor, adjust to get exatcly 10MHz. #define potiin PA0 //Schleifer Poti #define saveset PA1 //switch used by encoder push button #define phaseB PA2 //Eingänge rotary #define phaseA PA3 #define trigger PA5 //Triggerausgang für Osci bei wobbel Funktion #define reserve PA6 //wird FM substitution #define calib PA7 //Taster für Calibration ACHTUNG RUHEKONTAKT!!! #define wobble PB0 //Schalter für Funktionswahl #define phas9 PB1 //Schalter für 9 MHZ phasehift //--------------------------------------------------------------------------------------------- //Rotary r = Rotary(2, 3); // die Originallösung verwendet PINCHANGE Interrupt Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire); Si5351 si5351; unsigned long freq = FREQ_INIT; unsigned long freqold, fstep, f_zfold; //sehr feiner Unterschied gggg qqqq Vorsicht! long f_zf = ZF; long cal = XT_CAL_F; unsigned long long pll_freq = 90000000000ULL; //Angabe für PLL_A uint8_t encoder = 1; uint8_t stp, esaved = 0, leave = 1, done = 1; // esaved = EEPROM Werte hält mit 010101 unsigned long time_now = 0; //millis display active unsigned int sensorValue ; // lese potentiometer uint8_t flag; //Wert aus LUT uint8_t last; //alte Encoderphasen 2 bits rechts verscoben uint8_t aktuell; //aktuelle -"- /**********************Interrupt*********************************/ void handler_tim2(void){ const uint8_t enc_lut[16] = {0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0}; uint8_t index; // aktuell = PINA & 0b00001100; //leider geht das nur so für den 328er so kurz // aktuell = digitalRead(phaseA)|digitalRead(phaseB) & 0b00001100 ; //input und mask Phasen aktuell = GPIOA -> regs -> IDR & 0b0000000000001100; index = aktuell | last ; //erzeuge index für enc_lut last = aktuell >> 2; //altes last im Nirvana flag = enc_lut[index]; // flag ist ja global //im 328er sind die folgenden Zeilen jeweils in Main, damit interrupt max. kurz ausfällt if (flag == 2) {freq = freq + fstep; cal = cal + fstep; if (freq >= 120000000) freq = 12000000; if (cal >=150000) cal = 150000; } if (flag == 1) {freq = freq - fstep; cal = cal - fstep; if (freq <= 10000) freq = 10000; else if (freq < 10000) freq = 100000000; if (cal <= -150000) cal = -150000;//*/ } // TIMER2_BASE->SR = 0; GPIOC_BASE->ODR ^= 0x2000; //toggle PC13 } /* Ende ISR Routine**************Start setup*******************/ void setup() { Wire.begin(); Wire.setClock(400000); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextColor(WHITE); display.display(); const uint8_t enc_lut[16] = {0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0}; //neues setup für Interrupt aus stmduinoforum timer 2 setup TIMER2_BASE->PSC = 18; //72MHz/7200=10,000 Hz, TIMER2_BASE->ARR = 10000; //10000/10000=1Hz, nun 2,5 ms TIMER2_BASE->CNT = 0; //clear counter timer_attach_interrupt(TIMER2, 0, handler_tim2); //interrupt on timer update TIMER2_BASE->CR1 |= 0x0001; //enable timer. //GPIO setup GPIOC_BASE->CRH = 0x44244444; //enable PC13 as output GPIOC_BASE->BSRR = 0x20000000; //reset PC13 (low) pinMode(phaseA, INPUT_PULLUP); pinMode(phaseB, INPUT_PULLUP); pinMode(saveset, INPUT_PULLUP); // Encoderschalter = store last setting // EEPROM Ersatz noch offen pinMode(wobble, INPUT_PULLUP); // wobble pinMode(calib, INPUT_PULLUP); // Calibration pinMode(phas9, INPUT_PULLUP); // 9 MHZ 90° Phaseoffset pinMode(reserve, INPUT_PULLUP); pinMode(trigger, OUTPUT); // Triggerpuls output digitalWrite(trigger, LOW); // Init Triggerpuls für Oscillograph si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, cal); si5351.output_enable(SI5351_CLK0, 1); //1 - Enable / 0 - Disable CLK si5351.output_enable(SI5351_CLK1, 1); si5351.output_enable(SI5351_CLK2, 1); si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA); //Output current 2MA,4MA,6MA,8MA startup_text(); stp = 3; setstep(); layout(); tunegen(); displayfreq(); } /**************************************************************/ /*-----------------------Main Loop----------------------------*/ /**************************************************************/ void loop() { poti(); time_now = millis() ; setstep(); if (leave == 1) tunegen() ; //restore Frequenzen nach phase90 & calib else ((freqold != freq) || (f_zfold != f_zf )); { time_now = (millis() +300); tunegen(); freqold = freq; f_zfold = f_zf; leave = 0;} if (digitalRead(calib) == LOW) done = 1; {//if (done ==1) EEPROM.get(0, cal); //ausgehend vom letzten Wert while(digitalRead(calib) == LOW) //bleib in schleife bis Lissajou steht {done = 0; leave = 1; // 1x cal lesen reicht via done si5351.set_correction(cal, SI5351_PLL_INPUT_XO); delay(200); displaycal(); // EEPROM.put(0, cal); //geht so net mit BLuePill f_zf = 2500; // Phasenmodulation mit clk0 und clk1 gemeinsam } } if (digitalRead(saveset) == LOW) // Schalter vom Rotary speichert letzte Einstellung { time_now = (millis() + 300); // cal wird unabhängig gespeichert!! save_last(); esaved = 21; // noch kein EEPORM Ersatz da delay(300); } if (digitalRead(wobble) == LOW) //Wobbler { time_now = (millis() + 300); wobbler(); } if (digitalRead(phas9) == LOW) //SSB Basis { time_now = (millis() + 300); phase90(); } if (digitalRead(calib) == LOW) displaycal(); //zeige den Kalibrationswert in ppb else displayfreq(); layout(); } // Ende Main Loop /******************Subroutinen**************************************/ void tunegen() //if (digitalRead(9) != LOW) {// si5351.set_correction(cal, SI5351_PLL_INPUT_XO); //in main calib si5351.set_freq_manual((freq + f_zf) * 100ULL, pll_freq, SI5351_CLK1);//wobble, freq+f_zf si5351.set_freq_manual((freq + 0) * 100ULL, pll_freq, SI5351_CLK0); si5351.set_freq_manual(77500 * 100ULL, pll_freq, SI5351_CLK2); //für calibration Osci } void setstep() { switch (stp) { case 0: f_zf = freq; break; // setze IF offset case 1: fstep = 1; break; case 2: fstep = 10; break; case 3: fstep = 100; break; case 4: fstep = 1000; break; case 5: fstep = 5000; break; case 6: fstep = 9000; break; case 7: fstep = 10000; break; case 8: fstep = 100000; break; case 9: fstep = 1000000; break; case 10:f_zf = 0; break; // muss so gemacht werden, da freq < 10000 //ja nicht gewählt werden kann. } } void save_last() //save last setting in EEPROM 4x long, 1x byte ab addr 23 EEprom frei { /*//EEPROM.put(0, cal); //wird in der Calibration gespeichert EEPROM.put(5, freq); EEPROM.put(10, fstep); EEPROM.put(15, f_zf); EEPROM.write(20, stp); EEPROM.write(22, esaved); //Kennzeichen für unwahrscheinliche 10101 als Zufall im EEPROM*/ } void poti() // read in the analog in value: { sensorValue = analogRead(potiin); stp = map(sensorValue, 0, 4095, 0, 10); // map the analog potmeter value for switchcase } void wobbler() // auf clk0, clk1 zeigt fzentral von wobble { unsigned long fstart, wobstep; //Startfrequenz und Hub aus dem Input rechnen, 64 Steps register unsigned long fnow; // register: in den 31 Registern anlegen, fnow oft benutzt uint8_t i; fstart = (freq - (f_zf >>1)); wobstep = f_zf >>7; //Hub/2 und wobstep = Hub/128 display.clearDisplay(); layout(); while(digitalRead(wobble) == LOW) //wenn Schalter "Wobbler" gerückt sonst endet wobbeln //fstart = (freq - (f_zf >>1)); wobstep = f_zf >>7; //compiler Fehler wenn Zeile da { i = 0; //innere while Schleife neu starten digitalWrite(trigger, HIGH); // und Triggerpuls generieren delay(2); // Dauer für X-Y auf Osci via Sägezahn digitalWrite(trigger, LOW); // Triggerpuls für Osci-Timebase fertig //noInterrupts(); //wire braucht Interrupts LLLeider!! while( i++ <10) //zuerst Vergleich, dann increm. bis Abbruchkriterium { fnow = fstart + (i * wobstep); si5351.set_freq_manual((fnow) * 100ULL, pll_freq, SI5351_CLK0); //4,5 ms } //bin von BluePill enttäuscht //interrupts(); } } void phase90() // ehemalige Freq. für SSB-Quarzfilter 90°-phase clk0 zu clk1 { unsigned long freqi = 9000000; leave = 1; si5351.set_freq_manual((freqi) * 100ULL, pll_freq, SI5351_CLK0); si5351.set_freq_manual((freqi) * 100ULL, pll_freq, SI5351_CLK1); si5351.set_phase(SI5351_CLK0, 0); si5351.set_phase(SI5351_CLK1, 100); //900/9=100 also offset = 100 si5351.pll_reset(SI5351_PLLA); // aus library examples, ist nötig display.clearDisplay(); display.setTextSize(2); display.setCursor(20, 1); display.print(freqi); layout(); display.display(); while(digitalRead(phas9) == LOW) { delay(100); //bleib solange } } /***************************displayroutinen*********************************/ void displaycal() { display.clearDisplay(); display.setTextSize(2); display.setCursor(10, 1); display.print("Calibrate"); display.setCursor(20, 25); display.print(cal); display.display(); } void displayfreq() { unsigned int m = freq / 1000000; unsigned int k = (freq % 1000000) / 1000; unsigned int h = (freq % 1000) / 1; display.clearDisplay(); display.setTextSize(2); char buffer[15] = ""; if (m < 1) { display.setCursor(41, 1); sprintf(buffer, "%003d.%003d", k, h); } else if (m < 100) { display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%003d", m, k, h); } else if (m >= 100) { unsigned int h = (freq % 1000) / 10; display.setCursor(5, 1); sprintf(buffer, "%2d.%003d.%02d", m, k, h); } display.print(buffer); } void layout() { display.setTextColor(WHITE); display.drawLine(0, 20, 127, 20, WHITE); display.drawLine(0, 43, 127, 43, WHITE); display.drawLine(105, 24, 105, 39, WHITE); display.setTextSize(1); display.setCursor(2, 25); display.print("Step:"); display.setTextSize(2); if (digitalRead(wobble) == 0) display.print("wobbel"); if (digitalRead(wobble) == 1) { if (stp == 1) display.print("1Hz"); if (stp == 2) display.print("10Hz"); if (stp == 3) display.print("100Hz"); if (stp == 4) display.print("1kHz"); if (stp == 5) display.print("5kHz"); if (stp == 6) display.print("9kHz"); if (stp == 7) display.print("10kHz"); if (stp == 8) display.print("100kHz"); if (stp == 9) display.print("1MHz"); if (stp == 0) display.print("setIF");if (stp == 10) display.print("clrIF"); } display.setCursor(2, 48); display.setTextSize(1); display.print("ZF/Hub:"); display.setTextSize(2); display.print(f_zf/1000); if (f_zf != 0) display.print("k"); display.setTextSize(1); display.setCursor(110, 23); if (freq < 1000000) display.print("kHz"); if (freq >= 1000000) display.print("MHz"); display.setCursor(110, 33); if (f_zf == 0) display.print("VFO"); if (f_zf != 0) display.print("L O"); display.display(); } void startup_text() { display.setTextSize(1); display.setCursor(4, 5); display.print("Si5351"); display.setCursor(4, 20); display.print("Signal Generator"); display.setCursor(4, 35); display.print("Version 0.0"); display.setCursor(4, 50); display.print(">> eRDe <<"); display.display(); delay(2500); display.clearDisplay(); }