Uploaded first draft implementation
pull/13/head
ArjanteMarvelde 2021-04-03 21:39:24 +02:00 zatwierdzone przez GitHub
rodzic 8b4df6d160
commit b03bcb9c7a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 1131 dodań i 0 usunięć

46
CMakeLists.txt 100644
Wyświetl plik

@ -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)

267
dsp.c 100644
Wyświetl plik

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

11
dsp.h 100644
Wyświetl plik

@ -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

125
lcd.c 100644
Wyświetl plik

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

17
lcd.h 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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})

439
si5351.c 100644
Wyświetl plik

@ -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, &reg, 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);
}

33
si5351.h 100644
Wyświetl plik

@ -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 */

131
uSDR.c 100644
Wyświetl plik

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