tiistai 30. lokakuuta 2018

Si5351 I/Q VFO 3.2MHz to 58MHz 90 deg phase output


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;           










keskiviikko 24. lokakuuta 2018

Handheld 2...30MHz SSB 5W plus 50MHz and 70MHz


Technical data

 Dimensions: 35 x 115 x 230 mm
 Operating voltage 12V 3 pieces lithium ion batteries 3.8V / 3Ah

 3.5MHz to 30MHz performance
 Sensitivity: -130dBm is audible
 Power out flat 5W :

50MHz performance
Sensitivity -130dBm is audible
Power out 5.1W

70MHz performance
Sensitivity -120dBm is audible
Power out 2.6W

Circuit description


Direct conversion polyphase SSB modulator / demodulator receiver and transmitter with CW mode.
Most of RF section is own version of OH7SV juma TRX2 circuit
Display is Nextion touch screen and processor is Arduino Nano. Band switch is controlled with i2c bus with 8 channel extender board.
Local oscillator is si5351
Software is hand made with lot of borrowed code. Source code is available at request.
I got lot of help from OH7SV with RF questions. Hans Summers G0UPL explain, how to get clear frequency change without clicks and how to get I/Q 90 degree working
Power amplifier circuit is copy of FT817 PA. That because it is so wide from 2...70MHz
VFO tune coder is HRPG-ASCA. optical encoder with 120 step per round.
Radio have only 3 control knobs, af volume, VFO coder and PTT switch plus CW key connector
D connector is for charging batteries and later options for e.g control PA amplifier.
Everything else is controlled  in touch screen.
Most of components are SMD and as you see, this is prototype and not easily reproductable.

For questions: oh2btg (at) sral . fi
73 Eero OH2BTG




First QSO's with prototype