Compact Si5351 based SDR

Go here for the most up to date circuit /firmware mods: Design Updates

This is a revised version of my FV-1 based SDR. I replaced the CS2100 clk generator with the Si5351 clk generator. The Si5351 has some advantages over the CS2100, namely you can generate quadrature clks directly. This simplifies the hardware design and improves the quadrature accuracy. The sideband rejection in LSB/USB modes is impressive..somewhere around 60 db as best I can measure. The DSP processing is accomplished by the use of a FV-1 audio processor. The device makes the base band signal processing a snap. It requires some code to be loaded on a EEprom but the circuitry is simple and allows for up to 8 selectable programs. I created three: AM/USB/LSB . The FV-1 provides for three analog POT inputs to control any parameters you choose. Gain, variable filter bandwidth and depth, AGC are some examples of adjustable parameters if you desire. I kept it simple and created fixed band pass filters to taste. I did use one of the controls for AF gain. The design has no tuned circuits or band pass filters but they could easily be added.  It works just fine without them. Occasionally, I come across a ghost signal from harmonic mixing, when tuning, but not enough to matter. The design uses an OLED display and a rotary encoder for tuning. The frequency coverage is from 2.7 Mhz to 25Mhz. The bottom limit is created by the inability of the Si5351 to support quadrature below this frequency. Although I have improved my DSP programs for the FV-1 and have developed new display drivers and the new code for the Si5351, useful detail about using the Fv-1 can be found in my original design from a few years ago: https://circuitsalad.com/2015/06/19/comming-soon-stand-alone-software-defined-radio-baseband-demodulator-no-computer-required/

Schematic: Updated 05/17/2020

The design uses a LT1818 or THS4304 low noise op amp as an RF input with gain. It provides a constant and reliable resistive Rf termination for the sampling detector.  This allows for random antennas to be used without adversely affecting the input termination to the detector. All the code to operate the main processor(display/clk generator/tuning, band select and receive mode) was written in MikroC which is a C compiler for PIC and AVR processors. The generation of quadrature signals out of the Si5351 is not difficult to implement once you know how but..figuring that out took me a couple weeks of experimentation! You can connect switches, the encoder, volume pot and display directly to the main board for operation but I created a secondary board to mount the display and encoders. Instead of an analog pot and selection momentary switches, I used another microcontroller and two encoders(with one built in momentary push switch each) to create all of the switching signals, gain control, etc. This allowed me to have just two controls for all features.  The controls include: tuning, audio gain, mode, and tuning step. Tuning resolution is from 1Hz to 100KHz . For fun, I made the output of the FV-1 differential into the audio amp. This is not necessary.

Here is a link to all the files used to build this radio in a zip file(updated 2/07/20):

Tech File Download

The schematic and PCB was done with express pcb freeware. The C compiler used was MikroC, and FV-1 assemble was built in SpinAsm which is free and available from Spin Semiconductor(who makes the Fv-1). The gerber files provided were created for OSH park. I had my boards etched by them.  If anyone is interested in building this radio or leveraging elements of the design. I can answer questions.

Misc Notes: I use a 16650 3.7v lithium  rechargeable battery to power the radio. The current draw is about 100 mA with audio.  The radio works even when the regulators drop out so it will work at 3 v.

The enclosure is a machined aluminum 1590A style hammond box which you can buy on Ebay from alpinetech. They are $14.00 which is pricey but they are not cast. The quality is much nicer and you can anodize them.  It’s a different topic but home anodizing of aluminum is easy…and I do it with all my enclosures now. In this example, I anodized twice to create the base blue color and then the labeling as well. It looks really clean with this method. The nice thing about anodizing is if you make a mistake, it’s really easy to go back and redo the process.

Designers will note that the resistive terminations on the input RF OP amp contributes to the noise figure of the radio. As a practical matter. a negative impact on performance is not noticeable because of  atmospheric noise in the shortwave bands. For the best performance…no front end circuitry or a different front end input amplifier should be considered. Note that the op amp serves to bias the analog switches to half supply; so this bias must be provided to the sampling detector if the input termination is modified.  R10 set the impedance of the sampling detector, conversion gain, and low pass roll off. The schematic shows a value 0f 210 ohms…I think I am using 100 ohms actually now…which works well.

If you want quadrature out of the Si5351 below 3MHz you can create two outputs with 0 deg offset with one output at F and the other at 2F. You can then drive an analog mux with those signals and generate quadrature sampling for low frequency applications. Just note the output sequence of the samples change so you have to flip two outputs of the detector.

Top view of the circuit board:

sdr_top

Bottom View Showing FV-1 circuitry

sdr_bot

 

Display board

sdr_interface

Completed Radio

IMG_20190924_165851259

 

Demo Videos:

 

