Si5351 Adafruit development board with Arduino Nano + LCD display
Below is I/Q phase accuracy measurements with different frequencies.
I/Q phase shift value is 87...90degree within 3.2 MHz to 58 MHz
Outputs are not terminated so square wave signal is not clean.
//v.0.1.7 04.11.2018 Si5351 VCO from 3.3MHZ to 58MHz 90 degree I/Q with LCD display and rotary switch with button. OH2BTG
// Thanks to Hans Summers and others who contributed help and code
// https://qrp-labs.com/synth/si5351ademo.html#arduino
/**************************************************************************/
#include <Wire.h>
#include <LiquidCrystal.h>
#include <rotary.h>
#define ENCODER_A 3 // Encoder pin A
#define ENCODER_B 2 // Encoder pin B
#define ENCODER_BTN 4 // Encoder c button
Rotary r = Rotary(ENCODER_A, ENCODER_B);
uint32_t vfoSave = 0;
uint32_t pllFreq; // si5351 internal vco frequency
int menuX = 1;
int menuXsaved = 1;
int butPresTime = 1;
int menu_val = 1;
int settings_menu_state = 0;
int Itimer = 0;
byte UsbLsb = 1; // LSB = 0 USB = 1
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // LCD: RS,E,D4,D5,D6,D7 Erska
// OH2BTG additions
unsigned long vfo = 14267000;
unsigned long frequency = vfo;
unsigned long vfox2 = 2*vfo;
unsigned long fstep = 100; // frequency step in Hz
unsigned long fstepM = fstep;
boolean changed_f = 1;
// Original Hans Summers:
uint32_t divider = 1;
uint32_t divider_M = 0; // divider stored value
#include <inttypes.h>
void i2cInit();
uint8_t i2cSendRegister(uint8_t reg, uint8_t data);
uint8_t i2cReadRegister(uint8_t reg, uint8_t *data);
#define I2C_START 0x08
#define I2C_START_RPT 0x10
#define I2C_SLA_W_ACK 0x18
#define I2C_SLA_R_ACK 0x40
#define I2C_DATA_ACK 0x28
#define I2C_WRITE 0b11000000
#define I2C_READ 0b11000001
#ifndef SI5351A_H
#define SI5351A_H
#define SI_CLK0_CONTROL 16 // Register definitions
#define SI_CLK1_CONTROL 17
#define SI_CLK2_CONTROL 18
#define SI_SYNTH_PLL_A 26
#define SI_SYNTH_PLL_B 34
#define SI_SYNTH_MS_0 42
#define SI_SYNTH_MS_1 50
#define SI_SYNTH_MS_2 58
#define SI_CLK0_PHOFF 165 // OH2BTG addition for phase offset
#define SI_CLK1_PHOFF 166 // OH2BTG addition for phase offset
#define SI_CLK2_PHOFF 167 // OH2BTG addition for phase offset
#define SI_PLL_RESET 177
#define SI_R_DIV_1 0b00000000 // R-division ratio definitions
#define SI_R_DIV_2 0b00010000
#define SI_R_DIV_4 0b00100000
#define SI_R_DIV_8 0b00110000
#define SI_R_DIV_16 0b01000000
#define SI_R_DIV_32 0b01010000
#define SI_R_DIV_64 0b01100000
#define SI_R_DIV_128 0b01110000
#define SI_CLK_SRC_PLL_A 0b00000000
#define SI_CLK_SRC_PLL_B 0b00100000
#define XTAL_FREQ 25000000 // 25 MHz adafruit Crystal frequency
void si5351aOutputOff(uint8_t clk);
void si5351aSetFrequency(uint32_t frequency);
#endif //SI5351A_H
uint8_t i2cStart()
{
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
while (!(TWCR & (1<<TWINT))) ;
return (TWSR & 0xF8);
}
void i2cStop()
{
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
while ((TWCR & (1<<TWSTO))) ;
}
uint8_t i2cByteSend(uint8_t data)
{
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT))) ;
return (TWSR & 0xF8);
}
uint8_t i2cByteRead()
{
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT))) ;
return (TWDR);
}
uint8_t i2cSendRegister(uint8_t reg, uint8_t data)
{
uint8_t stts;
stts = i2cStart();
if (stts != I2C_START) return 1;
stts = i2cByteSend(I2C_WRITE);
if (stts != I2C_SLA_W_ACK) return 2;
stts = i2cByteSend(reg);
if (stts != I2C_DATA_ACK) return 3;
stts = i2cByteSend(data);
if (stts != I2C_DATA_ACK) return 4;
i2cStop();
return 0;
}
uint8_t i2cReadRegister(uint8_t reg, uint8_t *data)
{
uint8_t stts;
stts = i2cStart();
if (stts != I2C_START) return 1;
stts = i2cByteSend(I2C_WRITE);
if (stts != I2C_SLA_W_ACK) return 2;
stts = i2cByteSend(reg);
if (stts != I2C_DATA_ACK) return 3;
stts = i2cStart();
if (stts != I2C_START_RPT) return 4;
stts = i2cByteSend(I2C_READ);
if (stts != I2C_SLA_R_ACK) return 5;
*data = i2cByteRead();
i2cStop();
return 0;
}
// Init TWI (I2C)
//
void i2cInit()
{
TWBR = 92;
TWSR = 0;
TWDR = 0xFF;
PRR = 0;
}
void setup()
{
i2cInit(); // Hans Summers original
Serial.begin(115200);
Serial.println(" Erska Testaa");
// Initialize and clear the LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" OH2BTG ");
lcd.setCursor(0, 1);
lcd.print("1-150MHz v.0.1.7");
delay(2000); //2seconds
Wire.begin();
pinMode(ENCODER_BTN, INPUT_PULLUP);
PCICR |= (1 << PCIE2); // Enable pin change interrupt for the encoder
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
count_frequency(); // Count f and update the display
}
void loop() {
// Update the display if the frequency has been changed
if (changed_f)
{
count_frequency();
si5351aSetFrequency_IQ(frequency);
}
ReadButton(); //Read coder button
}
void readMenu()
{
switch (menuX)
{
case 1:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("LSB");
Serial.println("LSB");
break;
case 2:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("USB");
Serial.println("USB");
break;
case 3:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("100Hz");
Serial.println("100Hz");
break;
case 4:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("1kHz");
Serial.println("1kHz");
break;
case 5:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("100kHz");
Serial.println("100kHz");
break;
case 6:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("1MHz");
Serial.println("1MHz");
break;
case 7:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("3,699MHz LSB");
Serial.println("3,699MHz LSB");
break;
case 8:
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("14,000MHz USB");
Serial.println("14,000MHz USB");
break;
}
}
/**************************************/
/* Interrupt service routine for */
/* encoder frequency change */
/**************************************/
ISR(PCINT2_vect) {
unsigned char result = r.process();
if (result == DIR_CW)
{set_frequency(1);
set_menu_val(1);
}
else if (result == DIR_CCW)
{
set_frequency(-1);
set_menu_val(-1);
}
}
/**************************************/
/* Change the frequency */
/* dir = 1 Increment */
/* dir = -1 Decrement */
/**************************************/
void set_frequency(short dir)
{
if (dir == 1)
vfo += fstep;
if (dir == -1)
vfo -= fstep;
if (vfo > 150000000)
vfo = 100000;
if (vfo < 100000)
vfo = 150000000;
if (vfo < 0)
vfo = 100000;
changed_f = 1;
}
/**************************************/
/* Read the button with debouncing */
/**************************************/
boolean get_button()
{
if (!digitalRead(ENCODER_BTN))
{
delay(5);
if (!digitalRead(ENCODER_BTN))
{
while (!digitalRead(ENCODER_BTN));
return 1;
}
}
return 0;
}
void count_frequency()
{
uint16_t f, g;
f = vfo / 1000000; //variable is now vfo instead of 'frequency' vfo esim 145787500
lcd.clear();
lcd.setCursor(0, 0);
if (f < 10)
lcd.print("");
lcd.print(f);
lcd.print(",");
f = (vfo % 1000000) / 1000; // printtaa taajuuden 3 viim numeroa XXX.550.XXX
// Serial.println (f = (vfo % 1000000) / 1000);
if (f < 100)
lcd.print("0");
if (f < 10)
lcd.print("0");
lcd.print(f);
lcd.print(".");
// f = vfo % 1000;
f = (vfo % 1000) / 100; // removing 2 last digit from frequency reading
lcd.print(f); //
lcd.print("MHz ");
if (UsbLsb == 0)
{
lcd.setCursor(12,0);
lcd.print("LSB ");
}
if (UsbLsb == 1)
{
lcd.setCursor(12,0);
lcd.print("USB ");
}
}
void ReadButton()
{
if (digitalRead(ENCODER_BTN) == 0)
{
butPresTime = ++butPresTime;
if (butPresTime >= 6)
{
vfoSave = vfo;
settings_menu();
butPresTime = 1;
}
else
{
delay(200);
}
}
}
void settings_menu()
{
for ( Itimer=10; Itimer >= 0; Itimer--){
lcd.setCursor(10, 1);
lcd.print(" ");
lcd.setCursor(10, 1);
lcd.print("Menu");
lcd.setCursor(14, 1);
lcd.print(" ");
lcd.setCursor(14, 1);
lcd.print(Itimer);
settings_menu_state = 1;
delay(1500);
// settings_menu_state = 0;
saveMenuSel();
// saveUsbLsb();
}
}
void set_menu_val(int suunta )
{
if (Itimer >= 1 )
{
if (suunta == 1&& settings_menu_state ==1)
{
menu_val += 1;
Itimer = 9;
}
if (suunta == -1&& settings_menu_state ==1)
{
menu_val -= 1;
Itimer = 9;
}
if ( menu_val <= 0) menu_val = 8;
if ( menu_val >= 9) menu_val = 1;
menuX = menu_val;
readMenu();
saveMenuSel();
}
}
void saveMenuSel()
{
while (digitalRead(ENCODER_BTN) == 0){
menuXsaved = menuX;
lcd.setCursor(12,1);
Itimer = 1;
delay(50);
lcd.print("Save");
if (menuXsaved == 1)
{ UsbLsb = 2;
Serial.println("LSB saved");
}
if (menuXsaved == 2)
{
UsbLsb = 3;
Serial.println("USB saved");
}
if (menuXsaved == 3)
{
fstep = 100;
Serial.println("100Hz saved");
}
if (menuXsaved == 4)
{
fstep = 1000;
Serial.println("1kHz saved");
}
if (menuXsaved == 5)
{
fstep = 100000;
Serial.println("100kHz saved");
}
if (menuXsaved == 6)
{
fstep = 1000000;
Serial.println("1MHz saved");
}
if (menuXsaved == 7)
{
vfoSave = 3699000;
UsbLsb = 0;
Serial.println("3699kHz LSB");
}
if (menuXsaved == 8)
{
vfoSave = 14000000;
UsbLsb = 1;
Serial.println("14,000MHz USB");
}
}
vfo = vfoSave;
}
void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
{
uint32_t P1; // PLL config register P1
uint32_t P2; // PLL config register P2
uint32_t P3; // PLL config register P3
P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;
i2cSendRegister(pll + 0, (P3 & 0x0000FF00) >> 8);
i2cSendRegister(pll + 1, (P3 & 0x000000FF));
i2cSendRegister(pll + 2, (P1 & 0x00030000) >> 16);
i2cSendRegister(pll + 3, (P1 & 0x0000FF00) >> 8);
i2cSendRegister(pll + 4, (P1 & 0x000000FF));
i2cSendRegister(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 &
0x000F0000) >> 16));
i2cSendRegister(pll + 6, (P2 & 0x0000FF00) >> 8);
i2cSendRegister(pll + 7, (P2 & 0x000000FF));
}
// Set up MultiSynth with integer divider and R divider
// R divider is the bit value which is OR'ed onto the appropriate
// register, it is a #define in si5351a.h
//
void setupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
{
uint32_t P1; // Synth config register P1
uint32_t P2; // Synth config register P2
uint32_t P3; // Synth config register P3
P1 = 128 * divider - 512;
P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the divider
P3 = 1;
i2cSendRegister(synth + 0, (P3 & 0x0000FF00) >> 8);
i2cSendRegister(synth + 1, (P3 & 0x000000FF));
i2cSendRegister(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv);
i2cSendRegister(synth + 3, (P1 & 0x0000FF00) >> 8);
i2cSendRegister(synth + 4, (P1 & 0x000000FF));
i2cSendRegister(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 &
0x000F0000) >> 16));
i2cSendRegister(synth + 6, (P2 & 0x0000FF00) >> 8);
i2cSendRegister(synth + 7, (P2 & 0x000000FF));
}
//
// Switches off Si5351a output
// Example: si5351aOutputOff(SI_CLK0_CONTROL);
// will switch off output CLK0
//
void si5351aOutputOff(uint8_t clk)
{
i2cSendRegister(clk, 0x80); // Refer to SiLabs AN619 to see
//bit values - 0x80 turns off the output stage
}
void si5351aSetFrequency_IQ(uint32_t frequency)
{
uint32_t pllFreq;
uint32_t xtalFreq = XTAL_FREQ;
uint32_t l;
float f;
uint8_t mult;
uint32_t num;
uint32_t denom;
//uint32_t divider;
frequency = vfo; // vfo is frequency
Serial.println(frequency);
if (frequency >=8000000) divider = 900000000 / frequency;// Calculate the division ratio. 900,000,000 is the maximum internal
if (frequency <=8000000) divider = 400000000 / frequency;// Calculate the division ratio. 400,000,000 is the min internal
if (divider % 2) divider--; // Ensure an even integer
//division ratio
pllFreq = divider * frequency; // Calculate the pllFrequency:
Serial.print("PLL freq ");
Serial.println(pllFreq);
//the divider * desired output frequency
mult = pllFreq / xtalFreq; // Determine the multiplier to get to the required pllFrequency
Serial.print("PLL mult ");
Serial.println(mult);
l = pllFreq % xtalFreq; // It has three parts:
f = l; // mult is an integer that must be in the range 15..90
f *= 1048575; // num and denom are the fractional parts, the numerator and denominator
f /= xtalFreq; // each is 20 bits (range 0..1048575)
num = f; // the actual multiplier is mult + num / denom
denom = 1048575; // For simplicity we set the denominator to the maximum 1048575
// Set up PLL A with the calculated multiplication ratio
setupPLL(SI_SYNTH_PLL_A, mult, num, denom);
if ( divider_M != divider){
setupMultisynth(SI_SYNTH_MS_0, divider, SI_R_DIV_1);
setupMultisynth(SI_SYNTH_MS_1, divider, SI_R_DIV_1);
if (UsbLsb == 0){ // LSB mode select
i2cSendRegister(SI_CLK1_PHOFF,0x00); // set phase offset for I/Q
i2cSendRegister(SI_CLK0_PHOFF,(divider)); // set phase offset for I/Q
}
if (UsbLsb == 1){ // USB mode select
i2cSendRegister(SI_CLK0_PHOFF,0x00); // set phase offset for I/Q
i2cSendRegister(SI_CLK1_PHOFF,(divider)); // set phase offset for I/Q
}
i2cSendRegister(SI_PLL_RESET, 0xA0);
i2cSendRegister(SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);
i2cSendRegister(SI_CLK1_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);
divider_M = divider;
Serial.print("M divider ");
Serial.println(divider);
}
changed_f = 0;
}
Nice code! I’m going to try this myself. You say the phase between the 2 outputs varies From 90 degrees over the tuning range of the vfo by about 3 degrees off 90. Was this uniform, that is was it something you could correct for if you needed pretty much exactly 90 degrees across the whole tuning range? My plan is to use the oscillator outputs to feed the multiplexer of a Tayloe detector. Do you experience any phase jitter in the outputs when you tune the vfo?
VastaaPoistaThanks, for sharing this code. 73 Tim VK4QP
Nice to hear that you find my code useful. Im not sure about phase error. My experience is that 90 deg signal is enough good to build SSB signal using polyphase filter or experience SDR receivers. I have done one SSB tranceiver using this code and circuit. Here is some details: http://oh2btg.blogspot.com/2018/10/handheld-2-30mhz-ssb-5w-radio.html
PoistaHello! Why 3.2 MHz to 58 MHz only? si5351 works in wider range...
VastaaPoistaHello,
VastaaPoistaI have problems with the use of the software.
I am able to change the frequency up and down but only in 100 Hz steps.
When I push the button longer, in the second line a Menu countdown from Menu 10 to Menu 0 appears.
When I push again, I see MeSave.
That is all.
What I am doing wrong?
Btw, your program “Frequency generator 1...150MHz with 100Hz steps” works fine for me.
Thanks for your nice work!
73,
Bernd
Hello! Very nice code and project )
VastaaPoistaCould You add the possibility in this code to set also frequency on the output No3?