Arduino CTCSS encoder

Several years ago I've owned a Kenwood TR751E and I've always regretted that I've sold this rig as looking back it was the best 2m multimode transceiver I've ever owned. So when I suddenly got the chance to buy, in very good condition, not only the TR751E but also the TR851E as a pair of transceivers I didn't think twice !

However one disadvantage of older generation transceivers is that they don't have any CTCSS system. Especially the encoder part is a lack as most repeaters use these tones nowadays.

So I've decided to 'quickly' put some lines of code in an Arduino and make myself a CTCSS encoder. The handy and easy Arduino C-library however isn't particularly optimized when it comes to the utilization of the timer/counters of the ATMega328p that runs the Arduino. And to be honest not for a lot of other parts of the chip as well. But that's the trade-of: easy code for less features. So after a handful of lines of Arduino code I've realized that it would be way more efficient to do this the bits and bytes way and use the MCU registers.

The result is the code you can find here that produces a square wave on pin D9 with the CTCSS frequency selected with an "up" and "down" switch on pin's D2 and D3.

You can use, copy and extend this code freely as long as you include the license that gives these equal right to use, copy and extent it freely again to others !

/********************************************************************************
 *                                                                              *
 *                      ATMEGA328P HAM RADIO CTCSS ENCODER                      *
 *                                Version: V2.0                                 *
 *                                                                              *
 *                Created by Geert Vanhulle - ON7GF - November 2018             *
 *                                                                              *
 * Released under GPL3 - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007   *    
 *                                                                              *
 *    Derivative work can only be distributed under these same license terms    *
 *    For the full license text goto: fsf.org - The Free Software Foundation    *
 *                                                                              *
 ********************************************************************************/


/* ----------------------------------------------------------------------------
 *  
 *  dit is nog niet de uiteindelijke versie. De output van deze versie is een
 *  blokgolf signaal op pin D9 met de juiste frequentie, doch een blokgolf is
 *  niet 100% bruikbaar als ctcss omdat de harmonischen in het signaal 
 *  een ctcss-decoder in de war kunnen brengen. 
 *  
 *  De finale versie moet dus direct een sinus kunnen produceren.
 *   
   ---------------------------------------------------------------------------- */

// tabel gegevens
byte ocrL[50]; byte ocrH[50];

// ctcss tonen
float ctcssFreq[] =   {  67.0,    69.3,    71.9,    74.4,    77.0,   79.7,    82.5,    85.4,    88.5,    91.5,
                         94.8,    97.4,   100.0,   103.5,   107.2,  110.9,   114.8,   118.8,   123.0,   127.3, 
                        131.8,   136.5,   141.3,   146.2,   151.4,  156.7,   159.8,   162.2,   165.5,   167.9,  
                        171.3,   173.8,   177.3,   179.9,   183.5,  186.2,   189.9,   192.8,   196.6,   199.5,
                        203.5,   206.5,   210.7,   218.1,   225.7,  229.1,   233.6,   241.8,   250.3,    254.1      
};

// isr vlaggen
bool up = false;
bool dn = false;

// actieve toon uit de lijst
int toon=0;


// interrupt vector up
ISR(INT0_vect){
  up=true;
}

// interrupt vector dn
ISR(INT1_vect){
  dn=true;
}


void setup() {

 ctcsscalc();

 /* frequentie-deler */ 
 // configuratie Timer1
 TCCR1A = 0x50;
 TCCR1B = 0x0A;
 TCCR1C = 0x00;

 // zet pin PB1 = OCR1A = "D9" als output
 DDRB |= 1 << PB1; 

 // zet het deeltal in het output compare register
 OCR1AH = ocrH[0];
 OCR1AL = ocrL[0];

 /* interrupts */
 // pin 2 & 3 als input
 DDRD &= ~(1 << DDD2); 
 DDRD &= ~(1 << DDD3);
 
 // falling edge
 EICRA |=  (1 << ISC11);
 EICRA &= ~(1 << ISC10);
 // falling edge
 EICRA |=  (1 << ISC01);
 EICRA &= ~(1 << ISC00);

 // enable interrupt
 EIMSK |= (1 << INT0);
 EIMSK |= (1 << INT1);

 /* led's als display welke toon */
 // PC0 (A4) ... PC5 (A0) = uitgang 
 DDRC  = B00111111;
 PORTC = B00000000;

 Serial.begin(9600);

 displayToon();
}

void loop() {

  if (up) {
    for (double wait=0; wait<25000; wait++) { int a=0; } // debounce
    toon++;
    if (toon>49) {toon=0;}
    OCR1AH = ocrH[toon];
    OCR1AL = ocrL[toon];
    displayToon();
    up=false;
  }
  if (dn) {
    for (double wait=0; wait<25000; wait++) { int a=0; } // debounce
    toon--;
    if (toon<0) {toon=49;}
    OCR1AH = ocrH[toon];
    OCR1AL = ocrL[toon];
    displayToon();
    dn=false;
  }

}



// bereken deeltal ocrnx voor elke frequentie
void ctcsscalc() {

  int N = 8;
  int clk = 16; // 16 MHz clock

  for (int n=0; n<50; n++) {
  
    int ocrLH = ( clk * 1000000 ) / (( 2 * N * ctcssFreq[n] ) - 1 );
       
    ocrH[n] = ocrLH >> 8;
    ocrL[n] = ocrLH & 0x00FF;

  }
}

// display welke toon er actief is
void displayToon() {

    //LED's
    PORTC = toon;
    
    // serial monitor
    Serial.print(toon); Serial.print(" -- "); 
    Serial.print(ctcssFreq[toon]); Serial.print(" -- "); 
    Serial.print(ocrH[toon], HEX);  Serial.print(" -- ");  Serial.println(ocrL[toon], HEX);
}