Very Low power 3 digit LCD Display with serial control

I had a need for a LCD display that consumed very little power and generated very little digital noise . You can buy LCD’s with driver chips but I have had great difficulty with these modules because they create excessive wide band noise.

To solve this problem, I made an LCD driver out of a 18F26K22 PIC micro-controller and a low cost static LCD display module. A static module requires more pins( a pin for every segment) but the drive logic is easier to implement. You cannot simply drive the the LCD segment high or low, as with a seven segment LED display. Once the capacitance of the display charges up, the segment contrast fades. The solution is to cycle a given segment on and off  at rate fast enough not to strobe(100Hz in my case).

In my design, the  display is driven by a four byte serial data packet which consists of: <startbyte>,<digit1>,<digit2>,<digit3> . The data bytes are sent as rs232 data at 9600 baud.

The startbyte is one of  three values(shown here in HEX): oxA0(no decimal point), 0xA1(decimal point 1st digit), 0xA2(decimal point 2nd digit). The valid data bytes are 0-9(non ASCII) and ASCII characters, “A”, “C”, “E”,”F”, “H”, and “P”. If you send any other values for a given digit, it will clear(blank the digit)

The module is clocked using the internal RC clock at 1Mhz and only draws 500uA when operating. It must be operated at greater than 4v or the LCD will be faint. If using a 3v supply, you can use a simple Dickson voltage doubler circuit to generate the supply voltage.

Picture of module(LCD covers the micro-controller):

LCDisplay

Demo Video: (Radio Receiver using the display module)

Schematic Diagram:

LCD_MOD_3Char

Link:   Zip File with ExpressPCB layout file and HEX file

http://bit.ly/1rDKMx1

Slightly improved code for PIC DDS

I realized my original code was delaying the PWM loop some because it was waiting on a ADC read before finishing the loop. In the code below, the ADC read is started and read the next time around. Then the next time is started again, this eliminates the delay of waiting on the ADC.

 

#define ADCStart ADCON0.b1 = 1 //set this bit to begin ADC conversion
//////////////////////////////////////////////////////////////////////////////
const unsigned char sine[256] =
{// sine wave 8 bit resolution scaled to 90% max val
131,132,135,137,140,143,146,149,152,155,157,160,163,166,168,171,
174,176,179,181,184,186,189,191,194,196,198,200,202,205,207,209,
211,212,214,216,218,219,221,223,224,226,227,228,229,231,232,233,
234,234,235,236,237,237,238,238,239,239,239,239,240,240,240,239,
239,239,239,238,238,237,237,236,236,235,234,233,232,231,230,229,
227,226,225,223,222,220,219,217,216,214,212,210,208,206,204,202,
200,198,196,194,192,189,187,185,182,180,177,175,173,170,167,165,
162,160,157,154,152,149,146,144,141,138,135,133,130,127,124,122,
119,116,113,111,108,105,103,100, 97, 95, 92, 89, 87, 84, 82, 79,
77, 74, 72, 69, 67, 64, 62, 60, 58, 55, 53, 51, 49, 47, 45, 43,
41, 39, 37, 36, 34, 32, 31, 29, 28, 26, 25, 24, 22, 21, 20, 19,
18, 17, 16, 15, 15, 14, 13, 13, 12, 12, 12, 11, 11, 11, 11, 11,
11, 11, 11, 12, 12, 12, 13, 14, 14, 15, 16, 16, 17, 18, 19, 21,
22, 23, 24, 26, 27, 29, 30, 32, 33, 35, 37, 39, 41, 43, 45, 47,
49, 51, 53, 56, 58, 60, 63, 65, 68, 70, 73, 75, 78, 81, 83, 86,
89, 92, 94, 97, 100,103,106,108,111,114,117,120,123,126,129,130};
////////////////////////////////Global variable here//////////////////////////////////////////////
long PhaseAccum;//phase accumilator generates the cycle rate for lookup table
//loading therby changing frequency. The MSbyte is used to provide the byte address
//of the look up table value to be used.
long PhaseShift;//value added to PhaseAccum every PWM cycle. This makes the waveform
//lookup faster or slower – which changes frequency.
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////PIC Config routine here////////////////////////////////////
void Init_Main(){
//PIC12F1822 specific config
OPTION_REG = 0b10000000; // disable internal pull ups
OSCCON = 0b11110000; //8MHz clk //32Mhz pll
TRISA = 0b00011000; // configure IO/a2d(gpio0) and mclr set as inputs
T2CON = 0b00000100;// TMR2 ON, postscale 1:1, prescale 1:1
PR2 = (0x50);// sets PWM rate to approx 98.5KHz with 32Mhz internal oscillator
CCP1CON = 0b00001111;// CCP1 ON, and set to simple PWM mode
PhaseShift = 0x00FFFFFF;//frequency values loaded into
ANSELA = 0b00010000; //select RA4 as A2D input 32Mhz clk
ADCON0 = 0b00001101; // configure ADC
ADCON1 = 0b00100000; // configure ADC
}
////////////////////////////main program loop here/////////////////////////////////
void main() {
Init_Main();//configue part
while(1){ //alway do this
while(!PIR1.TMR2IF);// wait for TMR2 cycle to restart
CCPR1L = (sine[((char *)&PhaseAccum)[3]]) >> 2;// load MSbits 7-2 duty cycle value into CCPRIL
CCP1CON ^=((sine[((char *)&PhaseAccum)[3]]) & 0x03) << 4;// load in bits 1-0 into 5 and 4 of CCP1CON
//////duty cycle value byte is now loaded for next cycle comming//////
if(PIR1.ADIF == 0)ADCStart; //start ADC here to get value into PhaseShift to change Freq
//will have to go around the loop one time before ready flag is high
if(PIR1.ADIF){ //if ADC read complete load values and clear flag
((char *)&PhaseShift)[1] = ADRESL; //load ADRESL into PhaseShift
((char *)&PhaseShift)[2] = ADRESH; //load ADRESH into PhaseShift
PIR1.ADIF = 0;//clear flag so next time ADC can run
}
PhaseAccum = PhaseAccum + ((PhaseShift << 5) + 1); //move PhaseAccum through waveform values
//”<<5″ can be more or less and sets the frequency sweep range
// the +1 is just so there is never 0;
porta.b0 = ((char *)&PhaseAccum)[3].b7;
PIR1.TMR2IF = 0; // clear TMR2 int flag
}
}

