' PMK v2.11 (25-AUG-2023) '****************************************************************** ' PMK Copyright 2022-2023 Dave Benson K1SWL All rights reserved.* ' In collaboration with George Heron N2APB * '****************************************************************** ' This software is freely available for personal use only, * ' without any warranty or implied warranty of * ' merchantability or fitness for a particular purpose. * '****************************************************************** ' FULL DETAILS AT ... www.MidnightDesignSolutions.com/pmk * '****************************************************************************** ' VERSION HISTORY: * ' v1.0 23-AUG-2022 Original release, as published in QST for May 2023 * ' v2.0 24-MAR-2023 Formatted sourcecode release with integrated CCW features* ' v2.01 20-MAY-2023 Changed ">=90" to ">90" to accept Z char in callsign * ' v2.1 25-JUN-2023 Original PMK design thread *minus* the CCW features * ' v2.11 25-AUG-2023 This software, with Iambic modes A and B fixed. * '****************************************************************************** MM.Startup INITS ' Variable Initialization VAR RESTORE Dim Integer Lookup(59) =(128,255,128,128,254,128,128,128,128,128,128,128,128,206,134,86,148,252,124,60,28,12,4,132,196,228,244,128,128,128,128 ,148,50,128,96,136,168,144,64,40,208,8,32,120,176,72,224,160 ,240,104,216,80,16,192,48,24,112,152,184,200) '****************************************************** ' P R O G R A M E X E C U T I O N B E G I N S If Pin(GP5) = 1 Then UserInterface ' PMK Setup uses the GUI ' -------------------------------- ' M A I N L O O P : I D L E ' -------------------------------- IDLE: If Pin(GP16) = 0 Then DA = 1 If Pin(GP17) = 0 Then DI = 1 If DA =1 Or DI=1 Then KEYERlogic ' Call KeyerLogic to process pending dit/dah ' ----------------------------------------------------- ' M E S S A G E M E M O R Y H A N D L I N G If KM=0 Then ' Message 1-4 from Terminal Mode If Pin(GP8)=0 Or Pin(GP10)=0 Or Pin(GP12)=0 Or Pin(GP14)=0 Then '----- Message 1 handling If Pin(GP8)= 0 Then Pin(GP9)=1 D$ = MSG1$ SPEED FORMSTR1 STREAM VAR SAVE MSG1$ Pin(GP9)=0 EndIf '----- Message 2 handling If Pin(GP10)=0 Then Pin(GP11)=1 ' LED #2 ON D$= MSG2$ SPEED QSOCNT= QSOCNT+1 VAR SAVE QSOCNT FORMSTR2 MSG2$=D$ STREAM VAR SAVE MSG2$ Pin(GP11)=0 ' LED #2 OFF EndIf '----- Message 3 handling If Pin(GP12)=0 Then Pin(GP13)=1 ' LED #3 ON SPEED STREAM Pin(GP13)=0 ' LED #3 OFF EndIf '---- Message 4 Handling If Pin(GP14)= 0 Then Pin(GP15)=1 ' LED #4 ON SPEED QSOCNT = QSOCNT - 1 VAR SAVE QSOCNT Pause 700 Pin(GP15) = 0 ' LED #4 OFF EndIf EndIf EndIf '--------------------- ' Press RECORD button. If Pin(GP6)=0 Then Pin(GP7)=1 RFL=1: KM=1 ' ENABLES KeyerLogic to accumulate a string" B4=0:NOTb4=0:ONESY=0:TWOSEY=0:DONE=0 If Onesy=1 And Twosey=1 Then ONESY=0: TWOSEY=0 EndIf '----------------- ' Pressing button #4 BEFORE entering a message on paddles adds the QSO ' Count to the beginning of the message. (Needed for ARRL CW Sweepstakes) If DONE=1 Then GoTo Skip8 If RFL=1 Then Do If Pin(GP14) = 0 Then ' Button #4 pressed Pin(GP15)=1 ' Turn on LED #4 Pause 500 Pin(GP15)=0 ' Turn off LED #4 B4=1 EndIf Loop Until Pin(GP16)=0 Or Pin(GP17)=0 EndIf SKIP8: DONE =1 '------------------------------------------------------------- ' Poll until Button #1 or #2 is pressed. (Sensing the 'After' press.) If RFL=1 Then If Pin(GP14) = 0 Then ' Button #4 pressed Pin(GP15)=1 ' Turn on LED #4 Pause 500 Pin(GP15)=0 ' Turn off LED #4 NOTB4=1 EndIf EndIf If Pin(GP8) = 0 And RFL=1 Then TRANSFER_1 ' Copies contents of MMSG1$ to Message 1 memory If Pin(GP10)= 0 And RFL=1 Then TRANSFER_2 ' Copies contents of MMSG2$ to Message 2 memory If Pin(GP5)=0 Then KM=1 If DONE2=0 Then SPEED M3$= "010202000202130002103001100" ' "RESET SN ?" sent in Morse PLAYBACK_3 Timer =0 ' RESET MASTER TIME Do While Timer<3000 ' WAIT FOR paddles inputs for 3 seconds If DA=1 Or DI=1 Then DA=0:DI=0: QSOCNT=0:GoTo SKIP14 Loop SKIP14: Done2=1 EndIf EndIf '----------------- If Pin(GP8)=0 And KM=1 And RFL=0 Then ' Button #1 plays Msg1 Pin(GP9)=1 ' Msg1 LED on SPEED Playback_1 ' Play Msg1 Pin(GP9)=0 ' Msg1 LED off Pin(GP7)=0 ' REC LED off EndIf '------------------ If Pin(GP10)=0 And KM=1 And RFL=0 Then ' Button #2 plays Msg2 Pin(GP11)=1 ' Msg2 LED on QSOCNT= QSOCNT+1 VAR SAVE QSOCNT SPEED Playback_2 ' Play Msg2 Pin(GP11)=0 ' Msg2 LED off Pin(GP7)=0 ' REC LED off EndIf '------------------ If Pin(GP12)=0 And KM=1 And RFL=0 Then ' Button #3 repeats Message #2 ' in Standalone Mode Pin(GP13)=1 ' Msg3 LED on SPEED Playback_2 ' Play Msg3 Pin(GP13)=0 ' Msg3 LED off EndIf '------------------- If Pin(GP14)=0 And KM=1 And RFL=0 Then ' Button #4 decrements SN Pin(GP15)=1 ' Msg4 LED on QSOCNT=QSOCNT-1 Pause 500 Pin(GP15)=0 ' Msg4 LED off EndIf GoTo IDLE ' <<<<<<<<<<<< B A C K T O S T A R T O F M A I N L O O P '*********************** '*********************** ' S U B R O U T I N E S '*********************** '******************* Sub Playback_1 For W= 1 To Len(M1$) P$ = Mid$(M1$, W, 1) If P$ ="0" Then DDOUT =0: ELEMENT If P$ ="1" Then DDOUT =1: ELEMENT If P$ ="2" Then Pause (2*DotTime):GoTo skip5 If P$= "3" Then Pause (6*DotTime):GoTo skip5 If DI=1 Or DA=1 Then DA=0:DI=0: GoTo SKIP12 SKIP5: Next W VAR SAVE M1$ Skip12: End Sub '******************** Sub Playback_2 If B4=1 Then SSN For W= 1 To Len(M2$) P$ = Mid$(M2$, W, 1) If P$ ="0" Then DDOUT =0: ELEMENT If P$ ="1" Then DDOUT =1: ELEMENT If P$ ="2" Then Pause (2*DotTime):GoTo skip7 If P$= "3" Then Pause (6*DotTime):GoTo skip7 If DA=1 Or DI=1 Then DA=0:DI=0: GoTo SKIP13 SKIP7: Next W VAR SAVE M2$ If NOTB4=1 Then SSN SKIP13: End Sub '********************* Sub Playback_3 For W= 1 To Len(M3$) P$ = Mid$(M3$, W, 1) If P$ ="0" Then DDOUT =0: ELEMENT If P$ ="1" Then DDOUT =1: ELEMENT If P$ ="2" Then Pause (2*DotTime):GoTo skip11 If P$= "3" Then Pause (6*DotTime):GoTo skip11 If DA=1 Or DI=1 Then DA=0:DI=0:GoTo SKIP15 ' Paddle input. CLR INTRPTs, abort. SKIP11: Next W SKIP15: End Sub ' *************** Sub MSGFL ' Eliminate bogus characters from a string. MSGTEMP$ = "" A: For X =1 To Len(D$) C$ = Mid$(D$,X,1) If 31 <= Asc(C$) And Asc(C$) > 90 Then GoTo SKIP1 ' Skip 0-to-1F hex and 5B hex-to-end MSGTEMP$= MSGTEMP$ + C$ Skip1: Next X End Sub '******************* Sub Formstr1 MSGTEMP$="" A: For X =1 To Len(D$) C$ = Mid$(D$,X,1) If C$= "#" Then GoTo SKIP15 ' Ignore this character for message #1 MSGTEMP$= MSGTEMP$ + C$ Skip15: Next X MSG1$ = MSGTEMP$ D$= MSG1$ End Sub '******************* Sub formstr2 ' D$ = MSG2$ ' Scan Message and finds '#' character in string ------------- For W =1 To Len(D$) SNPOS= Instr(D$, "#") ' Return position at which "#" is found. If SNPOS > 0 Then SNFLAG=1 Next W ' loop back until end of string ' End loop. If "#" found, branch to string manipulation If SNFLAG =1 Then GoTo Build ' Build must be executed repeatedly as the serial number increments If SNPOS=0 Then GoTo BYPASS ' No "#" found BUILD: L_LEN= SNPOS-1 ' Found a "#". remove and replace w/ serial number. Static R_Len= Len(D$)- SNPOS SN$ = Str$(QSOCNT) ' Serial number (integer) converted to string Static G$=Left$(D$, L_LEN) H$= G$ + SN$ ' Add serial number after left side of string. 1st concatenation J$= H$ + Right$(D$, R_LEN) ' 2nd concatenation. String now complete BYPASS: If SNFLAG = 1 Then D$ = J$ End Sub '****************************** Sub STREAM ' Converts encoded Morse character to serial Morse Keyer and audio outputs For W = 1 To Len(D$) C$ = Mid$(D$,W,1) LTR = Asc(C$)-31 CODE = LOOKUP(LTR) If CODE =255, Then Pause 4*DotTime: GoTo SKIPPY ' space between words EOCtest = 128 LOOP1: If CODE = EOCtest Then GoTo EXIT1 CODE=CODE<<1 ' shift left 1 bit into bit 8 If CODE>256 Then ' bit was a '1' DDOUT=1 CODE=CODE-256 ' set bit 8 to zero Else DDOUT=0 EndIf ELEMENT If DI=1 Or DA=1 Then DI=0: DA=0: Exit Sub ' Abort if wrong button pressed. Oops. GoTo LOOP1 EXIT1: ' add pause at end of character Pause 2*DotTime SKIPPY: Next W End Sub '******************* Sub SPEED 'Returns speed in WPM SetPin 31, AIN WPM =Int( 8+(9*Pin(31))) DotTime =Int(1200/WPM) End Sub '****************** Sub ELEMENT ' Plays the Morse element ... ONLY in Memory message playback! If DDOUT = 1 Then EL_LEN = 3*DotTime 'In case of a dash If DDOUT = 0 Then EL_LEN = DotTime Pin(GP18) = 1 Play TONE 800,800, EL_LEN Pause EL_LEN Pin(GP18) = 0 ELPAUSE ' Append SPACE betw dots and dashes End Sub '****************** Sub ELPAUSE ' SPACE between dots/dashes Pause 1 Play TONE hifreq,hifreq,DotTime Pause DotTime End Sub '****************** Sub SERIALNO ' Inserts the SERIALNO string (from the QSOCNT integer) into Msg2$ TRIG$ = "#" SNPOS = Instr(D$, TRIG$) If SNPOS= 0 Then GoTo SNEXIT L_LEN= SNPOS-1 R_LEN = Len(D$)-SNPOS SN$ = Str$(QSOCNT) ' Type conversion from (Integer) QSOCNT To String G$=Left$(D$, L_LEN) G$= G$ + SN$ G$= G$ + Right$(D$, R_LEN) D$ = G$ MSG2$=D$ SNEXIT: End Sub '******************************************************************** ' K E Y E R L O G I C ' Generate a dot or dash ' Sub KEYERlogic SPEED LOOPBACK: If REV=0 Then ' Paddles normal If DA=0 And DI=1 Then DDOUT=0 : DI=0 If DA=1 And DI=0 Then DDOUT=1: DA=0 If DA=1 And DI=1 Then ' Mode B DDOUT=INV CC And &B00000001: DI=0: DA=0 EndIf CC=DDOUT EndIf If REV=1 Then ' Paddles reversed If DA=0 And DI =1 Then DDOUT=1: DI=0 If DA= 1 And DI=0 Then DDOUT=0:DA=0 If DA = 1 And DI=1 Then 'Mode B DDOUT=INV CC And &B00000001 : DA=0: DI=0 EndIf EndIf CC=DDOUT Pin(GP9)=0 ' Turn Msg 1 LED off ' ------------------------------------------- ' Key the transmitter and play the sound in headphones If DDOUT = 1 Then MMSG1$= MMSG1$ + "1": EL_LEN = 3*DotTime 'Dash If DDOUT = 0 Then MMSG1$= MMSG1$ + "0": EL_LEN = DotTime 'Dot Pin(GP18) = 1 ' KEY on Play TONE 800,800 arg = DotTime EL_PAUSE If Mode=1 Then ' It's Mode B DA=0: DI=0 ' Clear dot/dash mems at end of 1st DotTime EndIf If DDOUT=1 Then ' It's a dash arg = 2*DotTime ' Complete remainder of dash time EL_PAUSE EndIf ' (else it's a dot, so let it stop it right now) Pin(GP18) = 0 ' KEY off Play STOP arg = DotTime EL_PAUSE ' ends dot/dash element-pause ' Continue X: If Mode=0 Then DA=0: DI=0 ' (Mode A) Clear any dot/dash inputs ' arriving during current character If DA=1 Or DI=1 Then GoTo LOOPBACK ' ---------------------------------------------------------------- ' Detect the length of a gap between elements (for MEMORY PLAYBACK) Timer = 0 ' Timer master reset LOOP2: If Pin(GP16)=0 Or Pin(GP17)=0 Then GoTo SKIP3 If Int(Timer) >= (10*DotTime) Then GoTo SKIP3 ' Timeout GoTo LOOP2 SKIP3: If Int(Timer) >= (10*DotTime) Then MMSG1$ = MMSG1$+ "3": GoTo SKIP6 'Timeout T2 = Int(Timer) If T2 >= 6* DotTime Then MMSG1$ = MMSG1$ + "3" :GoTo SKIP6 ' Long pause If T2 >= DotTime Then MMSG1$ = MMSG1$ + "2" ' Short pause SKIP6: If RFL=1 Then GoTo SKIP4 MMSG1$="" ' Prevent overflow condition in normal operation SKIP4: Pin(GP9)=0: DI=0: DA=0 If Len(MMSG1$)=253 Then RFL=0: Pin(GP7)=0 ' Overflow safety End Sub '---------------- ' Code table: ' "0" = DOT ' "1" = DASH ' "2" = SHORT PAUSE ' "3" = LONG PAUSE ' "4" (unused) ' "5" = (unused) ' "6" (unused) ' "7" (unused) ' "8" (unused) ' "9" (unused)) '****************** Sub EL_PAUSE ' Loops here (interruptable!) until the Arg delay is reached Timer =0 Do While Timer < Arg If Pin(GP16)=0 Then DA=1 ' Paddle closure If Pin(GP17)=0 Then DI=1 ' Other paddle closure Loop ' Exit when Timer = Arg End Sub '****************** Sub Transfer_1 ' Copies contents of MMSG1$ to Message 1 memory, ' Triggered by pressing Message 1 button in Record Mode If ONESY=1 Then GoTo Skip9 M1$="" 'Go-path executed only once in Record mode. For W= 1 To Len(MMSG1$) P$= Mid$(MMSG1$, W, 1) M1$= M1$ + P$ Next W SKIP9: Pin(GP7)=0: RFL=0:MMSG1$="": ONESY=1 End Sub '****************** Sub Transfer_2 ' Copies contents of MMSG1$ to Message 2 memory 'Triggered by pressing Message 2 button in Record mode If TWOSEY=1 Or RFL=0 Then GoTo Skip10 M2$="" ' Go-path executed only once in Record mode. For W= 1 To Len(MMSG1$) P$= Mid$(MMSG1$, W, 1) M2$= M2$ + P$ Next W Pin(GP7)=0: RFL=0:MMSG1$="": TWOSEY=1 Skip10: End Sub '****************** Sub SSN 'STANDALONE mode Serial number If KM=1 Then D$ = Str$(QSOCNT) 'convert serial number to String STREAM EndIf End Sub '************************************************************************* ' U S E R I N T E R F A C E Sub UserInterface Print " ----------------------------------------------------------------------" Print " WELCOME TO THE K1SWL MEMORY KEYER Version 2.11 25-Aug-2023" Print " ----------------------------------------------------------------------" Print "" Print "Do you need to see the SETUP notes before running the Keyer?" Print "" Print "Enter Y/N [CR]: "; Input O$ : If UCase$(O$)="Y" Then SETTINGS Print "" Print "Do you need to see the OPERATIONAL notes for Keyer Control when running?" Print "Enter Y/N [CR]: "; Input O$ : If UCase$(O$) = "Y" Then OPNOTES Print "" Print "--------------------------------------------" Print "" Print " Do you need to reverse the DOT and DASH Paddles?" Print " (This setting is saved when power is removed.) " Print " Enter Y/N [CR]: "; Input PDL$ If UCase$(PDL$) = "Y" Then Rev=1 Else Rev=0 EndIf VAR SAVE Rev Print "" Print "--------------------------------------------" Print " Do you want Iambic Mode A or mode B?" Print " Mode A: Stops after the current character (preferrred by most)" Print " Mode B: Adds an extra character (i.e., 'Squeeze keying')." Print "" Print " Enter A/B [CR]: "; Input IAMB$ If UCase$(IAMB$)="B" Then Mode =1 Else Mode =0 EndIf VAR SAVE MODE Print "" Print "--------------------------------------------" Print "YOU'RE NOW OPERATIONAL ... GO WORK SOME DX!" SPEED: Print "" End Sub '************************************************************************* ' S E T T I N G S N O T E S Sub SETTINGS Print "" Print "--------------------------------------------" Print " FIRST-TIME SETTINGS NOTES:" Print "" Print "Before you get rolling, there are several SETTINGS steps to complete." Print "" Print "If you're reading this, you've already downloaded and started the software." Print """" Print "You'll need to do these steps JUST ONE TIME: " Print """" Print "1) Type Cntrl-C to halt program excution." Print "" Print "2) At the Command Prompt ('>') type OPTION AUDIO GP20,GP21 to enable the Audio Sidetone" Print "" Print "3) Save the PMK Software in one of the seven memory slots available." Print " Type FLASH SAVE # where # is the number you chose (1-7)." Print "" Print "4) When the command prompt returns, type OPTION AUTORUN #." Print " The software will auto start with the program on that memory slot at power-up." Print "" Print "5) To auto start WITHOUT a USB cable add a jumper to J5 (below Pico pins 7/8)" Print "" Print "6) Press the F2 key on your computer to restart the Pico software." Print "--------------------------------------------" End Sub '************************************************************************* ' O P E R A T I O N A L N O T E S Sub OPNOTES Print "" Print "--------------------------------------------" Print "OPERATIONAL NOTES ... Controlling the Keyer" Print "" Print "Paddle Reversal and Iambic mode selection rarely need to change," Print "so the OPERATIONAL dialog will ask only one question in Morse:" Print "" Print " 'RESET SN ?' ... Reset the Serial Number (QSO Count)?" Print "" Print " Tap either keyer paddle within 3 seconds to reset the count." Print " (Do nothing if you're not using the QSO Count in a contest.)" Print """""" Print "To enter messages #1 and #2 without a QSO Count:" Print """" Print " Press the REC pushbutton and the red LED will come on." Print " Enter your intended message on the keyer paddles." Print " Press MSG1 or MSG2 button to store the message to either of those memories." Print " The green message memory LED will come on for 0.5 sec and the red LED will turn off.""" Print """" Print "To enter QSO Count to MSG2 memory:" Print "" Print " QSO Count at the START:" Print " Press the REC pushbutton." Print " Press the DECR button *BEFORE* the message." Print " Enter message via the keyer paddles." Print " Press MSG2 button to store the message." Print " This feature is useful for the ARRL CW Seepstakes." Print """" Print " QSO Count at the END:" Print " Press the REC pushbutton." Print " Enter message via the keyer paddles." Print " Press the DECR button *AFTER* the message." Print " Press MSG2 button to store the message." End Sub '***************************************************************** ' V A R I A B L E I N I T I A L I Z A T I O N Sub INITS SetPin GP5, DIN, PULLUP ' Jumper ON for startup in STANDALONE mode SetPin GP8, DIN, PULLUP ' Message #1 pushbutton SetPin GP10, DIN, PULLUP ' Message #2 pushbutton SetPin GP12, DIN, PULLUP ' Message #3 pushbutton SetPin GP14, DIN, PULLUP ' Message #4 pushbutton SetPin GP6, DIN, PULLUP ' Record pushbutton SetPin GP9, DOUT ' Message #1 LED SetPin GP11, DOUT ' Message #2 LED SetPin GP13, DOUT ' Message #3 LED SetPin GP15, DOUT ' Message #4 LED SetPin GP7, DOUT ' Record LED SetPin GP18, DOUT: Pin(GP18)=0 ' Keyer output on J3 SetPin GP16, DIN, PULLUP ' Paddle RING contact SetPin GP17, DIN, PULLUP ' Paddle TIP contact SetPin GP5, DIN, PULLUP ' P2: Keyer Mode Jumper: STANDALONE mode(low) Dim DI = 0 ' DIT pending Dim DA = 0 ' DAH pending Dim REV =0 ' Reverse paddles Dim DD ' Ditdah- raw output of stream and keyer Dim CC =0 ' Current Character Dim MODE =0 ' 0= Iambic MODE A, 1= Iambic MODE B Dim LEN0 ' Length of Text input string Dim LEN1 ' Length of Serial number string Dim DDOUT, WPM Dim QSOCNT =0 ' Initial value of contact Serial number. Dim SNFLAG =0 Dim SNPOS ' Position of S/N in test stream. Assigned in FORMSTR Dim RFL=0 ' Record Flag (allows messages to accumulate into PB1/2) Dim KM =0 ' Keyboard Mode (as opposedto STANDALONE Mode) Dim STRING D$,SN$,G$,MSG1$,MSG2$, MSGTEMP$, MMSG1$,M1$, M2$,M3$ Dim B4,NOTB4, DONE, ONESY, TWOSEY Dim HIFREQ = 19000 'AUDIO SIDETONE in Hz- inaudible during pauses Const EOCTEST=128 Dim CODE UINT16 End Sub '****************** Sub MM.STARTUP Option DISPLAY 60,100 End Sub