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;
}