#include #include #include #include /* ////////////////////////////////////////////////////////////////// ARDUINO/Genuino (DUE) Si564 Evaluation Board - "Ultramod Synthesizer" https://changpuak.ch/electronics/Arduino-Shield-ULTRAMOD.php Software Version 2.0 29.09.2018 by ALEXANDER SSE FRANK NOTE: USE ARDUINO DUE TO HAVE 64 BIT PRECISION NOTE: WE USE WIRE1 HERE ! NOTE: YOU HAVE TO CHANGE hardware/arduino/sam/libraries/Wire/Wire.h THE NEW VALUE OF TWI_CLOCK = 400000; AS DESCRIBED HERE : http://forum.arduino.cc/index.php?topic=137588.0 Updated for remote operation via USB-cable, 2019-09-12, Christian Monstein ////////////////////////////////////////////////////////////////// */ // OLED 128x64 with SH1106 Controller // E.G. DM-OLED13-625 #define OLED_MOSI 4 #define OLED_CLK 3 #define OLED_DC 6 #define OLED_CS 7 #define OLED_RESET 5 // STATE OF THE ROTARY ENCODER const int RE2 = A3 ; // PRESSED const int RE1 = A2 ; const int RE0 = A1 ; unsigned int RE_now = 0; unsigned int RE_old = 0; unsigned int RE_xor = 0 ; unsigned int RE_one = 0 ; unsigned int RE_two = 0 ; unsigned int RE_eval = 0x0 ; // 0 = LEFT(-), 1=RIGHT(+), 2=PRESSED unsigned long RE_time0 = 0 ; unsigned long RE_time1 = 0 ; unsigned long RE_time2 = 0 ; unsigned long StartMilli ; unsigned long DurationMilli ; boolean PressedLong = false ; // SI564 VARIABLES / CONSTANTS unsigned long long F_VCO_MIN = 10800000000 ; unsigned long long F_VCO_MAX = 13122222022 ; unsigned long long F_OUT_MIN = 19999999 ; unsigned long long F_OUT_MAX = 2500000001 ; unsigned long long F_OUT = 2000000000 ; float FREQ = 0.0 ; // FOR DISPLAY const byte Si564_Address = 0x55 ; // ATTENUATOR SKY12347-362LF const int LE = 10 ; const int CLK = 8; const int DAT = 9; float AMPL_MAX = 13.0 ; float AMPL_MIN = AMPL_MAX - 17.5 ; // 31.5 is total attenuator range // 14 dB is slope compensation // REMAINING 17.5 dB float AMPL_EXT = 0.0 ; // dB float LACT = 7.0 ; // LEVEL ACTUAL float CAL[27]; // Menue Item to be displayed unsigned int MenueItem = 0 ; unsigned int ActiveDisplay = 0 ; // FREQUENCY boolean ShowCursor = true ; unsigned int CursorPosition = 1 ; unsigned int StartDelay = 199; // original 1999 which is too long for testing .... unsigned int DisplayDelay = 999; // original 9999 which is too long for testing .... // Buffers and constants for remote programming via serial port String buffer; // keeps the command until executed char tempbuf[20]; // keeps the command temporary until CRLF char Version[]="ULTRAMOD2.ino, 2019-09-12/cm"; // Version char Help[]="Commands via RS-232/USB port with 115200N81:"; // Help info #include #include #include Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); #if (SH1106_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SH1106.h!"); #endif void setup() { delay(StartDelay); // KNOB - ROTARY ENCODER pinMode(RE2, INPUT_PULLUP); pinMode(RE1, INPUT_PULLUP); pinMode(RE0, INPUT_PULLUP); Serial.begin(115200); Serial.println(Version); Serial.println(Help); Wire1.begin(); pinMode(LE, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DAT, OUTPUT); digitalWrite(LE, HIGH); // THIS IS THE CALCULATED ATTENUATION // TO HAVE -20dBm AT INPUT OF THE GALI-2 // AN EXTERNAL AMPLIFIER IS USED CAL[0] = 14.19 ; // AT 20.0 MHz CAL[1] = 14.0 ; // AT 100 MHz CAL[2] = 14.0 ; // AT 200 MHz CAL[3] = 13.70 ; // AT 300 MHz CAL[4] = 13.40 ; // AT 400 MHz CAL[5] = 13.30 ; // AT 500 MHz CAL[6] = 13.1 ; // AT 600 MHz CAL[7] = 12.5 ; // AT 700 MHz CAL[8] = 12.0 ; // AT 800 MHz CAL[9] = 11.35 ; // AT 900 MHz CAL[10] = 10.7 ; // AT 1000 MHz CAL[11] = 9.70 ; // AT 1100 MHz CAL[12] = 8.90 ; // AT 1200 MHz CAL[13] = 8.0 ; // AT 1300 MHz CAL[14] = 7.10 ; // AT 1400 MHz CAL[15] = 6.20 ; // AT 1500 MHz CAL[16] = 5.3 ; // AT 1600 MHz CAL[17] = 4.6 ; // AT 1700 MHz CAL[18] = 3.9 ; // AT 1800 MHz CAL[19] = 3.1 ; // AT 1900 MHz CAL[20] = 2.4 ; // AT 2000 MHz CAL[21] = 1.8 ; // AT 2100 MHz CAL[22] = 1.2 ; // AT 2200 MHz CAL[23] = 0.7 ; // AT 2300 MHz CAL[24] = 0.0 ; // AT 2400 MHz CAL[25] = 0.0 ; // AT 2500 MHz CAL[26] = 0.0 ; // AT 2600 MHz UpdateFreq(F_OUT); SetRFLevel(LACT, F_OUT); // INIT OLED display.begin(SH1106_SWITCHCAPVCC); // SHOW STARTUP SCREEN display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("Ultramod"); display.setTextSize(1); display.setCursor(0,21); display.println("AN UHF SYNTHESISER"); display.setCursor(0,33); display.println("BASED ON THE SI 564"); display.setCursor(0,45); display.println("(C) ETH QUANTUMOPTICS"); display.setCursor(0,57); display.println("BUILT 31.07.2018"); display.display(); delay(DisplayDelay); display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("Ultramod"); display.setTextSize(1); display.setCursor(0,21); display.println("FIRMWARE WRITTEN BY"); display.setCursor(0,33); display.println("MR. JOEL STEINEMANN"); display.setCursor(0,45); display.println("MR. MIRCO DILL"); display.setCursor(0,57); display.println("MR. ALEXANDER FRANK"); display.display(); delay(DisplayDelay); display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("Ultramod"); display.setTextSize(1); display.setCursor(0,21); display.println("HARDWARE DESIGNED BY"); display.setCursor(0,33); display.println("MR. ALEXANDER FRANK"); display.setCursor(0,45); display.println("MR. MANUEL LOPEZ"); display.setCursor(0,57); display.println("MR. TILMAN ESSLINGER"); display.display(); delay(DisplayDelay); } void remote(void) { while (Serial.available() > 0) { int tmp; char st[20]; char rx = Serial.read(); // read a single charecter buffer += rx; //add character to the string buffer //Serial.print(rx); // echo if ((rx == '\n') || (rx == '\r')) { buffer.toCharArray(tempbuf, 40); if (buffer.startsWith("P")) { sscanf(tempbuf,"P%s",&st); // extract attenuation as floating point number LACT = strtod(st,NULL); //Serial.print("Output level: "); //Serial.println(LACT); ShowAmplitudeMenue() ; SetRFLevel(LACT, F_OUT); } else if (buffer.startsWith("F")) { sscanf(tempbuf,"F%s",&st); // extract attenuation as floating point number F_OUT = strtod(st,NULL); //Serial.print("Frequency: "); //Serial.println(F_OUT); UpdateFreq(F_OUT); ShowFrequencyMenue() ; } else if (buffer.startsWith("-v")) // Check version of Arduino software { Serial.println(Version); } else if (buffer.startsWith("-h")) // Check version of Arduino software { Serial.println(Help); Serial.println("-h send help info (this list)"); Serial.println("-v send software version"); Serial.println("Px set power value, x=-4.5....13.0"); Serial.println("Fx set frequency in Hz, x=19999999 ... 2500000000"); } else { Serial.println("Unknown command!"); } buffer = ""; // erase buffer for next command } } } void loop() { switch (MenueItem) { case 0: ShowFrequencyMenue() ; ActiveDisplay = 0 ; MenueItem = 2 ; UpdateFreq(F_OUT); // MAYBE THE NEW FREQUENCY DEMANDS AN AMPLITUDE CORRECTION SetRFLevel(LACT, F_OUT); break; case 1: ShowAmplitudeMenue() ; ActiveDisplay = 1 ; MenueItem = 2 ; SetRFLevel(LACT, F_OUT); break; case 2: // ////////////////////////////////////////////////////////////// // READ THE ROTARY ENCODER EVERY 1 ms // Timer Interrupt does not work here // ////////////////////////////////////////////////////////////// RE_xor = 0x00 ; while (RE_xor == 0x00) { // WAITING FOR ACTION CheckRE() ; remote(); // check remote commands anyway <-------------------------------------- // delay(1) ; } RE_time0 = millis(); RE_one = RE_xor ; delay(5); // NOW WAIT FOR SOME MORE ACTION ... RE_xor = 0x00 ; while (RE_xor == 0x00) { CheckRE() ; // delay(1) ; } RE_time1 = millis(); RE_two = RE_xor ; // TIME DURATION RE_time2 = RE_time1 - RE_time0 ; PressedLong = false ; if (RE_time2 > 999 ) PressedLong = true ; // NOW EVALUATE //Serial.print("RE_one : ");Serial.println(RE_one); //Serial.print("RE_two : ");Serial.println(RE_two); //Serial.print("RE_time2 : ");Serial.println(RE_time2); RE_eval = 0xFF ; if ((RE_one == 0x1) & (RE_two == 0x2)) RE_eval = 0x1; // RIGHT if ((RE_one == 0x2) & (RE_two == 0x1)) RE_eval = 0x0; // LEFT if ((RE_one == 0x4) | (RE_two == 0x4)) RE_eval = 0x2; // PRESSED // ////////////////////////////////////////////////////////////// // ROTATION = LEFT = - // ////////////////////////////////////////////////////////////// if ((RE_eval == 0x0) && (ActiveDisplay == 0)) { switch (CursorPosition) { case 0: // -1 GHZ if (F_OUT > (F_OUT_MIN + 1E9)) F_OUT -= 1E9 ; break; case 1: // -100 MHz if (F_OUT > (F_OUT_MIN + 1E8)) F_OUT -= 1E8 ; break; case 2: // -10 MHz if (F_OUT > (F_OUT_MIN + 1E7)) F_OUT -= 1E7 ; break; case 3: // -1 MHz if (F_OUT > (F_OUT_MIN + 1E6)) F_OUT -= 1E6 ; break; case 4: // -100 kHz if (F_OUT > (F_OUT_MIN + 1E5)) F_OUT -= 1E5 ; break; case 5: // -10 kHz if (F_OUT > (F_OUT_MIN + 1E4)) F_OUT -= 1E4 ; break; case 6: // -1 kHz if (F_OUT > (F_OUT_MIN + 1E3)) F_OUT -= 1E3 ; break; case 7: // -100 Hz if (F_OUT > (F_OUT_MIN + 1E2)) F_OUT -= 1E2 ; break; case 8: // -10 Hz if (F_OUT > (F_OUT_MIN + 1E1)) F_OUT -= 1E1 ; break; case 9: // -1 Hz if (F_OUT > (F_OUT_MIN + 1E0)) F_OUT -= 1E0 ; break; } MenueItem = ActiveDisplay ; } // ////////////////////////////////////////////////////////////// // ROTATION = LEFT = - // ////////////////////////////////////////////////////////////// if ((RE_eval == 0x0) && (ActiveDisplay == 1)) { // DECREASE LEVEL LACT -= 0.5 ; if (LACT < AMPL_MIN) LACT = AMPL_MIN ; MenueItem = ActiveDisplay ; } // ////////////////////////////////////////////////////////////// // ROTATION = RIGHT = + // ////////////////////////////////////////////////////////////// if ((RE_eval == 0x1) && (ActiveDisplay == 0)) { switch (CursorPosition) { case 0: // +1 GHZ if (F_OUT < (F_OUT_MAX - 1E9)) F_OUT += 1E9 ; break; case 1: // +100 MHz if (F_OUT < (F_OUT_MAX - 1E8)) F_OUT += 1E8 ; break; case 2: // +10 MHz if (F_OUT < (F_OUT_MAX - 1E7)) F_OUT += 1E7 ; break; case 3: // +1 MHz if (F_OUT < (F_OUT_MAX - 1E6)) F_OUT += 1E6 ; break; case 4: // +100 kHz if (F_OUT < (F_OUT_MAX - 1E5)) F_OUT += 1E5 ; break; case 5: // +10 kHz if (F_OUT < (F_OUT_MAX - 1E4)) F_OUT += 1E4 ; break; case 6: // +1 kHz if (F_OUT < (F_OUT_MAX - 1E3)) F_OUT += 1E3 ; break; case 7: // +100 Hz if (F_OUT < (F_OUT_MAX - 1E2)) F_OUT += 1E2 ; break; case 8: // +10 Hz if (F_OUT < (F_OUT_MAX - 1E1)) F_OUT += 1E1 ; break; case 9: // +1 Hz if (F_OUT < (F_OUT_MAX - 1E0)) F_OUT += 1E0 ; break; } MenueItem = ActiveDisplay ; } // ////////////////////////////////////////////////////////////// // ROTATION = RIGHT = + // ////////////////////////////////////////////////////////////// if ((RE_eval == 0x1) && (ActiveDisplay == 1)) { // INCREASE LEVEL LACT += 0.5 ; if (LACT > AMPL_MAX) LACT = AMPL_MAX ; MenueItem = ActiveDisplay ; } // ////////////////////////////////////////////////////////////// // ROTARY ENCODER WAS PRESSED // ////////////////////////////////////////////////////////////// if (RE_eval == 0x2) { // CASE 1 : FREQUENCY MODE AND SHORT PRESS if ((ActiveDisplay == 0) && !PressedLong) { // MOVE CURSOR ONE POSITION RIGHT CursorPosition += 1; if (CursorPosition > 9) CursorPosition = 0 ; MenueItem = ActiveDisplay ; } // CASE 2 : FREQUENCY MODE AND LONG PRESS else if ((ActiveDisplay == 0) && PressedLong) { // SWITCH TO AMPLITUDE MODE ActiveDisplay = 1 ; MenueItem = ActiveDisplay ; } // CASE 3 : AMPLITUDE MODE AND SHORT PRESS else if ((ActiveDisplay == 1) && !PressedLong) { // NOTHING TO DO HERE delay(1); } // CASE 4 : AMPLITUDE MODE AND LONG PRESS else if ((ActiveDisplay == 1) && PressedLong) { // SWITCH TO FREQUENCY MODE ActiveDisplay = 0 ; MenueItem = ActiveDisplay ; } } break; default: delay(1); break; } } void UpdateFreq( unsigned long long F_OUT) { // NOTE: SIMPLIFIED CALCULATIONS, AS F_MIN > 6.4 MHz, // LSDIV IS ALWAYS 1, 0x00 if (F_OUT < F_OUT_MIN) F_OUT = F_OUT_MIN ; if (F_OUT > F_OUT_MAX) F_OUT = F_OUT_MAX ; FREQ = (float)F_OUT ; byte Aux = 0x00 ; unsigned long HSDIV ; unsigned long XTAL = 152600000 ; // Hz double FBDIV ; unsigned long INTEGER ; unsigned long long FRACTION ; unsigned long long F_VCO ; FBDIV = (double)(F_VCO_MIN * 1.0 / F_OUT ); HSDIV = (int)(FBDIV + 0.5) ; if ((HSDIV > 32) && (HSDIV % 2)) HSDIV += 1 ; // MIN MAX VALUE if (HSDIV < 4) HSDIV = 4 ; if (HSDIV > 2046) HSDIV = 2046 ; F_VCO = HSDIV * F_OUT ; FBDIV = (double)(F_VCO * 1.0 / XTAL) ; INTEGER = FBDIV ; // FLOOR :-) FRACTION = 4294967295.0 * (double)(FBDIV - INTEGER) ; // Get Device Ready for Update // Set page register to point to page 0 Si564_Write_Byte( 255, 0x00) ; // Disable FCAL override Si564_Write_Byte( 69, 0x00) ; // Synchronously disable output Si564_Write_Byte( 17, 0x00) ; // HSDIV [7:0] Aux = HSDIV & 0xFF ; Si564_Write_Byte( 23, Aux) ; // LSDIV[2:0]; HSDIV[10:8] Aux = (HSDIV >> 8) & 0x07 ; Si564_Write_Byte( 24, Aux) ; // FBDIV [7:0] Aux = FRACTION & 0xFF ; Si564_Write_Byte( 26, Aux) ; // FBDIV [15:8] Aux = (FRACTION >> 8 ) & 0xFF ; Si564_Write_Byte( 27, Aux) ; // FBDIV [23:16] Aux = (FRACTION >> 16 ) & 0xFF ; Si564_Write_Byte( 28, Aux) ; // FBDIV [31:24] Aux = (FRACTION >> 24 ) & 0xFF ; Si564_Write_Byte( 29, Aux) ; // FBDIV [39:32] Aux = INTEGER & 0xFF ; Si564_Write_Byte( 30, Aux) ; // FBDIV [42:40] Aux = (INTEGER >> 8 ) & 0x07 ; Si564_Write_Byte( 31, Aux) ; // Start FCAL using new divider values Si564_Write_Byte( 7, 0x08) ; // Synchronously enable output Si564_Write_Byte( 17, 0x01) ; } void Si564_Write_Byte( byte Register, byte Value) { byte Error = 0x00 ; Wire1.beginTransmission(Si564_Address); Wire1.write(Register); Wire1.write(Value); Error = Wire1.endTransmission(); //SerialHexOutput(Value) ; /* switch (Error) { case 0 : Serial.println("0:success"); break; case 1 : Serial.println("1:data too long to fit in transmit buffer"); break; case 2 : Serial.println("2:received NACK on transmit of address"); break; case 3 : Serial.println("3:received NACK on transmit of data"); break; case 4 : Serial.println("4:other error"); break; } */ } void SerialHexOutput(byte value) { Serial.print("0x"); if (value < 0x10) Serial.print("0"); Serial.println(value,HEX); } // ///////////////////////////////////////////////////////////////////// // SUBROUTINES DISPLAY. // ///////////////////////////////////////////////////////////////////// void ShowFrequencyMenue () { char str[20]; sprintf(str, "%10lld", F_OUT); int len=strlen(str); int offset = 0; display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); display.println("FREQUENCY"); for (int i=0; i<10; i++) { if (i>=0) offset = 0; if (i>=1) offset = 5; if (i>=4) offset = 10; if (i>=7) offset = 15; display.setTextColor(WHITE); display.setCursor(i*11+offset,20); if (i<10-len) display.print(" "); else display.print(str[i]); if ((ShowCursor) && (CursorPosition == i)) { display.setCursor(i*11+offset,26); display.print("_"); } } ShowInfo(); display.display(); } void ShowAmplitudeMenue () { display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); display.println("AMPLITUDE"); display.setCursor(0,20); if (LACT > 0.0) display.print("+"); if (LACT == 0.0) display.print(" "); display.print(LACT,1); display.print(" dBm "); ShowInfo(); display.display(); } void ShowInfo() { display.drawLine(0, 40, 128, 40, WHITE); display.setTextSize(1); display.setCursor(0,46); display.println("PRESS TO MOVE RIGHT"); display.setCursor(0,57); display.print("ROTATE TO INC/DEC"); } // CHECK ROTARY ENCODER void CheckRE() { // RE_old = RE_now ; RE_old = (digitalRead(RE2) << 2)|(digitalRead(RE1) << 1)|digitalRead(RE0); delay(2); RE_now = (digitalRead(RE2) << 2)|(digitalRead(RE1) << 1)|digitalRead(RE0); RE_xor = RE_now ^ RE_old ; } // ///////////////////////////////////////////////////////////////////// // SUBROUTINES ATTENUATOR. // ///////////////////////////////////////////////////////////////////// void SetRFLevel(float Level, unsigned long long FREQ) { unsigned int value ; byte index = FREQ / 100000000 ; float CAL_ATT = CAL[index] ; if (Level < AMPL_MIN) Level = AMPL_MIN ; if (Level > AMPL_MAX) Level = AMPL_MAX ; // YES, WE HEARD OF THIS THING CALLED "INTERPOLATION" float NecessaryAttenuation = CAL_ATT + (AMPL_MAX - Level); value = 64 - 2.0 * NecessaryAttenuation ; //Serial.print("ATT: "); //Serial.print(NecessaryAttenuation,2); //Serial.print(" ... "); //Serial.println(value,BIN); digitalWrite(LE, LOW); shiftOut(DAT, CLK, MSBFIRST, value); digitalWrite(LE, HIGH); } // ///////////////////////////////////////////////////////////// // END OF FILE. // /////////////////////////////////////////////////////////////