/*
see http://www.sadarc.org/xenforo/upload/index.php?threads/ic22s-arduinoized.275/
Originally, Frequ synth for TR-7200GII
Denys VK3ZYZ (with help from Google and others)
Fixes by Ray and Josh 20190728
CTCSS tones added :)
try to port it to OLED. (S,+,-)
Add mode display
Add CTCSS display *************** made it float. Check to see if tones still work! YES :)
Invert frequ display on TX
Had CTCSS on wrong channel Fixed!
made display reset = -1
Add CTCSS as per https://www.amateurradio.com.au/repeaters
Max number of channels = 61 as RAM clashes.
Adjust OLED freq display position.
20211202 add calibration.
Try to just use Aduino pins, no I2C.
Move TRX from 2 to A2
Move REV from 5 to A3
from sheet printed for AU frequs...
PLL N = (f -144.4)/.025
CHANGE PTT SENSE TO ACTIVE LOW. 20220720
20230308 CTCSS int x 10 to save space.
Use old duplex switch to select other options.??????
*/
#define Version "IC22S_20230309"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// synthesiser default settings on start up.
unsigned long RXgenfrequency = 146050000;
unsigned long TXgenfrequency = 146050000;
unsigned long IFoffset = 10700000; // IF offset
unsigned long PLLoffset = 131700000; //133690000; //check these frequs!
unsigned long chan_spacing = 25000; //15000;
float txDisplayFreq = 146.000;
float rxDisplayFreq = 146.000;
int mode = HIGH; // HIGH = normal, LOW = REV
#define RX HIGH
int TRXmode = RX; // HIGH = RX, LOW = TX
int channel = 2; // start on channel number "31" (starts from 0)
int encoder; // encoder direction: CCW=-1; NC=0; CW=1
int timer = 100; // sub tone setting. The higher the number, the slower the timing.
int i ;
int flag = LOW;
// encoder definitions
#define ENCODER_A A0
#define ENCODER_B A1
#define TRX A2 // PC2 input HIGH = TX, LOW = RX.
#define REV_SWITCH A3 // PC3 Encoder Button repeater reverse
// A4 = SDA
// A5 = SCL
#define ModeSwitch A6 // extra modes selector
// A7 // not used yet
// Use Port D to drive the PLL pins.
// NOTE the Arduino 0 and 1 pins swap the port bit order.
// 1 // PLL b0 PD0 (Serial TXD)
// 0 // PLL b1 PD1 (Serial RXD)
// 2 // PLL b2 PD2
// 3 // PLL b3 PD3
// 4 // PLL b4 PD4
// 5 // PLL b5 PD5
// 6 // PLL b6 PD6
// 7 // PLL b7 PD7
/* ********************* PortB0-PB4 used to generate CTCSS tones via R2R nerwork.
#define spare2 8 // Sub Tone R2R b0 PB0
#define spare3 9 // Sub Tone R2R b1 PB1
#define spare4 10 // Sub Tone R2R b2 PB2
#define spare5 11 // Sub Tone R2R b3 PB3
#define spare6 12 // Sub Tone R2R b4 PB4
*/
#define Output2 13 // not used yet
#define ENCODER_INPUT_PORT PINC
#define PLL_Port PIND
#define On 1
#define Off 0
#define RefFrequ 500000 // 16Mhz / 32 steps for CTCSS sine wave generator
int SineDivider = 4065; // default sine wave divider
long int RXFrequs [] // Edit this for RX frequencies wanted.
{ 146000,
146625,
146650,
146675,
146700,
146725,
146750,
146775,
146800,
146825,
146850,
146875,
146900,
146925,
146950,
146975,
147000,
146425,
146450,
146475,
146500,
146525,
146550,
146575,
146600,
147025,
147050,
147075,
147100,
147125,
147150,
147175,
147200,
147225,
147250,
147275,
147300,
147325,
147350,
147375,
147400,
147425,
147450,
147475,
147500,
147525,
147550,
147575,
147600,
144700,
144725,
144750,
144775,
144800,
144825,
144850,
144875,
144900,
145075,
145100,
145125, // 60 = chan 61
145150,
145175,
145200,
145225,
145250,
145275,
145300,
145325,
145350,
145375,
145400, // Chan = 72
};
int TXFrequs [] // Edit this for TX frequencies wanted. Added to RX frequs.
{ 0,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
-600,
0,
0,
0,
0,
0,
0,
0,
0,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
600,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0, // 60 = chan 61
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0, // Chan = 72
};
int SubTone [] // Edit this for CTCSS frequencies wanted.
{ 0, // "0 =" chan 1
0,
19, // chan 3
0,
10, // chan 5
0,
10, // chan 7
0,
0,
0,
0,
0,
0,
0,
10, // chan 15
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
19, // chan 26
0,
0,
10, // chan 29
10, // chan 30
0,
0,
0,
0,
0,
10, // chan 36
0,
0,
0,
10, // chan 40
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0, //"60"= Chan 61
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0, // Chan 72
};
float CTCSS = 0;
int CTCSS_Index = 0;
int CTCSS_Tones []
// 0 1 2 3 4 5 6 7 8 9
{ 0, 670, 693, 719, 744, 770, 797, 825, 854, 885, // +0
915, 948, 974, 1000, 1035, 1072, 1109, 1148, 1188, 1230, // +10
1273, 1318, 1365, 1413, 1462, 1500, 1514, 1567, 1598, 1622, // +20
1655, 1679, 1713, 1773, 1799, 1835, 1862, 1899, 1928, 1966, // +30
1995, 2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503, // +40
2541, // +50
};
int sinetable[] // one cycle sine wave via R2R network.
{ 0,1,2,3,5,7,10,13,16,19,22,25,27,28,29,30,31,30,29,28,27,25,22,19,16,13,10,7,5,3,2,1, };
void setup() {
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address may be 0x3D
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
display.display(); // Update screen
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE,BLACK); // Draw white text
display.setCursor(0,0); // Start at top-left corner
display.println(F(Version));
display.display(); // Update screen
delay(1000);
display.clearDisplay();
display.display(); // Update screen
delay(100);
display.setCursor(0,0);
display.print("Ch ");
display.display(); // Update screen
// configure rotary encoder & push-button
pinMode(ENCODER_A, INPUT_PULLUP); // encoder leads
pinMode(ENCODER_B, INPUT_PULLUP);
pinMode(REV_SWITCH, INPUT_PULLUP); // Hi = nomal, LOW = Reverse
pinMode(TRX, INPUT); // Hi = TX, LOW = RX
DDRB = B11111; // sets Arduino pins 8 to 12 as outputs.
PORTB = B10000; // R2R out at rest to half level.
DDRD = B11111111; // all bits outputs
encoder = read_encoder(); // returns +1/0/-1 (cw/nc/ccw)
mode=( digitalRead(REV_SWITCH));
TRXmode=(digitalRead(TRX));
set_synth(mode, TRXmode);
}
/******************************
main loop
*******************************/
void loop() {
if ( digitalRead(REV_SWITCH) != mode){
mode=( digitalRead(REV_SWITCH));
set_synth(mode, TRXmode);
}
if (digitalRead(TRX) !=TRXmode){
TRXmode=(digitalRead(TRX));
set_synth(mode, TRXmode);
}
if (TRXmode==RX)
{
// check the frequency
encoder = read_encoder(); // returns +1/0/-1 (cw/nc/ccw)
if (encoder != 0) {
channel = channel + encoder;
channel = constrain(channel, 0, 71); // max 72 channels.
set_synth(mode, TRXmode);
}
int temp2 = analogRead(ModeSwitch); // 2 readings the same?
if (temp2 == analogRead(ModeSwitch))
{
if (temp2 >= 700)
{if ( flag==LOW)
{ flag=HIGH;
ShowCTCSS();
}
}
while (analogRead(ModeSwitch) >= 700) // DupA >=300, spx = 0, DupB >=700 (461 and 839)
{
encoder = read_encoder();
if (encoder != 0)
{ int temp = (SubTone[channel]);
temp = temp + encoder;
temp = constrain(temp, 0, 50);
(SubTone[channel]) = temp;
ShowCTCSS();
}
}
if ((analogRead(ModeSwitch) <= 10)&& (flag==HIGH))
{ flag=LOW;
ShowCTCSS();
}
}
}
}
/******************************
read encoder direction
*******************************/
int read_encoder() {
// definitions
#define DELAY 100 // number of polling-loops
static unsigned short integrator; // encoder debounce
/* the array values are determined by combining the previous and current A1,A0
Gray Code bit-patterns to form an array-index. +1 is assigned to all clockwise
(CW) indexes; -1 is assigned to all counter-clockwise (CCW) indexes; and 0 is
assigned to the remaining positions to indicate no movement*/
static char encoder[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; // SWAPPED PHASES
static unsigned char encoder_index = 0;
static long count = 0; // CW rotation = +1; CCW rotation = -1
static long this_reading;
static long last_reading;
char value; // return value
/* check for encoder rotation. The absolute "count" value changes as the encoder rotates.
Typically there are four states (counts) per click, but often more due to contact bounce.
Bounce doesn't matter so long as the "count" decreases" for CCW rotation; remains constant
when there is no rotation; and increases with CW rotation. Bounce can be completely
eliminated if we look for the "detent" code and integrate as shown below. */
encoder_index <<= 2; // shift previous bit-pattern left two places
encoder_index |= (ENCODER_INPUT_PORT & 0x03); // get current bit-pattern and combine with previous
count += encoder[( encoder_index & 0x0f )];
// debounce encoder
if ((ENCODER_INPUT_PORT & 0x03) == 3) { // encoder on an indent (3 decimal == 00000011 binary)
if (integrator < DELAY) {
integrator++;
}
} else { // encoder not resting on indent
if (integrator > 0) {
integrator--;
}
}
// prepare the "return" value
if (integrator >= DELAY) { // encoder deemed to be stationary
this_reading = count;
if (this_reading < last_reading) value = -1;
if (this_reading == last_reading) value = 0;
if (this_reading > last_reading) value = 1;
last_reading = this_reading; // both readings now hold current "count"
integrator = 0; // reset integrator
} else {
value = 0;
}
return (value);
}
// set the display and PLL
void set_synth( int frmode, int trx )
{
display.setTextSize(1); // Normal 1:1 pixel scale
display.setCursor(18,0); // write channel number to the LCD 60
display.print(" "); // clear the old number
display.setCursor(18,0); // write channel number to the LCD
if (channel < 9) // "9" = 10 as array starts at 0
{
display.print(" "); // extra space for formalling a single digit.
}
display.print((channel + 1)); // display the channel number
display.setCursor(34,0); // write mode to the LCD
if (TXFrequs[channel]== 0)
{display.print(" SIM "); }
if (TXFrequs[channel] == 600)
{display.print("+600"); }
if (TXFrequs[channel] ==-600)
{display.print("-600"); }
ShowCTCSS();
// Select FWD or REV frequencies
if (frmode == HIGH) // test for fwd or rev repeater...
{ RXgenfrequency = (RXFrequs[channel]); // normal operation
TXgenfrequency = ((RXFrequs[channel]) + (TXFrequs[channel]));
}
else
{
TXgenfrequency = (RXFrequs[channel]); // reverse repeater operation
RXgenfrequency = ((RXFrequs[channel]) + (TXFrequs[channel]));
}
// Then display them
if ( trx == RX) // =RX.
{
rxDisplayFreq = float (RXgenfrequency)/1000; // display the RX frequency
display.setCursor(0,10); // was (10,10) for 6M. Start of second LCD line
display.setTextSize(3); // Normal 1:1 pixel scale
display.print(rxDisplayFreq, 3);
}
if ( trx == !RX) // =TX.
{
display.setTextColor(BLACK,WHITE); // Invert Frequ Display
display.setCursor(0,10); // was (10,10) for 6M
display.setTextSize(3); // large text
txDisplayFreq = float (TXgenfrequency)/1000;
display.print(txDisplayFreq, 3);
}
display.display(); // Update screen
display.setTextColor(WHITE,BLACK); // back to normal text
display.display(); // Update screen
// is it transmittig or receiving?
if ( trx == RX) // =RX.
{
setPLL(RXgenfrequency);
TIMSK1 = 0; // subtone off
}
else // =TX.
{
setPLL(TXgenfrequency);
// do we need subtones?
if (CTCSS_Index != 0)
// { SineDivider = (RefFrequ/(float)(CTCSS_Tones[CTCSS_Index]/10)); // Subtone table as Int = Float/10
{ SineDivider = (RefFrequ/CTCSS);
CTCSS_Tone(); // subtone on
}
else
{
TIMSK1 = 0; // subtone off
}
}
}
void CTCSS_Tone()
{
cli(); // interupsts off for setup
TCCR1A = 0; //
TCCR1B = 0; //B00000010; // no prescaler
// set compare match register for 1hz increments
OCR1A = SineDivider ; // = (16*10^6) / (1*1024) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 no prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // interupsts on
}
ISR (TIMER1_COMPA_vect) {
PORTB=(sinetable[i++]); // write value from table to PORTB
if(i>=32){
i=0;
}
}
// make this the drive for the diode matrix replacement.
void setPLL(unsigned long frq)
{ // from Oz ducumentation
int PLLdivider = ((frq - 144400)/25);
PORTD=(PLLdivider);
}
void ShowCTCSS(){
if (flag==HIGH)
{ display.setTextColor(BLACK,WHITE); // invert text
}
display.setTextSize(1); // Normal 1:1 pixel scale
display.setCursor(70,0); // write CTCSS to the LCD
display.print(" ");
CTCSS_Index=(SubTone[channel]);
if(CTCSS_Index!=0)
{display.setCursor(70,0);
display.print("tone");
CTCSS = ((CTCSS_Tones[CTCSS_Index])/10);
if(CTCSS<100) // pad leading spacee if needed
{ display.print(" ");}
display.print(CTCSS,1);
}
display.display();
display.setTextColor(WHITE,BLACK); // back to normal text
}