diff --git a/CMakeLists.txt b/CMakeLists.txt index 966a8f7..e5e6686 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,6 @@ set(CMAKE_CXX_STANDARD 17) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) -# set(PICO_SDK_PATH "/home/jabba/pico-sdk") - set(PICO_BOARD pico CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) @@ -25,15 +23,16 @@ project(pico-hf-oscillator-test 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(pico-hf-oscillator-test) pico_generate_pio_header(pico-hf-oscillator-test ${CMAKE_CURRENT_LIST_DIR}/piodco/dco.pio) target_sources(pico-hf-oscillator-test PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/lib/assert.c + ${CMAKE_CURRENT_LIST_DIR}/lib/assert.c + ${CMAKE_CURRENT_LIST_DIR}/lib/thirdparty/strnstr.c ${CMAKE_CURRENT_LIST_DIR}/piodco/piodco.c + ${CMAKE_CURRENT_LIST_DIR}/gpstime/GPStime.c + ${CMAKE_CURRENT_LIST_DIR}/debug/logutils.c ${CMAKE_CURRENT_LIST_DIR}/test.c ) @@ -41,14 +40,17 @@ pico_set_program_name(pico-hf-oscillator-test "pico-hf-oscillator-test") pico_set_program_version(pico-hf-oscillator-test "0.9") pico_enable_stdio_uart(pico-hf-oscillator-test 1) -pico_enable_stdio_usb(pico-hf-oscillator-test 0) +pico_enable_stdio_usb(pico-hf-oscillator-test 1) # Add the standard include files to the build target_include_directories(pico-hf-oscillator-test PRIVATE ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required + ${CMAKE_CURRENT_LIST_DIR}/gpstime + ${CMAKE_CURRENT_LIST_DIR}/.. ) +add_compile_options(-Wall) + # Add any user requested libraries target_link_libraries( pico-hf-oscillator-test @@ -58,7 +60,7 @@ target_link_libraries( hardware_timer hardware_clocks hardware_pio - hardware_vreg + #hardware_vreg ) pico_add_extra_outputs(pico-hf-oscillator-test) diff --git a/debug/logutils.c b/debug/logutils.c new file mode 100644 index 0000000..39ac59d --- /dev/null +++ b/debug/logutils.c @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// logutils.h - A set of utilities for logging/debugging. +// +// DESCRIPTION +// - +// +// HOWTOSTART +// - +// +// PLATFORM +// Raspberry Pi pico. +// +// REVISION HISTORY +// - +// +// PROJECT PAGE +// https://github.com/RPiks/pico-WSPR-tx +// +// 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 +#include +#include + +#include "hardware/clocks.h" +#include "pico/stdlib.h" + +void StampPrintf(const char* pformat, ...) +{ + static uint32_t sTick = 0; + if(!sTick) + { + stdio_init_all(); + } + + uint64_t tm_us = to_us_since_boot(get_absolute_time()); + + const uint32_t tm_day = (uint32_t)(tm_us / 86400000000ULL); + tm_us -= (uint64_t)tm_day * 86400000000ULL; + + const uint32_t tm_hour = (uint32_t)(tm_us / 3600000000ULL); + tm_us -= (uint64_t)tm_hour * 3600000000ULL; + + const uint32_t tm_min = (uint32_t)(tm_us / 60000000ULL); + tm_us -= (uint64_t)tm_min * 60000000ULL; + + const uint32_t tm_sec = (uint32_t)(tm_us / 1000000ULL); + tm_us -= (uint64_t)tm_sec * 1000000ULL; + + printf("%02lud%02lu:%02lu:%02lu.%06llu [%04lu] ", tm_day, tm_hour, tm_min, tm_sec, tm_us, sTick++); + + va_list argptr; + va_start(argptr, pformat); + vprintf(pformat, argptr); + va_end(argptr); + + printf("\n"); +} diff --git a/debug/logutils.h b/debug/logutils.h new file mode 100644 index 0000000..0f88db9 --- /dev/null +++ b/debug/logutils.h @@ -0,0 +1,6 @@ +#ifndef LOGUTILS_H_ +#define LOGUTILS_H_ + +void StampPrintf(const char* pformat, ...); + +#endif diff --git a/defines.h b/defines.h index 6b2bf5f..7a9dada 100644 --- a/defines.h +++ b/defines.h @@ -67,12 +67,19 @@ #define RAM_A __not_in_flash("A") /* Place time-critical var in RAM */ /* A macro for arithmetic right shifts, with casting of the argument. */ -#define iSAR(arg, rcount) (((int32_t)(arg)) >> (rcount)) +#define iSAR32(arg, rcount) (((int32_t)(arg)) >> (rcount)) +#define iSAR64(arg, rcount) (((int64_t)(arg)) >> (rcount)) /* A macro of multiplication guarantees of doing so using 1 ASM command. */ -#define iMUL32ASM(a,b) __mul_instruction((int32_t)(a), (int32_t)(b)) +#define iMUL32ASM(a, b) __mul_instruction((int32_t)(a), (int32_t)(b)) /* Performing the square by ASM. */ -#define iSquare32ASM(x) (iMUL32ASM((x), (x))) +#define iSquare32ASM(x) (iMUL32ASM((x), (x))) + +#define ABS(x) ((x) > 0 ? (x) : -(x)) + +#define INVERSE(x) ((x) = -(x)) + +#define asizeof(a) (sizeof (a) / sizeof ((a)[0])) #endif diff --git a/doc/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf b/doc/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf new file mode 100644 index 0000000..edce4d0 Binary files /dev/null and b/doc/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf differ diff --git a/doc/Raspberry-Pi-Pico-Pinout.png b/doc/Raspberry-Pi-Pico-Pinout.png new file mode 100644 index 0000000..9062931 Binary files /dev/null and b/doc/Raspberry-Pi-Pico-Pinout.png differ diff --git a/doc/pico-hf-oscillator.pdf b/doc/pico-hf-oscillator.pdf new file mode 100644 index 0000000..a398faa Binary files /dev/null and b/doc/pico-hf-oscillator.pdf differ diff --git a/doc/pico-hf-oscillator.png b/doc/pico-hf-oscillator.png new file mode 100644 index 0000000..1af48a7 Binary files /dev/null and b/doc/pico-hf-oscillator.png differ diff --git a/doc/u-blox6-GPS-GLONASS-QZSS-V14_ReceiverDescrProtSpec_(GPS.G6-SW-12013)_Public.pdf b/doc/u-blox6-GPS-GLONASS-QZSS-V14_ReceiverDescrProtSpec_(GPS.G6-SW-12013)_Public.pdf new file mode 100644 index 0000000..62b0eec Binary files /dev/null and b/doc/u-blox6-GPS-GLONASS-QZSS-V14_ReceiverDescrProtSpec_(GPS.G6-SW-12013)_Public.pdf differ diff --git a/gpstime/GPStime.c b/gpstime/GPStime.c new file mode 100644 index 0000000..8e452e2 --- /dev/null +++ b/gpstime/GPStime.c @@ -0,0 +1,327 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY, PhD +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// gpstime.c - GPS time reference utilities for digital controlled radio freq +// oscillator based on Raspberry Pi Pico. +// +// DESCRIPTION +// +// GPS time utilities for PioDco oscillator calculates a precise frequency +// shift between the local Pico oscillator and reference oscill. of GPS system. +// The value of the shift is used to correct PioDco generated frequency. The +// practical precision of this solution within tenths millihertz range. +// The value of this accuracy depends on quality of navigation solution of GPS +// receiver. This quality can be estimated by GDOP and TDOP parameters received +// in NMEA-0183 message packet from GPS receiver. +// Owing to the meager PioDco frequency step in millihertz range, we obtain +// a quasi-analog precision frequency source (if the GPS navigation works ok). +// 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 including extremely ones such as QRSS. +// I gracefully appreciate any thoughts or comments on that matter. +// +// PLATFORM +// Raspberry Pi pico. +// +// REVISION HISTORY +// +// Rev 0.1 25 Nov 2023 Initial release +// +// PROJECT PAGE +// https://github.com/RPiks/pico-hf-oscillator +// +// 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 "GPStime.h" + +static GPStimeContext *spGPStimeContext = NULL; +static GPStimeData *spGPStimeData = NULL; + +/// @brief Initializes GPS time module Context. +/// @param uart_id UART id to which GPS receiver is connected, 0 OR 1. +/// @param uart_baud UART baudrate, 115200 max. +/// @param pps_gpio GPIO pin of PPS (second pulse) from GPS receiver. +/// @return the new GPS time Context. +GPStimeContext *GPStimeInit(int uart_id, int uart_baud, int pps_gpio) +{ + ASSERT_(0 == uart_id || 1 == uart_id); + ASSERT_(uart_baud <= 115200); + ASSERT_(pps_gpio < 29); + + // Set up our UART with the required speed & assign pins. + uart_init(uart_id ? uart1 : uart0, uart_baud); + gpio_set_function(uart_id ? 8 : 12, GPIO_FUNC_UART); + gpio_set_function(uart_id ? 9 : 13, GPIO_FUNC_UART); + + GPStimeContext *pgt = calloc(1, sizeof(GPStimeContext)); + ASSERT_(pgt); + + pgt->_uart_id = uart_id; + pgt->_uart_baudrate = uart_baud; + pgt->_pps_gpio = pps_gpio; + + spGPStimeContext = pgt; + spGPStimeData = &pgt->_time_data; + + gpio_init(pps_gpio); + gpio_set_dir(pps_gpio, GPIO_IN); + gpio_set_irq_enabled_with_callback(pps_gpio, GPIO_IRQ_EDGE_RISE, true, &GPStimePPScallback); + + uart_set_hw_flow(uart_id ? uart1 : uart0, false, false); + uart_set_format(uart_id ? uart1 : uart0, 8, 1, UART_PARITY_NONE); + uart_set_fifo_enabled(uart_id ? uart1 : uart0, false); + irq_set_exclusive_handler(uart_id ? UART1_IRQ : UART0_IRQ, GPStimeUartRxIsr); + irq_set_enabled(uart_id ? UART1_IRQ : UART0_IRQ, true); + uart_set_irq_enables(uart_id ? uart1 : uart0, true, false); + + return pgt; +} + +/// @brief Deinits the GPS module and destroys allocated resources. +/// @param pp Ptr to Ptr of the Context. +/// @attention *NOT* implemented completely so far. !FIXME! +void GPStimeDestroy(GPStimeContext **pp) +{ + ASSERT_(pp); + ASSERT_(*pp); + + uart_deinit((*pp)->_uart_id ? uart1 : uart0); + free(*pp); + *pp = NULL; +} + +/// @brief The PPS interrupt service subroutine. +/// @param gpio The GPIO pin of Pico which is connected to PPS output of GPS rec. +void RAM (GPStimePPScallback)(uint gpio, uint32_t events) +{ + const uint64_t tm64 = GetUptime64(); + if(spGPStimeData) + { + spGPStimeData->_u64_sysclk_pps_last = tm64; + ++spGPStimeData->_ix_last; + spGPStimeData->_ix_last %= eSlidingLen; + + const int64_t dt_per_window = tm64 - spGPStimeData->_pu64_sliding_pps_tm[spGPStimeData->_ix_last]; + spGPStimeData->_pu64_sliding_pps_tm[spGPStimeData->_ix_last] = tm64; + + if(ABS(dt_per_window - eCLKperTimeMark * eSlidingLen) < eMaxCLKdevPPM * eSlidingLen) + { + if(spGPStimeData->_u64_pps_period_1M) + { + spGPStimeData->_u64_pps_period_1M += iSAR64((int64_t)eDtUpscale * dt_per_window + - spGPStimeData->_u64_pps_period_1M + 2, 2); + spGPStimeData->_i32_freq_shift_ppb = (spGPStimeData->_u64_pps_period_1M + - (int64_t)eDtUpscale * eCLKperTimeMark * eSlidingLen + + (eSlidingLen >> 1)) / eSlidingLen; + } + else + { + spGPStimeData->_u64_pps_period_1M = (int64_t)eDtUpscale * dt_per_window; + } + } + +#ifdef NOP + const int64_t dt_1M = (dt_per_window + (eSlidingLen >> 1)) / eSlidingLen; + const uint64_t tmp = (spGPStimeData->_u64_pps_period_1M + (eSlidingLen >> 1)) / eSlidingLen; + printf("%llu %lld %llu %lld\n", spGPStimeData->_u64_sysclk_pps_last, dt_1M, tmp, + spGPStimeData->_i32_freq_shift_ppb); +#endif + + } +} + +/// @brief Calculates current unixtime using data available. +/// @param pg Ptr to the context. +/// @param u32_tmdst Ptr to destination unixtime val. +/// @return 0 if OK. +/// @return -1 There was NO historical GPS fixes. +/// @return -2 The fix was expired (24hrs or more time ago). +int GPStimeGetTime(const GPStimeContext *pg, uint32_t *u32_tmdst) +{ + assert_(pg); + assert(u32_tmdst); + + /* If there has been no fix, it's no way to get any time data... */ + if(!pg->_time_data._u32_utime_nmea_last) + { + return -1; + } + + const uint64_t tm64 = GetUptime64(); + const uint64_t dt = tm64 - pg->_time_data._u64_sysclk_nmea_last; + const uint32_t dt_sec = PicoU64timeToSeconds(dt); + + /* If expired. */ + if(dt_sec > 86400) + { + return -2; + } + + *u32_tmdst = pg->_time_data._u32_utime_nmea_last + dt_sec; + + return 0; +} + +/// @brief UART FIFO ISR. Processes another N chars receiver from GPS rec. +void RAM (GPStimeUartRxIsr)() +{ + if(spGPStimeContext) + { + uart_inst_t *puart_id = spGPStimeContext->_uart_id ? uart1 : uart0; + for(;;uart_is_readable(puart_id)) + { + uint8_t chr = uart_getc(puart_id); + spGPStimeContext->_pbytebuff[spGPStimeContext->_u8_ixw++] = chr; + spGPStimeContext->_is_sentence_ready = ('\n' == chr); + break; + } + + if(spGPStimeContext->_is_sentence_ready) + { + spGPStimeContext->_u8_ixw = 0; + spGPStimeContext->_i32_error_count -= GPStimeProcNMEAsentence(spGPStimeContext); + } + } +} + +/// @brief Processes a NMEA sentence GPRMC. +/// @param pg Ptr to Context. +/// @return 0 OK. +/// @return -2 Error: bad lat format. +/// @return -3 Error: bad lon format. +/// @return -4 Error: no final '*' char ere checksum value. +/// @attention Checksum validation is not implemented so far. !FIXME! +int GPStimeProcNMEAsentence(GPStimeContext *pg) +{ + assert_(pg); + + uint8_t *prmc = (uint8_t *)strnstr((char *)pg->_pbytebuff, "$GPRMC,", sizeof(pg->_pbytebuff)); + if(prmc) + { + ++pg->_time_data._u32_nmea_gprmc_count; + + uint64_t tm_fix = GetUptime64(); + uint8_t u8ixcollector[16] = {0}; + uint8_t chksum = 0; + for(uint8_t u8ix = 0, i = 0; u8ix != sizeof(pg->_pbytebuff); ++u8ix) + { + uint8_t *p = pg->_pbytebuff + u8ix; + chksum ^= *p; + if(',' == *p) + { + *p = 0; + u8ixcollector[i++] = u8ix + 1; + if('*' == *p || 12 == i) + { + break; + } + } + } + + pg->_time_data._u8_is_solution_active = 'A' == prmc[u8ixcollector[1]]; + + if(pg->_time_data._u8_is_solution_active) + { + pg->_time_data._i64_lat_100k = (int64_t)(.5f + 1e5 * atof((const char *)prmc + u8ixcollector[2])); + if('N' == prmc[u8ixcollector[3]]) { } + else if('S' == prmc[u8ixcollector[3]]) + { + INVERSE(pg->_time_data._i64_lat_100k); + } + else + { + return -2; + } + + pg->_time_data._i64_lon_100k = (int64_t)(.5f + 1e5 * atof((const char *)prmc + u8ixcollector[4])); + if('E' == prmc[u8ixcollector[5]]) { } + else if('W' == prmc[u8ixcollector[5]]) + { + INVERSE(pg->_time_data._i64_lon_100k); + } + else + { + return -3; + } + + if('*' != prmc[u8ixcollector[11] + 1]) + { + return -4; + } + + pg->_time_data._u32_utime_nmea_last = GPStime2UNIX(prmc + u8ixcollector[8], prmc + u8ixcollector[0]); + pg->_time_data._u64_sysclk_nmea_last = tm_fix; + } + } + + return 0; +} + +/// @brief Converts GPS time and date strings to unix time. +/// @param pdate Date string, 6 chars in work. +/// @param ptime Time string, 6 chars in work. +/// @return Unix timestamp (epoch). 0 if bad imput format. +uint32_t GPStime2UNIX(const char *pdate, const char *ptime) +{ + assert_(pdate); + assert_(ptime); + + if(strlen(pdate) == 6 && strlen(ptime) > 5) + { + struct tm ltm = {0}; + + ltm.tm_year = 100 + DecimalStr2ToNumber(pdate + 4); + ltm.tm_mon = DecimalStr2ToNumber(pdate + 2) - 1; + ltm.tm_mday = DecimalStr2ToNumber(pdate); + + ltm.tm_hour = DecimalStr2ToNumber(ptime); + ltm.tm_min = DecimalStr2ToNumber(ptime + 2); + ltm.tm_sec = DecimalStr2ToNumber(ptime + 4); + + return mktime(<m); + } + + return 0; +} + +/// @brief Dumps the GPS data struct to stdio. +/// @param pd Ptr to Context. +void GPStimeDump(const GPStimeData *pd) +{ + assert_(pd); + + printf("\nGPS solution is active:%u\n", pd->_u8_is_solution_active); + printf("GPRMC count:%lu\n", pd->_u32_nmea_gprmc_count); + printf("NMEA unixtime last:%lu\n", pd->_u32_utime_nmea_last); + printf("NMEA sysclock last:%llu\n", pd->_u64_sysclk_nmea_last); + printf("GPS Latitude:%lld Longtitude:%lld\n", pd->_i64_lat_100k, pd->_i64_lon_100k); + printf("PPS sysclock last:%llu\n", pd->_u64_sysclk_pps_last); + printf("PPS period *1e6:%llu\n", (pd->_u64_pps_period_1M + (eSlidingLen>>1)) / eSlidingLen); + printf("FRQ correction ppb:%lld\n\n", pd->_i32_freq_shift_ppb); +} diff --git a/gpstime/GPStime.h b/gpstime/GPStime.h new file mode 100644 index 0000000..aecb82f --- /dev/null +++ b/gpstime/GPStime.h @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY, PhD +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// gpstime.h - GPS time reference utilities for digital controlled radio freq +// oscillator based on Raspberry Pi Pico. +// +// DESCRIPTION +// +// GPS time utilities for PioDco oscillator calculates a precise frequency +// shift between the local Pico oscillator and reference oscill. of GPS system. +// The value of the shift is used to correct PioDco generated frequency. The +// practical precision of this solution within tenths millihertz range. +// The value of this accuracy depends on quality of navigation solution of GPS +// receiver. This quality can be estimated by GDOP and TDOP parameters received +// in NMEA-0183 message packet from GPS receiver. +// Owing to the meager PioDco frequency step in millihertz range, we obtain +// a quasi-analog precision frequency source (if the GPS navigation works ok). +// 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 including extremely ones such as QRSS. +// I gracefully appreciate any thoughts or comments on that matter. +// +// PLATFORM +// Raspberry Pi pico. +// +// REVISION HISTORY +// +// Rev 0.1 25 Nov 2023 Initial release +// +// PROJECT PAGE +// https://github.com/RPiks/pico-hf-oscillator +// +// 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. +/////////////////////////////////////////////////////////////////////////////// +#ifndef GPSTIME_H_ +#define GPSTIME_H_ + +#include +#include +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/uart.h" +#include "../defines.h" +#include "../lib/assert.h" +#include "../lib/utility.h" +#include "../lib/thirdparty/strnstr.h" + +#define ASSERT_(x) assert_(x) + +enum +{ + eDtUpscale = 1000000, + eSlidingLen = 32, + eCLKperTimeMark = 1000000, + eMaxCLKdevPPM = 250 +}; + +typedef struct +{ + uint8_t _u8_is_solution_active; /* A navigation solution is valid. */ + uint32_t _u32_utime_nmea_last; /* The last unix time received from GPS. */ + uint64_t _u64_sysclk_nmea_last; /* The sysclk of the last unix time received. */ + int64_t _i64_lat_100k, _i64_lon_100k; /* The lat, lon, degrees, multiplied by 1e5. */ + uint32_t _u32_nmea_gprmc_count; /* The count of $GPRMC sentences received */ + + uint64_t _u64_sysclk_pps_last; /* The sysclk of the last rising edge of PPS. */ + uint64_t _u64_pps_period_1M; /* The PPS avg. period *1e6, filtered. */ + + uint64_t _pu64_sliding_pps_tm[eSlidingLen]; /* A sliding window to store PPS periods. */ + uint8_t _ix_last; /* An index of last write to sliding window. */ + + int64_t _i32_freq_shift_ppb; /* Calcd frequency shift, parts per billion. */ + +} GPStimeData; + +typedef struct +{ + int _uart_id; + int _uart_baudrate; + int _pps_gpio; + + GPStimeData _time_data; + + uint8_t _pbytebuff[256]; + uint8_t _u8_ixw; + uint8_t _is_sentence_ready; + int32_t _i32_error_count; + +} GPStimeContext; + +GPStimeContext *GPStimeInit(int uart_id, int uart_baud, int pps_gpio); +void GPStimeDestroy(GPStimeContext **pp); + +int GPStimeProcNMEAsentence(GPStimeContext *pg); + +void RAM (GPStimePPScallback)(uint gpio, uint32_t events); +void RAM (GPStimeUartRxIsr)(); + +int GPStimeGetTime(const GPStimeContext *pg, uint32_t *u32_tmdst); +uint32_t GPStime2UNIX(const char *pdate, const char *ptime); + +void GPStimeDump(const GPStimeData *pd); + +#endif diff --git a/lib/thirdparty/strnstr.c b/lib/thirdparty/strnstr.c new file mode 100644 index 0000000..ca12ba1 --- /dev/null +++ b/lib/thirdparty/strnstr.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 2001 Mike Barcroft + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)strstr.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ +#include +__FBSDID("$FreeBSD: src/lib/libc/string/strnstr.c,v 1.5 2009/02/03 17:58:20 danger Exp $"); + +#include + +/* + * Find the first occurrence of find in s, where the search is limited to the + * first slen characters of s. + */ +char * +strnstr(const char *s, const char *find, size_t slen) +{ + char c, sc; + size_t len; + + if ((c = *find++) != '\0') { + len = strlen(find); + do { + do { + if (slen-- < 1 || (sc = *s++) == '\0') + return (NULL); + } while (sc != c); + if (len > slen) + return (NULL); + } while (strncmp(s, find, len) != 0); + s--; + } + return ((char *)s); +} diff --git a/lib/thirdparty/strnstr.h b/lib/thirdparty/strnstr.h new file mode 100644 index 0000000..059e691 --- /dev/null +++ b/lib/thirdparty/strnstr.h @@ -0,0 +1,7 @@ +#ifndef STRNSTR_H_ +#define STRNSTR_H_ + +char * +strnstr(const char *s, const char *find, size_t slen); + +#endif diff --git a/lib/utility.h b/lib/utility.h new file mode 100644 index 0000000..b5edcb4 --- /dev/null +++ b/lib/utility.h @@ -0,0 +1,37 @@ +#ifndef UTILITY_H_ +#define UTILITY_H_ + +#include +#include "pico/stdlib.h" + +inline uint64_t GetUptime64(void) +{ + const uint32_t lo = timer_hw->timelr; + const uint32_t hi = timer_hw->timehr; + + return ((uint64_t)hi << 32U) | lo; +} + +inline uint32_t GetTime32(void) +{ + return timer_hw->timelr; +} + +inline uint32_t PicoU64timeToSeconds(uint64_t u64tm) +{ + return u64tm / 1000000U; // No rounding deliberately! +} + +inline uint32_t DecimalStr2ToNumber(const char *p) +{ + return 10U * (p[0] - '0') + (p[1] - '0'); +} + +inline void PRN32(uint32_t *val) +{ + *val ^= *val << 13; + *val ^= *val >> 17; + *val ^= *val << 5; +} + +#endif diff --git a/piodco/piodco.c b/piodco/piodco.c index 57f22d4..fc2170e 100644 --- a/piodco/piodco.c +++ b/piodco/piodco.c @@ -69,7 +69,7 @@ #include "build/dco.pio.h" -int32_t si32precise_cycles; +int32_t si32precise_cycles; /* External in order to support ISR. */ /// @brief Initializes DCO context and prepares PIO hardware. /// @param pdco Ptr to DCO context. @@ -95,18 +95,17 @@ int PioDCOInit(PioDco *pdco, int gpio, int cpuclkhz) sm_config_set_set_pins(&pdco->_pio_sm, pdco->_gpio, 1); pio_gpio_init(pdco->_pio, pdco->_gpio); pio_sm_init(pdco->_pio, pdco->_ism, pdco->_offset, &pdco->_pio_sm); - //pio_sm_set_enabled(pdco->_pio, pdco->_ism, true); return 0; } /// @brief Sets DCO working frequency in Hz: Fout = ui32_frq_hz + ui32_frq_millihz * 1e-3. /// @param pdco Ptr to DCO context. -/// @param ui32_frq_hz The `coarse` part of frequency [Hz]. +/// @param i32_frq_hz The `coarse` part of frequency [Hz]. Might be negative. /// @param ui32_frq_millihz The `fine` part of frequency [Hz]. /// @return 0 if OK. -1 invalid freq. /// @attention The func can be called while DCO running. -int PioDCOSetFreq(PioDco *pdco, uint32_t ui32_frq_hz, uint32_t ui32_frq_millihz) +int PioDCOSetFreq(PioDco *pdco, uint32_t ui32_frq_hz, int32_t ui32_frq_millihz) { assert_(pdco); assert(pdco->_clkfreq_hz); @@ -117,14 +116,43 @@ int PioDCOSetFreq(PioDco *pdco, uint32_t ui32_frq_hz, uint32_t ui32_frq_millihz) pdco->_frq_cycles_per_pi = (int32_t)(((int64_t)pdco->_clkfreq_hz * (int64_t)(1<<24) * 1000LL +(i64denominator>>1)) / i64denominator); - //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; } +/// @brief Obtains the frequency shift [milliHz] which is calculated for a given frequency. +/// @param pdco Ptr to Context. +/// @param u64_desired_frq_millihz The frequency for which we want to calculate correction. +/// @return The value of correction we need to subtract from desired freq. to compensate +/// @return Pico's reference clock shift. 2854974. +int32_t PioDCOGetFreqShiftMilliHertz(const PioDco *pdco, uint64_t u64_desired_frq_millihz) +{ + assert_(pdco); + if(!pdco->_pGPStime) + { + return 0U; + } + + static int64_t i64_last_correction = 0; + const int64_t dt = pdco->_pGPStime->_time_data._i32_freq_shift_ppb; /* Parts per billion. */ + if(dt) + { + i64_last_correction = dt; + } + + int32_t i32ret_millis; + if(i64_last_correction) + { + int64_t i64corr_coeff = (u64_desired_frq_millihz + 500000LL) / 1000000LL; + i32ret_millis = (i64_last_correction * i64corr_coeff + 50000LL) / 1000000LL; + + return i32ret_millis; + } + + return 0U; +} + /// @brief Starts the DCO. /// @param pdco Ptr to DCO context. void PioDCOStart(PioDco *pdco) @@ -151,12 +179,10 @@ void RAM (PioDCOWorker)(PioDco *pDCO) register PIO pio = pDCO->_pio; register uint sm = pDCO->_ism; - register int32_t i32acc_error = 0; - register uint32_t *preg32 = pDCO->_ui32_pioreg; register uint8_t *pu8reg = (uint8_t *)preg32; - //si32precise_cycles = pDCO->_frq_cycles_per_pi; + for(;;) { const register int32_t i32reg = si32precise_cycles; @@ -167,12 +193,11 @@ 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(i32reg - i32acc_error + (1<<23), 24); + const int32_t i32wc = iSAR32(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. */ + /* RPix: Calculate the difference betwixt calculated value scaled to + fine resolution back and precise value of DCO cycles per CPU CLK cycle. + This forms a phase locked loop which provides precise freq */ i32acc_error += (i32wc<<24) - i32reg; /* RPix: Set PIO array contents corrected by pio program delay @@ -183,3 +208,27 @@ void RAM (PioDCOWorker)(PioDco *pDCO) dco_program_puts(pio, sm, preg32); } } + +/// @brief Sets DCO running mode. +/// @param pdco Ptr to DCO context. +/// @param emode Desired mode. +/// @attention Not actual so far. User-independent freq. correction not impl'd yet. !FIXME! +void PioDCOSetMode(PioDco *pdco, enum PioDcoMode emode) +{ + assert_(pdco); + pdco->_mode = emode; + + switch(emode) + { + case eDCOMODE_IDLE: + PioDCOStop(pdco); + break; + + case eDCOMODE_GPS_COMPENSATED: + PioDCOStart(pdco); + break; + + default: + break; + } +} diff --git a/piodco/piodco.h b/piodco/piodco.h index 550520e..27c94c6 100644 --- a/piodco/piodco.h +++ b/piodco/piodco.h @@ -72,8 +72,18 @@ #include "defines.h" +#include "../gpstime/GPStime.h" + +enum PioDcoMode +{ + eDCOMODE_IDLE = 0, /* No output. */ + eDCOMODE_GPS_COMPENSATED= 2 /* Internally compensated, if GPS available. */ +}; + typedef struct { + enum PioDcoMode _mode; /* Running mode. */ + PIO _pio; /* Worker PIO on this DCO. */ int _gpio; /* Pico' GPIO for DCO output. */ @@ -87,14 +97,19 @@ typedef struct uint32_t _clkfreq_hz; /* CPU CLK freq, Hz. */ + GPStimeContext *_pGPStime; /* Ptr to GPS time context. */ + } PioDco; int PioDCOInit(PioDco *pdco, int gpio, int cpuclkhz); -int PioDCOSetFreq(PioDco *pdco, uint32_t ui32_frq_hz, uint32_t ui32_frq_millihz); +int PioDCOSetFreq(PioDco *pdco, uint32_t u32_frq_hz, int32_t u32_frq_millihz); +int32_t PioDCOGetFreqShiftMilliHertz(const PioDco *pdco, uint64_t u64_desired_frq_millihz); void PioDCOStart(PioDco *pdco); void PioDCOStop(PioDco *pdco); +void PioDCOSetMode(PioDco *pdco, enum PioDcoMode emode); + void RAM (PioDCOWorker)(PioDco *pDCO); #endif diff --git a/test.c b/test.c index da7e485..e79bd71 100644 --- a/test.c +++ b/test.c @@ -68,6 +68,7 @@ /////////////////////////////////////////////////////////////////////////////// #include #include +#include #include "defines.h" @@ -75,14 +76,17 @@ #include "build/dco.pio.h" #include "hardware/vreg.h" #include "pico/multicore.h" +#include "pico/stdio/driver.h" #include "./lib/assert.h" +#include "./debug/logutils.h" #include "hwdefs.h" +#include + #define GEN_FRQ_HZ 9400000L -PioDco DCO; -void PRN32(uint32_t *val); +PioDco DCO; /* External in order to access in both cores. */ /* This is the code of dedicated core. We deal with extremely precise real-time task. */ @@ -206,26 +210,66 @@ void RAM (SpinnerWide4FSKTest)(void) } } +/* This example sets the OUT frequency to 5.555 MHz. + Next every ~1 sec the shift of the OUT frequency relative to GPS + reference is calculated and the OUT frequency is corrected. + The example is working only when GPS receiver provides an + accurate PPS output (pulse per second). If no such option, + the correction parameter is zero. +*/ +void RAM (SpinnerGPSreferenceTest)(void) +{ + const uint32_t ku32_freq = 5555000UL; + const int kigps_pps_pin = 2; + + int32_t i32_compensation_millis = 0; + + GPStimeContext *pGPS = GPStimeInit(0, 9600, kigps_pps_pin); + assert_(pGPS); + DCO._pGPStime = pGPS; + int tick = 0; + for(;;) + { + PioDCOSetFreq(&DCO, ku32_freq, -2*i32_compensation_millis); + + /* LED signal */ + gpio_put(PICO_DEFAULT_LED_PIN, 1); + sleep_ms(2500); + + i32_compensation_millis = + PioDCOGetFreqShiftMilliHertz(&DCO, (uint64_t)(ku32_freq * 1000LL)); + + gpio_put(PICO_DEFAULT_LED_PIN, 0); + sleep_ms(2500); + + if(0 == ++tick % 6) + { + stdio_set_driver_enabled(&stdio_uart, false); + GPStimeDump(&(pGPS->_time_data)); + stdio_set_driver_enabled(&stdio_uart, true); + } + } +} + int main() { const uint32_t clkhz = PLL_SYS_MHZ * 1000000L; set_sys_clock_khz(clkhz / 1000L, true); + stdio_init_all(); + sleep_ms(1000); + printf("Start\n"); + gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); multicore_launch_core1(core1_entry); //SpinnerSweepTest(); - SpinnerMFSKTest(); + //SpinnerMFSKTest(); //SpinnerRTTYTest(); //SpinnerMilliHertzTest(); //SpinnerWide4FSKTest(); + SpinnerGPSreferenceTest(); } -void PRN32(uint32_t *val) -{ - *val ^= *val << 13; - *val ^= *val >> 17; - *val ^= *val << 5; -}