diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1e03693 --- /dev/null +++ b/CMakeLists.txt @@ -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) + diff --git a/dsp.c b/dsp.c new file mode 100644 index 0000000..fc040cd --- /dev/null +++ b/dsp.c @@ -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; +} diff --git a/dsp.h b/dsp.h new file mode 100644 index 0000000..263b5a1 --- /dev/null +++ b/dsp.h @@ -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 diff --git a/lcd.c b/lcd.c new file mode 100644 index 0000000..6522686 --- /dev/null +++ b/lcd.c @@ -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 +#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/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}) diff --git a/si5351.c b/si5351.c new file mode 100644 index 0000000..6c0961b --- /dev/null +++ b/si5351.c @@ -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 +#include +#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= 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); + +} + diff --git a/si5351.h b/si5351.h new file mode 100644 index 0000000..f83679f --- /dev/null +++ b/si5351.h @@ -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 */ \ No newline at end of file diff --git a/uSDR.c b/uSDR.c new file mode 100644 index 0000000..f7b2dd2 --- /dev/null +++ b/uSDR.c @@ -0,0 +1,131 @@ +#include +#include +#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 "); + 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