DDS audio generator made from PIC Microcontroller

I needed a sine wave generator and I didn’t want a whole lot of parts……. so I thought I would try making a Direct Digital Synthesizer out of a cheapo 8 pin micro controller.

I chose a 12F1822 PIC, but any micro with a PWM generator and an ADC will work.

A number of AVR and Microchip products are available. The 12f683 or the ATTINY85  are other devices that meet the basic  requirement. If using an AVR the code will have to changed but you can get the basic idea from the PIC implementation. If  you use another PIC, the code may need some minor tweaks , such as the pin assignments and/or the config register settings.

I settled on the  12f1822 for the following reasons: its cheap, its small, my C compiler supports it, it has an internal clk that runs up to 32MHz – which allows for 8 bit PWM at greater than 100kHz, which makes filtering the output trivial.  Also it is very power efficient.

The way DDS works  is by cycling through a look up table of values representing a sine wave(or any other wave form you choose… a triangle for example). These values range from 0-255 (8bit) and there are 256 values in  the table. Each value is loaded into a PWM generator in succession which varies the duty cycle of a roughly 100KHz PWM signal. The faster you  cycle through the values, the higher the frequency is generated.

I don’t want to explain all the details of how  DDS works- so go here to learn more about DDS:

Click to access MT-085.pdf

Here are the basics of my design:

I use the 10 bit ADC input to read a voltage of a POT and convert this to a number from 0 -1024. I use the PWM generator as a DAC to reproduce the sampled analog wave form. Then there is a simple filter to remove the high frequency artifacts caused by the 100Khz PWM signal. Every cycle of the PWM generator’s overflow flag – I add the value of the ADC to a 32 bit register which acts as a phase accumulator. The most significant byte of the phase accumulator is loaded as the address for the look up table value and as this value increments every cycle, a new value is loaded. If a smaller value is loaded into the phase accumulator, the frequency is lower. With a larger number loaded every cycle the frequency increases. The frequency resolution is given by 1/  (PWM period/2^32). This gives resolution less than a milli Hertz! The ADC value can be scaled as needed to get you in the frequency range of interest.

A note about my lookup table. The PWM generator will create distortion if the look up table values are not naturalized. This is because as the look up values are going up and down they should shift from when the off time occurs(either before the on time or after). So if you just generate the sine values they will work but not as well as they could. The solution is to naturalize the look up values. Which is to say shift them slightly. This process and how I did it is discussed here:

http://www.romanblack.com/onesec/Sine1kHz.htm

Also I scaled my sinewave to 90% of max and shifted it up 5%.  This makes it so that the PWM has no values to close to the extreme edges of its range. Also the output is from 1-4 volts instead of going all the way down to 0 volts. This way a single supply active filter can be employed.

Originally, I used a 12f683 PIC running at 8Mhz but could only support 20KHz PWM at 8bit resolution. The circuit worked but made it harder to filter the output and limited the frequency range to about 1Khz. the part I  used the 12f1822 has an internal clk that works up to 32 MHz. This allows a much faster PWM rate. Now I can get anywhere from .00005Hz to 5Khz output with a simple filter.

