For our local repeater ON0WV, I was looking out for a possibility to decode the assigned CTCSS tone (sent from 'friends'), and next to that, detect CTCSS from signals sent to other repeaters in the surrounding area, on same frequency (sent from 'foes').
Instead of providing almost 10 separate dedicated decoders, I explored the possibilities to handle this by one ARDUINO board.
First of all, the CTCSS signal must be extracted from the receiver discriminator output and leveled so that it can be fed into ARDUINO interrupt port. For this, at least one - preferably two - audio low pass filters (cutoff 300 Hz) in cascade must be used, followed by a signal conditioner. See the circuit which I used here around LM358 op amp's, able to operate with 5v supply. In presence of a sine wave CTCSS, the output to ARDUINO will now result in a clean square wave from 0 to +5v amplitude - that is a good start ! If you want to further improve the detection reliability, especially in the noise at low signals, by rejection of 'voice' audio frequencies, it is advisable to add an additional LPF in front of the circuit. Here you can download a schematic diagram of a 300 Hz low pass filter, if your CTCSS frequency is lower, feel free to lower the cut-off frequency accordingly ...
Now, decoding of a CTCSS in presence of a signal properly modulated with a CTCSS subtone can be quite easily implemented.
Adrian YO3HJV provided a sketch for this, you can download it here or look into into below this page. Thanks for sharing Adrian !
The method used by Adrian is to count a number of rising pulses (by using interrupts) during a sampling period (typically set to 100ms), and deduct the frequency of the signal by comparing the timestamp (in µs) of first and last pulse during sampling period.
This works perfectly when there is a clean RF signal, but in case of absence of
signal (noise from discriminator), generates continuously (every 2 or 3 seconds)
random reads ... furthermore, in presence of voice modulation, signals with
noise (due to flutter), the readout is not stable and reliable.
Off course, this could be filtered in some manner, stating that at least X
identical decodes would be required before a CTCSS is considered as 'valid', but
this is not acceptable in practice as 100 tot 200ms is really the maximum to
have the repeater reacting to an incoming signal.
So, the challenge is in fact not to decode in a random manner CTCSS in case of no RF signal , when only noise is present from the discriminator output.
First of all, ARDUINO interrupts can be set to handle 'CHANGE' events instead of 'RISING' or 'FALLING' - this provides us with the double of events during one sampling period (typically 100ms, which is quite short, but in practice the maximum to remain 'transparent' to repeater users/operators) . As a matter of facts, the more events we have in one sampling period, the better we can analyse the signal.
This was already a first improvement introduced in the detection method.
The second improvement was a 'waveform analysis' algorithm developed by Paul ON4ADI, in order to determine if a 'clean' CTCSS is present or not. The logic behind his algorithm can be downloaded here ( in Dutch - pdf of 3MB).
After passing a digital noise filter (to eliminate spikes), the quality of the signal is assessed by measurement of the cycle-to-cycle period variance during the 100ms sampling timer and, provided it is sufficiently good, the frequency is marked as 'valid'.
Hereby frequencies beyond CTCSS limits are eliminated by a digital high and low-pass filter, and it is analysed if the pulse waveform is consistent / coherent during the sampling period. Pulses which are deviating a certain % from the wave format are not considered in the counting process. This helps to eliminate false counts by noise spikes.
As a third improvement, when the sampling has been carried out, it is checked
how many valid pulses were detected against the supposed CTCSS frequency.
If the % amount of 'valid counts' is below a preset quota, it is
considered that the sampling has not lead to a reliable output result.
The result is that the CTCSS decoding is now rock-solid :
when only noise is present, at most 1 stray decode of the FRIEND CTCSS will occur per hour.
when a signal is present, the correct CTCSS will be decoded in 100ms time, even if the signal is weak in the noise.
when voice modulation is present, the decode holds well, unless the signal is very over-modulated and distorted.
The sketch hereby provided is quite generic and used to demonstrate the ability of decoding on a 2x16 LCD display. It can be used as a plug-and-play module in your own development, where you will have to provide all the logic in function of CTCSS decoded.
The parameters commented in the sketch
allow to finetune the detection in accordance with applicable CTCSS tone(s) and
how 'stringent' this detection is set. The result can be seen on the LCD,
indicating on the first line the frequency measured and the number of valid
counts detected in the sample ( 'vv ), on the second the corresponding CTCSS
validated, the number of counts in the sample ('c' ) and the number of
'friend' CTCSS detected (rolling counter 00 - 99, useful to check how many valid
decodes occurred over a certain period of time).
If parameters are set too stringent, decoding will not be assured in a reliable manner in presence
of - for example - voice signals or DTMF tones.
If parameters are not set 'tight' enough, risk of stray CTCSS
decoding wil occur in presence of noise from discriminator output.
Adjust the audio level fed to the LPF and
signal conditioner (making square waves out of sine) to a point where a reliable detection of the
highest desired CTCSS frequency possible, and in the noise the number of 'valid
decodes' is 0 or 1 (as shown on the LCD display 2nd line at right) , sometimes
2, but does not exceed 2.
Increasing further the audio level (valid decodes >2) will only increase the
risk of random decodes.
Normally, in presence of only noise, maximum 1 stray decode of FRIEND CTCSS will occur per hour.
See the demo of sketch on Youtube
The sketch was compiled with IDE version 0022.- IMPORTANT : please use the same or you might get errors when compiling ! So it is still with 'pde' extension. You still can download previous versions from ARDUINO website (see my other ARDUINO stuff under 'PROJECTS' page)
// Frequency counter sketch, for measuring frequencies low enough to execute an interrupt for each cycle // Connect the frequency source to the INT0 pin (digital pin 2 on an Arduino Uno) // By Adrian, YO3HJV // This work is released to Public Domain // First published on January, 18th 2015 on // http://yo3hjv.blogspot.ro/2015/01/ctcss-decoder-with-arduino.html // This code can be used for free but I will appreciate if you mention the author. // 73 de Adrian, YO3HJV #include <LiquidCrystal.h> //#include <Wire.h> /* May be uncomment for standard Arduino LCD library. I am using a weird library here... */ LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // set for my configuration. Let PIN2 to be the input for the signal. volatile unsigned long timpPrimImpuls; volatile unsigned long timpUltimImpuls; volatile unsigned long numImpuls; void setup() { //Serial.begin(19200); // print for debugging. Uncomment if necessary lcd.begin(16, 2); } // Measure the frequency over the specified sample time in milliseconds, returning the frequency in Hz float readFrequency(unsigned int sampleTime) { numImpuls = 0; // start a new reading attachInterrupt(0, counter, RISING); // enable the interrupt delay(sampleTime); detachInterrupt(0); return (numImpuls < 3) ? 0 : (996500.0 * (float)(numImpuls - 2))/(float)(timpUltimImpuls - timpPrimImpuls); } // NOTE: 996500.0 is the value find by me. The theoretic value is "1000000000.0" // Start with this value and check the precision against a good frequency meter. void loop() { float freq = readFrequency(100); lcd.setCursor(0, 0); lcd.print("Freq: "); lcd.print (freq); lcd.print(" "); lcd.print("Hz "); lcd.setCursor (0, 1); // Too low but over 10 Hz if ((freq > 10) && (freq < 65.8)) { lcd.print(" TOO LOW "); //DO SOMETHING } else if ((freq > 66.00) && (freq < 68.00)) { lcd.print("CT: 67.0 Hz, XZ "); //DO SOMETHING } else if ((freq > 68.30) && (freq < 70.30)) { lcd.print("CT: 69.3 Hz, WZ "); //DO SOMETHING } else if ((freq > 70.90) && (freq < 72.90)) { lcd.print("CT: 71.9 Hz, XA "); //DO SOMETHING } else if ((freq > 73.40) && (freq < 75.40)) { lcd.print("CT: 74.4 Hz, WA "); //DO SOMETHING } else if ((freq > 76.00) && (freq < 78.00)) { lcd.print("CT: 77.0 Hz, XB "); //DO SOMETHING } else if ((freq > 78.70) && (freq < 79.70)) { lcd.print("CT: 79.70 Hz, WB "); //DO SOMETHING } else if ((freq > 81.50) && (freq < 83.50)) { lcd.print("CT: 82.5 Hz, YZ "); //DO SOMETHING } else if ((freq > 84.30) && (freq < 86.50)) { lcd.print("CT: 85.4 Hz, YA "); //DO SOMETHING } else if ((freq > 87.40) && (freq < 89.60)) { lcd.print("CT: 88.5 Hz, YB "); //DO SOMETHING } else if ((freq > 90.40) && (freq < 92.60)) { lcd.print("CT: 91.5 Hz, ZZ "); //DO SOMETHING } else if ((freq > 93.7) && (freq < 95.90)) { lcd.print("CT: 94.8 Hz, ZA "); //DO SOMETHING } else if ((freq > 96.30) && (freq < 98.5)) { lcd.print("CT: 97.4 Hz, ZB "); //DO SOMETHING } else if ((freq > 99.00) && (freq < 101.00)) { lcd.print("CT: 100.0 Hz, 1Z "); //DO SOMETHING } else if ((freq > 102.40) && (freq < 104.60)) { lcd.print("CT: 103.5 Hz, 1A "); //DO SOMETHING } else if ((freq > 106.10) && (freq < 108.30)) { lcd.print("CT: 107.2 Hz, 1B "); //DO SOMETHING } else if ((freq > 109.80) && (freq < 112.00)) { lcd.print("CT: 110.9 Hz, 2Z "); //DO SOMETHING } else if ((freq > 113.60) && (freq < 116.00)) { lcd.print("CT: 114.8 Hz, 2A "); //DO SOMETHING } else if ((freq > 117.60) && (freq < 119.90)) { lcd.print("CT: 118.8 Hz, 2B "); //DO SOMETHING } else if ((freq > 122.00) && (freq < 124.00)) { lcd.print("CT: 123.0 Hz, 3Z "); //DO SOMETHING } else if ((freq > 126.20) && (freq < 128.40)) { lcd.print("CT: 127.3 Hz, 3A "); } else if ((freq > 130.40) && (freq < 133.00)) { lcd.print("CT: 131.8 Hz, 3B "); } else if ((freq > 135.00) && (freq < 138.00)) { lcd.print("CT: 136.5 Hz, 4Z "); //DO SOMETHING } else if ((freq > 140.00) && (freq < 142.80)) { lcd.print("CT: 141.3 Hz, 4A "); //DO SOMETHING } else if ((freq > 145.00) && (freq < 147.80)) { lcd.print("CT: 146.2 Hz, 4B "); //DO SOMETHING } else if ((freq > 150.00) && (freq < 152.80)) { lcd.print("CT: 151.4 Hz, 5Z "); //DO SOMETHING } else if ((freq > 156.00) && (freq < 158.80)) { lcd.print("CT: 157.7 Hz, 5A "); //DO SOMETHING } // NON-STANDARD else if ((freq > 159.00) && (freq < 161.00)) { lcd.print("CT: 159.8 Hz, -- "); //DO SOMETHING } else if ((freq > 161.00) && (freq < 163.50)) { lcd.print("CT: 162.2 Hz, 5B "); //DO SOMETHING } // NON-STANDARD else if ((freq > 164.00) && (freq < 166.30)) { lcd.print("CT: 165.5 Hz, -- "); //DO SOMETHING } else if ((freq > 166.60) && (freq < 169.00)) { lcd.print("CT: 167.9 Hz, 6Z "); //DO SOMETHING } // NON-STANDARD else if ((freq > 170.00) && (freq < 172.40)) { lcd.print("CT: 171.3 Hz, -- "); //DO SOMETHING } else if ((freq > 172.60) && (freq < 175.00)) { lcd.print("CT: 173.8 Hz, 6A "); //DO SOMETHING } //NON-STANDARD else if ((freq > 176.00) && (freq < 178.50)) { lcd.print("CT: 177.3 Hz, -- "); //DO SOMETHING } else if ((freq > 178.6) && (freq < 181.00)) { lcd.print("CT: 179.9 Hz, 6Z "); //DO SOMETHING } //NON-STANDARD else if ((freq > 182.00) && (freq < 184.80)) { lcd.print("CT: 183.5 Hz, -- "); //DO SOMETHING } else if ((freq > 185.00) && (freq < 187.50)) { lcd.print("CT: 186.2 Hz, 7Z "); //DO SOMETHING } //NON-STANDARD else if ((freq > 188.40) && (freq < 191.30)) { lcd.print("CT: 189.9 Hz, -- "); //DO SOMETHING } else if ((freq > 191.00) && (freq < 194.00)) { lcd.print("CT: 192.8 Hz, 7A "); //DO SOMETHING } //NON-STANDARD else if ((freq > 195.40) && (freq < 198.00)) { lcd.print("CT: 196.6 Hz, -- "); //DO SOMETHING } //NON-STANDARD else if ((freq > 198.30) && (freq < 201.00)) { lcd.print("CT: 199.5 Hz, -- "); //DO SOMETHING } else if ((freq > 202.00) && (freq < 204.00)) { lcd.print("CT: 203.5 Hz, M1 "); //DO SOMETHING } else if ((freq > 205.00) && (freq < 208.00)) { lcd.print("CT: 206.5 Hz, 8Z "); //DO SOMETHING } else if ((freq > 209) && (freq < 212.00)) { lcd.print("CT: 210.7 Hz, M2 "); //DO SOMETHING } else if ((freq > 217.00) && (freq < 219.30)) { lcd.print("CT: 218.1 Hz, M3 "); } else if ((freq > 224.00) && (freq < 227.00)) { lcd.print("CT: 225.7 Hz, M4 "); } else if ((freq > 227.60) && (freq < 231.30)) { lcd.print("CT: 229.1 Hz, 9Z "); //DO SOMETHING } else if ((freq > 231.70) && (freq < 235.00)) { lcd.print("CT: 233.6 Hz, -- "); //DO SOMETHING } else if ((freq > 239.60) && (freq < 243.00)) { lcd.print("CT: 241.8 Hz, M6 "); //DO SOMETHING } else if ((freq > 248.00) && (freq < 252.00)) { lcd.print("CT: 250.3 Hz, M7 "); //DO SOMETHING } else if ((freq > 252.70) && (freq < 256.80)) { lcd.print("CT: 254.1 Hz, 0Z "); //DO SOMETHING } else if (freq > 256.80) { lcd.print(" TOO HIGH "); //DO SOMETHING } else { lcd.setCursor (0, 1); lcd.print(" NOISE "); // or, comment the line above and // uncomment the line below for an empty LCD line // lcd.print(" "); } delay(50); // lcd.clear(); //Not necessary. uncomment but will flicker! } //END OF LOOP void counter() { unsigned long now = micros(); if (numImpuls == 1) { timpPrimImpuls = now; } else { timpUltimImpuls = now; } ++numImpuls; }
// Demo version on 2x16 LCD display #include <LiquidCrystal.h> LiquidCrystal lcd(12,11,10,9,8,7); ////////////////// PARAMETERS WHICH CAN BE FINETUNED FOR OPTIMAL DECODING ///////////////////////////////////////////////////////// // Decoding of a CTCSS in presence of a signal properly modulated with a CTCSS subtone is no problem and can be easily implemented. // The challenge is however NOT TO DECODE in a random manner CTCSS in case of no RF signal, when only noise is present from the dicriminator output. // The sketch below includes a waveform analysis performed during the sampling period, to determine if a 'clean' CTCSS is present or not. // Some latitude is however permitted to take into account flutter, short spikes, interaction with voice modulation etc. and still indicate a // stable and reliable CTCSS tone. // The parameters below allow to finetune the detection in accordance with CTCSS tone(s) to be detected. // If parameters are set too stringent, decoding will not be assured in a realiable manner in presence of voice signals // If parameters are not set 'tight' enough, stray decoding of 'FRIEND' CTCSS wil occur in presence of noise (signal from discriminator output) // the sketch allows decode of 'FRIEND' and 'FOE' CTCSS // Adjust the audio level fed to LPF and signal conditioner (making square waves out of sine) to a point where a reliable detection of highest desired CTCSS is possible, // and in the noise the number of 'valid decodes' is 0 or 1, sometimes 2, but does not exceed 2. Increasing further the audio level (valid decodes > 2) will only increase // the risk of random decodes. Normally, in preesence of only noise, maximum 1 stray decode of FRIEND CTCSS will occur per hour. volatile unsigned long ctcssBand = 10; // This is +/- % allowed error in waveform format detected, to filter out noise (ideally , waveform should be a pure square wave) // Do not set too low or decoding will not be possible when voice signals are present float validdecodes = 50; // This is the % of valid CTCSS waveforms decoded in one sampling period, required to eliminate spikes & random decode in noise int FmaxCtcss = 150; // Highest CTCSS frequency to decode. This will set a spikes filter and prevent false decode in noise int FminCtcss = 62; // Lowest CTCSS frequency to decode. This will set a spikes filter and prevent false decode in noise //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// byte ctcssGood = 0; // debug counter valid # FRIEND decodes byte ctcssFriend = 0; // When friend ctcss decoded, goes to 1 volatile unsigned long counter = 0; volatile unsigned long oddEven = 0; volatile unsigned long startStopOk = 0; volatile unsigned long secondLastStartStopOk = 0; volatile unsigned long startStopTime = 0; volatile unsigned long lastOddTime = 0; volatile unsigned long secondLastOddTime = 0; volatile unsigned long lastEvenTime = 0; volatile unsigned long numPeriodesOk = 0; volatile unsigned long totalTimeOk = 0; volatile unsigned long periodTimeMinus = 0; volatile unsigned long periodTimePlus = 0; volatile unsigned long periodTime = 0; float result = 0; float freq = 0.0; volatile unsigned long low_passF = 0; volatile unsigned long high_passF = 0; //////////////////////////////////////////////////////////////////////////////// void setup() { // START OF setup pinMode(13, OUTPUT); digitalWrite(13, LOW); lcd.begin(16, 2); // Recompute some variables ctcssBand = ctcssBand * 100; validdecodes = validdecodes / 100; low_passF = 500000 / FmaxCtcss; // Spikes filter high_passF = 1000000 / FminCtcss; // Spikes filter } // END OF setup //////////////////////////////////////////////////////////////////////////////// void loop() { // START OF loop freq = getFrequency(100); // copy 7EQ lcd.setCursor(0, 0); lcd.print("F: "); if (freq < 100) lcd.print(" "); if (freq < 10) lcd.print(" "); lcd.print (freq,2); lcd.print(" Hz"); lcd.setCursor(13, 0); lcd.print("v"); // show # valid waveforms if (numPeriodesOk < 10) lcd.print(" "); lcd.print (numPeriodesOk); // lcd.setCursor (0, 1); // Too low but if (freq < 65.8) { lcd.print("NO CTCSS"); digitalWrite(13, LOW); // visual indication FRIEND CTCS ctcssFriend = 0; //reset friend ctcss status //DO SOMETHING } //----------------------------------------- else if ((freq > 66.00) && (freq < 68.00)) { lcd.print("CT: 67.0"); //DO SOMETHING } //----------------------------------------- else if ((freq > 68.30) && (freq < 70.30)) { lcd.print("CT: 69.3"); //DO SOMETHING } else if ((freq > 70.90) && (freq < 72.90)) { lcd.print("CT 71.9"); //DO SOMETHING } else if ((freq > 73.40) && (freq < 75.40)) { lcd.print("CT 74.4"); //DO SOMETHING } else if ((freq > 76.00) && (freq < 78.00)) { lcd.print("CT 77.0"); //DO SOMETHING } else if ((freq > 78.70) && (freq < 80.70)){ lcd.print("CT 79.7"); digitalWrite(13, HIGH); if (ctcssFriend == 0){ ctcssFriend = 1; ++ctcssGood; if(ctcssGood == 100) ctcssGood = 0; } //DO SOMETHING WITH FRIEND DECODED } else if ((freq > 81.50) && (freq < 83.50)) { lcd.print("CT 82.5"); //DO SOMETHING } else if ((freq > 84.40) && (freq < 86.40)) lcd.print("CT: 85.4"); //DO SOMETHING else if ((freq > 87.50) && (freq < 89.50)) { lcd.print("CT 88.5"); //DO SOMETHING } else if ((freq > 90.50) && (freq < 92.50)) { lcd.print("CT: 91.5"); //DO SOMETHING } else if ((freq > 93.8) && (freq < 95.80)) { lcd.print("CT 94.8"); //DO SOMETHING } else if ((freq > 96.40) && (freq < 98.4)) { lcd.print("CT: 97.4"); //DO SOMETHING } else if ((freq > 99.00) && (freq < 101.00)) { lcd.print("CT: 100.0"); //DO SOMETHING } else if ((freq > 102.50) && (freq < 104.50)) { lcd.print("CT 103.5"); //DO SOMETHING } else if ((freq > 106.20) && (freq < 108.20)) { lcd.print("CT: 107.2"); //DO SOMETHING } else if ((freq > 109.90) && (freq < 111.90)) { lcd.print("CT 110.9"); //DO SOMETHING } else if ((freq > 113.80) && (freq < 115.80)) { lcd.print("CT: 114.8"); //DO SOMETHING } else if ((freq > 117.80) && (freq < 119.80)) { lcd.print("CT 118.8Hz"); //DO SOMETHING } else if ((freq > 122.00) && (freq < 124.00)) { lcd.print("CT 123.0"); //DO SOMETHING } else if ((freq > 126.20) && (freq < 128.40)) { lcd.print("CT: 127.3"); //DO SOMETHING } else if ((freq > 130.80) && (freq < 132.80)) { lcd.print("CT: 131.8"); //DO SOMETHING } else if ((freq > 135.50) && (freq < 137.50)) { lcd.print("CT: 136.5"); //DO SOMETHING } else if ((freq > 140.30) && (freq < 142.30)) { lcd.print("CT: 141.3"); //DO SOMETHING } else if ((freq > 145.20) && (freq < 147.20)) { lcd.print("CT: 146.2"); //DO SOMETHING } else if ((freq > 150.40) && (freq < 152.40)) { lcd.print("CT: 151.4"); //DO SOMETHING } else if ((freq > 156.70) && (freq < 158.70)) { lcd.print("CT: 157.7"); //DO SOMETHING } // NON-STANDARD else if ((freq > 158.80) && (freq < 160.80)) { lcd.print("CT: 159.8"); //DO SOMETHING } else if ((freq > 161.20) && (freq < 163.20)) { lcd.print("CT: 162.2"); //DO SOMETHING } // NON-STANDARD else if ((freq > 164.50) && (freq < 166.50)) { lcd.print("CT: 165.5"); //DO SOMETHING } else if ((freq > 166.90) && (freq < 168.90)) { lcd.print("CT: 167.9"); //DO SOMETHING } // NON-STANDARD else if ((freq > 170.30) && (freq < 172.30)) { lcd.print("CT: 171.3"); //DO SOMETHING } else if ((freq > 172.80) && (freq < 17.80)) { lcd.print("CT: 173.8"); //DO SOMETHING } //NON-STANDARD else if ((freq > 176.00) && (freq < 178.50)) { lcd.print("CT: 177.3"); //DO SOMETHING } else if ((freq > 178.9) && (freq < 180.90)) { lcd.print("CT: 179.9"); //DO SOMETHING } //NON-STANDARD else if ((freq > 182.50) && (freq < 184.50)) { lcd.print("CT: 183.5"); //DO SOMETHING } else if ((freq > 185.20) && (freq < 187.20)) { lcd.print("CT: 186.2"); //DO SOMETHING } //NON-STANDARD else if ((freq > 188.90) && (freq < 190.90)) { lcd.print("CT: 189.9"); //DO SOMETHING } else if ((freq > 191.80) && (freq < 198.80)) { lcd.print("CT: 192.8"); //DO SOMETHING } //NON-STANDARD else if ((freq > 195.60) && (freq < 197.60)) { lcd.print("CT: 196.6"); //DO SOMETHING } //NON-STANDARD else if ((freq > 198.50) && (freq < 200.50)) { lcd.print("CT: 199.5"); //DO SOMETHING } else if ((freq > 202.50) && (freq < 204.50)) { lcd.print("CT: 203.5"); //DO SOMETHING } else if ((freq > 205.50) && (freq < 207.50)) { lcd.print("CT: 206.5"); //DO SOMETHING } else if ((freq > 209.7) && (freq < 211.70)) { lcd.print("CT: 210.7"); //DO SOMETHING } else if ((freq > 217.10) && (freq < 219.10)) { lcd.print("CT: 218.1"); } else if ((freq > 224.70) && (freq < 226.70)) { lcd.print("CT: 225.7"); //DO SOMETHING } else if ((freq > 228.10) && (freq < 230.10)) { lcd.print("CT: 229.1"); //DO SOMETHING } else if ((freq > 232.60) && (freq < 234.60)) { lcd.print("CT: 233.6"); //DO SOMETHING } else if ((freq > 240.80) && (freq < 242.80)) { lcd.print("CT: 241.8"); //DO SOMETHING } else if ((freq > 249.30) && (freq < 251.30)) { lcd.print("CT: 250.3"); //DO SOMETHING } else if ((freq > 253.10) && (freq < 255.10)) { lcd.print("CT: 254.1"); //DO SOMETHING } else if (freq > 255.10) { lcd.print("TOO HIGH "); //DO SOMETHING } else { // No known CTCSS or noise lcd.setCursor (0, 1); lcd.print("NO CTCSS"); digitalWrite(13, LOW); // visual indication FRIEND CTCS ctcssFriend = 0; //reset friend ctcss status } ///// DO EVERY LOOP // print counter # interrupts lcd.setCursor (13, 1); lcd.print("c"); if ((counter-1)/2 < 10) lcd.print(" "); lcd.print ((counter-1)/2) ; // debug counter # interrupts in one sampling period // print counter # valid friend decodes (Counts till 99 and rolls-over to 00) lcd.setCursor (9, 1); lcd.print(" "); if (ctcssGood < 10) lcd.print("0"); lcd.print (ctcssGood,DEC); } // END OF loop //////////////////////////////////////////////////////////////////////////////// float getFrequency(unsigned int sampleTime) { // START OF getFrequency FUNCTION counter = 1; totalTimeOk = 0; numPeriodesOk = 0; attachInterrupt(0, interruptHandlerChange, CHANGE); delay(sampleTime); detachInterrupt(0); if (totalTimeOk < 40000) { result = 0.0; } else if ((totalTimeOk / numPeriodesOk) < 4000){ // digital filter Low Pass result = 0.0; } else if ((totalTimeOk / numPeriodesOk) > 16000){ // digital filter High Pass result = 0.0; } else if (numPeriodesOk <= (((counter-1)/2)*validdecodes)) // check how many valid waves in one sample { result = (0.0); } else { result = (1000700.0 * (float)(numPeriodesOk))/(float)(totalTimeOk); // NOTE: 1000700.0 is the value found by me. The theoretic value is "1.000.000,0" // Start with this value and check the precision against a good frequency meter. } return result; } // END OF getFrequency FUNCTION //////////////////////////////////////////////////////////////////////////////// void interruptHandlerChange() { // START OF interruptHandlerChange ROUTINE startStopTime = micros(); if (counter == 1) // if counter is 1 : we have to initialize { oddEven = 1; startStopOk = 2; secondLastStartStopOk = 2; lastOddTime = startStopTime; secondLastOddTime = startStopTime; lastEvenTime = startStopTime; } if (oddEven == 1) // lets pretend : odd is a rising change { oddEven = 2; startStopOk = 1; if (((startStopTime - lastEvenTime) < low_passF)) // Spikes filter { startStopOk = 2; } if (((startStopTime - lastOddTime) < (low_passF * 2))) { startStopOk = 2; secondLastStartStopOk = 2; } if (((startStopTime - lastOddTime) > high_passF)) // Spikes filter { startStopOk = 2; secondLastStartStopOk = 2; } lastOddTime = startStopTime; } else // lets pretend : even is a falling change { oddEven = 1; if (((startStopTime - lastOddTime) < low_passF)) { startStopOk = 2; } if ((startStopOk == 1) && (secondLastStartStopOk == 1)) { if (numPeriodesOk == 0) { numPeriodesOk++; totalTimeOk = totalTimeOk + lastOddTime; totalTimeOk = totalTimeOk - secondLastOddTime; } else { periodTime = totalTimeOk * ctcssBand; // Wave form analysis periodTimeMinus = (totalTimeOk * 10000); periodTimePlus = periodTimeMinus; periodTimeMinus = (periodTimeMinus - periodTime); periodTimePlus = (periodTimePlus + periodTime); periodTimeMinus = (periodTimeMinus / numPeriodesOk); periodTimePlus = (periodTimePlus / numPeriodesOk); periodTime = (lastOddTime - secondLastOddTime); periodTime = (periodTime * 10000); if ((periodTime > periodTimeMinus) && (periodTime < periodTimePlus)) { numPeriodesOk++; totalTimeOk = totalTimeOk + lastOddTime; totalTimeOk = totalTimeOk - secondLastOddTime; } else { if (numPeriodesOk < 3) { numPeriodesOk = 1; totalTimeOk = lastOddTime - secondLastOddTime; } } } } if ( startStopOk == 1 ) { secondLastOddTime = lastOddTime; secondLastStartStopOk = startStopOk; } lastEvenTime = startStopTime; } counter++; // debug good decodes } // END OF interruptHandlerChange ROUTINE ////////////////////////////////////////////////////////////////////////////////