/************************************************************************* KD8CEC's Memory Keyer for HAM This source code is written for All amateur radio operator, I have not had amateur radio communication for a long time. CW has been around for a long time, and I do not know what kind of keyer and keying software is fashionable. So I implemented the functions I need mainly. To minimize the use of memory space, we used bitwise operations. For the alphabet, I put Morsecode in 1 byte. The front 4Bit is the length and the 4Bit is the Morse code. Because the number is fixed in length, there is no separate length information. The 5Bit on the right side is the Morse code. I wrote this code myself, so there is no license restriction. So this code allows anyone to write with confidence. But keep it as long as the original author of the code. DE Ian KD8CEC ----------------------------------------------------------------------------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************/ #include //27 + 10 + 18 + 1(SPACE) = //56 const PROGMEM uint8_t cwAZTable[27] = {0b00100100 , 0b01001000 , 0b01001010 , 0b00111000 , 0b00010000, 0b01000010, 0b00111100, 0b01000000 , //A ~ H 0b00100000, 0b01000111 ,0b00111010, 0b01000100, 0b00101100, 0b00101000 , 0b00111110, 0b01000110, 0b01001101, 0b00110100, //I ~ R 0b00110000, 0b00011000, 0b00110010, 0b01000001, 0b00110110, 0b01001001, 0b01001011, 0b01001100}; //S ~ Z PGM_P pCwAZTable = reinterpret_cast(cwAZTable); const PROGMEM uint8_t cw09Table[27] = {0b00011111, 0b00001111, 0b00000111, 0b00000011, 0b00000001, 0b00000000, 0b00010000, 0b00011000, 0b00011100, 0b00011110}; PGM_P pcw09Table = reinterpret_cast(cw09Table); //# : AR, ~:BT, [:AS, ]:SK, ^:KN const PROGMEM uint8_t cwSymbolIndex[] = {'.', ',', '?', '"', '!', '/', '(', ')', '&', ':', ';', '=', '+', '-', '_', '\'', '@', '#', '~', '[', ']', '^' }; PGM_P pCwSymbolIndex = reinterpret_cast(cwSymbolIndex); const PROGMEM uint8_t cwSymbolTable[] = {0b11010101, 0b11110011, 0b11001100, 0b11011110, 0b11101011, 0b10100100, 0b10101100, 0b11101101, 0b10010000, 0b11111000, 0b11101010, 0b10100010, 0b10010100, 0b11100001, 0b11001101, 0b11010010, 0b11011010, 0b10010100, 0b10100010, 0b10010000, 0b11000101, 0b10101100}; PGM_P pCwSymbolTable = reinterpret_cast(cwSymbolTable); ////const PROGMEM uint8_t cwSymbolLength[] = {6, 6, 6, 6, 6, 5, 5, 6, 5, 6, 6, 5, 5, 6, 6, 6, 6, 5, 5, 5, 6, 5}; // ":(Start"), ':(End "), >: My callsign, <:QSO Callsign (Second Callsign), #:AR, ~:BT, [:AS, ]:SK byte knobPosition = 0; //byte cwTextData[30]; //Maximum 30 Remarked by KD8CE -> Direct Read EEPROM byte autoCWSendCharEndIndex = 0; byte autoCWSendCharIndex = 0; unsigned long autoCWbeforeTime = 0; //for interval time between chars byte pttBeforeStatus = 1; //PTT : default high byte isKeyStatusAfterCWStart = 0; //0 : Init, 1 : Keyup after auto CW Start, 2 : Keydown after byte selectedCWTextIndex = 0; unsigned long autoCWKeydownCheckTime = 0; //for interval time between chars byte changeReserveStatus = 0; byte isAutoCWHold = 0; //auto CW Pause => Manual Keying => auto void autoSendPTTCheck() { if (isCWAutoMode == 2) { //Sending Mode //check PTT Button //short Press => reservation or cancel //long Press => Hold if (digitalRead(PTT) == LOW) { //if (isKeyStatusAfterCWStart == 0) //Yet Press PTT from start TX //{ //} if (isKeyStatusAfterCWStart == 1) //while auto cw send, ptt up and ptt down again { //Start Time autoCWKeydownCheckTime = millis() + 200; //Long push time isKeyStatusAfterCWStart = 2; //Change status => ptt down agian } else if (isKeyStatusAfterCWStart == 2 && autoCWKeydownCheckTime < millis()) { //Hold Mode isAutoCWHold = 1; isKeyStatusAfterCWStart = 3; } else if (isKeyStatusAfterCWStart == 3) { autoCWKeydownCheckTime = millis() + 200; } } else { //PTT UP if (isKeyStatusAfterCWStart == 2) //0 (down before cw start) -> 1 (up while cw sending) -> 2 (down while cw sending) { if (autoCWKeydownCheckTime > millis()) //Short : Reservation or cancel Next Text { if (autoCWSendReservCount == 0 || (autoCWSendReservCount < AUTO_CW_RESERVE_MAX && autoCWSendReserv[autoCWSendReservCount - 1] != selectedCWTextIndex)) { //Reserve autoCWSendReserv[autoCWSendReservCount++] = selectedCWTextIndex; changeReserveStatus = 1; } else if (autoCWSendReservCount > 0 && autoCWSendReserv[autoCWSendReservCount - 1] == selectedCWTextIndex) { autoCWSendReservCount--; changeReserveStatus = 1; } } // end of Short Key up } else if (isKeyStatusAfterCWStart == 3) //play from Hold (pause Auto CW Send) { isAutoCWHold = 0; } isKeyStatusAfterCWStart = 1; //Change status => ptt up (while cw send mode) } //end of PTT UP } } //Send 1 char void sendCWChar(char cwKeyChar) { byte sendBuff[7]; byte i, j, charLength; byte tmpChar; //For Macrofunction //replace > and < to My callsign, qso callsign, use recursive function call if (cwKeyChar == '>' || cwKeyChar == '<') { uint16_t callsignStartIndex = 0; uint16_t callsignEndIndex = 0; if (cwKeyChar == '>') //replace my callsign { if (userCallsignLength > 0) { callsignStartIndex = 0; callsignEndIndex = userCallsignLength; } } else if (cwKeyChar == '<') //replace qso callsign { //ReadLength callsignEndIndex = EEPROM.read(CW_STATION_LEN); if (callsignEndIndex > 0) { callsignStartIndex = CW_STATION_LEN - callsignEndIndex - USER_CALLSIGN_DAT; callsignEndIndex = callsignStartIndex + callsignEndIndex; } } if (callsignStartIndex == 0 && callsignEndIndex == 0) return; for (uint16_t i = callsignStartIndex; i <= callsignEndIndex; i++) { sendCWChar(EEPROM.read(USER_CALLSIGN_DAT + i)); autoSendPTTCheck(); //for reserve and cancel next CW Text if (changeReserveStatus == 1) { changeReserveStatus = 0; updateDisplay(); } if (i < callsignEndIndex) delay_background(cwSpeed * 3, 4); // } return; } else if (cwKeyChar >= 'A' && cwKeyChar <= 'Z') //Encode Char by KD8CEC { tmpChar = pgm_read_byte(pCwAZTable + (cwKeyChar - 'A')); charLength = (tmpChar >> 4) & 0x0F; for (i = 0; i < charLength; i++) sendBuff[i] = (tmpChar << i) & 0x08; } else if (cwKeyChar >= '0' && cwKeyChar <= '9') { charLength = 5; for (i = 0; i < charLength; i++) sendBuff[i] = (pgm_read_byte(pcw09Table + (cwKeyChar - '0')) << i) & 0x10; } else if (cwKeyChar == ' ') { charLength = 0; delay_background(cwSpeed * 4, 4); //7 -> basic interval is 3 } else if (cwKeyChar == '$') //7 digit { charLength = 7; for (i = 0; i < 7; i++) sendBuff[i] = (0b00010010 << i) & 0x80; //...1..1 } else { //symbol for (i = 0; i < 22; i++) { if (pgm_read_byte(pCwSymbolIndex + i) == cwKeyChar) { tmpChar = pgm_read_byte(pCwSymbolTable + i); charLength = ((tmpChar >> 6) & 0x03) + 3; for (j = 0; j < charLength; j++) sendBuff[j] = (tmpChar << (j + 2)) & 0x80; break; } else { charLength = 0; } } } for (i = 0; i < charLength; i++) { cwKeydown(); if (sendBuff[i] == 0) delay_background(cwSpeed, 4); else delay_background(cwSpeed * 3, 4); cwKeyUp(); if (i != charLength -1) delay_background(cwSpeed, 4); } } byte isNeedScroll = 0; unsigned long scrollDispayTime = 0; #define scrollSpeed 500 byte displayScrolStep = 0; void controlAutoCW(){ int knob = 0; byte i; byte cwStartIndex, cwEndIndex; if (cwAutoDialType == 0) knob = enc_read(); if (knob != 0 || beforeCWTextIndex == 255 || isNeedScroll == 1){ //start display if (knobPosition > 0 && knob < 0) knobPosition--; if (knobPosition < cwAutoTextCount * 10 -1 && knob > 0) knobPosition++; selectedCWTextIndex = knobPosition / 10; if ((beforeCWTextIndex != selectedCWTextIndex) || (isNeedScroll == 1 && beforeCWTextIndex == selectedCWTextIndex && scrollDispayTime < millis())) { //Read CW Text Data Position From EEProm EEPROM.get(CW_AUTO_DATA + (selectedCWTextIndex * 2), cwStartIndex); EEPROM.get(CW_AUTO_DATA + (selectedCWTextIndex * 2 + 1), cwEndIndex); if (beforeCWTextIndex == selectedCWTextIndex) { if (++displayScrolStep > cwEndIndex - cwStartIndex) displayScrolStep = 0; } else { displayScrolStep = 0; } #ifdef USE_SW_SERIAL //Not need Scroll //Display_AutoKeyTextIndex(selectedCWTextIndex); SendCommand1Num('w', selectedCWTextIndex); //Index SendEEPromData('a', cwStartIndex + CW_DATA_OFSTADJ, cwEndIndex + CW_DATA_OFSTADJ, 0) ; //Data SendCommand1Num('y', 1); //Send YN isNeedScroll = 0; #else printLineFromEEPRom(0, 2, cwStartIndex + displayScrolStep + CW_DATA_OFSTADJ, cwEndIndex + CW_DATA_OFSTADJ, 0); isNeedScroll = (cwEndIndex - cwStartIndex) > 14 ? 1 : 0; Display_AutoKeyTextIndex(selectedCWTextIndex); #endif scrollDispayTime = millis() + scrollSpeed; beforeCWTextIndex = selectedCWTextIndex; } } //end of check knob if (isCWAutoMode == 1) { //ready status if (digitalRead(PTT) == LOW) //PTT Down : Start Auto CW or DialMode Change { if (pttBeforeStatus == 1) //High to Low Change { autoCWbeforeTime = millis() + 500; //Long push time pttBeforeStatus = 0; } else if (autoCWbeforeTime < millis()) //while press PTT, OK Long push then Send Auto CW Text { sendingCWTextIndex = selectedCWTextIndex; //Information about Auto Send CW Text autoCWSendCharEndIndex = cwEndIndex; //length of CW Text //ianlee autoCWSendCharIndex = cwStartIndex; //position of Sending Char //ianlee isCWAutoMode = 2; //auto sending start autoCWbeforeTime = 0; //interval between chars, 0 = always send isKeyStatusAfterCWStart = 0; //Init PTT Key status autoCWSendReservCount = 0; //Init Reserve Count isAutoCWHold = 0; if (!inTx){ //if not TX Status, change RX -> TX keyDown = 0; startTx(TX_CW, 0); //disable updateDisplay Command for reduce latency time updateDisplay(); delay_background(delayBeforeCWStartTime * 2, 2); //for External AMP or personal situation } } } else if (pttBeforeStatus == 0 && autoCWbeforeTime > 0) //while reade status LOW -> HIGH (before Auto send Before) { pttBeforeStatus = 1; //HIGH if (autoCWbeforeTime > millis()) //short Press -> ? DialModeChange { cwAutoDialType = (cwAutoDialType == 1 ? 0 : 1); //Invert DialMode between select CW Text and Frequency Tune if (cwAutoDialType == 0) printLineF1(F("Dial:Select Text")); else printLineF1(F("Dial:Freq Tune")); delay_background(1000, 0); updateDisplay(); } } } //end of isCWAutoMode == 1 condition if (isCWAutoMode == 2) { //Sending Mode autoSendPTTCheck(); //check interval time, if you want adjust interval between chars, modify below if (isAutoCWHold == 0 && (millis() - autoCWbeforeTime > cwSpeed * 3)) { if (!inTx){ //if not TX Status, change RX -> TX keyDown = 0; startTx(TX_CW, 0); //disable updateDisplay Command for reduce latency time } sendCWChar(EEPROM.read(CW_AUTO_DATA + autoCWSendCharIndex++)); if (autoCWSendCharIndex > autoCWSendCharEndIndex) { //finish auto cw send //check reserve status if (autoCWSendReservCount > 0) { //prepare sendingCWTextIndex = autoCWSendReserv[0]; for (i = 0; i < AUTO_CW_RESERVE_MAX -1; i++) autoCWSendReserv[i] = autoCWSendReserv[i + 1]; EEPROM.get(CW_AUTO_DATA + (sendingCWTextIndex * 2), cwStartIndex); EEPROM.get(CW_AUTO_DATA + (sendingCWTextIndex * 2 + 1), cwEndIndex); //Information about Auto Send CW Text autoCWSendCharEndIndex = cwEndIndex; //length of CW Text //ianlee autoCWSendCharIndex = cwStartIndex; //position of Sending Char //ianlee autoCWSendReservCount--; //Decrease sendCWChar(' '); //APPLY SPACE between CW Texts changeReserveStatus = 1; } else { isCWAutoMode = 1; //ready status delay_background(cwDelayTime * 10, 2); stopTx(); } } autoCWbeforeTime = millis(); if (changeReserveStatus == 1) { changeReserveStatus = 0; updateDisplay(); } } } //abort if this button is down if (btnDown()) { isCWAutoMode = 0; //dsiable Auto CW Mode printLine2ClearAndUpdate(); delay_background(1000, 0); } }