Синтезатор на Si5351A на Arduino
И начнем мы с обзора особенностей модуля. Сразу хочется отметить главные преимущества: функциональность и доступность по цене. Ну и не стоит забывать о практичности устройства. Всем нам известно, что синтезаторы частот служат источниками стабильных колебаний. Именно эту их черту конкретно в данном приборе могут задействовать радиолюбители для конструирования и программирования в своих проектах (там, где необходима настройка на разные частоты в широком диапазоне).
По сути, датчик подойдет для применения в разработке и моделировании современных HDTV ∕DVD / Blu-ray девайсов, аудио ∕ видео оборудования, сканеров ∕ принтеров ∕ факсимильных аппаратов, серверов и т.д.
Генератор не сложен в сборке (не требует дополнительной пайки, разве что вы приобрели микросхему отдельно), имеет следующие технические параметры:
- напряжение питания: 3.3-5 V;
- выходной импеданс: 50 Ом;
- диапазон: 2.5кГц - 160Мгц;
- тип интерфейса: I2C;
- рабочая температура: -40°C ... +85°C;
- габаритные размеры корпуса: 3 x 3 мм.
Arduino и Si5351A
А теперь обратимся к схеме подключения. Для нее нам понадобится следующее аппаратное «железо»: микроконтроллерный блок Arduino Pro Mini (Nano также подойдет), микросхема синтезатора Si5351, жидкокристаллический OLED дисплей, энкодер, макетная плата, кварцевый резонатор, соединительные провода (для подключения к компьютеру - USB-кабель).Пример сборки:
Для реализации проекта понадобится скачать специализированную библиотеку - Si5351Arduino. В самой среде Ардуино IDE идем по пути: Скетч > Подключить библиотеку > Добавить .ZIP. Открываем архив со скачанной ранее библиотекой.
https://github.com/etherkit/Si5351Arduino
Для OLED дисплея нужны иные библиотеки - Adafruit_SSD1306 и Adafruit-GFX. Их также необходимо инсталлировать в папку libraries.
https://github.com/adafruit/Adafruit_SSD1306
https://github.com/adafruit/Adafruit-GFX-Library
Не забываем выставить корректное наименование платы (раздел Инструменты), а также порт и процессор. Кроме того, в софте задаются границы диапазонов частот, стартовая частота при включении синтезатора и другие значения.
Заливаем скетч:
#include <Rotary.h>
#include <si5351.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#define ENCODER_A 2 // Encoder pin A INT0/PCINT18 D2
#define ENCODER_B 3 // Encoder pin B INT1/PCINT19 D3
#define ENCODER_BTN 4 // Encoder pushbutton D4
#define OLED_RESET 8 // OLED reset
#define Calibrbtn 5 // Calibrate
#define RIT_Switch 6 // RIT Switch
#define TX_Switch 7 // Select TRx or BFO
#define F_MIN 1000000UL // Lower frequency limit
#define F_MAX 300000000UL // Upper frequency limit
/**************************************************************************
* EEPROM data locations
**************************************************************************/
#define EE_SAVED_RADIX 0 // Stepsize pointer
#define EE_SAVED_AFREQ 4 // Actual Tx Frequency (CLK0)
#define EE_SAVED_BFREQ 8 // BFO (IF) Frequency (CLK2)
#define EE_SAVED_XFREQ 12 // X-tal frequency (25 or 27 MHz)
#define EE_SAVED_OFSET 16 // store correction
#define EE_SAVED_CALBR 20 // calibrated indicator
Adafruit_SSD1306 display(OLED_RESET);
Si5351 si5351;
Rotary r = Rotary(ENCODER_A, ENCODER_B);
/**************************************************************************
* Declarations
**************************************************************************/
volatile uint32_t bfo_f = 900000000ULL / SI5351_FREQ_MULT; // CLK0 start IF
volatile uint32_t vfo_t = 1420000000ULL / SI5351_FREQ_MULT; // CLK2 start Tx freq
volatile uint32_t vfo_r = vfo_t - bfo_f; // CLK1 start Rx freq
volatile uint32_t vfo_s = vfo_t; // Saved for RIT
uint32_t vco_c = 0; // X-tal correction factor
uint32_t xt_freq;
long radix = 100L, old_radix = 100L; //start step size
boolean changed_f = 0, stepflag = 0, calflag = 0, modeflag = 0, ritset = 0;
boolean calibrate = 0;
byte act_clk = 0, disp_txt = 0;
/**************************************/
/* Interrupt service routine for */
/* encoder frequency change */
/**************************************/
ISR(PCINT2_vect) {
char result = r.process();
if (result == DIR_CW)
set_frequency(1);
else if (result == DIR_CCW)
set_frequency(-1);
}
/**************************************/
/* Change the frequency */
/* dir = 1 Increment */
/* dir = -1 Decrement */
/**************************************/
void set_frequency(short dir)
{
switch (act_clk)
{
case 0: // Tx frequency
if (dir == 1)
vfo_t += radix;
if (dir == -1)
vfo_t -= radix;
break;
case 1: // Tx frequency (only if RIT is on)
if (dir == 1)
vfo_t += radix;
if (dir == -1)
vfo_t -= radix;
break;
case 2: // BFO frequency
if (dir == 1)
bfo_f += radix;
if (dir == -1)
bfo_f -= radix;
break;
}
if(vfo_t > F_MAX)
vfo_t = F_MAX;
if(vfo_t < F_MIN)
vfo_t = F_MIN;
changed_f = 1;
}
/**************************************/
/* Read the buttons with debouncing */
/**************************************/
boolean get_button()
{
if (!digitalRead(ENCODER_BTN)) // Stepsize
{
delay(20);
if (!digitalRead(ENCODER_BTN))
{
while (!digitalRead(ENCODER_BTN));
stepflag = 1;
}
}
else if (!digitalRead(Calibrbtn)) // Calibrate
{
delay(20);
if (!digitalRead(Calibrbtn))
{
while (!digitalRead(Calibrbtn));
calflag = 1;
}
}
else if (!digitalRead(TX_Switch)) // Selection
{
delay(20);
if (!digitalRead(TX_Switch))
{
while (!digitalRead(TX_Switch));
modeflag = 1;
}
}
if (stepflag | calflag | modeflag) return 1;
else return 0;
}
/********************************************************************************
* RIT switch handling
* Switch to small stepsize (100 Hz)
*******************************************************************************/
void RIT_switch() // Read RIT_switch
{
if (!digitalRead(RIT_Switch) && ritset == 0){ // RIT on
act_clk = 1;
ritset = 1;
vfo_s = vfo_t; // Save Tx freq
old_radix = radix; // Save actual stepsize
radix = 100; // Set stepsize to 100 Hz
}
else if (digitalRead(RIT_Switch) && ritset == 1){ // RIT 0ff
act_clk = 0; // RTx mode
ritset = 0;
vfo_t = vfo_s; // Restore to original vco_t
radix = old_radix; // Back to old stepsize
disp_txt = 0; // Clear line
// Update Rx frequency based on the restored Tx frequency
if (vfo_t <= bfo_f) vfo_r = vfo_t + bfo_f; // Upper / lower mixing
else vfo_r = vfo_t - bfo_f;
si5351.set_freq((vfo_r * SI5351_FREQ_MULT), SI5351_CLK1);
}
}
/**************************************/
/* Displays the frequency and stepsize*/
/**************************************/
void display_frequency()
{
display.setTextSize(2);
char LCDstr[12];
char Mhz[5], Herz[12];
int p,q;
unsigned long freq;
display.clearDisplay();
switch(act_clk)
{
case 0: // Tx frequency
freq = vfo_t;
break;
case 1: // Tx frequency (Used in RIT Mode)
freq = vfo_t;
break;
case 2: // MF frequency
freq = bfo_f;
break;
}
Herz[1]='\0'; // empty arry
sprintf(LCDstr, "%ld", freq); // convert freq to string
p=strlen(LCDstr); // determine length
strncpy(Mhz,LCDstr,(p-6)); // get MHz digits (1-3)
q=p-6;
Mhz[q]='\0'; // end with null character
strcpy(Herz,LCDstr); // get Herz digits (6)
strcpy(LCDstr+q,Herz+(q-1)); // copy into LCDstr
LCDstr[q]='.'; // decimal point
display.setCursor(10,0);
display.println(LCDstr);
display_settings();
}
/**************************************/
/* Displays step, mode and version */
/**************************************/
void display_settings()
{
// Stepsize
display.setCursor(8, 40);
display.setTextSize(1);
display.print(F("Step:"));
switch (radix)
{
case 1:
display.println(F(" 1Hz"));
break;
case 10:
display.println(F(" 10Hz"));
break;
case 100:
display.println(F(" 100Hz"));
break;
case 1000:
display.println(F(" 1kHz"));
break;
case 10000:
display.println(F(" 10kHz"));
break;
case 100000:
display.println(F("100kHz"));
break;
case 1000000:
display.println(F(" 1MHz"));
break;
}
// Mode
display.setCursor(100, 40);
switch (act_clk)
{
case 0:
display.println(F("TRx"));
break;
case 1:
display.println(F("RIT"));
break;
case 2:
display.println(F("BFO"));
break;
}
// Version
display.setCursor(15, 55);
display.print(F("Si5351 vfo V.1.0"));
// Messages
display.setCursor(12, 25);
switch (disp_txt)
{
case 0:
display.print(F(" ")); // clear line
break;
case 1:
display.print(F("** Turn RIT Off *"));
break;
case 2:
display.print(F("*** Set to TRx **"));
break;
case 3:
display.print(F("** Calibration **"));
break;
case 4:
display.print(F("* Calibration OK!"));
break;
}
display.display();
}
/**************************************/
/* S E T U P */
/**************************************/
void setup()
{
Serial.begin(115200);
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x64)
// Read EEPROM
radix = eeprom_read_dword((const uint32_t *)EE_SAVED_RADIX);
if ((radix < 10UL) | (radix > 1000000UL)) radix = 100UL;
vfo_t = eeprom_read_dword((const uint32_t *)EE_SAVED_AFREQ);
if ((vfo_t < F_MIN) | (vfo_t > F_MAX)) vfo_t = 14000000ULL;
bfo_f = eeprom_read_dword((const uint32_t *)EE_SAVED_BFREQ);
if ((bfo_f < F_MIN) | (bfo_f > F_MAX)) bfo_f = 9000000ULL;
vco_c = 0;
if (eeprom_read_dword((const uint32_t *)EE_SAVED_CALBR) == 0x60) {
vco_c = eeprom_read_dword((const uint32_t *)EE_SAVED_OFSET);
}
xt_freq = SI5351_XTAL_FREQ + vco_c;
//initialize the Si5351
si5351.set_correction(0); // Set to zero because I'm using an other calibration method
si5351.init(SI5351_CRYSTAL_LOAD_8PF, xt_freq, 0); //If you're using a 27Mhz crystal, put in 27000000 instead of 0
// 0 is the default crystal frequency of 25Mhz.
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
// Set CLK0 to output the starting "vfo" frequency as set above by vfo = ?
si5351.set_freq((vfo_t * SI5351_FREQ_MULT), SI5351_CLK0);
si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_2MA);
// Set CLK1 to output the Rx frequncy = vfo +/- bfo frequency
if (vfo_t <= bfo_f) vfo_r = vfo_t + bfo_f; // Upper / lower mixing
else vfo_r = vfo_t - bfo_f;
si5351.set_freq((vfo_r * SI5351_FREQ_MULT), SI5351_CLK1);
si5351.drive_strength(SI5351_CLK1,SI5351_DRIVE_2MA);
// Set CLK2 to output bfo frequency
si5351.set_freq((bfo_f * SI5351_FREQ_MULT), SI5351_CLK2);
si5351.drive_strength(SI5351_CLK2,SI5351_DRIVE_2MA);
// Clear the buffer.
display.clearDisplay();
display.display();
// text display tests
display.setTextSize(1);
display.setTextColor(WHITE);
// Encoder setup
pinMode(ENCODER_BTN, INPUT_PULLUP);
PCICR |= (1 << PCIE2); // Enable pin change interrupt for the encoder
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
sei();
// Pin Setup
pinMode(Calibrbtn, INPUT_PULLUP); // Calibrate
pinMode(RIT_Switch, INPUT_PULLUP); // RIT Switch
pinMode(TX_Switch, INPUT_PULLUP); // Select TRx or BFO
// Display first time
display_frequency(); // Update the display
}
/**************************************/
/* L O O P */
/**************************************/
void loop()
{
if (disp_txt == 4) {
delay(3000); // Display calibration OK and wait 3 seconds
disp_txt = 0;
}
// Update the display if the frequency has been changed
if (changed_f) {
display_frequency();
if (act_clk == 0 && !calibrate) // No Tx update during calibrate
si5351.set_freq((vfo_t * SI5351_FREQ_MULT), SI5351_CLK0);
else if (act_clk == 2) // BFO update
si5351.set_freq((bfo_f * SI5351_FREQ_MULT), SI5351_CLK2);
// Update Rx frequency
if (vfo_t <= bfo_f) vfo_r = vfo_t + bfo_f; // Upper / lower mixing
else vfo_r = vfo_t - bfo_f;
si5351.set_freq((vfo_r * SI5351_FREQ_MULT), SI5351_CLK1);
changed_f = 0;
disp_txt = 0; // Clear line
}
RIT_switch(); // read RIT switch
// Button press changes the frequency change step for 1 Hz steps
// Also stored the last used frequency together with the step size before store
if (get_button()) {
if (stepflag) { // Stepsize button
eeprom_write_dword((uint32_t *)EE_SAVED_RADIX, radix);
eeprom_write_dword((uint32_t *)EE_SAVED_AFREQ, vfo_t);
switch (radix)
{
case 1:
radix = 10;
break;
case 10:
radix = 100;
break;
case 100:
radix = 1000;
break;
case 1000:
radix = 10000;
break;
case 10000:
radix = 100000;
break;
case 100000:
radix = 1000000;
break;
case 1000000:
radix = 10;
break;
}
stepflag = 0;
}
else if (modeflag) { // Mode button
if (act_clk == 0) act_clk = 2; else act_clk = 0;
eeprom_write_dword((uint32_t *)EE_SAVED_BFREQ, bfo_f);
modeflag = 0;
disp_txt = 0; // Clear line
}
else if (calflag) { // Calibrate button
if (!digitalRead(RIT_Switch)){ // RIT is on
disp_txt = 1; // Message RIT off
}
else if (act_clk == 2){ // BFO mode on
disp_txt = 2; // Message BFO off
}
else if (!calibrate) { // Start calibrate
vfo_s = vfo_t; // Save actual freq
old_radix = radix; // and stepsize
vfo_t = SI5351_XTAL_FREQ; // en set to default x-tal
disp_txt = 3; // Message Calibrate
calibrate = 1;
radix = 10; // Set to 10 Hz
si5351.set_freq((vfo_t * SI5351_FREQ_MULT), SI5351_CLK0); // Set CLK0
}
else if (calibrate) { // after tuning x-tal freq
calibrate = 0;
vco_c = vfo_t - SI5351_XTAL_FREQ; // difference
vfo_t = vfo_s; // restore freq
radix = old_radix; // and stepsize
disp_txt = 4; // Message Calibrate OK
eeprom_write_dword((uint32_t *)EE_SAVED_OFSET, vco_c); // store correction
xt_freq = SI5351_XTAL_FREQ + vco_c; // Calibrated x-tal freq
eeprom_write_dword((uint32_t *)EE_SAVED_CALBR, 0x60); // Calibrated
si5351.init(SI5351_CRYSTAL_LOAD_8PF, xt_freq, 0); // Initialize
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.set_freq(bfo_f * SI5351_FREQ_MULT, SI5351_CLK2); // correct BFO frequency
si5351.set_freq(vfo_t * SI5351_FREQ_MULT, SI5351_CLK0); // Correct Tx freq
if (vfo_t <= bfo_f) vfo_r = vfo_t + bfo_f; // Upper / lower mixing
else vfo_r = vfo_t - bfo_f;
si5351.set_freq(vfo_r * SI5351_FREQ_MULT, SI5351_CLK1); // correct Rx frequency
}
calflag = 0;
}
}
display_frequency(); // Update display
} // end while loop
Изучайте Arduino и развивайтесь во всех направлениях. До скорой встречи!
Сегодня поговорим о том, как воплотить в жизнь подключение FPM10A к Arduino, что это за модуль, где используется, каковы параметры его работы и т.д. Речь идет...
В новом информационном обзоре рассматриваем GSM модуль SIM800L к Arduino. Почему выбрано именно это устройство? Во-первых, оно достаточно известно и популярно...
В этой статье мы хотим продолжить тему подключения шаговых двигателей к Ардуино – на очереди рассказ о ШД Nema 17. Почему именно он? Эта модель считается...
Подключение BMP180 к Arduino – процесс не сложный, но вполне практичный. Если вы всерьез занимаетесь проектами на базе этой программной среды, считаете себя...
Наш новый информационный обзор посвящен подключению AD9833 к Ардуино. Мы постараемся рассмотреть базовые особенности устройства, его преимущества и схему...