You can use an external crystal and get a more accurate and stable output, but the internal oscillator works pretty well. The harmonics are all 40db or more down from the fundamental.

Video demo:

Schematic:

C CODE: (compiled with MikroC ) will compile on freeware version with room to spare

Note!!!!! There is new code and is available here: https://circuitsalad.com/2014/06/13/slightly-improved-code-for-pic-dds/

#define ADCStart ADCON0.b1 = 1 //set this bit to begin ADC conversion
//////////////////////////////////////////////////////////////////////////////
const unsigned char sine[256] =
{// sine wave 8 bit resolution scaled to 90% max val
131,132,135,137,140,143,146,149,152,155,157,160,163,166,168,171,
174,176,179,181,184,186,189,191,194,196,198,200,202,205,207,209,
211,212,214,216,218,219,221,223,224,226,227,228,229,231,232,233,
234,234,235,236,237,237,238,238,239,239,239,239,240,240,240,239,
239,239,239,238,238,237,237,236,236,235,234,233,232,231,230,229,
227,226,225,223,222,220,219,217,216,214,212,210,208,206,204,202,
200,198,196,194,192,189,187,185,182,180,177,175,173,170,167,165,
162,160,157,154,152,149,146,144,141,138,135,133,130,127,124,122,
119,116,113,111,108,105,103,100, 97, 95, 92, 89, 87, 84, 82, 79,
77, 74, 72, 69, 67, 64, 62, 60, 58, 55, 53, 51, 49, 47, 45, 43,
41, 39, 37, 36, 34, 32, 31, 29, 28, 26, 25, 24, 22, 21, 20, 19,
18, 17, 16, 15, 15, 14, 13, 13, 12, 12, 12, 11, 11, 11, 11, 11,
11, 11, 11, 12, 12, 12, 13, 14, 14, 15, 16, 16, 17, 18, 19, 21,
22, 23, 24, 26, 27, 29, 30, 32, 33, 35, 37, 39, 41, 43, 45, 47,
49, 51, 53, 56, 58, 60, 63, 65, 68, 70, 73, 75, 78, 81, 83, 86,
89, 92, 94, 97, 100,103,106,108,111,114,117,120,123,126,129,130};
////////////////////////////////Global variable here//////////////////////////////////////////////
long PhaseAccum;//phase accumilator generates the cycle rate for lookup table
//loading therby changing frequency. The MSbyte is used to provide the byte address
//of the look up table value to be used.
long PhaseShift;//value added to PhaseAccum every PWM cycle. This makes the waveform
//lookup faster or slower – which changes frequency.
////////////////////////////////////////////////////////////////////////////////////////////
void ADCRead(){
ADCStart; //start ADC conversion
while(ADCON0.b1); //wait till done
((char *)&PhaseShift)[1] = ADRESL; //load ADRESL into PhaseShift
((char *)&PhaseShift)[2] = ADRESH; //load ADRESH into PhaseShift
}
////////////////////////////////PIC Config routine here////////////////////////////////////
void Init_Main(){
//PIC12F1822 specific config
OPTION_REG = 0b10000000; // disable internal pull ups
OSCCON = 0b11110000; //8MHz clk //32Mhz pll
TRISA = 0b00011000; // configure IO/a2d(gpio0) and mclr set as inputs
T2CON = 0b00000100;// TMR2 ON, postscale 1:1, prescale 1:1
PR2 = (0x50);// sets PWM rate to approx 98.5KHz with 32Mhz internal oscillator
CCP1CON = 0b00001111;// CCP1 ON, and set to simple PWM mode
PhaseShift = 0x00000000;//frequency values loaded into
ANSELA = 0b00010000; //select RA4 as A2D input 32Mhz clk
ADCON0 = 0b00001101; // configure ADC
ADCON1 = 0b00100000; // configure ADC
}
////////////////////////////main program loop here/////////////////////////////////
void main() {
Init_Main();//configure part
while(1){ //alway do this
while(!PIR1.TMR2IF);// wait for TMR2 cycle to restart
CCPR1L = (sine[((char *)&PhaseAccum)[3]]) >> 2;// load MSbits 7-2 duty cycle value into CCPRIL
CCP1CON ^=((sine[((char *)&PhaseAccum)[3]]) & 0x03) << 4;// load in bits 1-0 into 5 and 4 of CCP1CON
//////duty cycle value byte is now loaded for next cycle comming//////
ADCRead(); //read ADC here to get value into PhaseShift to change Freq
PhaseAccum = PhaseAccum + ((PhaseShift << 5) + 1); //move PhaseAccum through waveform values
//”<<5″ can be more or less and sets the frequency sweep range
// the +1 is just so there is never 0;
PIR1.TMR2IF = 0; // clear TMR2 int flag
}
}