// ********************************************************************* void Display(){ display.clearDisplay(); // Clear the buffer display.setCursor(line0X,line0Y); // point to Proto line display display.print("Setup="); // Setup Options 0=No, 1=Yes display.print(setupOpt); // setup Opt byte if(setupOpt==0){ display.setCursor(55,line0Y); display.print("Freq"); // display last Frequency Set 'protoID' pointer display.setCursor(83,line0Y); display.print("Rds="); // display last Frequency Set 'protoID' pointer display.print(protoID);} // identifiy what memory group to work with if(setupOpt==1){ // on condition you are in normal running mode display.setCursor(55,line0Y); // Setup=1 reads the same in every Mode now. display.print("Freq"); // Adjust Mode used to be entered from this display.setCursor(83,line0Y); // screen and labelled itself here; as of V2.04 display.print("Set="); // it runs from Setup=0 like Modes 1-3 and display.print(protoID);} // paints its own screen instead. if(setupOpt==2){ display.setCursor(77,line0Y); // display.print("Mode="); // if here display which Mode program is running in display.setCursor(107,line0Y); // set the display cursor position display.print(modeID); // display the mode number display.setCursor(line1X,line1Y); // set the display cursor position display.print("1=SIMPLE 2=PROTOCOL"); display.setCursor(line2X,line2Y); // set to next display line display.print("3=SWEEP 4=ADJUST"); display.setCursor(line2X,line3Y); // set to next display line display.print("Version "); // version number hard coded into code display.print(VERSION);} // VERSION number is from a #define parameter if(setupOpt==3){ display.print(" Protocol"); display.setCursor(line1X,line1Y); // set the display cursor position display.print("Range Starting = "); // display line label display.print(startFreqGrpID); // display starting group id display.setCursor(line2X,line2Y); // set to next display line display.print("Range Stopping = "); // display line label display.print(stopFreqGrpID); // display stopping group id } if(setupOpt==4){ display.print(" Sweep"); display.setCursor(line1X,line1Y); // set the display cursor position display.print("Range Starting = "); // display line label display.print(startSweepGrpID); // display starting group id display.setCursor(line2X,line2Y); // set to next display line display.print("Range Stopping = "); // display line label display.print(stopSweepGrpID); // display stopping group id display.setCursor(0,line3Y); // set to next display line display.print("Freq. Inc. = "); // display line label display.setCursor(82,line3Y); // set to next display line if(sweepFreqInc<1000) // display leading zeros for Frequency 1 display.print('0'); // on condition display the zero if(sweepFreqInc<100) display.print('0'); // on condition display the zero if(sweepFreqInc<10) display.print('0'); // on condition display the zero display.print(sweepFreqInc); // display stopping group id } if(setupOpt==5){ display.setCursor(line1X,line1Y); // set the display cursor position display.print("Freq Time = "); // Dispaly line label runTime=endTime1/1000; // "runTime" is a free work field if(runTime<1000) // on condition display leading zeros for runTime1 display.print('0'); // if here display the zero if(runTime<100) display.print('0'); // if here display the leading zero if(runTime<10) display.print('0'); // if here display the leading zero display.print(runTime); // now display the "runTime" value display.setCursor(line2X,line2Y); // set to next display line display.print("Progm Time = "); // DISPLAY LINE LABEL runTime=endTime2/60000; // "runTime" is a free work field if(runTime<1000) // on condition display leading zeros for runTime2 display.print('0'); // if here display the zero if(runTime<100) display.print('0'); // if here display the leading zero if(runTime<10) display.print('0'); // if here display the leading zero display.print(runTime); // display endTime2 in minutes //************* NEW PROTOCOL FILE SELECTION ROUTINE ADDED 05-10-21 ******************* display.setCursor(linePX,linePY); // set the display cursor position display.print("Protocol"); display.setCursor(57,linePY); display.print("File = "); display.print(fileNo); } if(setupOpt<2){ display.setCursor(line1X,line1Y); // set display to point to line 2 of the display display.print("F1="); // display Frequency 1 designator if(F1<10000) // display leading zeros for Frequency 1 display.print('0'); // on condition display the zero if(F1<1000) display.print('0'); // on condition display the zero if(F1<100) display.print('0'); // on condition display the zero if(F1<10) display.print('0'); // on condition display the zero display.print(F1); // now display the frequency F1 display.print(" D1="); // display the Duty Cycle 1 designator display.print(D1); // display Duty Cycle 1 display.setCursor(line2X,line2Y); // set display to point to line 3 of the display display.print("F2="); // display Frequency 2 designator if(F2<10000) // on condition display leading zeros for F2 display.print('0'); // if here display the zero if(F2<1000) display.print('0'); // if here display the zero if(F2<100) display.print('0'); // if here display the zero if(F2<10) display.print('0'); // if here display the zero display.print(F2); // now display the frequency F2 display.print(" D2="); // display the Duty Cycle 2 designator display.print(D2); // display Duty Cycle 2 display.setCursor(line3X,line3Y); // set display to point to line 3 of the display display.print("F3="); // display Frequency 2 designator if(F3<10000) // on condition display leading zeros for F2 display.print('0'); // if here display the zero if(F3<1000) display.print('0'); // if here display the zero if(F3<100) display.print('0'); // if here display the zero if(F3<10) display.print('0'); // if here display the zero display.print(F3); // now display the frequency F2 display.print(" D3="); // display the Duty Cycle 2 designator display.print(D3); // display Duty Cycle 2 display.setCursor(line4X,line4Y); // set display to point to line 3 of the display display.print("F4="); // display Frequency 2 designator if(F4<10000) // on condition display leading zeros for F2 display.print('0'); // if here display the zero if(F4<1000) display.print('0'); // if here display the zero if(F4<100) display.print('0'); // if here display the zero if(F4<10) display.print('0'); // if here display the zero display.print(F4); // now display the frequency F2 display.print(" D4="); // display the Duty Cycle 2 designator display.print(D4); // display Duty Cycle 2 } display.setCursor(curX,curY); // fetch the user cursor position display.print("_"); // using the underscore to show display cursor position display.display(); // This actually displays everything to the display change = change2 = 0; // clear out frequency and cursor change fields for next round } // ********************************************************************* void displayRunning(){ display.clearDisplay(); // Clear the OLED buffer display.setCursor(0,0); // Set the OLED cursor postion display.setTextSize(2); // Set the OLED text size display.print("Running"); // Display "Running" display.setTextSize(1); // switch back to the smaller text size //display.print(" T="); // display a label for temperature -- removed 2026-08-06, //temper=temp; // unused leftover from a previous version, never wired //display.print(temper); // to a real sensor display.setCursor(90,10); // Position to display 'protoID' or 'sweepShowID' display.print("ID="); // display Number label if(modeID!=3) // if not a sweep then display 'protoID' display.print(protoID); // *** display 'protoID' *** else display.print(sweepShowID); // a sweep stays on its start Group, and shows the // stop Group only on the step that hits the limit displayCommon(); } // ********************************************************************* void displaySuspended(){ display.clearDisplay(); // Clear the OLED buffer displayCommon(); display.setCursor(0,0); // Set the OLED cursor postion display.setTextSize(2); // Set the OLED text size display.print("Suspend"); // Display "Running" display.setTextSize(1); // switch back to the smaller text size //display.print(" T="); // display a label for temperature -- removed 2026-08-06, //temper=temp; // unused leftover from a previous version, never wired //display.print(temper); // to a real sensor display.setCursor(90,10); // Position to display 'protoID' or 'sweepShowID' display.print("ID="); // display Number label if(modeID!=3) // if not a sweep then display 'protoID' display.print(protoID); // *** display 'protoID' *** else display.print(sweepShowID); // a sweep stays on its start Group, and shows the // stop Group only on the step that hits the limit displayCommon(); } // ********************************************************************* // Mode 4 (Adjust Mode) screen. Only Channel 1 is shown, because only Channel 1 // is being adjusted -- channels 2-4 continue running on the record's own values. // The line currently under the knob's control is drawn in inverse video, which // marks it without needing a cursor character; at text size 2 there is only // room for ten characters per line and "F=50000.00" already uses all ten. void displayAdjust(float f1, float d1, byte editDuty){ display.clearDisplay(); // Clear the OLED buffer display.setTextSize(1); // small text for the heading display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.print("ADJUST MODE"); display.setCursor(86,0); display.print("Rds="); display.print(protoID); // which record this started from display.setTextSize(2); // large text for the two live values display.setCursor(0,18); if(editDuty==0) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // inverse = knob is here else display.setTextColor(SSD1306_WHITE); if(f1>9999.99) display.print("F="); else display.print(" F="); display.print(f1); display.setCursor(0,42); if(editDuty==1) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // inverse = knob is here else display.setTextColor(SSD1306_WHITE); display.print(" D="); display.print(d1); display.setTextColor(SSD1306_WHITE); // leave the colour as every other screen display.setTextSize(1); // expects to find it display.display(); // push the frame to the OLED } // ********************************************************************* // Wanders a star around the OLED until somebody touches the front panel. The // knob, the black button and the red button all bring the dial screen back, and // NONE of them does anything else -- the wake press is swallowed. // // That matters most for the red button. The screen saver can only appear because // the unit has been sitting stopped for twenty minutes, which means whatever it // was doing finished a long time ago. Somebody walking back up to it has come to // decide what to do next, not to re-run whatever happens to still be loaded, and // Start is a button with real output on the other end of it. Waking a screen is // not an instruction, so it is not treated as one. // // The buttons are polled, so the loop runs every 10 ms while only drawing a star // every third of a second -- polling at the drawing rate would miss a quick tap. void displayScreenSaver(){ byte startAB = (digitalRead(rotaryA) << 1) | digitalRead(rotaryB); // knob at rest unsigned long nextStar = millis(); // draw the first one straight away byte wokeBy = 0; // 1 = red button, 2 = black button, 3 = knob while(1){ if(runFlag != 0) { wokeBy = 1; break; } // red button if(digitalRead(cursorPin) == LOW) { wokeBy = 2; break; } // black button byte nowAB = (digitalRead(rotaryA) << 1) | digitalRead(rotaryB); if(nowAB != startAB){ // knob left its detent. Read it again before delay(20); // believing it, so one noisy sample on a if(((digitalRead(rotaryA) << 1) | digitalRead(rotaryB)) != startAB) { wokeBy = 3; break; } // resting contact cannot dismiss the screen } if((long)(millis() - nextStar) >= 0){ // wrap-safe, see handleInterrupt() display.clearDisplay(); // Clear the OLED buffer randNumX = random(120); // X cursor coordinate randNumY = random(54); // Y cursor coordinate display.setCursor(randNumX,randNumY); // Set the OLED cursor postion display.print("*"); // Display the star display.display(); // display screen saver nextStar = millis() + 333; // next one a third of a second from now } delay(10); // poll rate, and it keeps the core free } // Swallow whatever woke us, so the dial screen comes back and nothing else. // Both buttons are waited out first. Deciding while a finger is still down is // no good: the black button would move the cursor the moment cursorMove() next // ran, and the red button's RELEASE bounce is a string of fresh falling edges, // so clearing runFlag on the way past would only have it set again a moment // later -- and the generator would start after all. Wait for the release, THEN // clear it and hold the debounce open across the bounce that follows. if(wokeBy == 1){ // Red button while(digitalRead(interruptPin) == LOW) // let go before anything is decided delay(10); portENTER_CRITICAL(&mux); runFlag = 0; // its interrupt flipped this before interruptDebounce = millis() + debounceTime;// we ever looked -- put it back, and portEXIT_CRITICAL(&mux); // ignore the release bounce } if(wokeBy == 2){ // Black button while(digitalRead(cursorPin) == LOW) delay(10); delay(50); // let the contact settle } // The knob needs nothing extra: resetting the decoder below discards the turn. // Hand the encoder back the way adjustEncoderStop() does, so rotary() resumes // from a known state instead of decoding a transition it never saw the start of. lastAB = (digitalRead(rotaryA) << 1) | digitalRead(rotaryB); stepAccumulator = 0; saverTime = saverStartTime + millis(); // MUST re-arm: saverTime is in the past // by definition here, so without this the // caller drops straight back in on its // very next pass Display(); // put the dial screen back up } // ********************************************************************* void displayUpdate(){ fileChange = 0; // insure "fileChange" flag is reset display.clearDisplay(); // Clear the OLED buffer display.setCursor(0,0); // Set the OLED cursor postion display.setTextSize(2); // Set the OLED text size if(fileNo != fileNoHold){ display.print("Load"); // "Proto = nn" (10 chars) still fits size 2 on one display.setCursor(0,20); // line (128px / 12px per char = ~10 chars max) display.print("Proto = "); display.print(fileNo); display.display(); unsigned long confirmDeadline = millis() + 5000; // 5 second confirm window, but don't while ((long)(millis() - confirmDeadline) < 0 && digitalRead(cursorPin)) { // wrap-safe; digitalWrite(BLINK_LED, HIGH); // black button is pressed sooner -- blink while waiting delay(25); digitalWrite(BLINK_LED, LOW); delay(25); } if(!digitalRead(cursorPin)){ display.clearDisplay(); // Clear the OLED buffer display.setCursor(0,0); // Set the OLED cursor postion display.setTextSize(2); // Set the OLED text size display.print("UPDATING"); // Display "UPDATING" display.display(); // This command actually displays text to the OLED fileChange = 1; // set the file Change flag to active } } if(fileChange == 0) { display.clearDisplay(); // Clear the OLED buffer display.setCursor(0,0); // Set the OLED cursor postion display.print("Update "); // Display "Running" display.setTextSize(1); // switch back to the smaller text size //display.print(" T="); // display a label for temperature -- removed 2026-08-06, //temper=temp; // unused leftover from a previous version, never wired //display.print(temper); // to a real sensor display.setCursor(90,10); // Position to display 'protoID' or 'sweepShowID' display.print("ID="); // display Number label if(modeID!=3) // if not a sweep then display 'protoID' display.print(protoID); // *** display 'protoID' *** else display.print(sweepShowID); // a sweep stays on its start Group, and shows the // stop Group only on the step that hits the limit displayCommon(); blinker(8, 125); // Allow enough time to read the screen message } } // ********************************************************************* void displayCommon(){ display.setCursor(0,21); // display.print("F1="); // now display the Main Frequency 'F1' display.print(F1); // F1 is the channel 1 frequency display.setCursor(82,21); display.print("D1="); // now display the Main Frequency 'F1' display.print(D1); // now display the Main Frequency 'F1' display.setCursor(0,33); display.print("F2="); // now display the Main Frequency 'F1' display.print(F2); // F1 is the channel 1 frequency display.setCursor(82,33); display.print("D2="); // now display the Main Frequency 'F1' display.print(D2); // now display the Main Frequency 'F1' display.setCursor(0,45); display.print("F3="); // now display the Main Frequency 'F1' display.print(F3); // F1 is the channel 1 frequency display.setCursor(82,45); display.print("D3="); // now display the Main Frequency 'F1' display.print(D3); // now display the Main Frequency 'F1' display.setCursor(0,57); display.print("F4="); // now display the Main Frequency 'F1' display.print(F4); // F1 is the channel 1 frequency display.setCursor(82,57); display.print("D4="); // now display the Main Frequency 'F1' display.print(D4); // now display the Main Frequency 'F1' display.display(); // This command actually displays text to the OLED } // ********************************************************************* void cursorMove(){ if(!digitalRead(cursorPin)){ delay(90); // Needed to slow up cursor motion change2=1; // if here then move cursor switch (curX){ case 40: curY=line0YC; // if(setupOpt<3) // means 3, 4, or 5 curX=107; // if here goto "mode" or "protoid" else { curY=line1YC; // now positioned at Freq Time if(setupOpt==5) curX=72; // if here then it was a 5 else curX=102; // if here then setupOpt is 3 or 4 } break; case 107: // if here the go back to setupOpt if(setupOpt!=1) curX=40; // if here go back setupOpt else { curX=18; // if here goto 1st frequency1 position curY=line1YC; } break; // ************** First Frequency and Duty Cycle line *************** case 18: // at F1 1st Frequency1 position curX=24; // if here goto F1 2nd postion break; case 24: // at F1 2nd Frequency1 position curX=30; // if here goto F1 3rd position break; case 30: // at F1 3rd Frequency1 position curX=36; // if here goto f1 4th position break; case 36: // at F1 4th Frequency1 position curX=42; // if here goto F1 5th position break; case 42: // at F1 5th Freqyency1 position curX=54; // if here goto F1 6th position break; case 54: // at F1 6th Frequency1 position curX=60; // if here goto F1 7th position break; case 60: // at F1 7th Frequency1 position curX=108; // if here goto D1 1st duty cycle position break; case 72: // at 1st frequency time position curX=78; // if here goto 2nd frequency time position break; case 78: // at 2nd frequency time position curX=84; // if here goto 3rd frequency time position break; case 84: // at 3rd frequency time position curX=90; // if here goto 4th frequency time position break; case 90: // at 4th frequency time postion curX=79; // if here got to 1st program time position curY=line2YC; // program time start on next line break; case 102: // at starting frequency or Sweep protocol group id curX=103; // if here goto stopping frequency protocol group id curY=line2YC; // stopping frequency protocol start on the next break; case 108: // at D1 1st duty cycle1 position curX=114; // if here goto D1 2nd duty cyele position break; case 114: // at D1 2nd duty cycle position curX=19; // if here goto F2 1st frequency position curY=line2YC; // frequency2 starts on the next line break; // **************Second Frequency and Duty Cycle line *************** case 19: // at F2 1st frequency2 position curX=25; // if here goto 2nd frequency position break; case 25: // at 2nd position of frequency2 curX=31; // if here goto 3rd frequency position break; case 31: // at 3rd position of frequency 2 curX=37; // if here goto 4th frequency position break; case 37: // at 4th position of frequency 2 curX=43; // if here goto 5th frequency position break; case 43: // at 5th position of frequency 2 curX=55; // if here next goto 6th frequency position break; case 55: // at 6th position of frequency 2 curX=61; // if here next goto 7th frequency position break; case 61: // at 7th position of frequency 2 curX=109; // if here next goto 1st Duty Cycle 2 position break; case 79: // at 1st position of program time curX=85; // if here next goto 2nd position of program time break; case 85: // at 2nd position of program time curX=91; // if here next goto 3rd position of program time break; case 91: // at 3rd position of program time curX=97; // if here next goto 4th position of program time break; case 97: // on condition at 4th position of program time curX=99; // if here goto 1st position of File Number curY=linePYC; // if here goto 1st position of File Number break; case 99: curX=line0XC; // if here next goto setupOpt position curY=line0YC; // setupOpt is on the 1st line break; case 103: // on condition at stopping frequency or sweep group ID if(setupOpt==3){ curX=line0XC; // if here next goto setupOpt position curY=line0YC; break; } else { // if here setupOPt == 4 curX=82; curY=line3YC; break; } case 109: // at D2 1st duty cycle1 position curX=115; // if here goto D1 2nd duty cyele position break; case 115: // at D2 2nd duty cycle position curX=20; // if here goto F2 1st frequency position curY=line3YC; // frequency2 starts on the next line break; // ************** Third Frequency and Duty Cycle line *************** case 20: // at F3 1st frequency3 position curX=26; // if here goto 3rd frequency position break; case 26: // at 2nd position of frequency3 curX=32; // if here goto 3rd frequency position break; case 32: // at 3rd position of frequency 3 curX=38; // if here goto 4th frequency position break; case 38: // at 4th position of frequency 3 curX=44; // if here goto 5th frequency position break; case 44: // at 5th position of frequency 3 curX=56; // if here next goto 6th frequency position break; case 56: // at 6th position of frequency 3 curX=62; // if here next goto 7th frequency position break; case 62: // at 7th position of frequency 3 curX=110; // if here next goto 1st Duty Cycle 2 position break; case 82: curX=88; // at 1st position of Sweep Freq Increment break; case 88: curX=94; // at 2nd position of Sweep Freq Increment break; case 94: curX=100; // at 3rd position of Sweep Freq Increment break; case 100: curX=112; // at 4th position of Sweep Freq Increment break; case 112: curX=118; // at 5th position of Sweep Freq Increment break; case 118: curX=line0XC; // at 6th position of Sweep Freq Increment curY=line0YC; break; case 110: // at D3 1st duty cycle1 position curX=116; // if here goto D1 2nd duty cyele position break; case 116: // at D3 2nd duty cycle position curX=21; // if here goto F2 1st frequency position curY=line4YC; // frequency2 starts on the next line break; // ************** Forth Frequency and Duty Cycle line *************** case 21: // at F4 1st frequency4 position curX=27; // if here goto 2nd frequency position break; case 27: // at 2nd position of frequency 4 curX=33; // if here goto 3rd frequency position break; case 33: // at 3rd position of frequency 4 curX=39; // if here goto 4th frequency position break; case 39: // at 4th position of frequency 4 curX=45; // if here goto 5th frequency position break; case 45: // at 5th position of frequency 4 curX=57; // if here next goto 6th frequency position break; case 57: // at 6th position of frequency 4 curX=63; // if here next goto 7th frequency position break; case 63: // at 7th position of frequency 4 curX=111; // if here next goto 1st Duty Cycle 2 position break; case 111: // at D4 1st duty cycle1 position curX=117; // if here goto D1 2nd duty cyele position break; case 117: // at D4 2nd duty cycle position curX=40; // if here goto F2 1st frequency position curY=line0YC; // frequency2 starts on the next line break; } delay(90); // Needed to slow up cursor motion } }