kopia lustrzana https://github.com/ArjanteMarvelde/uSDR-pico
rodzic
8b4df6d160
commit
b03bcb9c7a
|
@ -0,0 +1,46 @@
|
||||||
|
# Generated Cmake Pico project file
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
# initalize pico_sdk from installed location
|
||||||
|
# (note this can come from environment, CMake cache etc)
|
||||||
|
set(PICO_SDK_PATH "C:/Users/Arjan/Documents/Pico/pico-sdk")
|
||||||
|
|
||||||
|
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(uSDR C CXX ASM)
|
||||||
|
|
||||||
|
# Initialise the Raspberry Pi Pico SDK
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# Add executable. Default name is the project name, version 0.1
|
||||||
|
|
||||||
|
add_executable(uSDR uSDR.c lcd.c si5351.c dsp.c)
|
||||||
|
|
||||||
|
pico_set_program_name(uSDR "uSDR")
|
||||||
|
pico_set_program_version(uSDR "0.1")
|
||||||
|
|
||||||
|
# Pull in our pico_stdlib which aggregates commonly used features
|
||||||
|
target_link_libraries(uSDR pico_stdlib)
|
||||||
|
|
||||||
|
# Disable uart output, enable usb output
|
||||||
|
pico_enable_stdio_uart(uSDR 0)
|
||||||
|
pico_enable_stdio_usb(uSDR 1)
|
||||||
|
|
||||||
|
# Add any user requested libraries
|
||||||
|
target_link_libraries(uSDR
|
||||||
|
hardware_i2c
|
||||||
|
hardware_pwm
|
||||||
|
hardware_pio
|
||||||
|
hardware_timer
|
||||||
|
hardware_clocks
|
||||||
|
hardware_pll
|
||||||
|
hardware_adc
|
||||||
|
)
|
||||||
|
|
||||||
|
pico_add_extra_outputs(uSDR)
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
* dsp.c
|
||||||
|
*
|
||||||
|
* Created: Mar 2021
|
||||||
|
* Author: Arjan
|
||||||
|
*
|
||||||
|
* Signal processing of RX and TX branch
|
||||||
|
* Each branch has a dedicated timer routine that runs on set times.
|
||||||
|
* The callback period is set with RX_US and TX_US, e.g. 16 means every 16 usec, or 62.5 kHz.
|
||||||
|
*
|
||||||
|
* The RX branch:
|
||||||
|
* - Sample I and Q QSD channels intermittently, and shift into I and Q delay line (31.25 kHz per channel)
|
||||||
|
* - Low pass filter: Fc=4kHz
|
||||||
|
* - Interpolate last two I samples to correct sampling phase difference with Q
|
||||||
|
* - Quarter rate (7.8125 kHz) to improve low F behavior of Hilbert transform
|
||||||
|
* - Calculate 15 tap Hilbert transform on Q
|
||||||
|
* - Demodulate, SSB: Q - I, taking proper delays into account
|
||||||
|
* - Push to Audio output DAC
|
||||||
|
*
|
||||||
|
* The TX branch:
|
||||||
|
* - Sample the Audio input channel (62.5 kHz)
|
||||||
|
* - Low pass filter: Fc=4kHz
|
||||||
|
* - Eight rate (7.8125 kHz) to improve low F behavior of Hilbert transform
|
||||||
|
* - Generate Q samples by doing a Hilbert transform
|
||||||
|
* - Push I and Q to QSE output DACs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/pwm.h"
|
||||||
|
#include "hardware/adc.h"
|
||||||
|
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
/* Test sine waves 1, 2 and 4 kHz (@ 62.5kS/s rate) *
|
||||||
|
uint16_t wave1[64] =
|
||||||
|
{
|
||||||
|
500,549,597,645,691,735,777,817,853,886,915,940,961,978,990,997,999,997,990,978,961,940,915,886,853,817,777,735,691,645,597,549,
|
||||||
|
500,450,402,354,308,264,222,182,146,113,84,59,38,21,9,2,0,2,9,21,38,59,84,113,146,182,222,264,308,354,402,450
|
||||||
|
};
|
||||||
|
uint16_t wave2[64] =
|
||||||
|
{
|
||||||
|
500,597,691,777,853,915,961,990,999,990,961,915,853,777,691,597,500,402,308,222,146,84,38,9,0,9,38,84,146,222,308,402,
|
||||||
|
500,597,691,777,853,915,961,990,999,990,961,915,853,777,691,597,500,402,308,222,146,84,38,9,0,9,38,84,146,222,308,402
|
||||||
|
};
|
||||||
|
uint16_t wave4[64] =
|
||||||
|
{
|
||||||
|
500,691,853,961,999,961,853,691,500,308,146,38,0,38,146,308,500,691,853,961,999,961,853,691,500,308,146,38,0,38,146,308,
|
||||||
|
500,691,853,961,999,961,853,691,500,308,146,38,0,38,146,308,500,691,853,961,999,961,853,691,500,308,146,38,0,38,146,308
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DAC_RANGE defines PWM cycle, determining DAC resolution and PWM frequency.
|
||||||
|
* DAC resolution = Vcc / DAC_RANGE
|
||||||
|
* PWM frequency = Fsys / DAC_RANGE
|
||||||
|
* A value of 500 means 125MHz/500=250kHz [or 250 and 500kHz]
|
||||||
|
* ADC is 12 bit, so resolution is by definition 4096
|
||||||
|
*/
|
||||||
|
#define DAC_RANGE 250
|
||||||
|
#define ADC_RANGE 4096
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback timeout, determines frequency of TX and RX loops
|
||||||
|
* Exact time is obtained by passing the value negative.
|
||||||
|
* Here we use 16us (62.5 kHz == PWM freq/4 [or 8])
|
||||||
|
*/
|
||||||
|
#define RX_US 16
|
||||||
|
#define TX_US 16
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Low pass filters Fc=3, 7 and 15 kHz (see http://t-filter.engineerjs.com/)
|
||||||
|
* for sample rates 62.500 , 31.250 or 15.625 kHz , stopband appr -40dB
|
||||||
|
* 8 bit precision, so divide sum by 256
|
||||||
|
*/
|
||||||
|
int16_t lpf3_62[15] = { 3, 3, 5, 7, 9, 10, 11, 11, 11, 10, 9, 7, 5, 3, 3}; // Pass: 0-3000, Stop: 6000-31250
|
||||||
|
int16_t lpf3_31[15] = { -2, -3, -3, 1, 10, 21, 31, 35, 31, 21, 10, 1, -3, -3, -2}; // Pass: 0-3000, Stop: 6000-15625
|
||||||
|
int16_t lpf3_15[15] = { 3, 4, -3,-14,-15, 6, 38, 53, 38, 6,-15,-14, -3, 4, 3}; // Pass: 0-3000, Stop: 4500-7812
|
||||||
|
int16_t lpf7_62[15] = { -2, -1, 1, 7, 16, 26, 33, 36, 33, 26, 16, 7, 1, -1, -2}; // Pass: 0-7000, Stop: 10000-31250
|
||||||
|
int16_t lpf7_31[15] = { -1, 4, 9, 2,-12, -2, 40, 66, 40, -2,-12, 2, 9, 4, -1}; // Pass: 0-7000, Stop: 10000-15625
|
||||||
|
int16_t lpf15_62[15] = { -1, 3, 12, 6,-12, -4, 40, 69, 40, -4,-12, 6, 12, 3, -1}; // Pass: 0-15000, Stop: 20000-31250
|
||||||
|
|
||||||
|
volatile uint16_t dac_iq, dac_audio;
|
||||||
|
volatile bool tx_enabled;
|
||||||
|
|
||||||
|
|
||||||
|
/* RX TIMER and callback */
|
||||||
|
volatile int16_t i_s[15], q_s[15], i_dc, q_dc, i_prev;
|
||||||
|
struct repeating_timer rx_timer;
|
||||||
|
bool rx_callback(struct repeating_timer *t)
|
||||||
|
{
|
||||||
|
static bool q_phase;
|
||||||
|
int16_t sample;
|
||||||
|
int32_t accu;
|
||||||
|
int16_t qh;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (tx_enabled) return(true); // Early bailout when TX-ing
|
||||||
|
|
||||||
|
if (q_phase)
|
||||||
|
{
|
||||||
|
adc_select_input(1); // Q channel ADC
|
||||||
|
sample = (int16_t)adc_read() - ADC_RANGE/2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shift Q samples
|
||||||
|
*/
|
||||||
|
for (i=0; i<14; i++)
|
||||||
|
q_s[i] = q_s[i+1]; // Q samples delay line
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove DC and store new sample
|
||||||
|
* w(t) = x(t) + a*w(t-1) (use a=7/8, ca 0.87)
|
||||||
|
* y(t) = w(t) - w(t-1)
|
||||||
|
*/
|
||||||
|
// sample += (((q_dc<<3)-q_dc)>>3); // Use sample as temporary q_dc
|
||||||
|
// q_s[14] = sample - q_dc; // Calculate output
|
||||||
|
// q_dc = sample; // Store new q_dc
|
||||||
|
q_s[14] = sample;
|
||||||
|
q_phase = false; // Next: I branch
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adc_select_input(0); // I channel ADC
|
||||||
|
sample = (int16_t)adc_read() - ADC_RANGE/2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shift I samples
|
||||||
|
*/
|
||||||
|
for (i=0; i<14; i++)
|
||||||
|
i_s[i] = i_s[i+1]; // I samples delay line
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove DC and store new sample: average last two to get in phase with Q
|
||||||
|
* w(t) = x(t) + a*w(t-1) (use a=7/8, ca 0.87)
|
||||||
|
* y(t) = w(t) - w(t-1)
|
||||||
|
*/
|
||||||
|
// sample += (((i_dc<<3)-i_dc)>>3); // Use sample as temporary i_dc
|
||||||
|
// i_s[14] = sample - i_dc; // Calculate output
|
||||||
|
// i_dc = sample; // Store new i_dc
|
||||||
|
// sample = i_s[14]; // Get out uncorrected sample
|
||||||
|
i_s[14] = (sample + i_prev)/2; // Correct phase difference with Q channel
|
||||||
|
i_prev = sample; // Remember last sample for next I-phase
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hilbert transform: A0 = 2/128, A2 = 8/128, A4 = 21/128, A6 = 79/128
|
||||||
|
*/
|
||||||
|
// qh = (q_s[0]-q_s[14])/64 +
|
||||||
|
// (q_s[2]-q_s[12])/16 +
|
||||||
|
// (q_s[4]-q_s[10])/8 + (q_s[4]-q_s[10])*5/128 +
|
||||||
|
// (q_s[6]-q_s[ 8])/8 - (q_s[6]-q_s[8])/128 + (q_s[6]-q_s[8])/2;
|
||||||
|
/*
|
||||||
|
* Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills):
|
||||||
|
*/
|
||||||
|
accu = (q_s[0]-q_s[14])*315L + (q_s[2]-q_s[12])*440L + (q_s[4]-q_s[10])*734L + (q_s[6]-q_s[ 8])*2202L;
|
||||||
|
qh = accu / 4096;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SSB demodulate: I[7] - Qh
|
||||||
|
* Range should be within DAC_RANGE
|
||||||
|
* Add 250 offset and send to audio DAC output
|
||||||
|
*/
|
||||||
|
sample = (i_s[7] - qh)/32;
|
||||||
|
pwm_set_chan_level(dac_audio, PWM_CHAN_A, DAC_RANGE/2 + sample);
|
||||||
|
|
||||||
|
q_phase = true; // Next: Q branch
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TX TIMER and callback */
|
||||||
|
volatile int16_t a_s_pre[15], a_s[15], a_dc;
|
||||||
|
struct repeating_timer tx_timer;
|
||||||
|
bool tx_callback(struct repeating_timer *t)
|
||||||
|
{
|
||||||
|
static int tx_phase = 0;
|
||||||
|
int16_t sample;
|
||||||
|
int32_t accu;
|
||||||
|
int16_t qh;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!tx_enabled) return(true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get sample and shift into delay line
|
||||||
|
*/
|
||||||
|
adc_select_input(2); // Audio channel ADC
|
||||||
|
for (i=0; i<14; i++)
|
||||||
|
a_s_pre[i] = a_s_pre[i+1]; // Audio samples delay line
|
||||||
|
a_s_pre[14] = (int16_t)adc_read()-ADC_RANGE/2; // Subtract half range (is appr. dc bias)
|
||||||
|
|
||||||
|
tx_phase = (tx_phase+1)&0x01; // Count to 2
|
||||||
|
if (tx_phase != 0) //
|
||||||
|
return true; // early bail out 1 out of 2 times
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Downsample and low pass
|
||||||
|
*/
|
||||||
|
for (i=0; i<14; i++) // Shift decimated samples
|
||||||
|
a_s[i] = a_s[i+1];
|
||||||
|
accu = 0;
|
||||||
|
for (i=0; i<15; i++) // Low pass FIR filter
|
||||||
|
accu += (int32_t)a_s_pre[i]*lpf3_62[i]; // 3kHz, at 62.5 kHz sampling
|
||||||
|
a_s[14] = accu / 256;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove DC and store new sample
|
||||||
|
* w(t) = x(t) + a*w(t-1) (use a=31/32, ca 0.97)
|
||||||
|
* y(t) = w(t) - w(t-1)
|
||||||
|
*/
|
||||||
|
//temp = a_dc; // a_dc is w(t-1)
|
||||||
|
//sample += (int16_t)(((temp<<5)-temp)>>5); // Use sample as w(t)
|
||||||
|
//a_s[14] = sample - a_dc; // Calculate output
|
||||||
|
//a_dc = sample; // Store new w(t)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Classic Hilbert transform 15 taps, 12 bits (see Iowa Hills):
|
||||||
|
*/
|
||||||
|
accu = (a_s[0]-a_s[14])*315L + (a_s[2]-a_s[12])*440L + (a_s[4]-a_s[10])*734L + (a_s[6]-a_s[ 8])*2202L;
|
||||||
|
qh = accu / 4096;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write I and Q to QSE DACs, phase is 7 back.
|
||||||
|
* Need to multiply AC with DAC_RANGE/ADC_RANGE (appr 1/16, but compensate for losses)
|
||||||
|
*/
|
||||||
|
pwm_set_chan_level(dac_iq, PWM_CHAN_A, DAC_RANGE/2 + (a_s[7]/8));
|
||||||
|
pwm_set_chan_level(dac_iq, PWM_CHAN_B, DAC_RANGE/2 + (qh/8));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int dsp_init()
|
||||||
|
{
|
||||||
|
uint16_t slice_num;
|
||||||
|
|
||||||
|
/* Initialize DACs */
|
||||||
|
gpio_set_function(0, GPIO_FUNC_PWM); // GP0 is PWM for I DAC (Slice 0, Channel A)
|
||||||
|
gpio_set_function(1, GPIO_FUNC_PWM); // GP1 is PWM for Q DAC (Slice 0, Channel B)
|
||||||
|
dac_iq = pwm_gpio_to_slice_num(0); // Get PWM slice for GP0 (Same for GP1)
|
||||||
|
pwm_set_clkdiv_int_frac (dac_iq, 1, 0); // clock divide by 1
|
||||||
|
pwm_set_wrap(dac_iq, DAC_RANGE); // Set cycle length
|
||||||
|
pwm_set_enabled(dac_iq, true); // Set the PWM running
|
||||||
|
|
||||||
|
gpio_set_function(2, GPIO_FUNC_PWM); // GP2 is PWM for Audio DAC (Slice 1, Channel A)
|
||||||
|
dac_audio = pwm_gpio_to_slice_num(2); // Find PWM slice for GP2
|
||||||
|
pwm_set_clkdiv_int_frac (dac_audio, 1, 0); // clock divide by 1
|
||||||
|
pwm_set_wrap(dac_audio, DAC_RANGE); // Set cycle length
|
||||||
|
pwm_set_enabled(dac_audio, true); // Set the PWM running
|
||||||
|
|
||||||
|
/* Initialize ADCs */
|
||||||
|
adc_init();
|
||||||
|
adc_gpio_init(26); // GP26 is ADC 0 for I channel
|
||||||
|
adc_gpio_init(27); // GP27 is ADC 1 for Q channel
|
||||||
|
adc_gpio_init(28); // GP28 is ADC 2 for Audio channel
|
||||||
|
adc_select_input(0); // Select ADC 0
|
||||||
|
|
||||||
|
tx_enabled = false; // RX mode
|
||||||
|
|
||||||
|
//add_repeating_timer_us(-TX_US, tx_callback, NULL, &tx_timer);
|
||||||
|
add_repeating_timer_us(-RX_US, rx_callback, NULL, &rx_timer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef __DSP_H__
|
||||||
|
#define __DSP_H__
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/adc.h"
|
||||||
|
#include "hardware/pwm.h"
|
||||||
|
|
||||||
|
|
||||||
|
int dsp_init();
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Grove 16x2 LCD, HD44780 chip with JHD1804 I2C interface
|
||||||
|
* Display RAM addresses 0x00-0x1f for top row and 0x40-0x5f for bottom row
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
#include "hardware/timer.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "lcd.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// commands
|
||||||
|
#define LCD_CLEARDISPLAY 0x01
|
||||||
|
#define LCD_RETURNHOME 0x02
|
||||||
|
#define LCD_ENTRYMODESET 0x04
|
||||||
|
#define LCD_DISPLAYCONTROL 0x08
|
||||||
|
#define LCD_CURSORSHIFT 0x10
|
||||||
|
#define LCD_FUNCTIONSET 0x20
|
||||||
|
#define LCD_SETCGRAMADDR 0x40
|
||||||
|
#define LCD_SETDDRAMADDR 0x80
|
||||||
|
|
||||||
|
// flags for display entry mode: LCD_ENTRYMODESET
|
||||||
|
#define LCD_ENTRYRIGHT 0x00
|
||||||
|
#define LCD_ENTRYLEFT 0x02
|
||||||
|
#define LCD_ENTRYSHIFTINC 0x01
|
||||||
|
#define LCD_ENTRYSHIFTDEC 0x00
|
||||||
|
|
||||||
|
// flags for display on/off control: LCD_DISPLAYCONTROL
|
||||||
|
#define LCD_DISPLAYON 0x04
|
||||||
|
#define LCD_DISPLAYOFF 0x00
|
||||||
|
#define LCD_CURSORON 0x02
|
||||||
|
#define LCD_CURSOROFF 0x00
|
||||||
|
#define LCD_BLINKON 0x01
|
||||||
|
#define LCD_BLINKOFF 0x00
|
||||||
|
|
||||||
|
// flags for display/cursor shift: LCD_CURSORSHIFT
|
||||||
|
#define LCD_DISPLAYMOVE 0x08
|
||||||
|
#define LCD_CURSORMOVE 0x00
|
||||||
|
#define LCD_MOVERIGHT 0x04
|
||||||
|
#define LCD_MOVELEFT 0x00
|
||||||
|
|
||||||
|
// flags for function set: LCD_FUNCTIONSET
|
||||||
|
#define LCD_8BITMODE 0x10
|
||||||
|
#define LCD_4BITMODE 0x00
|
||||||
|
#define LCD_2LINE 0x08
|
||||||
|
#define LCD_1LINE 0x00
|
||||||
|
#define LCD_5x10DOTS 0x04
|
||||||
|
#define LCD_5x8DOTS 0x00
|
||||||
|
|
||||||
|
|
||||||
|
/* I2C address and pins */
|
||||||
|
#define I2C_LCD 0x3E
|
||||||
|
#define I2C0_SDA 8
|
||||||
|
#define I2C0_SCL 9
|
||||||
|
|
||||||
|
|
||||||
|
void lcd_init(void)
|
||||||
|
{
|
||||||
|
uint8_t txdata[8];
|
||||||
|
|
||||||
|
/* I2C0 initialisation at 400Khz. */
|
||||||
|
i2c_init(i2c0, 400*1000);
|
||||||
|
gpio_set_function(I2C0_SDA, GPIO_FUNC_I2C);
|
||||||
|
gpio_set_function(I2C0_SCL, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(I2C0_SDA);
|
||||||
|
gpio_pull_up(I2C0_SCL);
|
||||||
|
|
||||||
|
txdata[0] = 0x80;
|
||||||
|
txdata[1] = LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE | LCD_5x8DOTS;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(39);
|
||||||
|
txdata[1] = LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(39);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_ctrl(uint8_t cmd, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
uint8_t txdata[8];
|
||||||
|
|
||||||
|
txdata[0] = 0x80;
|
||||||
|
switch(cmd)
|
||||||
|
{
|
||||||
|
case LCD_CLEAR:
|
||||||
|
txdata[1] = LCD_CLEARDISPLAY;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(1530);
|
||||||
|
break;
|
||||||
|
case LCD_HOME:
|
||||||
|
txdata[1] = LCD_RETURNHOME;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(39);
|
||||||
|
break;
|
||||||
|
case LCD_GOTO: // 2-row is 0x00-0x27 per row, only 0x00-0x1F are visible
|
||||||
|
if (y==1)
|
||||||
|
txdata[1] = x | 0xc0;
|
||||||
|
else
|
||||||
|
txdata[1] = x | 0x80;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(39);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_write(uint8_t *s, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
uint8_t *p;
|
||||||
|
uint8_t txdata[8];
|
||||||
|
|
||||||
|
txdata[0] = 0x40;
|
||||||
|
p=s;
|
||||||
|
for (i=0; i<len; i++)
|
||||||
|
{
|
||||||
|
txdata[1] = *p++;
|
||||||
|
i2c_write_blocking(i2c0, I2C_LCD, txdata, 2, false);
|
||||||
|
sleep_us(43);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef __LCD_H__
|
||||||
|
#define __LCD_H__
|
||||||
|
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
|
||||||
|
#define LCD_CLEAR 0
|
||||||
|
#define LCD_HOME 1
|
||||||
|
#define LCD_BLINK 2
|
||||||
|
#define LCD_GOTO 3
|
||||||
|
|
||||||
|
void lcd_init(void);
|
||||||
|
void lcd_ctrl(uint8_t cmd, uint8_t x, uint8_t y);
|
||||||
|
void lcd_write(uint8_t *s, uint8_t len);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,62 @@
|
||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
|
@ -0,0 +1,439 @@
|
||||||
|
/*
|
||||||
|
* si5351.c
|
||||||
|
*
|
||||||
|
* Created: 12 Jan 2020 21:45:00
|
||||||
|
* Author: Arjan
|
||||||
|
|
||||||
|
Si5351 principle:
|
||||||
|
=================
|
||||||
|
+-------+ +-------+ +------+
|
||||||
|
- Fxtal --> | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout -->
|
||||||
|
+-------+ +-------+ +------+
|
||||||
|
|
||||||
|
---Derivation of Fout---
|
||||||
|
MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c
|
||||||
|
MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c)
|
||||||
|
|
||||||
|
---Derivation of register values, for MSN and MSi---
|
||||||
|
P1 = 128*a + Floor(128*b/c) - 512
|
||||||
|
P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode, or calculated for MSN tuning)
|
||||||
|
P3 = c (P3 = 1 for MSi integer mode, or P3 = 1000000 for MSN tuning)
|
||||||
|
|
||||||
|
This VFO implementation assumes PLLA is used for clk0 and clk1, PLLB is used for clk2
|
||||||
|
|
||||||
|
Algorithm to get from frequency to synthesizer settings:
|
||||||
|
(this assumes that the current settings are consistent, i.e. must be initialized at startup)
|
||||||
|
| calculate new MSN from the desired Fout, based on current Ri and MSi
|
||||||
|
| if MSN is still inside [600/Fxtal, 900/Fxtal]
|
||||||
|
| then
|
||||||
|
| just write the MSN parameter registers
|
||||||
|
| else
|
||||||
|
| re-calculate MSi, Ri and MSN from desired Fout
|
||||||
|
| write the MSi and Ri parameter registers, including phase offset (MSi equals phase offset for 90 deg, use INV to shift 180deg more)
|
||||||
|
| write the MSN parameter registers
|
||||||
|
| reset PLL
|
||||||
|
|
||||||
|
Ri=128 for Fout <1 MHz
|
||||||
|
Ri= 32 for Fout 1-6 MHz
|
||||||
|
Ri= 1 for Fout >6 MHz
|
||||||
|
|
||||||
|
Some boundary values:
|
||||||
|
Ri MSi Lo MHz Hi MHz
|
||||||
|
1 4 150.000000 225.000000
|
||||||
|
1 126 4.761905 7.142857
|
||||||
|
32 4 4.687500 7.031250
|
||||||
|
32 126 0.148810 0.223214
|
||||||
|
128 4 1.171875 1.757813
|
||||||
|
128 126 0.037202 0.055804
|
||||||
|
|
||||||
|
MSi: target for mid-band, i.e. Fvco=750MHz
|
||||||
|
MSi = 750MHz/(Fout*Ri)
|
||||||
|
MSi &= 0xfe // Make it even
|
||||||
|
|
||||||
|
MSN = MSi*Ri*Fout/Fxtal (should be between 24 and 36)
|
||||||
|
|
||||||
|
Only use MSi even-integers, i.e. d=[4, 6, 8..126], e=0 and f=100000, and set INT bits in reg 22, 23.
|
||||||
|
Phase offset MS0 (reg 165) must be 0, MS1 (reg 166) must be equal to MS1 for 90 deg. Set INV bit in reg 17 to add 180 deg.
|
||||||
|
NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Control Si5351:
|
||||||
|
================
|
||||||
|
----+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||||
|
@ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
----+---------+---------+---------+---------+---------+---------+---------+---------+
|
||||||
|
0 |SYS_INIT | LOL_B | LOL_A | LOS | Reserved | REVID[1:0] |
|
||||||
|
1 |SYS_INIT | LOS_B | LOL_A | LOS | Reserved |
|
||||||
|
| _STKY| _STKY| _STKY| _STKY| Reserved |
|
||||||
|
2 |SYS_INIT | LOS_B | LOL_A | LOS | Reserved |
|
||||||
|
| _MASK| _MASK| _MASK| _MASK| Reserved |
|
||||||
|
3 | Reserved | CLK2_EN | CLK1_EN | CLK0_EN |
|
||||||
|
====
|
||||||
|
9 | Reserved |OEB_MASK2|OEB_MASK1|OEB_MASK0|
|
||||||
|
====
|
||||||
|
15 | CLKIN_DIV[2:0] | 0 | 0 | PLLB_SRC| PLLA_SRC| 0 | 0 |
|
||||||
|
16 | CLK0_PDN| MS0_INT | MS0_SRC | CLK0_INV| CLK0_SRC[1:0] | CLK0_IDRV[1:0] |
|
||||||
|
17 | CLK1_PDN| MS1_INT | MS1_SRC | CLK1_INV| CLK1_SRC[1:0] | CLK1_IDRV[1:0] |
|
||||||
|
18 | CLK2_PDN| MS2_INT | MS2_SRC | CLK2_INV| CLK2_SRC[1:0] | CLK2_IDRV[1:0] |
|
||||||
|
====
|
||||||
|
22 | Reserved| FBA_INT | Reserved |
|
||||||
|
23 | Reserved| FBB_INT | Reserved |
|
||||||
|
24 | Reserved | CLK2_DIS_STATE | CLK1_DIS_STATE | CLK0_DIS_STATE |
|
||||||
|
====
|
||||||
|
26 | MSNA_P3[15:8] |
|
||||||
|
27 | MSNA_P3[7:0] |
|
||||||
|
28 | Reserved | MSNA_P1[17:16] |
|
||||||
|
29 | MSNA_P1[15:8] |
|
||||||
|
30 | MSNA_P1[7:0] |
|
||||||
|
31 | MSNA_P3[19:16] | MSNA_P2[19:16] |
|
||||||
|
32 | MSNA_P2[15:8] |
|
||||||
|
33 | MSNA_P2[7:0] |
|
||||||
|
|
||||||
|
34: Same pattern for PLLB
|
||||||
|
|
||||||
|
42 | MS0_P3[15:8] |
|
||||||
|
43 | MS0_P3[7:0] |
|
||||||
|
44 | Reserved| R0_DIV[2:0] | MS0_DIVBY4[1:0] | MS0_P1[17:16] |
|
||||||
|
45 | MS0_P1[15:8] |
|
||||||
|
46 | MS0_P1[7:0] |
|
||||||
|
47 | MS0_P3[19:16] | MS0_P2[19:16] |
|
||||||
|
48 | MS0_P2[15:8] |
|
||||||
|
49 | MS0_P2[7:0] |
|
||||||
|
|
||||||
|
50: Same pattern for CLK1
|
||||||
|
|
||||||
|
58: Same pattern for CLK2
|
||||||
|
|
||||||
|
====
|
||||||
|
165 | Reserved| CLK0_PHOFF[6:0] |
|
||||||
|
166 | Reserved| CLK1_PHOFF[6:0] |
|
||||||
|
167 | Reserved| CLK2_PHOFF[6:0] |
|
||||||
|
====
|
||||||
|
177 | PLLB_RST| Reserved| PLLA_RST| Reserved |
|
||||||
|
====
|
||||||
|
183 | XTAL_CL | Reserved |
|
||||||
|
====
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Implicit type conversion precedence:
|
||||||
|
- long double
|
||||||
|
- double
|
||||||
|
- float
|
||||||
|
- unsigned long int
|
||||||
|
- long int
|
||||||
|
- unsigned int
|
||||||
|
- int
|
||||||
|
- other
|
||||||
|
conversion is always to highest type, this is also the result of an operation.
|
||||||
|
|
||||||
|
Maximum UL = 4,294,967,295 (0xffffffff)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
#include "hardware/timer.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "si5351.h"
|
||||||
|
|
||||||
|
#define I2C_VFO 0x60 // I2C address
|
||||||
|
|
||||||
|
// SI5351 register address definitions
|
||||||
|
#define SI_CLK_OE 3
|
||||||
|
#define SI_CLK0_CTL 16
|
||||||
|
#define SI_CLK1_CTL 17
|
||||||
|
#define SI_CLK2_CTL 18
|
||||||
|
#define SI_SYNTH_PLLA 26
|
||||||
|
#define SI_SYNTH_PLLB 34
|
||||||
|
#define SI_SYNTH_MS0 42
|
||||||
|
#define SI_SYNTH_MS1 50
|
||||||
|
#define SI_SYNTH_MS2 58
|
||||||
|
#define SI_SS_EN 149
|
||||||
|
#define SI_CLK0_PHOFF 165
|
||||||
|
#define SI_CLK1_PHOFF 166
|
||||||
|
#define SI_CLK2_PHOFF 167
|
||||||
|
#define SI_PLL_RESET 177
|
||||||
|
#define SI_XTAL_LOAD 183
|
||||||
|
|
||||||
|
// CLK_OE register 3 values
|
||||||
|
#define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output
|
||||||
|
#define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output
|
||||||
|
#define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output
|
||||||
|
|
||||||
|
// CLKi_CTL register 16, 17, 18 values
|
||||||
|
// Normally 0x4f for clk 0 and 1, 0x6f for clk 2
|
||||||
|
#define SI_CLK_INT 0b01000000 // Set integer mode
|
||||||
|
#define SI_CLK_PLL 0b00100000 // Select PLL B as MS source (default 0 = PLL A)
|
||||||
|
#define SI_CLK_INV 0b00010000 // Invert output (i.e. phase + 180deg)
|
||||||
|
#define SI_CLK_SRC 0b00001100 // Select output source: 11=MS, 00=XTAL direct
|
||||||
|
#define SI_CLK_DRV 0b00000011 // Select output drive, increasingly: 2-4-6-8 mA (best risetime, use max = 11)
|
||||||
|
|
||||||
|
// PLL_RESET register 177 values
|
||||||
|
#define SI_PLLB_RST 0b10000000 // Reset PLL B
|
||||||
|
#define SI_PLLA_RST 0b00100000 // Reset PLL A
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define SI_XTAL_FREQ 24998851UL // Replace with measured crystal frequency of XTAL for CL = 10pF (default)
|
||||||
|
#define SI_MSN_LO ((0.6e9)/SI_XTAL_FREQ)
|
||||||
|
#define SI_MSN_HI ((0.9e9)/SI_XTAL_FREQ)
|
||||||
|
#define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting
|
||||||
|
|
||||||
|
|
||||||
|
/* I2C1 pins */
|
||||||
|
#define I2C1_SDA 10
|
||||||
|
#define I2C1_SCL 11
|
||||||
|
|
||||||
|
vfo_t vfo[2]; // 0: clk0 and clk1 1: clk2
|
||||||
|
|
||||||
|
int si_getreg(uint8_t *data, uint8_t reg, uint8_t len)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = i2c_write_blocking(i2c1, I2C_VFO, ®, 1, true);
|
||||||
|
if (ret<0) printf ("I2C write error\n");
|
||||||
|
ret = i2c_read_blocking(i2c1, I2C_VFO, data, len, false);
|
||||||
|
if (ret<0) printf ("I2C read error\n");
|
||||||
|
return(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set up MSN PLL divider for vfo[i], assuming MSN has been set in vfo[i]
|
||||||
|
// Optimize for speed, this may be called with short intervals
|
||||||
|
// See also SiLabs AN619 section 3.2
|
||||||
|
void si_setmsn(uint8_t i)
|
||||||
|
{
|
||||||
|
uint8_t data[16]; // I2C trx buffer
|
||||||
|
uint32_t P1, P2; // MSN parameters
|
||||||
|
uint32_t A;
|
||||||
|
uint32_t B;
|
||||||
|
|
||||||
|
i=(i>0?1:0);
|
||||||
|
/*
|
||||||
|
P1 = 128*a + Floor(128*b/c) - 512
|
||||||
|
P2 = 128*b - c*Floor(128*b/c)
|
||||||
|
P3 = c (P3 = 1000000 for MSN tuning)
|
||||||
|
*/
|
||||||
|
A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN
|
||||||
|
B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant)
|
||||||
|
P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C));
|
||||||
|
P1 = (uint32_t)(128 * A + P2 - 512);
|
||||||
|
P2 = (uint32_t)(128 * B - SI_PLL_C * P2);
|
||||||
|
|
||||||
|
// transfer registers
|
||||||
|
data[0] = (i==0?SI_SYNTH_PLLA:SI_SYNTH_PLLB);
|
||||||
|
data[1] = (SI_PLL_C & 0x0000FF00) >> 8;
|
||||||
|
data[2] = (SI_PLL_C & 0x000000FF);
|
||||||
|
data[3] = (P1 & 0x00030000) >> 16;
|
||||||
|
data[4] = (P1 & 0x0000FF00) >> 8;
|
||||||
|
data[5] = (P1 & 0x000000FF);
|
||||||
|
data[6] = ((SI_PLL_C & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16);
|
||||||
|
data[7] = (P2 & 0x0000FF00) >> 8;
|
||||||
|
data[8] = (P2 & 0x000000FF);
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up registers with MS and R divider for vfo[i], assuming values have been set in vfo[i]
|
||||||
|
// In this implementation we only use integer mode, i.e. b=0 and P3=1
|
||||||
|
// See also SiLabs AN619 section 4.1
|
||||||
|
void si_setmsi(uint8_t i)
|
||||||
|
{
|
||||||
|
uint8_t data[16]; // I2C trx buffer
|
||||||
|
uint32_t P1;
|
||||||
|
uint8_t R;
|
||||||
|
|
||||||
|
i=(i>0?1:0);
|
||||||
|
/*
|
||||||
|
P1 = 128*a + Floor(128*b/c) - 512
|
||||||
|
P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode)
|
||||||
|
P3 = c (P3 = 1 for MSi integer mode)
|
||||||
|
*/
|
||||||
|
P1 = (uint32_t)(128*(uint32_t)floor(vfo[i].msi) - 512);
|
||||||
|
R = vfo[i].ri;
|
||||||
|
R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r)
|
||||||
|
|
||||||
|
data[0] = (i==0?SI_SYNTH_MS0:SI_SYNTH_MS2);
|
||||||
|
data[1] = 0x00;
|
||||||
|
data[2] = 0x01;
|
||||||
|
data[3] = ((P1 & 0x00030000) >> 16) | (R << 4 );
|
||||||
|
data[4] = (P1 & 0x0000FF00) >> 8;
|
||||||
|
data[5] = (P1 & 0x000000FF);
|
||||||
|
data[6] = 0x00;
|
||||||
|
data[7] = 0x00;
|
||||||
|
data[8] = 0x00;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
// If vfo[0] also set clk 1
|
||||||
|
if (i==0)
|
||||||
|
{
|
||||||
|
data[0] = SI_SYNTH_MS1; // Same data in synthesizer
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
if (vfo[0].phase&1) // Phase is either 90 or 270 deg?
|
||||||
|
{
|
||||||
|
data[0] = SI_CLK1_PHOFF;
|
||||||
|
data[1] = vfo[0].msi;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data[0] = SI_CLK1_PHOFF;
|
||||||
|
data[1] = 0;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
}
|
||||||
|
if (vfo[0].phase&2) // Phase is 180 or 270 deg?
|
||||||
|
{
|
||||||
|
data[0] = SI_CLK1_CTL;
|
||||||
|
data[1] = 0x5f; // CLK1: INT, PLLA, INV, MS, 8mA
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset associated PLL
|
||||||
|
data[0] = SI_PLL_RESET;
|
||||||
|
data[1] = (i==1)?0x80:0x20;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For each vfo, calculate required MSN setting, MSN = MSi*Ri*Fout/Fxtal
|
||||||
|
// If in range, just set MSN registers
|
||||||
|
// If not in range, recalculate MSi and Ri and also MSN
|
||||||
|
// Set MSN, MSi and Ri registers (implicitly resets PLL)
|
||||||
|
void vfo_evaluate(void)
|
||||||
|
{
|
||||||
|
float msn;
|
||||||
|
|
||||||
|
if (vfo[0].flag)
|
||||||
|
{
|
||||||
|
msn = (float)(vfo[0].msi); // Re-calculate MSN
|
||||||
|
msn = msn * (float)(vfo[0].ri);
|
||||||
|
msn = msn * (float)(vfo[0].freq) / SI_XTAL_FREQ;
|
||||||
|
if ((msn>=SI_MSN_LO)&&(msn<SI_MSN_HI))
|
||||||
|
{
|
||||||
|
vfo[0].msn = msn;
|
||||||
|
si_setmsn(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vfo[0].ri = (vfo[0].freq<1000000)?128:((vfo[0].freq<3000000)?32:1); // Pre-scale Ri, stretch down Ri=1 range
|
||||||
|
if ((vfo[0].freq >= 3000000)&&(vfo[0].freq < 6000000)) // Low end of Ri=1 range
|
||||||
|
vfo[0].msi = (uint8_t)126; // Maximum MSi on Fvco=(4x126)MHz
|
||||||
|
else
|
||||||
|
vfo[0].msi = (uint8_t)(750000000UL / (vfo[0].freq * vfo[0].ri)) & 0xfe; // Calculate MSi on Fvco=750MHz
|
||||||
|
msn = (float)(vfo[0].msi); // Re-calculate MSN
|
||||||
|
msn = msn * (float)(vfo[0].ri);
|
||||||
|
msn = msn * (float)(vfo[0].freq) / SI_XTAL_FREQ;
|
||||||
|
vfo[0].msn = msn;
|
||||||
|
si_setmsn(0);
|
||||||
|
si_setmsi(0);
|
||||||
|
}
|
||||||
|
vfo[0].flag = 0;
|
||||||
|
}
|
||||||
|
if (vfo[1].flag)
|
||||||
|
{
|
||||||
|
vfo[1].flag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize the Si5351 VFO registers
|
||||||
|
void vfo_init(void)
|
||||||
|
{
|
||||||
|
uint8_t data[16]; // I2C trx buffer
|
||||||
|
|
||||||
|
i2c_init(i2c1, 400*1000);
|
||||||
|
gpio_set_function(I2C1_SDA, GPIO_FUNC_I2C);
|
||||||
|
gpio_set_function(I2C1_SCL, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(I2C1_SDA);
|
||||||
|
gpio_pull_up(I2C1_SCL);
|
||||||
|
|
||||||
|
// Hard initialize Synth registers: all 10MHz, CLK1 90 deg ahead, PLLA for CLK 0&1, PLLB for CLK2
|
||||||
|
// Ri=1,
|
||||||
|
// MSi=68, P1=8192, P2=0, P3=1
|
||||||
|
// MSN=27.2 P1=2969, P2=600000, P3=1000000
|
||||||
|
vfo[0].freq = 10000000;
|
||||||
|
vfo[0].flag = 0;
|
||||||
|
vfo[0].phase = 2;
|
||||||
|
vfo[0].ri = 1;
|
||||||
|
vfo[0].msi = 68;
|
||||||
|
vfo[0].msn = 27.2;
|
||||||
|
vfo[1].freq = 10000000;
|
||||||
|
vfo[1].flag = 0;
|
||||||
|
vfo[1].phase = 0;
|
||||||
|
vfo[1].ri = 1;
|
||||||
|
vfo[1].msi = 68;
|
||||||
|
vfo[1].msn = 27.2;
|
||||||
|
|
||||||
|
// PLLA: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
|
||||||
|
data[0] = SI_SYNTH_PLLA;
|
||||||
|
data[1] = 0x42; // MSNA_P3[15:8]
|
||||||
|
data[2] = 0x40; // MSNA_P3[7:0]
|
||||||
|
data[3] = 0x00; // 0b000000 , MSNA_P1[17:16]
|
||||||
|
data[4] = 0x0b; // MSNA_P1[15:8]
|
||||||
|
data[5] = 0x99; // MSNA_P1[7:0]
|
||||||
|
data[6] = 0xf9; // MSNA_P3[19:16] , MSNA_P2[19:16]
|
||||||
|
data[7] = 0x27; // MSNA_P2[15:8]
|
||||||
|
data[8] = 0xc0; // MSNA_P2[7:0]
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
|
||||||
|
// PLLB: MSN P1=0x00000b99, P2=0x000927c0, P3=0x000f4240
|
||||||
|
data[0] = SI_SYNTH_PLLB; // Same content
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
// MS0 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||||
|
data[0] = SI_SYNTH_MS0;
|
||||||
|
data[1] = 0x00; // MS0_P3[15:8]
|
||||||
|
data[2] = 0x01; // MS0_P3[7:0]
|
||||||
|
data[3] = 0x00; // 0b0, R0_DIV[2:0] , MS0_DIVBY4[1:0] , MS0_P1[17:16]
|
||||||
|
data[4] = 0x20; // MS0_P1[15:8]
|
||||||
|
data[5] = 0x00; // MS0_P1[7:0]
|
||||||
|
data[6] = 0x00; // MS0_P3[19:16] , MS0_P2[19:16]
|
||||||
|
data[7] = 0x00; // MS0_P2[15:8]
|
||||||
|
data[8] = 0x00; // MS0_P2[7:0]
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
// MS1 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||||
|
data[0] = SI_SYNTH_MS1; // Same content
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
// MS2 P1=0x00002000, P2=0x00000000, P3=0x00000001, R=1
|
||||||
|
data[0] = SI_SYNTH_MS2; // Same content
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 9, false);
|
||||||
|
|
||||||
|
// Phase offsets for 3 clocks
|
||||||
|
data[0] = SI_CLK0_PHOFF;
|
||||||
|
data[1] = 0x00; // CLK0: phase 0 deg
|
||||||
|
data[2] = 0x44; // CLK1: phase 90 deg (=MSi)
|
||||||
|
data[3] = 0x00; // CLK2: phase 0 deg
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 4, false);
|
||||||
|
|
||||||
|
// Output port settings for 3 clocks
|
||||||
|
data[0] = SI_CLK0_CTL;
|
||||||
|
data[1] = 0x4f; // CLK0: INT, PLLA, nonINV, MS, 8mA
|
||||||
|
data[2] = 0x4f; // CLK1: INT, PLLA, nonINV, MS, 8mA
|
||||||
|
data[3] = 0x6f; // CLK2: INT, PLLB, nonINV, MS, 8mA
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 4, false);
|
||||||
|
|
||||||
|
// Disable spread spectrum (startup state is undefined)
|
||||||
|
data[0] = SI_SS_EN;
|
||||||
|
data[1] = 0x00;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
|
||||||
|
// Reset both PLL
|
||||||
|
data[0] = SI_PLL_RESET;
|
||||||
|
data[1] = 0xa0;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
|
||||||
|
// Enable all outputs
|
||||||
|
data[0] = SI_CLK_OE;
|
||||||
|
data[1] = 0x00;
|
||||||
|
i2c_write_blocking(i2c1, I2C_VFO, data, 2, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef _SI5351_H
|
||||||
|
#define _SI5351_H
|
||||||
|
/*
|
||||||
|
* si5351.h
|
||||||
|
*
|
||||||
|
* Created: 13 March 2021
|
||||||
|
* Author: Arjan
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t freq; // type can hold up to 4GHz
|
||||||
|
uint8_t flag; // flag != 0 when update needed
|
||||||
|
uint8_t phase; // in quarter waves (0, 1, 2, 3)
|
||||||
|
uint8_t ri; // Ri (1 .. 128)
|
||||||
|
uint8_t msi; // MSi parameter a (4, 6, 8 .. 126)
|
||||||
|
float msn; // MSN (24.0 .. 35.9999)
|
||||||
|
} vfo_t;
|
||||||
|
extern vfo_t vfo[2]; // Table contains all control data for three clk outputs, but 0 and 1 are coupled in vfo[0]
|
||||||
|
|
||||||
|
int si_getreg(uint8_t *data, uint8_t reg, uint8_t len);
|
||||||
|
void vfo_init(void);
|
||||||
|
void vfo_evaluate(void);
|
||||||
|
|
||||||
|
|
||||||
|
#define SI_GETFREQ(i) ((((i)>=0)&&((i)<2))?vfo[(i)].freq:0)
|
||||||
|
#define SI_INCFREQ(i, d) if ((((i)>=0)&&((i)<2))&&((vfo[(i)].freq)<(150000000-(d)))) { vfo[(i)].freq += (d); vfo[(i)].flag = 1;}
|
||||||
|
#define SI_DECFREQ(i, d) if ((((i)>=0)&&((i)<2))&&((vfo[(i)].freq)>(d))) { (vfo[(i)].freq) -= (d); vfo[(i)].flag = 1;}
|
||||||
|
#define SI_SETFREQ(i, f) if ((((i)>=0)&&((i)<2))&&((f)<150000000)) { vfo[(i)].freq = (f); vfo[(i)].flag = 1;}
|
||||||
|
|
||||||
|
#endif /* _SI5351_H */
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/pwm.h"
|
||||||
|
#include "hardware/pio.h"
|
||||||
|
#include "hardware/timer.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
|
||||||
|
#include "lcd.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
#include "si5351.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Monitor definitions */
|
||||||
|
#define ENDSTDIN 255
|
||||||
|
#define CR 13
|
||||||
|
#define LF 10
|
||||||
|
#define CMD_LEN 32
|
||||||
|
|
||||||
|
uint8_t display1[16];
|
||||||
|
uint8_t display2[16];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* LED TIMER definition and callback */
|
||||||
|
struct repeating_timer led_timer;
|
||||||
|
bool led_callback(struct repeating_timer *t)
|
||||||
|
{
|
||||||
|
static bool led_state;
|
||||||
|
|
||||||
|
gpio_put(PICO_DEFAULT_LED_PIN, led_state);
|
||||||
|
led_state = (led_state?false:true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t si5351_reg[200];
|
||||||
|
char delim[] = " ";
|
||||||
|
#define NCMD 3
|
||||||
|
char *shell[NCMD] = {"si", "fa", "fb"};
|
||||||
|
void mon_parse(char* s)
|
||||||
|
{
|
||||||
|
char *p;
|
||||||
|
int base, nreg, i;
|
||||||
|
|
||||||
|
p = strtok(s, delim); // Get command part of string
|
||||||
|
for (i=0; i<NCMD; i++)
|
||||||
|
if (strcmp(p, shell[i]) == 0) break;
|
||||||
|
switch(i)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// Next: p = strtok(NULL, delim); (returns NULL if none left)
|
||||||
|
for (i=0; i<nreg; i++) si5351_reg[i] = 0xaa;
|
||||||
|
si_getreg(si5351_reg, (uint8_t)base, (uint8_t)nreg);
|
||||||
|
for (i=0; i<nreg; i++) printf("%02x ",(int)(si5351_reg[i]));
|
||||||
|
printf("\n");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
printf("%s\n", p);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
printf("%s\n", p);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
/* Initialize IOs */
|
||||||
|
stdio_init_all();
|
||||||
|
|
||||||
|
/* Initialize LED pin output */
|
||||||
|
gpio_init(PICO_DEFAULT_LED_PIN);
|
||||||
|
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
|
||||||
|
gpio_put(PICO_DEFAULT_LED_PIN, true); // Set LED on
|
||||||
|
add_repeating_timer_ms(-1000, led_callback, NULL, &led_timer);
|
||||||
|
|
||||||
|
/* Initialize PWM */
|
||||||
|
dsp_init();
|
||||||
|
|
||||||
|
/* Initialize Si5351 vfo */
|
||||||
|
vfo_init();
|
||||||
|
|
||||||
|
/* Initialize LCD */
|
||||||
|
lcd_init();
|
||||||
|
lcd_ctrl(LCD_GOTO, 0, 0);
|
||||||
|
sprintf(display1, "A: 7074.0 kHz");
|
||||||
|
lcd_write(display1,13);
|
||||||
|
|
||||||
|
SI_SETFREQ(0, 2*7074000UL); // Set freq to 2*7074 kHz
|
||||||
|
|
||||||
|
/* Initialize monitor terminal */
|
||||||
|
printf("Pico> ");
|
||||||
|
int c, i=0;
|
||||||
|
char mon_cmd[CMD_LEN+1];
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
/* Check for monitor input */
|
||||||
|
c = getchar_timeout_us(100000); // 1 try per 100 msec
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case PICO_ERROR_TIMEOUT: // just go-on
|
||||||
|
break;
|
||||||
|
case CR: // CR or LF:
|
||||||
|
case LF: // need to parse command string
|
||||||
|
putchar((char)c); // echo character
|
||||||
|
if (i==0) break; // already did a parse, only do it once
|
||||||
|
mon_cmd[i] = 0; // terminate command string
|
||||||
|
i=0; // reset index
|
||||||
|
mon_parse(mon_cmd); // process command
|
||||||
|
printf("Pico> "); // prompt
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if ((c<32)||(c>=128)) break; // Alfanumeric?
|
||||||
|
putchar((char)c); // echo character
|
||||||
|
mon_cmd[i] = (char)c; // store in command string
|
||||||
|
if (i<CMD_LEN) i++; // check range and increment
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check whether VFO settings have changed */
|
||||||
|
vfo_evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Ładowanie…
Reference in New Issue