ARDUINO CTCSS decoder

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.

The challenge in decoding CTCSS

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.

The solution !

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 :

The sketch

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

Remarks

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)


YO3HJV Original Sketch

// 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;
}


ON4ADI / ON7EQ improved CTCSS detection

// 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

////////////////////////////////////////////////////////////////////////////////