Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
I have choosen this CB transceiver because it can not be used here in the Netherlands legally on 27MHz. There is no FM on it and originally it has only 40 channels. There are other brands like Palomar, Universe and President that use the same PCB with 7.8MHz IF, which can also be used.
For this project, I designed a PCB on which there is an Arduino, DDS and a PLL. The PCB is connected to an OLED display, encoder with switch, a potmeter, and to the CB transceiver.
In the CB transceiver the PLL, reference oscillators, display and channel switch are removed and their function is take over by the PCB.
The RF circuits of receiving part and the transmitting part that are tuned to 27MHz are changed to 50MHz. This hardest part of the project was to solve oscillation of the transmitter by adding sufficient decoupling, shielding and grounding.
The Arduino has some inputs: - encoder with switch -clarifier potmeter - USB, lsb mode and transmit.
The Arduino has some outputs to: -OLED display -PLL - DDS
The VCO in the transceiver can be tuned from 57.8MHz to 59.8MHz. This is 7.8MHz (Intermediate frequency of the transceiver) above the actual working frequency of 50 to 52MHz. The VCO frequency is divided by a factor 588 in the PLL. The phase comparitor of the PLL works at about 100kHz. That is also the frequency the DDS makes. The DDS makes the reference frequency of the PLL. This way a very small frequency grid of 10Hz can be used. This is very conveinient when working in SSB.
The clarifier potmeter is in the original design connected to a reference oscillator. This oscillator is removed and the botmeter is connected to an analog input (A7) of the arduino. The receiving frequency can be changed + and - 512 Hz by tuning the clarifier. During transmitting it is disabled in the software.
In the next block diagram i hope to make things more clear:
Picture with the channel switch and display removed:
Now added an OLED display and encoder switch:
And how the firmware is uploaded to the transceiver:
A film of how the actual transceiver is working at 50MHz:
And it does not only work on 50MHz , but i have been able to add the PCB to a MAJOR M588 which is converted to the 70MHz band. This band is only legal in Europe as far as i know.
50MHz_OLED_PLL_DDS
ArduinoIt reads an encoder switch and sends data to an OLED display , PLL and DDS
/* Display for CB transceiver Arduino mini and SSD1606 Display
* Pins used :
* Display D0=4 D1=5 Reset =6 DC=7
* Encoder pin 2 , pin3 , Switch =A0
* PLL 10 , 11 , 12
*/
#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
#define pulseLow(pin) {digitalWrite(pin, LOW); digitalWrite(pin, HIGH); }
int encoderPin1 = 3; // encoder
int encoderPin2 = 2; // encoder
int lsbpin = 18 ; // assign LSB to pin D18/A4 pin D13 (spare) happen to be connected to a LED on the pro mini PCB
int usbpin = 8 ; // assign usb to D8
int txpin= 9; // assign tx to D9
int PLL_enb = 11; // assign PLL enable to D11
int PLL_dta = 10; // assign PLL data to D10
int PLL_clk = 12; // assign PLL clock to D12
int DDS_SDA = 17;
int DDS_SCL = 16;
int DDS_Fsync = 15;
volatile int lastEncoded = 0; // encoder
long lastencoderValue = 0; // encoder
int lastMSB = 0; // encoder
int lastLSB = 0; // encoder
int encoderValue = 0; // encoder
long rx=100000; // Starting frequency of VFO in 1Hz steps
const char* hertz = "PA5HE";
int hertzPosition = 5;
long rx2=1; // variable to hold the updated frequency
long increment = 10000; // starting VFO update increment in 10kHZ.
int buttonstate = 0; // if the button is pressed or not
int memstatus = 1; // value to notify if memory is current or old. 0=old, 1=current.
int_fast32_t timepassed = millis(); // int to hold the arduino millis since startup
int mode, oldmode =0;
int oldpotmeterValue=512; //mid position potmeter
/*
U8glib Example Overview:
Frame Buffer Examples: clearBuffer/sendBuffer. Fast, but may not work with all Arduino boards because of RAM consumption
Page Buffer Examples: firstPage/nextPage. Less RAM usage, should work with all Arduino boards.
U8x8 Text Only Example: No RAM usage, direct communication with display controller. No graphics, 8x8 Text only.
This is a page buffer example.
*/
// Please UNCOMMENT one of the contructor lines below
// U8g2 Contructor List (Picture Loop Page Buffer)
// The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected
U8G2_SSD1306_128X64_NONAME_1_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 4, /* data=*/ 5, /* cs=*/ U8X8_PIN_NONE, /* dc=*/ 7, /* reset=*/ 6);
//U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 12, /* dc=*/ 4, /* reset=*/ 6); // Arduboy (Production, Kickstarter Edition)
//U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_1_3W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // All Boards without Reset of the Display
//U8G2_SSD1306_128X64_NONAME_1_6800 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_1_8080 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8);
//U8G2_SSD1306_128X64_VCOMH0_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8); // same as the NONAME variant, but maximizes setContrast() range
// End of constructor list
void setup(void) {
u8g2.begin();
pinMode(encoderPin1, INPUT);
pinMode(encoderPin2, INPUT);
pinMode(usbpin,INPUT);
pinMode(lsbpin,INPUT);
pinMode(txpin,INPUT);
digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
digitalWrite(encoderPin2, HIGH); //turn pullup resistor on
attachInterrupt(0, updateEncoder, CHANGE); //interrupt connected to the encoder
attachInterrupt(1, updateEncoder, CHANGE);
pinMode(A0,INPUT); // Connect to a button that goes to GND on push
digitalWrite(A0,HIGH); //turn pullup resistor on
pinMode(PLL_enb, OUTPUT); // All pins connected to the PLL are outputs
pinMode(PLL_dta, OUTPUT);
pinMode(PLL_clk, OUTPUT);
pinMode(DDS_Fsync, OUTPUT);
pinMode(DDS_SDA, OUTPUT);
pinMode(DDS_SCL, OUTPUT);
resetPLL();
setupDDS();
setupPLL(); //set the PLL
// Serial.begin(9600); //for debugging
}
void loop(void) {
byte lsb=digitalRead(lsbpin);
byte usb=digitalRead(usbpin);
byte tx=digitalRead(txpin);
mode=lsb+usb+tx;
int frequencyerror = 400-512; // add here the frequencyerror at 50MHz in Hz 512 is the offset from the clarifierpotmeter
int offset=0; //has to do with the 7.8MHz IF of the Palomar
int potmeterValue = analogRead(A7); //read potmeter connected to pin 7 10bits ADC 0....1024
if ((rx != rx2) || (mode!=oldmode)||(potmeterValue!=oldpotmeterValue)){ //determine if something has changed
if (rx >=2000000){rx=rx2;}; // UPPER VFO LIMIT 52.000.0 MHz
if (rx <=0){rx=rx2;}; // LOWER VFO LIMIT 50.000.0 MHz
if (tx){ offset=frequencyerror+2902-(lsb*5128);}
else {offset=frequencyerror+(usb*2390)-(lsb*2738)+potmeterValue;} //tx switches the clarifier off
sendFrequency(rx+offset); // frequency is send to the DDS
showFreq(); //frequency is send to the display
rx2 = rx; //memory
if (rx2 >=2000000){rx2=2000000;}; // Correction in case there have been 2 interrupts
if (rx2 <=0){rx2=0;}; //
}
buttonstate = digitalRead(A0); // button is connected to A0
oldmode=mode;
oldpotmeterValue=potmeterValue;
if(buttonstate == LOW) {
setincrement();
};
if(memstatus == 0){ // Write the frequency to memory if not stored and 2 seconds have passed since the last frequency change.
if(timepassed+2000 < millis()){
// storeMEM();
}
}
}
void setincrement(){
if(increment == 10){increment = 50; hertz = "50 Hz";}
else if (increment == 50){increment = 100; hertz = "100 Hz";}
else if (increment == 100){increment = 500; hertz="500 Hz"; }
else if (increment == 500){increment = 1000; hertz="1 kHz"; }
else if (increment == 1000){increment = 5000; hertz="5 kHz"; }
else if (increment == 5000){increment = 10000; hertz="10 kHz"; }
else if (increment == 10000){increment = 100000; hertz="100 kHz";}
else{increment = 10; hertz = "10 Hz"; };
showFreq();
delay(200); // added delay to get a more smooth reaction of the push button
}
void updateEncoder(){
int MSB = digitalRead(encoderPin1); //MSB = most significant bit
int LSB = digitalRead(encoderPin2); //LSB = least significant bit
int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;
if(encoderValue == 4) {rx=rx+increment ; encoderValue=0;} // there are 4 changes between one click of the encoder
if(encoderValue == -4) {rx=rx-increment ; encoderValue=0;} // it can be neccessary to reduce these to 2 for other encoders
lastEncoded = encoded; //store this value for next time
}
void showFreq(){ // 0 frequency 1 frequencystep
char m_str[2];
byte millions=rx/1000000; //MHz
byte hundredk = ((rx/100000)%10); //100kHz
byte tenk = ((rx/10000)%10); //10kHz
byte onek = ((rx/1000)%10); //1kHz
byte hundreds = ((rx/100)%10); //100Hz
byte tens =((rx/10)%10); //10Hz
u8g2.firstPage();
do { u8g2.setFont(u8g2_font_logisoso16_tf);
u8g2.drawStr(0,39,"5");
u8g2.drawStr(10,39,strcpy(m_str, u8x8_u8toa(millions, 1)));
u8g2.drawStr(108,39,strcpy(m_str, u8x8_u8toa(hundreds, 1)));
u8g2.drawStr(118,39,strcpy(m_str, u8x8_u8toa(tens, 1)));
u8g2.drawStr(0,63,hertz);
u8g2.setFont(u8g2_font_logisoso38_tn);
u8g2.drawStr(18,39,".");
u8g2.drawStr(32,39,strcpy(m_str, u8x8_u8toa(hundredk, 1)));
u8g2.drawStr(56,39,strcpy(m_str, u8x8_u8toa(tenk, 1)));
u8g2.drawStr(80,39,strcpy(m_str, u8x8_u8toa(onek, 1)));
} while ( u8g2.nextPage() );
timepassed = millis();
memstatus = 0; // Trigger memory write
};
void resetPLL(void){ //RA2 enb RA0 clk RA1 dta
int counter=0; // enable high 5 clocks
digitalWrite(PLL_enb, HIGH);
digitalWrite(PLL_clk,LOW);
while (counter!=5 )
{ digitalWrite(PLL_clk, HIGH);
delayMicroseconds(3);
digitalWrite(PLL_clk, LOW);
delayMicroseconds(3) ;
++counter; }
counter =0;
digitalWrite(PLL_clk, LOW);
digitalWrite(PLL_dta, LOW);
digitalWrite(PLL_enb, LOW);
delayMicroseconds(3);
while (counter!=3 )
{ digitalWrite(PLL_clk, HIGH);
delayMicroseconds(3);
digitalWrite(PLL_clk, LOW);
delayMicroseconds(3) ;
++counter;
}
digitalWrite(PLL_dta, HIGH);
delayMicroseconds(3);
digitalWrite(PLL_clk, HIGH);
delayMicroseconds(3);
digitalWrite(PLL_clk, LOW);
digitalWrite(PLL_dta, LOW);
delayMicroseconds(3);
digitalWrite(PLL_clk, HIGH);
delayMicroseconds(3);
digitalWrite(PLL_clk, LOW);
delayMicroseconds(3);
digitalWrite(PLL_enb, HIGH);
}
void sendFrequency(double frequency) { // frequency in hz above 50MHz
frequency += 57800000 ; //add IF and basefrequency
long freq = frequency *268435456/1470000000; // 25 MHz clock on AD9833
// 1470 = 58.8 x 25Mhz
// Serial.print(freq,DEC); // DEBUG
// Serial.print("\n");
digitalWrite(DDS_Fsync, LOW);
tfr_byte(0x2000); //control word
digitalWrite(DDS_Fsync, HIGH);
digitalWrite(DDS_Fsync, LOW);
tfr_byte(0x4000|(freq & 0x3FFF)); //LSB first
freq>>=14;
tfr_byte(0x4000|(freq & 0x3FFF)); //MSB next
digitalWrite(DDS_Fsync, HIGH);
}
void tfr_byte(unsigned int data) { // transfers 16 bits, MSB first to the 9833 via serial DDS_SDA line
for (int i=0; i<16; i++, data<<=1) {
if ((data&0x8000) == 0) { digitalWrite(DDS_SDA,0);}
else {digitalWrite(DDS_SDA,1); }
pulseLow(DDS_SCL); //after each bit sent, CLK is pulsed low
}
}
void pllregister(unsigned int count,unsigned int bits){
digitalWrite(PLL_enb, LOW);
digitalWrite(PLL_clk, LOW);
unsigned int mask ;
mask = 0x01UL << (count-1);
while (count!=0)
{ if ((mask&bits) != 0){
digitalWrite(PLL_dta, HIGH); // dta to 1
delayMicroseconds(3); // not too fast
digitalWrite(PLL_clk, HIGH); // clk to 1
}
else { digitalWrite(PLL_dta, LOW); // dta to 0
delayMicroseconds(3);
digitalWrite(PLL_clk, HIGH); // clk to 1
}
delayMicroseconds(3);
digitalWrite(PLL_clk, LOW); // clk back to low
delayMicroseconds(3);
count--;
mask=mask>>1;
}
digitalWrite(PLL_enb, HIGH);
digitalWrite(PLL_clk, LOW);
}
void setupDDS(){
digitalWrite(DDS_Fsync, LOW);
tfr_byte(0x100); // reset DDS
tfr_byte(0x2000); // set DDS 0x2020=block 0x2000=sine
digitalWrite(DDS_Fsync, HIGH);
}
void setupPLL() {
pllregister (8,0x70) ; //write C register 70hex
pllregister (16,0x24C) ; //write N register N=588
pllregister (15,10); //write R register 10 = 100kHz
}
Comments