diff --git a/CMakeLists.txt b/CMakeLists.txt index 532b19f..75eb213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,6 @@ pico_generate_pio_header(pico-DCO-test ${CMAKE_CURRENT_LIST_DIR}/piodco/dco.pio) target_sources(pico-DCO-test PUBLIC ${CMAKE_CURRENT_LIST_DIR}/lib/assert.c ${CMAKE_CURRENT_LIST_DIR}/piodco/piodco.c - ${CMAKE_CURRENT_LIST_DIR}/dcoisr/dcoisr.c ${CMAKE_CURRENT_LIST_DIR}/test.c ) @@ -55,6 +54,7 @@ target_link_libraries( pico-DCO-test pico_stdlib pico_sync + pico_multicore hardware_timer hardware_clocks hardware_pio diff --git a/README.md b/README.md index 0db6e53..bc5cce4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ # pico-DCO -Digital controlled oscillator for raspberry pi pico (DC to ~30MHz frequency band) +# Digital controlled radio frequency oscillator for Raspberry Pi Pico + +The library for Raspberry Pi Pico includes the headers and source code and all +necessary build files to build a custom application which turns pico into +precise PLL digital frequency oscillator of the portion of HF radio spectrum +1.1 to 9.4MHz with high resolution. + +# Precise frequency resolution +The library provides 1 Hz frequency resolution in initialize function. This +resolution can be easily increased to 23 millihertz, which is limited by +24-bit register which is used in algorithm. +Currently the upper freq. limit is about 9.8 MHz and it is achieved only using +pi pico overclocking to 270MHz. + +# Phased locked loop in C +The DCO uses phase locked loop principle programmed in C. + +# Integer arithmetic +The DCO does *NOT* use any floating point operations - all time-critical +instructions run in 1 CPU cycle. + +# Radio transmitters +Owing to the meager frequency step, it is possible to use 3, 5, or 7th harmonics +of generated frequency. Such solution completely cover all HF and low band up to +65.8 MHz. + +# For what? +This is an experimental project of amateur radio class and it is devised by me on +the free will base in order to experiment with QRP narrowband digital modes. + +# Feedback +I gracefully appreciate any thoughts or comments on that matter. + +# Quick-start +1. Install Raspberry Pi Pico SDK. Configure environment variables. Test whether +it is built successfully. + +2. git clone this repository. cd pico-DCO ; ./build.sh +Check whether output file ./build/pico-DCO.uf2 appears. + +3. Prepare the surrogate antenna (if you possess an SSB receiver) or pin-out +for an oscilloscope or a spectrum analyser. The default output pin is GPIO6. + +4. Load the .uf2 file (2) into the Pico. + +5. Initialy the running frequency is 9.4 MHz. + +6. Set any other frequency ranging from 1.1 to 9.4 MHz by #define GEN_FRQ_HZ and build the project. + +7. Provide the feedback by clicking like on the github page of the project. diff --git a/piodco/piodco.c b/piodco/piodco.c index e508e2a..a7363a1 100644 --- a/piodco/piodco.c +++ b/piodco/piodco.c @@ -12,7 +12,7 @@ // DESCRIPTION // // The oscillator provides precise generation of any frequency ranging -// from 1.5 to 9.8 MHz with tenth's of millihertz resolution (please note that +// from 1.1 to 9.4 MHz with tenth's of millihertz resolution (please note that // this is relative resolution owing to the fact that the absolute accuracy of // onboard crystal of pi pico is limited). // The DCO uses phase locked loop principle programmed in C. @@ -66,6 +66,8 @@ #include "build/dco.pio.h" +int32_t si32precise_cycles; + /// @brief Initializes DCO context and prepares PIO hardware. /// @param pdco Ptr to DCO context. /// @param gpio The GPIO of DCO output. @@ -105,16 +107,15 @@ int PioDCOSetFreq(PioDco *pdco, uint32_t ui32_frq_hz) assert_(pdco); assert(pdco->_clkfreq_hz); - if(pdco->_clkfreq_hz / ui32_frq_hz < 27) - { - //return -1; - } + ui32_frq_hz <<= 1; /* RPix: Calculate an accurate value of phase increment of the freq per 1 tick of CPU clock, here 2^24 is scaling coefficient. */ pdco->_frq_cycles_per_pi = (int32_t)(((int64_t)pdco->_clkfreq_hz * (int64_t)(1<<24) + (ui32_frq_hz>>1)) / (int64_t)ui32_frq_hz); + si32precise_cycles = pdco->_frq_cycles_per_pi; + return 0; } @@ -138,7 +139,6 @@ void PioDCOStop(PioDco *pdco) /// @brief the dedicated pi pico core. /// @param pDCO Ptr to DCO context. /// @return No return. It spins forever. -static int32_t si32precise_cycles; void RAM (PioDCOWorker)(PioDco *pDCO) { assert_(pDCO); @@ -150,9 +150,10 @@ void RAM (PioDCOWorker)(PioDco *pDCO) register uint32_t *preg32 = pDCO->_ui32_pioreg; register uint8_t *pu8reg = (uint8_t *)preg32; - si32precise_cycles = pDCO->_frq_cycles_per_pi; + //si32precise_cycles = pDCO->_frq_cycles_per_pi; for(;;) { + const register int32_t i32reg = si32precise_cycles; /* RPix: Load the next precise value of CPU CLK cycles per DCO cycle, scaled by 2^24. It yields about 24 millihertz resolution at @10MHz DCO frequency. */ @@ -160,13 +161,13 @@ void RAM (PioDCOWorker)(PioDco *pDCO) { /* RPix: Calculate the integer number of CPU CLK cycles per next DCO cycle, corrected by accumulated error (feedback of the PLL). */ - const int32_t i32wc = iSAR(si32precise_cycles - i32acc_error + (1<<23), 24); + const int32_t i32wc = iSAR(i32reg - i32acc_error + (1<<23), 24); /* RPix: Calculate the difference btw calculated value scaled to `fine` state and precise value of DCO cycles per CPU CLK cycle. This forms a phase locked loop which provides precise freq on long run. */ - i32acc_error += (i32wc<<24) - si32precise_cycles; + i32acc_error += (i32wc<<24) - i32reg; /* RPix: Set PIO array contents corrected by pio program delay of N CPU CLK cycles owing to pio asm instructions. */ diff --git a/piodco/piodco.h b/piodco/piodco.h index 5d54f3a..b7d6de9 100644 --- a/piodco/piodco.h +++ b/piodco/piodco.h @@ -12,7 +12,7 @@ // DESCRIPTION // // The oscillator provides precise generation of any frequency ranging -// from 1.5 to 9.8 MHz with tenth's of millihertz resolution (please note that +// from 1.1 to 9.4 MHz with tenth's of millihertz resolution (please note that // this is relative resolution owing to the fact that the absolute accuracy of // onboard crystal of pi pico is limited). // The DCO uses phase locked loop principle programmed in C. diff --git a/test.c b/test.c index ccb6185..12e1a28 100644 --- a/test.c +++ b/test.c @@ -1,3 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// test.c - Simple test of digital controlled radio freq oscillator based on PIO. +// +// +// DESCRIPTION +// +// The oscillator provides precise generation of any frequency ranging +// from 1.1 to 9.4 MHz with tenth's of millihertz resolution (please note that +// this is relative resolution owing to the fact that the absolute accuracy of +// onboard crystal of pi pico is limited). +// The DCO uses phase locked loop principle programmed in C. +// The DCO does *NOT* use any floating point operations - all time-critical +// instructions run in 1 CPU cycle. +// Currently the upper freq. limit is about 9.8 MHz and it is achieved only +// using pi pico overclocking to 270MHz. +// Owing to the meager frequency step, it is possible to use 3, 5, or 7th +// harmonics of generated frequency. Such solution completely cover all HF and +// low band up to about 66 MHz. +// This is an experimental project of amateur radio class and it is devised +// by me on the free will base in order to experiment with QRP narrowband +// digital modes. +// I gracefully appreciate any thoughts or comments on that matter. +// +// HOWTOSTART +// Set frequency by #define GEN_FRQ_HZ and build the project. The default +// output pin is GPIO6. +// +// PLATFORM +// Raspberry Pi pico. +// +// REVISION HISTORY +// +// Rev 0.1 05 Nov 2023 +// Initial release. +// +// LICENCE +// MIT License (http://www.opensource.org/licenses/mit-license.php) +// +// Copyright (c) 2023 by Roman Piksaykin +// +// Permission is hereby granted, free of charge,to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction,including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY,WHETHER IN AN ACTION OF CONTRACT,TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +/////////////////////////////////////////////////////////////////////////////// #include #include @@ -6,67 +71,66 @@ #include "piodco/piodco.h" #include "build/dco.pio.h" #include "hardware/vreg.h" +#include "pico/multicore.h" #include "./lib/assert.h" #include "hwdefs.h" -#include "./dcoisr/dcoisr.h" -void __not_in_flash_func (WorkerCycle)(PioDco *pDCO, const int32_t i32frq_in_clk, uint32_t *preg32) +#define GEN_FRQ_HZ 9400000L + +PioDco DCO; + +/* This is the code of dedicated core. + We deal with extremely precise real-time task. */ +void core1_entry() { - register int32_t acc_phase_error = 0; - register uint8_t *preg8 = (uint8_t *)preg32; - register PIO pio = pDCO->_pio; - register uint sm = pDCO->_ism; + const uint32_t clkhz = PLL_SYS_MHZ * 1000000L; + const uint32_t freq_hz = GEN_FRQ_HZ; - pio_sm_set_enabled(pio, sm, true); + /* Initialize DCO */ + assert_(0 == PioDCOInit(&DCO, 6, clkhz)); + /* Run DCO. */ + PioDCOStart(&DCO); + + /* Set initial freq. */ + assert_(0 == PioDCOSetFreq(&DCO, freq_hz)); + + /* Run the main DCO algorithm. It spins forever. */ + PioDCOWorker(&DCO); +} + +void RAM (Spinner)(void) +{ + int i = 0; for(;;) { - preg8 = (uint8_t *)preg32; - for(int i = 0; i < 32; ++i) - { - register const int32_t i32wc = (i32frq_in_clk - acc_phase_error + (1<<23)) >> 24; - acc_phase_error += (i32wc<<24) - i32frq_in_clk; + /* This example sets new frequency every ~500 ms. + Frequency shift is 5 Hz for each step. + */ + PioDCOSetFreq(&DCO, GEN_FRQ_HZ - 5*i); - *preg8++ = i32wc - PIOASM_DELAY_CYCLES; - } + /* LED signal */ + gpio_put(PICO_DEFAULT_LED_PIN, 1); + sleep_ms(500); + gpio_put(PICO_DEFAULT_LED_PIN, 0); + sleep_ms(500); - dco_program_puts(pio, sm, preg32); + /* Return to initial freq after 20 steps (100 Hz). */ + if(++i == 20) + i = 0; } } int main() { - PioDco DCO; - //DcoIsrContext ISRC; - const uint32_t clkhz = PLL_SYS_MHZ * 1000000L; set_sys_clock_khz(clkhz / 1000L, true); gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); - const uint32_t freq_hz = 2*10000000L; + multicore_launch_core1(core1_entry); - assert_(0 == PioDCOInit(&DCO, 6, clkhz)); - PioDCOStart(&DCO); - assert_(0 == PioDCOSetFreq(&DCO, freq_hz)); - PioDCOWorker(&DCO); - -/* - //DcoIsrInit(&ISRC, &DCO); - - uint32_t preg32[8] = {0}; - uint8_t *preg8 = (uint8_t *)preg32; - - //const uint64_t frqhz = 2*(3573000L); - //const uint64_t frqhz = 2*7074000L + 1; - const uint64_t frqhz = 2*10000000L; - //const uint64_t frqhz = 2*10136000L; - //const int64_t frqhz = 2*(14074000L); - const int64_t i64frq_in_clk = ((int64_t)clkhz * (int64_t)(1<<24) + (frqhz>>1)) / frqhz; - const int32_t i32frq_in_clk = i64frq_in_clk; - - WorkerCycle(&DCO, i32frq_in_clk, preg32); -*/ + Spinner(); }