// ******************************************** // *** SIMPLEX REPEATER CONTROLLER *** // *** by ON7EQ jan 2013 *** // ******************************************** #include // required for NTC readout #include #include // Watchdog function // initialize the LCS library with the numbers of the interface pins /* * on typical 2x 16 LCD display * LCD RS pin to digital pin 7 * LCD Enable pin to digital pin 8 * LCD D4 pin to digital pin 9 * LCD D5 pin to digital pin 10 * LCD D6 pin to digital pin 11 * LCD D7 pin to digital pin 12 * LCD R/W pin to ground * Trimmer to LCD VO pin (pin 3) (contrast) */ LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // build LCD specific characters 'degrees' byte degree [8] = { B00100, B01010, B00100, B00000, B00000, B00000, B00000, }; /////////////// Simple Arduino CW Beacon ////////////////// // Written by Mark VandeWettering K6HX // Email: k...@arrl.net // // Thx Mark de ON7EQ ! // struct t_mtab { char c, pat; } ; struct t_mtab morsetab[] = { {'.', 106}, {',', 115}, {'?', 76}, {'/', 41}, {'A', 6}, {'B', 17}, {'C', 21}, {'D', 9}, {'E', 2}, {'F', 20}, {'G', 11}, {'H', 16}, {'I', 4}, {'J', 30}, {'K', 13}, {'L', 18}, {'M', 7}, {'N', 5}, {'O', 15}, {'P', 22}, {'Q', 27}, {'R', 10}, {'S', 8}, {'T', 3}, {'U', 12}, {'V', 24}, {'W', 14}, {'X', 25}, {'Y', 29}, {'Z', 19}, {'1', 62}, {'2', 60}, {'3', 56}, {'4', 48}, {'5', 32}, {'6', 33}, {'7', 35}, {'8', 39}, {'9', 47}, {'0', 63} } ; #define N_MORSE (sizeof(morsetab)/sizeof(morsetab[0])) #define CW_SPEED (18) #define DOTLEN (1200/CW_SPEED) #define DASHLEN (3.5*(1200/CW_SPEED)) // CW weight 3.5 / 1 ////////// end CW section //////////// // Temperature routine double Thermister(int RawADC) { double Temp; Temp = log(((10240000/RawADC) - 10000)); Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp ); Temp = Temp - 273.15; // Convert Kelvin to Celcius // Temp = (Temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit return Temp; } ///////////////// R E P E A T E R P A R A M E T E R S ////////////////////// #define RPT_call ("ON7EQ") // Call sign of the repeater #define RPT_ctcss ("79.7") // CTCSS tone / letter #define RPT_loc ("JO11OC") // Repeater QTH locator //////////////////////////////////////////////////////////////////////////////////// // ISD 2560 pins control #define ISD_mode_pin (2) // Pin for ISD mode control : Play = H / Record = L (ISD2560 pin 27 PDIP) #define ISD_PD_pin (4) // Pin for ISD PD/STOP/RESET : Normal = L / STOP or RESET (from EOM) = Pulse H (ISD2560 pin 24 PDIP) #define ISD_CE_pin (5) // Pin for ISD CE control : Normal = H / START = Pulse L (ISD2560 pin 23 PDIP) //PRM8020 - GM350 pins control #define PRM_PWR_pin (6) // Pin for enabling DC power to rig : Power on = H / power off = L (controlling power FET) #define PRM_PTT_pin (13) // Pin for PTT control : TX = H / RX = L - connect via 'open collector' transistor #define PRM_SQL_pin (A0) // Pin for SQL input (to be added in PRM8020) this is to check BUSY frequecy (= SQL break, disregarding CTCSS) #define PRM_CTCSS_pin (A1) // Pin for Mute input : When muted by CTCSS = L / Unmuted by CTCSS detect = H (gm350 GP3 H=no CTCSS L=CTCSS) //CW tone output #define CW_pin (A5) // Pin for CW audio out #define CW_PITCH (900) // CW pitch //FAN CONTROL #define NTC_pin (A2) // Pin for 10k NTC input. NTC must be on ground side, pullup to +5v by R 10k #define FAN_pin (A3) // Pin for FAN control. Fan ON = H / Fan off = L #define FAN_Pulse_pin (0) // Pin for FAN Running control. Fan ON = L / Fan off = H // RSS read #define RSS_pin (A6) // Pin for Signal Strenght input //POWER SUPPLY #define Vcc_pin (A4) // Pin for Power Voltage sense input #define R1 (12) // from GND to Vcc_pin, express in 100R (12 = 1200 Ohm) #define R2 (47) // from + power supply to Vcc_pin, express in 100R (47 = 4700 Ohm) // voltage divider at Vcc_pin - select proper values so that voltage never exceeds 5v on Analog input ! // With R1 = 1k2 and R2 = 4k7, max input voltage = 25v //RF POWER #define Po_pin (A7) // Pin for RF Power sense input //BUZZER #define BUZZER_pin (3) // Pin for BUZZER control. Buzzer ON = H / Buzzer off = L //////////////// Define some time related variables /////////////////// unsigned long BeaconTime = 0; // timestamp beacon repeat interval unsigned long ActBeaconTime = 0; // timestamp beacon repeat while repeater active unsigned long RecMsgTime = 0; // timestamp message record START unsigned long RecEndTime = 0; // timestamp message record END unsigned long RecMsgLength = 0; // message recorded length unsigned long MiniMsgTime = 0; // timer message minimum length unsigned long PlayMsgTime = 0; // timer message play length unsigned long PlayStartTime = 0; // timestamp message play start unsigned long ClrQRGTime = 0; // timer for clear frequency delay unsigned long KerchunkTime = 0; // timer anti-kerchunk unsigned long SQLdropTime = 0; // timer to define the max allowed signal/SQL drop (flutter) unsigned long BeepDropTime = 0; // timer to define the delay SQL routine inhibited after 'frequency busy' beep is generated unsigned long MsgDelayTime = 0; // timer for delay before message repeat starts after PTT keyed unsigned long ErrorTime; // timer for timestamp error detected unsigned long ErrorSinceSec; // seconds since fatal error occured unsigned long ErrorSinceMin = (0); // minutes since fatal error occured unsigned long ErrorSinceHr = (0); // hours since fatal error occured unsigned long ErrorSinceDay = (0); // days since fatal error occured //////////////// Define some time related constants /////////////////// unsigned long V_BeaconTime = 10; // beacon repeat interval while repeater idle (minutes) unsigned long V_ActBeaconTime = 3; // beacon repeat interval while repeater active (minutes) unsigned long V_RptActTime = 5; // Time interval to consider repeater 'active' (minutes - must be > than V_ActBeaconTime) unsigned long V_RecMsgTime = 60; // message max record lenght - in function of the voice recorder IC (seconds) unsigned long V_MiniMsgTime = 3; // message minimum length (seconds) unsigned long V_PlayMsgTime = 60; // message max play lenght - in function of the voice recorder IC (seconds) unsigned long V_MsgAgeTime = 10; // max message age (to declare it obsolete)(seconds) unsigned long V_ClrQRGTime = 2; // clear frequency waiting delay before repeating & beaconing (seconds) unsigned long V_KerchunkTime = 50; // timer anti-kerchunk (milliseconds)= minimum time to consider effective signal unsigned long V_SQLdropTime = 200; // max allowed signal/SQL drop (flutter)(milliseconds) unsigned long V_MsgDelayTime = 300; // delay before message repeat starts after PTT key signal (milliseconds) unsigned long V_SQLTailTime = 350; // total delay SQL dropoff - used to stop play before EOM (milliseconds) unsigned long V_BeepDropTime = 2000;// delay SQL routine inhibited after'frequency busy' beep is generated (milliseconds) // Define misc variables int dummy = (0); // allround dummy variable not allocated int scrap = (0); // allround scrap variable not allocated int TEMP = (254); // Temperature unsigned long Pout = (0); // TX RF Output power (measured, expressed in tenths of Watt) unsigned long Pmin = (15); // TX RF Output power (minimum for error detect - expressed in Watt) unsigned long Pmax = (35); // TX RF Output power (maximum for error detect - expressed in Watt) byte Beacon_RQ = (0); // 1 = Beacon Request pending byte A_Beacon_RQ = (0); // 1 = Beacon Request while active pending byte MaxMsgLenght = (0); // 1 = max Message Lenght reached byte RPT_active = (0); // 1 = Repeater has been active during ActBeaconTime byte RPT_status = (0); // Repeater status: // 0 = startup phase // 1 = Beacon mode // 2 = Record mode // 3 = Play mode // 94 = error mode - RF ERROR // 95 = error mode - NTC ERROR // 96 = error mode - FAN FAULT // 97 = error mode - UNDERVOLTAGE // 98 = error mode - OVERVOLTAGE // 99 = error mode - OVERTEMP byte Clear_QRG = (0); // 1 = Clear QRG status byte CTCSS_QRG = (0); // 1 = CTCSS is present on QRG during a mimimum period to initiate REC status evaluation byte TX_status = (0); // 1 = TX mode / 0 = RX mode byte RxBusy = (0); // 1 = Receiver Unsquelched, disregarding CTCSS present or not byte RxCTCSS = (0); // 1 = Receiver unmuted by CTCSS tone present byte PlayOnce = (0); // 1 = initate the Play sequence int LCD_refresh = (-1); // LCD refresh counter (every X cycles, perform a refresh of some data) int LCD_refresh2 =(-1); // LCD refresh counter (every X cycles, perform a refresh of some data) byte ISD_status = (0); // Status of ISD chip 1= not defined 2 = Record mode 3 = Play mode // RSSI calibration GM-350 'RSS_pin' unsigned int RSSI_uV =(0); // RSSI µV byte S_units = (0); // S units byte last_S_units = (0); // last known S units #define uV_0 (215) // Voltage x 10 mV for 0 µV - check with your RTX ! #define uV_02 (243) // Voltage x 10 mV for 0,2 µV #define uV_05 (274) // Voltage x 10 mV for 0,5 µV #define uV_1 (299) // Voltage x 10 mV for 1 µV #define uV_2 (326) // Voltage x 10 mV for 3 µV #define uV_5 (348) // Voltage x 10 mV for 5 µV #define uV_10 (361) // Voltage x 10 mV for 10 µV #define uV_20 (385) // Voltage x 10 mV for 20 µV #define uV_50 (419) // Voltage x 10 mV for 50 µV #define uV_100 (432) // Voltage x 10 mV for 100 µV #define uV_200 (438) // Voltage x 10 mV for 200 µV //////////////////////////////////// S E T U P /////////////////////////////////// void setup() { wdt_enable(WDTO_8S); // Enable watchdog, timeout 8 seconds // Define & initialise I/O pins digitalWrite(PRM_PWR_pin, LOW); // make sure transceiver in OFF befor selftest initiated pinMode(PRM_PWR_pin, OUTPUT); digitalWrite(PRM_PTT_pin, LOW); // PTT pin init, and make sure transceiver in RX mode ! pinMode(PRM_PTT_pin, OUTPUT); pinMode(BUZZER_pin, OUTPUT); digitalWrite(BUZZER_pin, LOW); // BUZZER pin init, and make sure it is silent pinMode(FAN_pin, OUTPUT); digitalWrite(FAN_pin, LOW); // FAN control pinMode(FAN_Pulse_pin, INPUT); // H = fan running pinMode(CW_pin, OUTPUT); digitalWrite(CW_pin, LOW); // CW pin init pinMode(PRM_SQL_pin, INPUT); pinMode(PRM_CTCSS_pin, INPUT); // Enable ISD2560 Chip digitalWrite(ISD_mode_pin, HIGH); // PLAY mode pinMode(ISD_mode_pin, OUTPUT); digitalWrite(ISD_PD_pin, HIGH); // reset chip & power down pinMode(ISD_PD_pin, OUTPUT); // delay (100); // digitalWrite(ISD_PD_pin, LOW); // enable chip digitalWrite(ISD_CE_pin, HIGH); pinMode(ISD_CE_pin, OUTPUT); // set up the LCD's number of columns and rows: lcd.begin(24, 2); delay (100); // create degree sign lcd.createChar(0, degree); lcd.clear(); lcd.setCursor(2,0); lcd.print("***"); lcd.setCursor(9,0); lcd.print(RPT_call); // Call sign of repeater lcd.setCursor(19,0); lcd.print("***"); lcd.setCursor(4, 1); lcd.print("SIMPLEX REPEATER"); delay (3000); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Firmware v"); lcd.print("2.5"); // the firmware version lcd.print(" "); lcd.print("01/01/2013"); // the date released lcd.setCursor(0, 1); lcd.print("Testing system "); digitalWrite(BUZZER_pin, HIGH); delay (300); digitalWrite(BUZZER_pin, LOW); delay (1500); wdt_reset(); // Reset Watchdog timer lcd.print("."); delay (1000); lcd.print("."); delay (1000); lcd.print("."); delay (1000); ////////////////////// PERFORM SOME SELT-TESTS ////////////////////////////// ///// TEST SCREEN 1 lcd.clear(); lcd.setCursor(0, 0); lcd.print("Vcc "); // Read power supply voltage dummy = map(analogRead(Vcc_pin), 0,1023,0,(50*(R2+R1)/R1)); if (dummy < 100) lcd.print(" "); // less than 10v lcd.print((dummy/10), DEC); lcd.print("."); lcd.print((dummy)%10, DEC); lcd.print("v = "); wdt_reset(); // Reset Watchdog timer if (dummy > 149) RPT_status = (98); if (dummy < 115) RPT_status = (97); if ((RPT_status == 97) or (RPT_status == 98)) { // We have a voltage error ! lcd.print("FAIL !"); digitalWrite(BUZZER_pin, HIGH); delay (2000); digitalWrite(BUZZER_pin, LOW); ErrorHandling(); // Error routine } else { lcd.print("PASS"); delay (2000); lcd.setCursor(0, 1); lcd.print("Switching on Transceiver"); delay (1000); digitalWrite(PRM_PWR_pin, HIGH); // power up RTX delay (2000); } lcd.setCursor(0, 1); digitalWrite(FAN_pin, HIGH); // FAN test lcd.print("FAN test = Checking ..."); delay (1000); wdt_reset(); // Reset Watchdog timer dummy = digitalRead (FAN_Pulse_pin); digitalWrite(FAN_pin, LOW); // end of FAN test lcd.setCursor(0, 1); lcd.print("FAN test = "); lcd.setCursor(12, 1); if (dummy == 1) { // We have a fan error ! RPT_status = (96); lcd.print("FAIL !"); digitalWrite(BUZZER_pin, HIGH); digitalWrite(FAN_pin, LOW); // end of FAN test delay (2000); digitalWrite(BUZZER_pin, LOW); ErrorHandling(); // Error routine } else { lcd.print("PASS"); delay (2000); } ///// TEST SCREEN 2 lcd.clear(); // SQL test lcd.setCursor(0, 0); lcd.print("SQL test = "); wdt_reset(); // Reset Watchdog timer squelchtest(); if ((RxBusy == 1) or (RxCTCSS == 1)){ // We have a SQL error ! if (RxBusy == 1)lcd.print("BUSY "); if ((RxBusy == 1) and (RxCTCSS == 1))lcd.print("& "); if (RxCTCSS == 1)lcd.print("CTCSS"); digitalWrite(BUZZER_pin, HIGH); delay (2000); digitalWrite(BUZZER_pin, LOW); } else { lcd.print("PASS"); delay (2000); } lcd.setCursor(0, 1); lcd.print("Temp "); wdt_reset(); // Reset Watchdog timer TEMP = int((Thermister(1023 - analogRead(NTC_pin))) + 0.5); // Read temp if (abs(TEMP)>99) TEMP = 99; if (TEMP >= 0) lcd.print(" "); // If temp positive, print space, else a '-' will show up if (abs(TEMP)<10) lcd.print(" "); lcd.print(TEMP,DEC); lcd.write(0); lcd.print(" = "); if (TEMP > 45) RPT_status = (99); if ((TEMP > 60) or (TEMP < -15)){ // We have a NTC error ! lcd.print("FAIL !"); digitalWrite(BUZZER_pin, HIGH); delay (2000); digitalWrite(BUZZER_pin, LOW); RPT_status = (95); } else { lcd.print("PASS"); delay (2000); } ///// TEST SCREEN 3 lcd.clear(); // PWR test lcd.setCursor(0, 0); lcd.print("R F test = "); wdt_reset(); // Reset Watchdog timer digitalWrite(PRM_PTT_pin, HIGH); // PTT enable tone(CW_pin,CW_PITCH); delay(500); // Wait for TX to go live + stabilize measurement Pout = map(analogRead(Po_pin), 0,1023,0,1000); // @ 25w probe voltage = 2,50 v and 25w RF = 50v peak digitalWrite(PRM_PTT_pin, LOW); noTone(CW_pin); Pout = (Pout * Pout) / 1000 ; // now Pout on 50 Ohm in tenths of Watt if (Pout < 100) lcd.print(" "); // less than 10w lcd.print((Pout/10), DEC); lcd.print("."); lcd.print((Pout)%10, DEC); lcd.print("W"); delay(2000); // Recalculate some misc variables Pmin = Pmin * 10; Pmax = Pmax * 10; lcd.setCursor(12, 0); if ((Pout > Pmax) or (Pout < Pmin)) { // we have RF Error ! RPT_status = (94); lcd.print("FAIL ! "); digitalWrite(BUZZER_pin, HIGH); delay (2000); digitalWrite(BUZZER_pin, LOW); ErrorHandling(); // Error routine } else { lcd.print("PASS "); delay (2000); } lcd.setCursor(0, 1); lcd.print("Going live "); delay (1500); wdt_reset(); // Reset Watchdog timer lcd.print("."); delay (1000); lcd.print("."); delay (1000); lcd.print("."); delay (1000); // Recalculate some pre-defined time constants in milliseconds V_BeaconTime = V_BeaconTime * 60000; V_ActBeaconTime = V_ActBeaconTime * 60000; V_RptActTime = V_RptActTime * 60000; V_RecMsgTime = V_RecMsgTime * 1000; V_MiniMsgTime = V_MiniMsgTime * 1000; V_PlayMsgTime = V_PlayMsgTime * 1000; V_MsgAgeTime = V_MsgAgeTime * 1000; V_ClrQRGTime = V_ClrQRGTime * 1000; // Orft jolly we go ... prepare the 1st beacon RPT_status = (1); // Set in beaconing mode at startup Beacon_RQ = (1); // Force beacon send immediately at startup BeaconTime = millis(); // Generate display lcd.clear(); update_display(); } //////////////////////////////////////////////////////// L O O P //////////////////////////////////////////////////////////////// void loop() { wdt_reset(); // Reset Watchdog timer // Test if any critical error condition , each cycle dummy = map(analogRead(Vcc_pin), 0,1023,0,(50*(R2+R1)/R1)); // supply voltage if (dummy > 149) RPT_status = (98); if (dummy < 115) RPT_status = (97); TEMP = int((Thermister(1023 - analogRead(NTC_pin))) + 0.5); // Read temp if (TEMP > 60) RPT_status = (99); if (RPT_status > 90) ErrorHandling(); // Test if frequency is free for a while // squelchtest(); // test SQL if ((RxBusy == 1) or (RxCTCSS == 1)){ // QRG not free ! ClrQRGTime = millis (); Clear_QRG = (0); } if ((millis() - ClrQRGTime) > V_ClrQRGTime) { Clear_QRG = (1); // QRG is now clear ! } //////////// R E C O R D A N D P L A Y T O G G L E /////////////////// // While in RX , test if a CTCSS is present --> prepare for RECORD status if ((TX_status == 0) and ((millis() - BeepDropTime) > V_BeepDropTime)) { if ((RxCTCSS == 0) and ((millis() - SQLdropTime) > V_SQLdropTime)) { //No CTCSS present, no flutter dropout KerchunkTime = millis (); CTCSS_QRG = (0); if (RPT_status == 2) { RPT_status = (3); // end of RECORD status, now we must PLAY PlayOnce = (1); // stop the chip RecEndTime = (millis () - V_SQLTailTime); // Remove SQL crash MsgDelayTime = millis(); RecMsgLength = RecEndTime - RecMsgTime; } } if ((RxCTCSS == 1) and (millis() - KerchunkTime) > V_KerchunkTime) { //CTCSS present if (CTCSS_QRG == 0){ // we detect CTCSS event 'UP' RecMsgTime = millis(); // we timestamp start of record PlayOnce = (0); // disregard any msg possibly waiting in memory, start a new one! CTCSS_QRG = (1); // A valid CTCSS is present during a minimum time RPT_status = (2); // Toggle to Record mode MaxMsgLenght = (0); // Reset overflow condition, if valid } SQLdropTime = millis(); // Flutter drop timestamp if ((millis() - RecMsgTime) >= V_RecMsgTime) MaxMsgLenght = (1); // Set flag for OVERFLOW } } ////////////////////////////// R E C O R D M O D E ///////////////////////////// if (RPT_status == 2) { if (ISD_status != 2) isd_start_record() ; // start record on chip if (MaxMsgLenght == 1) { // Test if max msg length reached RPT_status = (3); // Toggle to play mode PlayOnce = (1); isd_stop_record() ; // stop recording on the chip, memory is full !! RecEndTime = millis (); RecMsgLength = RecEndTime - RecMsgTime ; } } ////// END of RECORD mode ///////// ////////////////////////////// P L A Y M O D E ///////////////////////////////// if (RPT_status == 3) { if (ISD_status == 2) isd_stop_record() ; // stop recording on the chip // if (RecMsgLength < V_MiniMsgTime) { // recorded message too short, revert to beacon mode RPT_status = (1); digitalWrite(PRM_PTT_pin, LOW); // Just in case :o) MaxMsgLenght = (0); // Just in case :o) TX_status = (0); CTCSS_QRG = (0); PlayOnce = (0); Beacon_RQ = (0); loop(); } // test once if msg obsolete to play if (((millis() - SQLdropTime) > (V_MsgAgeTime + V_ClrQRGTime + 1000) ) and (PlayOnce == 1)) { // recorded message obsolete, return to beacon mode RPT_status = (1); digitalWrite(PRM_PTT_pin, LOW); // Just in case :o) TX_status = (0); //reset some parameters, before jumping to start of loop CTCSS_QRG = (0); PlayOnce = (0); Beacon_RQ = (0); A_Beacon_RQ = 0; // there is non CTCSS activity, cancel repeater 'active' status RPT_active = 0; MaxMsgLenght = (0); loop(); } // message is OK, now play if (Clear_QRG == 1) { // Frequency is clear ! if (PlayOnce == 1) tone(CW_pin,CW_PITCH+500); // generate signalling beep at start of message digitalWrite(PRM_PTT_pin, HIGH); // PTT enable delay(100); // Wait for TX to go live and RX unsquelch TX_status = (1); RPT_active = (1); // Repeater has played a msg and is considered 'active' update_display(); if (PlayOnce == 1) { //initiate the play PlayOnce = (0); LCD_refresh2 =(-1); //force update of some display data lcd.setCursor(13, 0); if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print(RecMsgLength / 1000,DEC); lcd.print("s"); delay(V_MsgDelayTime/2); // Lead in delay tone(CW_pin,CW_PITCH); delay(V_MsgDelayTime/2); // Lead in delay PlayStartTime = millis(); RPT_active = (1); // Repeater has played a msg and is considered 'active' isd_start_play (); // start playing of chip message /// Generate beacon 'while active' if ((RecMsgLength >= 10000) and (A_Beacon_RQ == 1)){ // we only generate beacon if message lenght > 10sec delay(V_MsgDelayTime); // Lead in delay lcd.setCursor(13, 0); // shown beacon TX in display lcd.print(" BCN"); sendmsg(RPT_call) ; // send call ActBeaconTime = millis(); // reset timers BeaconTime = millis(); A_Beacon_RQ = 0; // reset request } } // end playonce if ((millis() - PlayStartTime) >= RecMsgLength ) { // entire message played and remove SQL crash at EOM; ISD_status = (1); //Chip status now undefined, message is played digitalWrite(ISD_PD_pin, HIGH); // disable/reset chip if ((RecMsgLength >= V_MiniMsgTime) and (MaxMsgLenght == 0)) { // only 'K' if effective message repeated delay (300); lcd.setCursor(13, 0); lcd.print("RPRT"); // if (last_S_units == 0) sendmsg ("S0"); if (last_S_units == 1) sendmsg ("S1"); if (last_S_units == 2) sendmsg ("S2"); if (last_S_units == 3) sendmsg ("S3"); if (last_S_units == 4) sendmsg ("S4"); if (last_S_units == 5) sendmsg ("S5"); if (last_S_units == 6) sendmsg ("S6"); if (last_S_units == 7) sendmsg ("S7"); if (last_S_units == 8) sendmsg ("S8"); if (last_S_units == 9) sendmsg ("S9"); if (last_S_units == 95) sendmsg ("S9+05"); if (last_S_units == 19) sendmsg ("S9+10"); if (last_S_units == 15) sendmsg ("S9+15"); if (last_S_units == 29) sendmsg ("S9+20"); if (last_S_units == 25) sendmsg ("S9+25"); if (last_S_units == 39) sendmsg ("S9+30"); if (last_S_units == 35) sendmsg ("S9+35"); if (last_S_units == 49) sendmsg ("S9+40"); lcd.setCursor(13, 0); lcd.print(" K "); delay (300); sendmsg ("K"); delay (100); } if (MaxMsgLenght == 1) { // play msg if OVERFLOW detected delay (500); sendmsg ("OVER"); delay (100); } /* if ((RecMsgLength < V_MiniMsgTime) and (RecMsgLength > 100) ) { // if record too short play 'S' delay (500); sendmsg ("S"); delay (100); } */ digitalWrite(PRM_PTT_pin, LOW); // PTT disable MaxMsgLenght = (0); // reset overflow indicator TX_status = (0); RPT_status = (1); // Now set beacon mode Beacon_RQ = (0); MaxMsgLenght = (0); RPT_active = (1); // Repeater has played a msg and is considered 'active' ActBeaconTime = millis(); // Reset the activity timer last_S_units = (0); // Reset last S unit loop (); } // end message played routine } } ////// END of PLAY mode ///////// /////////////////////////// B E A C O N M O D E /////////////////////////////// if (RPT_status == 1) { // We are in BCN mode // Check for rollover millis() // // 4,294,967,295 = 49 days roll-over // 2,592,000,000 = 30 days roll-over // 604,800,000 = 7 days roll-over // 86,400,000 = 24 h roll-over // only reset while in BCN mode, not waiting for send BCN, freq clear, repeater not active // if ((millis() > 86400000) and (Beacon_RQ == 0) and (RxBusy == 0) and (RPT_active == 0)) softReset(); if ((millis() > 2592000000) and (Beacon_RQ == 0) and (RxBusy == 0) and (RPT_active == 0)) softReset(); // Beacon Request // if (((millis() - BeaconTime) > V_BeaconTime)) { Beacon_RQ = (1); // A beacon must be generated, set Beacon Request = 1 } else { // Beacon_RQ = (0); //reset Beacon request - uncomment --> no beacon at startup digitalWrite(PRM_PTT_pin, LOW); // Sure in RX mode ! TX_status = (0); } // Beacon Send // if ((Beacon_RQ == 1) and (Clear_QRG == 1)) { Beacon_RQ = (0); //reset Beacon request // BeaconTime = millis(); digitalWrite(PRM_PTT_pin, HIGH); // PTT enable TX_status = (1); update_display(); delay(V_MsgDelayTime); // Lead in delay update_display(); sendmsg(RPT_call) ; //send call update_display(); delay(7*DOTLEN) ; update_display(); sendmsg("QRV") ; //send info delay(7*DOTLEN) ; update_display(); sendmsg(RPT_ctcss) ; //send ctcss info update_display(); delay(7*DOTLEN) ; sendmsg(RPT_loc) ; //send qth locator delay(3*DOTLEN) ; digitalWrite(PRM_PTT_pin, LOW); // PTT disable BeaconTime = millis(); // reset time beacon sent ActBeaconTime = millis(); // reset as well beacon time 'while active' A_Beacon_RQ = 0; // a full beacon was sent, cancel any pending request TX_status = (0); update_display(); Clear_QRG = (0); // Reset Free frequency indicator ClrQRGTime = millis (); // Reset Clr QRG timer (inhibit immediate Re-TX) } } /// END OF BCN MODE update_display(); /// Every cycle, display is updated. In CW-TX mode also in the character send routine } //////// E N D L O O P ////////// ///////////////////////// CW GENERATION ROUTINES ////////////////////////// void dash() { tone(CW_pin,CW_PITCH); delay(DASHLEN); noTone(CW_pin); delay(DOTLEN) ; } //////////////////////////// void dit() { tone(CW_pin,CW_PITCH); delay(DOTLEN); noTone(CW_pin); delay(DOTLEN); } /////////////////////////// void send(char c) { int i ; if (c == ' ') { delay(7*DOTLEN) ; return ; } if (c == '+') { delay(4*DOTLEN) ; dit(); dash(); dit(); dash(); dit(); delay(4*DOTLEN) ; return ; } for (i=0; i V_RptActTime) { RPT_active = 0; A_Beacon_RQ = 0; } lcd.setCursor(5, 1); // Show Active status in display if (RPT_active == 1) lcd.print("A"); if ((RPT_active == 0) and (RPT_status < 90)) lcd.print(" "); if ((RPT_active == 1) and (((millis() - ActBeaconTime) > V_ActBeaconTime))) A_Beacon_RQ = 1; // Beacon request valid // 1st row, mode lcd.setCursor(0, 0); if (RPT_status == 0) lcd.print("BOOT"); if ((RPT_status == 1) and (RxBusy == 0) and (RxCTCSS == 0)) lcd.print("STBY"); // beacon mode lcd.setCursor(5, 0); if ((PlayOnce == 1) and (Clear_QRG == 0) and ((millis() - RecEndTime) > V_ClrQRGTime)) lcd.print("H"); // Hold transmission if (PlayOnce == 0) lcd.print(" "); lcd.setCursor(6, 0); if (MaxMsgLenght == 1) lcd.print("O"); if (MaxMsgLenght == 0) lcd.print(" "); lcd.setCursor(9, 0); if (RPT_status == 1) lcd.print("BCN="); // beacon mode if ((RPT_status == 1) and (Beacon_RQ == 0) and (TX_status == 0) ) { // we are in countdown mode lcd.setCursor(13, 0); dummy = (V_BeaconTime + 500 - (millis() - BeaconTime)) / 1000 ; // countdown time in sec if (dummy >= 60) { dummy = dummy / 6 ; if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print((dummy/10), DEC); lcd.print("m"); lcd.print((dummy)%10, DEC); } else { if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print(dummy,DEC); lcd.print("s"); } } if (RPT_status == 2) { // Record mode lcd.setCursor(9, 0); lcd.print("REC="); dummy = (millis() - RecMsgTime) / 1000 ; // countup time in sec if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print(dummy,DEC); lcd.print("s"); } if (RPT_status == 3) { // Play mode lcd.setCursor(9, 0); lcd.print("PLY="); dummy = (RecMsgLength - (millis() + 500 - PlayStartTime)) / 1000 ; // countdown time in sec if (dummy < 0) dummy = RecMsgLength / 1000; // show play time before play has started if (dummy >= 0) { if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print(dummy,DEC); lcd.print("s"); } } lcd.setCursor(13, 0); if ((RPT_status == 1)and (Beacon_RQ == 1)) lcd.print("HOLD"); if ((RPT_status == 1)and (TX_status == 1)) lcd.print("SEND"); // 2nd row , RX status lcd.setCursor(0, 1); if (TX_status == 0) { // update status while in RX squelchtest(); if ((RxBusy == 1) and (RxCTCSS == 0)) lcd.print("BUSY"); if (RxCTCSS == 1) lcd.print("TONE"); if ((RxBusy == 0) and (RxCTCSS == 0)) lcd.print(" "); lcd.setCursor(0, 0); if ((RxBusy == 1) or (RxCTCSS == 1)) lcd.print("-RX-"); // We are in RX, now show the RSSI LCD_refresh2 = LCD_refresh2 ++ ; // refresh partially some LCD data if ((LCD_refresh2 >= 50) or (LCD_refresh2 == 0) ) { // refresh some data only every X cycles LCD_refresh2 = (0); lcd.setCursor(19, 1); dummy = map(analogRead(RSS_pin),0,1023,0,500); // 0 to 500 if ( dummy <= uV_0 ) dummy = uV_0; if ( dummy <= uV_02 ) RSSI_uV = 1000 + map(dummy,uV_0,uV_02,0,20); // trick to display RSSI < 1 (should be float) if (( dummy > uV_02 ) and ( dummy <= uV_05 ) )RSSI_uV = 1000 + map(dummy,uV_02,uV_05,20,50); // trick to display RSSI < 1 (should be float) if (( dummy > uV_05 ) and ( dummy <= uV_1 ) )RSSI_uV = 1000 + map(dummy,uV_05,uV_1,50,100); // trick to display RSSI < 1 (should be float) if (( dummy > uV_1 ) and ( dummy <= uV_2 ) )RSSI_uV = 1000 + map(dummy,uV_1,uV_2,100,200); // trick to display RSSI 1 .0 to 9.9 (should be float) if (( dummy > uV_2 ) and ( dummy <= uV_5 ) ) RSSI_uV = 1000 + map(dummy,uV_2,uV_5,200,500); // trick to display RSSI 1 .0 to 9.9 (should be float) if (( dummy > uV_5 ) and ( dummy < uV_10 ) )RSSI_uV = 1000 + map(dummy,uV_5,uV_10,500,1000); // trick to display RSSI 1 .0 to 9.9 (should be float) if (( dummy >= uV_10) and ( dummy <= uV_20 ) )RSSI_uV = map(dummy,uV_10,uV_20,10,20); if (( dummy > uV_20 ) and ( dummy <= uV_50 ) )RSSI_uV = map(dummy,uV_20,uV_50,20,50); if (( dummy > uV_50 ) and ( dummy <= uV_100 ) )RSSI_uV = map(dummy,uV_50,uV_100,50,100); if (( dummy > uV_100) and ( dummy <= uV_200 ) )RSSI_uV = map(dummy,uV_100,uV_200,100,200); if ( dummy > uV_200) RSSI_uV = 500; if (RSSI_uV < 1000) { // RSSI > 9.9 µV if (RSSI_uV < 100) lcd.print(" "); if (RSSI_uV < 10 ) lcd.print(" "); lcd.print(RSSI_uV, DEC); lcd.print("uV"); // now show the corresponding S report lcd.setCursor(19, 0); lcd.print("S"); if ((RSSI_uV >= 5)and(RSSI_uV <= 8)) { lcd.print("9 "); S_units = (9);} if ((RSSI_uV > 8)and(RSSI_uV <= 15)) { lcd.print("9+05"); S_units = (95);} if ((RSSI_uV >15)and (RSSI_uV <=27)) { lcd.print("9+10"); S_units = (19); } if ((RSSI_uV >27)and (RSSI_uV <= 49)) { lcd.print("9+15"); S_units = (15); } if ((RSSI_uV >49)and (RSSI_uV <= 88)) { lcd.print("9+20"); S_units = (29); } if ((RSSI_uV >88)and (RSSI_uV <= 159)) { lcd.print("9+25"); S_units = (25); } if ((RSSI_uV >159)and (RSSI_uV <= 280)) { lcd.print("9+30"); S_units = (39); } if ((RSSI_uV >280)and (RSSI_uV <= 499)) { lcd.print("9+35"); S_units = (35); } if ( RSSI_uV >499){ lcd.print("9+40"); S_units = (49); } } if (RSSI_uV >= 1000) { // RSSI between 0 & 9.9 µV RSSI_uV = (RSSI_uV - 1000) / 10 ; lcd.print((RSSI_uV/10), DEC); lcd.print("."); lcd.print((RSSI_uV)%10, DEC); lcd.print("uV"); // now show the corresponding S report lcd.setCursor(19, 0); lcd.print("S"); if (RSSI_uV < 1) { lcd.print("0 "); S_units = (0);} if (RSSI_uV == 1) { lcd.print("3 "); S_units = (3);} if (RSSI_uV == 2) { lcd.print("4 "); S_units = (4);} if ((RSSI_uV >= 3)and (RSSI_uV <= 5)) { lcd.print("5 "); S_units = (5);} if ((RSSI_uV > 5) and (RSSI_uV <= 12)) { lcd.print("6 "); S_units = (6);} if ((RSSI_uV > 12)and (RSSI_uV <= 24)) { lcd.print("7 "); S_units = (7);} if ((RSSI_uV > 24)and (RSSI_uV <= 49)) { lcd.print("8 "); S_units = (8);} if ((RSSI_uV > 49)and (RSSI_uV <= 159)) { lcd.print("9 "); S_units = (9);} } if ((S_units > last_S_units) and (PlayOnce == 0) )(last_S_units = S_units); // memorize S meter readout, do not update if message waiting } // end RSSI every x cycles } // end display update in RX mode /////// update status while in TX MODE ///////////////// if (TX_status == 1) { lcd.setCursor(0, 0); lcd.print("-TX-"); lcd.setCursor(0, 1); lcd.print(" "); LCD_refresh2 = LCD_refresh2 ++ ; // refresh partially some LCD data if ((LCD_refresh2 >= 50) or (LCD_refresh2 == 0) or (RPT_status == 1) ) { // refresh some data only every X cycles // except in BCN mode, refresh every cycle LCD_refresh2 = (0); lcd.setCursor(19, 0); // P meter display lcd.print("Power"); lcd.setCursor(19, 1); Pout = map(analogRead(Po_pin), 0,1023,0,1000); // @ 25w probe voltage = 2,50 v and 25w RF = 50v peak Pout = (Pout * Pout) / 1000 ; // now Pout on 50 Ohm in tenths of Watt if (Pout < 100) lcd.print(" "); // less than 10w lcd.print((Pout/10), DEC); lcd.print("."); lcd.print((Pout)%10, DEC); lcd.print("W"); //test if RF output OK if ((Pout > Pmax) or (Pout < Pmin)) { // we have RF Error ! delay (300); // let's try again ! Pout = map(analogRead(Po_pin), 0,1023,0,1000); // @ 25w probe voltage = 2,50 v and 25w RF = 50v peak Pout = (Pout * Pout) / 1000 ; // now Pout on 50 Ohm in tenths of Watt if ((Pout > Pmax) or (Pout < Pmin)) { RPT_status = (94); // we definitely have a RF error ErrorHandling(); } } } } // clear old data lcd.setCursor(17, 0); lcd.print(" "); lcd.setCursor(17, 1); lcd.print(" "); LCD_refresh = LCD_refresh ++ ; // refresh partially some LCD data if ((LCD_refresh >= 200) or (LCD_refresh == 0) ) { // refresh some data (temp, voltage)only every X cycles LCD_refresh = (0); lcd.setCursor(8, 1); // we read temperature TEMP = int((Thermister(1023 - analogRead(NTC_pin))) + 0.5); // Read temp if ((TEMP > 65) or (TEMP < -15)){ // We have a NTC error ! RPT_status = (95); ErrorHandling(); } if ((TEMP > 5) or (TEMP == 0)) lcd.print(" "); // If temp more than +5° or zero, print space, if ((TEMP > 0) and (TEMP <= 5))lcd.print("+"); // If temp between 0° and +5°, print + // if temp < 0° the '-' will show up. if (abs(TEMP)<10) lcd.print(" "); lcd.print(TEMP,DEC); lcd.write(0); if (TEMP >= 40) { digitalWrite(FAN_pin, HIGH); // FAN ON // Test if FAN is effectively running wdt_reset(); // Reset Watchdog timer dummy = digitalRead (FAN_Pulse_pin); // H = not running / L = running if (dummy == 1) { // Fan just starting up ? delay(300); // wait to start... dummy = digitalRead (FAN_Pulse_pin); // read again ! if (dummy == 1) { // Fan definitely not running! RPT_status = (96); ErrorHandling(); } } lcd.setCursor(6, 1); lcd.print("F"); } if (TEMP <= 38) { digitalWrite(FAN_pin, LOW); // FAN OFF lcd.setCursor(6, 1); lcd.print(" "); } if (TEMP >= 50) { delay (100); // sure no spike ? TEMP = int((Thermister(1023 - analogRead(NTC_pin))) + 0.5); // Read temp if (TEMP >= 50) RPT_status = (99); // overtemp ! } // Read power supply voltage lcd.setCursor(12, 1); dummy = map(analogRead(Vcc_pin), 0,1023,0,(50*(R2+R1)/R1)); if (dummy < 100) lcd.print(" "); if (dummy < 10) lcd.print(" "); lcd.print((dummy/10), DEC); lcd.print("."); lcd.print((dummy)%10, DEC); lcd.print("v"); if (dummy > 149) RPT_status = (98); if (dummy < 115) RPT_status = (97); } } ////////// SQUELCH READ ROUTINE //////// void squelchtest() { RxBusy = (0); // assume RX is squelched, no signal if (analogRead(PRM_SQL_pin) < 720) RxBusy = (1); // RX is unsquelched, change status ( < 3.5v) // GM350 RxCTCSS = (0); // assume RX is muted, no CTCSS if (analogRead(PRM_CTCSS_pin) < 720) RxCTCSS = (1); // RX is unmuted, change status ( < 3.5v) // GM350 if ((millis() - BeepDropTime) < V_BeepDropTime) { // keep record active after 'frequency busy' beep RxBusy = (1); RxCTCSS = (1); } } ////////// ISD2560 RECORD MODE //////// void isd_start_record() { wdt_reset(); digitalWrite(ISD_PD_pin, HIGH); // Power down chip to reset digitalWrite(ISD_mode_pin, LOW); // RECORD mode ISD_status = (2); digitalWrite(BUZZER_pin, HIGH); // tone(CW_pin,1300); // send short beep to signal repeater is in record mode // if (RPT_active == 1) digitalWrite(PRM_PTT_pin, HIGH); // PTT enable to send beep delay(20); digitalWrite(ISD_PD_pin, LOW); // enable & reset pointer chip delay(20); // if (RPT_active == 1) delay(50); // digitalWrite(PRM_PTT_pin, LOW); // PTT disable BeepDropTime = millis(); // do not uncomment // noTone(CW_pin); digitalWrite(ISD_CE_pin, LOW); // Start record delay(300); digitalWrite(BUZZER_pin, LOW); last_S_units = (0); // reset the S meter reading } void isd_stop_record() { ISD_status = (1); digitalWrite(ISD_CE_pin, HIGH); // Stop record delay(50); digitalWrite(ISD_PD_pin, HIGH); // disable/reset chip } ////////// ISD2560 PLAY MODE //////// void isd_start_play() { digitalWrite(ISD_PD_pin, HIGH); // Power down chip to reset digitalWrite(ISD_CE_pin, HIGH); digitalWrite(ISD_mode_pin, HIGH); // PLAY mode ISD_status = (3); delay (20); digitalWrite(ISD_PD_pin, LOW); // enable chip & reset pointer delay(20); digitalWrite(ISD_CE_pin, LOW); // Start play by pulse on CE delay(10); digitalWrite(ISD_CE_pin, HIGH); noTone(CW_pin); // end of signal beep at start of msg } //////////// RESET CONTROLLER ////////////// void softReset(){ lcd.clear(); lcd.print("Resetting ... pls STBY"); digitalWrite(BUZZER_pin, HIGH); digitalWrite(PRM_PTT_pin, HIGH); // PTT enable delay(300); digitalWrite(BUZZER_pin, LOW); sendmsg(RPT_call) ; //send call sendmsg(" RESET PLS STAND BY"); //send reset msg delay(300); digitalWrite(PRM_PTT_pin, LOW); // PTT disable delay(300); digitalWrite(PRM_PWR_pin, LOW); // power OFF RTX digitalWrite(FAN_pin, LOW); // FAN OFF asm volatile (" jmp 0"); // start software from scratch } //////////// ERROR HANDLING ////////////// void ErrorHandling(){ // Fatal errors if (ErrorTime == 0) { // The first time an error occured lcd.clear(); ErrorTime = millis(); // timestamp/flag error occurance lcd.setCursor(0, 0); lcd.print(" SYSTEM FAULT ! "); digitalWrite(PRM_PWR_pin, LOW); // power OFF RTX digitalWrite(PRM_PTT_pin, LOW); // Just 2 be sure digitalWrite(FAN_pin, LOW); // FAN OFF } wdt_reset(); lcd.setCursor(0, 1); if (RPT_status == 94) lcd.print(" RF OUTPUT ERROR "); if (RPT_status == 95) lcd.print(" N T C ERROR "); if (RPT_status == 96) lcd.print(" FAN NOT RUNNING "); if (RPT_status == 97) lcd.print(" POWER SUPPLY LOW VOLT "); if (RPT_status == 98) lcd.print(" POWER SUPPLY HIGH VOLT "); if (RPT_status == 99) lcd.print(" TRANSCEIVER HIGH TEMP "); digitalWrite(BUZZER_pin, HIGH); delay(200); digitalWrite(BUZZER_pin, LOW); delay(1800); lcd.setCursor(0, 1); lcd.print(" "); delay(500); // generate time since occurance lcd.setCursor(0, 1); lcd.print(" Since "); lcd.setCursor(9, 1); ErrorSinceSec = (millis() - ErrorTime)/1000; ErrorSinceDay = ErrorSinceSec / 86400; // days ErrorSinceHr = (ErrorSinceSec - (ErrorSinceDay * 86400)) / 3600; // hours ErrorSinceMin = (ErrorSinceSec - (ErrorSinceDay * 86400) - (ErrorSinceHr * 3600) ) / 60; // minutes ErrorSinceSec = ErrorSinceSec - (ErrorSinceDay * 86400) - (ErrorSinceHr * 3600) - (ErrorSinceMin * 60) ; // seconds if (ErrorSinceDay < 10) lcd.print(" "); if (ErrorSinceDay == 0) lcd.print(" "); if (ErrorSinceDay > 0) { lcd.print(ErrorSinceDay, DEC); lcd.print("d "); }; if (ErrorSinceHr < 10) lcd.print("0"); lcd.print(ErrorSinceHr, DEC); lcd.print("h"); if (ErrorSinceMin < 10) lcd.print("0"); lcd.print(ErrorSinceMin, DEC); lcd.print("m"); if (ErrorSinceSec < 10) lcd.print("0"); lcd.print(ErrorSinceSec, DEC); lcd.print("s "); delay(2000); lcd.setCursor(0, 1); lcd.print(" "); delay(500); ErrorHandling(); }