/* Icom serial CI-V to RF-Kit UDP (XML-formatted) frequency data conversion Compiled for ESP8266 and loaded to NodeMCU board using AVRISP mkII programmer In addition to libraries named in #include directives, the BusIO.h library must also be installed. LM Sept. 2022 - CC attribution - https://creativecommons.org/licenses/by/3.0/us/ Rev.1 - Add KHZ_ONLY optional throttle Rev.2 - Add VHF/UHF frequencies for IC-7100 Rev.3 - Add support for non-Icom CAT Rev.4 - Add two DIP switches - 1 Select UDP listener, 2 Select CI-V or CAT Important note: DIP-1 must be OFF (GPIO3=High) to flash NodeMCU. */ #include #include #include #include #include // ************ Conditional compile settings ************ #define TEST_MODE // Comment-out or delete for production use #define OLED // Comment-out if no OLED display #define UDP_OK // Comment-out to disable sending UDP packets // ************ Auxiliary DIP switches - Usage to be defined ************ // GPIO 1 and GPIO2 are NOT available for generic use. Revisit this concept later. #define AUX_DIP_1 3 // Select UDP target listener. OFF = Amplfier, ON = Test listener #define AUX_DIP_2 A0 // Select CI-V or generic CAT. OFF = CI-V, ON = generic CAT #define MY_LOW 128 // ADC0 is low if less than MY_LOW // ************ Interface parameters section (Implementation-specific) ************ boolean generic_CAT = false; // False for Icom CI-V, true for Yaesu, etc. // Compile this boolean for now - To do: Link to a switchable GPIO // WiFi access credentials #define WIFI_SSID "WiFi network name goes here" #define WIFI_PASS "WiFi password goes here" // Amplifier and test server network addresses and amplifier listening port (use same port# for test listener) IPAddress testIPaddress = {192, 168, 1, 85}; // IPV4 address of test server (example--Substitute appropriate value) IPAddress ampIPaddress = {192, 168, 1, 163}; // IPV4 address of amplifier (example--Substitute appropriate value) IPAddress udpIPaddress = ampIPaddress; // Default - Override using AUX_DIP_1 switch boolean testListener = false; // Default - Override using AUX_DIP_1 switch #define ampPort 12060 // Amplifier's UDP listening port (default is 12060) // Radio number that amplifier is configured to follow #define RadioNr 1 // Radio number - Which radio is sending UDP packet // Transceiver - Uncomment one of the following C-IV addresses for the transceiver being interfaced //const byte civ_address = 0x98; // IC-7610 const byte civ_address = 0x94; // IC-7300 //const byte civ_address = 0x88; // IC-7100 // General const boolean SEND_ALL = false; // If true, UDP message for each CI-V (or CAT) message, (cf KHZ_ONLY below) // else update periodically (recommended), as specified by the following - const unsigned long UDP_DELAY = 1000; // Milliseconds interval between periodic UDP messages // Default setting one second (1000 ms) const boolean KHZ_ONLY = SEND_ALL; // Send only when KHz changes (throttle) ... // If true overrides periodic as well as 'all'. const unsigned long CAT_FREQ_REQ_DELAY = 500; // Always throttle outgoint frequency requests to transceiver // ********** End Interface parameters section (Implementation-specific) ********** // UDP - Instantiate WiFiUDP Udp; // Message buffers const int UDP_BUFFER_DIM = 256; char udpTxBuffer[UDP_BUFFER_DIM]; char udpRxBuffer[UDP_BUFFER_DIM]; // Not used #ifdef OLED // https://randomnerdtutorials.com/esp8266-0-96-inch-oled-display-with-arduino-ide/ #define DSPL_WIDTH 128 #define DSPL_HEIGHT 64 #define DSPL_RESET -1 // Instantiate display Adafruit_SSD1306 display(DSPL_WIDTH, DSPL_HEIGHT, &Wire, DSPL_RESET); // Color definitions (not used) #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define YELLOW 0xFFE0 #endif // Software serial // https://github.com/plerup/espsoftwareserial/ #define SS_TX 12 #define SS_RX 13 #define SS_BAUD 9600 // Set to the same as transceiver // Instantiate SoftwareSerial xcvr; // Transceiver serial interface // CI-V const int RX_BUFFER_DIM = 80; char serRxBuffer[RX_BUFFER_DIM]; int rxNdx = 0; byte EOC = 0xFD; // End of CIV message, revalued in setup() for non_CIV CAT String sLastFreq = ""; String sLastKHz = ""; unsigned long lastUpdate = 0; unsigned long lastFreqRequest = 0; // CAT request frequency update const char catReqFreq[4] = {'F', 'A', ';', 0}; // Non-CIV (Kenwood, Yaesu, etc.) // Time const unsigned long JIFFY = 200; const unsigned long ONESEC = 1000; const unsigned long TWOSEC = 2000; const unsigned long FIVESEC = 5000; // Application #define BAUD 115200 // USB Serial monitor (not software serial) // Pushbuttons #define SCRN_PB 14 // Toggle OLED screen display on/off #define TEST_PB 16 // Send test UDP packet (fixed frequency) boolean screen_on = true; // Toggle OLED screen on/off (screen-saving) // Takes effect after setup is complete // No effect if OLED is not defined void setup() { Serial.begin(BAUD); delay(JIFFY); Serial.println(); // Pushbuttons pinMode(SCRN_PB, INPUT); pinMode(TEST_PB, INPUT); // DIP switches pinMode(AUX_DIP_1, INPUT); pinMode(AUX_DIP_2, INPUT); if (digitalRead(AUX_DIP_1) == LOW) { testListener = true; udpIPaddress = testIPaddress; } // Initialize WiFi WiFi.begin(WIFI_SSID, WIFI_PASS); Serial.print("Connecting to "); Serial.print(WIFI_SSID); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(JIFFY); Serial.print("."); } Serial.println(); Serial.print("Connected! IP address: "); Serial.println(WiFi.localIP()); // Initialize software serial (transceiver interface) xcvr.begin(SS_BAUD, SWSERIAL_8N1, SS_RX, SS_TX, false); if (!xcvr) { Serial.println("Software serial initialization failed."); while (true) ; // Do not proceed } Serial.println(F("Software serial initialized.")); if (analogRead(AUX_DIP_2) < MY_LOW) { generic_CAT = true; } // Initialize OLED (if present) #ifdef OLED if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("OLED initialization failed")); while (true) ; // Do not proceed } Serial.println ("OLED initialized."); display.display(); // Adafruit logo delay(TWOSEC); myClearDisplay(); displaySplash(); delay(TWOSEC); displayHelp(); delay(TWOSEC); displayMode(); delay(TWOSEC); if (testListener) { displayTest(); delay(TWOSEC); } delay(TWOSEC); myClearDisplay(); #endif // OLED if (generic_CAT) { EOC = 0x3B; // End of command = ';' Serial.println("Generic CAT mode selected"); catRequestFrequency(); } else Serial.println("Icom C-IV mode selected"); Serial.println ("Setup complete"); } void loop() { if (generic_CAT) catListen(); else civListen(); #ifdef UDP_OK if (SEND_ALL) { ; // UDP sender called from processCIVcommand() or processCATcommand() if (generic_CAT) // Frequency requests TO transceiver if (millis() - CAT_FREQ_REQ_DELAY > lastFreqRequest) { catRequestFrequency(); lastFreqRequest = millis(); } } else { // Periodic update if ((millis() > (lastUpdate + UDP_DELAY)) && (sLastFreq.length() > 0)) { sendFrequency(sLastFreq); lastUpdate = millis(); if (generic_CAT) catRequestFrequency(); } } #endif #ifdef TEST_MODE if (testBtnPress()) { #ifdef UDP_OK sendTestFrequency(); Serial.println("Test frequency sent."); #endif // UDP_OK #ifndef UDP_OK // not OK Serial.println("Test frequency not sent."); #endif // UDP not OK } #endif // TEST_MODE if (oledBtnPress()) { screen_on = !screen_on; // Toggle OLED screen on/off if (!screen_on) myClearDisplay(); } } void flushXmtBuffer() { for (int i = 0; i < UDP_BUFFER_DIM; i++) udpTxBuffer[i] = (byte) 0; } void zeroRxBuffer() { serRxBuffer[0] = (byte) 0; rxNdx = 0; } void civListen() { byte data_byte; while (xcvr.available()) { data_byte = xcvr.read(); Serial.print("0x"); Serial.print((byte) data_byte < 16 ? "0" : ""); Serial.print(data_byte, HEX); Serial.print(" "); if (data_byte == EOC) Serial.println(); if (rxNdx < RX_BUFFER_DIM) { serRxBuffer[rxNdx++] = data_byte; if (data_byte == EOC) { processCIVcommand(); zeroRxBuffer(); } } else zeroRxBuffer(); // Overflow (Should not happen) } } void catListen() { byte data_byte; while (xcvr.available()) { data_byte = xcvr.read(); Serial.print("0x"); Serial.print(data_byte, HEX); Serial.print(" "); if (data_byte > 31) { // ASCII printable only if (rxNdx < RX_BUFFER_DIM) { serRxBuffer[rxNdx++] = data_byte; if (data_byte == EOC) { Serial.println(); processCATcommand(); zeroRxBuffer(); } } else zeroRxBuffer(); // Overflow } } } void catRequestFrequency() { // Serial send 'FA;' - non-CIV request for frequency for (int i = 0; i < 3; i++) xcvr.write(catReqFreq[i]); Serial.print("Sending 'FA;' via software-serial"); } void processCIVcommand() { // Icom //printHex(serRxBuffer); byte xcvrFreq[5] = ""; // Transceiver frequency as byte array String s = ""; // As string of digits if ((serRxBuffer[0] == 0xFE) && // Fixed preamble (serRxBuffer[1] == 0xFE)) if (serRxBuffer[2] == 0x00) if (serRxBuffer[3] == civ_address) // Transceiver CI-V address if (serRxBuffer[4] == 0x00) // Frequency command group for (int i = 5; i < 11; i++) { // Data area if (serRxBuffer[i] == EOC) break; xcvrFreq[i - 5] = serRxBuffer[i]; } else return; else return; else return; else return; // Convert frequency data to decimal digits // Assumes frequency < 1 GHz int hundreds = rightDigit(xcvrFreq[4]); // Hundreds of MHz (IC-7100 includes up to 70 cm band) int tens = leftDigit(xcvrFreq[3]); // 10 MHz and above if (hundreds > 0) { // Rev.2 for IC7100 VHF/UHF s.concat(String(hundreds)); s.concat(String(tens)); } else if (tens > 0) // No leading zeros s.concat(String(tens)); s.concat(String(rightDigit(xcvrFreq[3]))); s.concat(String(leftDigit(xcvrFreq[2]))); s.concat(String(rightDigit(xcvrFreq[2]))); s.concat(String(leftDigit(xcvrFreq[1]))); s.concat(String(rightDigit(xcvrFreq[1]))); s.concat(String(leftDigit(xcvrFreq[0]))); // Omit 1's digit - Not used by RF-Kit Serial.println(); Serial.print("Freq: "); Serial.println(s); // To do: Validate that s is a valid frequency and in an amplifier-supported band. if (SEND_ALL) { sendFrequency(s); lastUpdate = millis(); } else { sLastFreq = s; } } void processCATcommand() { String s = ""; if (serRxBuffer[0] == 0x46 or serRxBuffer[0] == 0x66) { if (serRxBuffer[1] == 0x41 or serRxBuffer[1] == 0x61) { if (serRxBuffer[2] == EOC) { ; // Ignore command FA; request for frequency } else { // Set VFO-A for (int i = 2; i < 11; i++) if (isNumeric((char) serRxBuffer[i])) s.concat((char) serRxBuffer[i]); // 8-digit frequency in Hz (string) else return; // Abort - Caller zeros the Rx buffer // Remove leading zeros (may not be any) while (s.charAt(0) == '0') s = s.substring(1); // Clip Hz digit -- sendFrequency() wants 10's s = s.substring(0, s.length() - 1); if (SEND_ALL) { sendFrequency(s); lastUpdate = millis(); } else { sLastFreq = s; } } } // Ignore VFO-B updates and all other commands } } int leftDigit(byte b) { return b / 16; } int rightDigit(byte b) { return b % 16; } boolean isKHzNew(String fTens) { String fKHz = ""; fKHz.concat(fTens.substring(0, fTens.length() - 2)); if (fKHz == sLastKHz) return false; sLastFreq = fTens; sLastKHz = fKHz; return true; } void sendFrequency(String f) { // RF Kit amplifier frequencies are specified in multiples of 10 Hz // For example 14.06 MHz would be 1406000 if (KHZ_ONLY && !isKHzNew(f)) return; flushXmtBuffer(); Serial.println("Send frequency ..."); Serial.println(); const char Q = 34; String s = ""); s.concat(""); s.concat(""); s.concat(RadioNr); s.concat(""); s.concat(""); s.concat(f); s.concat(""); s.concat(""); for (int i = 0; i < s.length(); i++) udpTxBuffer[i] = s.charAt(i); Udp.beginPacket(udpIPaddress, ampPort); Udp.write(udpTxBuffer); Udp.endPacket(); #ifdef OLED displayFrequency(f); #endif // OLED } boolean oledBtnPress() { if (digitalRead(SCRN_PB) == HIGH) return false; delay(JIFFY); while (digitalRead(SCRN_PB) == LOW) ; // Debounce return true; } boolean testBtnPress() { if (digitalRead(TEST_PB) == HIGH) return false; delay(JIFFY); while (digitalRead(TEST_PB) == LOW) ; // Debounce return true; } #ifdef OLED void myClearDisplay() { display.clearDisplay(); display.display(); } void displaySplash() { myClearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20, 0); display.cp437(true); // Font cp 437 - See testDrawChar() example below display.print("www.lloydm.net"); display.display(); } void displayHelp() { myClearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(20, 0); display.cp437(true); // Font cp 437 - See testDrawChar() example below display.print(" W A 4 E F S"); display.setCursor(5, 20); display.print(" Tune transceiver"); display.setCursor(5, 30); display.print("to init. frequency."); display.display(); } void displayMode() { display.setCursor(5, 40); if (generic_CAT) display.print(" Generic CAT mode"); else display.print(" Icom CI-V mode"); display.display(); } void displayTest() { display.setCursor(5, 50); display.print(" UDP Test Listener"); display.display(); } void displayFrequency(String s) { myClearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(15, 30); display.print(formatFreq(s)); if (screen_on) display.display(); } #endif String formatFreq(String s) { while (s.length() < 8) // Rev.2 - 100 MHz digit for IC7100 VHF/UHF s = ' ' + s; return s.substring(0, 3) + ',' + s.substring(3, 6) + ',' + s.substring(6) + "0 Hz."; } boolean isNumeric(char c) { if (47 < (byte) c < 58) return true; return false; } // -------------------- Test functions below this line -------------------- #ifdef TEST_MODE // https://randomnerdtutorials.com/esp8266-0-96-inch-oled-display-with-arduino-ide/ #ifdef OLED void testdrawchar(void) { display.clearDisplay(); display.setTextSize(1); // Normal 1:1 pixel scale display.setTextColor(WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.cp437(true); // Use full 256 char 'Code Page 437' font // Not all the characters will fit on the display. This is normal. // Library will draw what it can and the rest will be clipped. for (int16_t i = 0; i < 256; i++) { if (i == '\n') display.write(' '); else display.write(i); } if (screen_on) display.display(); delay(TWOSEC); } #endif void sendTestFrequency() { sendFrequency("1406000"); } void printHex(char a[]) { for (int i = 0; i < sizeof(a); i++) { Serial.print("0x"); Serial.print((byte) a[i] < 16 ? "0" : ""); Serial.print(a[i], HEX); Serial.print(" "); if (i % 8 == 0) Serial.println(); } Serial.println(); } #endif // TEST_MODE // Other test functions void printAllGPIO() { // Debug - List digital value of each GPIO pin Serial.println(); Serial.println("digitalRead() Test"); for (int i=0; i<=16; i++) { Serial.print("Arduino pin "); Serial.print(i); Serial.print(" = "); Serial.println(digitalRead(i)); } Serial.println(); }