From 6c5ad458ce8a8ade6728510ab5a7faba3832b334 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 16 Nov 2023 02:52:44 +0300 Subject: [PATCH] WIP: It creates an example WSPR packet using 3rd-party libs. --- BitEmitter/bitemitter.c | 53 -- CMakeLists.txt | 13 +- TxChannel/TxChannel.c | 97 ++++ .../bitemitter.h => TxChannel/TxChannel.h | 32 +- WSPRbeacon/WSPRbeacon.c | 32 ++ WSPRbeacon/WSPRbeacon.h | 29 + WSPRbeacon/thirdparty/WSPRutility.c | 517 ++++++++++++++++++ WSPRbeacon/thirdparty/WSPRutility.h | 45 ++ WSPRbeacon/thirdparty/nhash.c | 384 +++++++++++++ WSPRbeacon/thirdparty/nhash.h | 14 + debug/logutils.c | 85 +++ debug/logutils.h | 6 + defines.h | 12 + docs/wspr_coding_process.pdf | Bin 0 -> 46431 bytes main.c | 55 +- pico-hf-oscillator | 2 +- 16 files changed, 1302 insertions(+), 74 deletions(-) delete mode 100644 BitEmitter/bitemitter.c create mode 100644 TxChannel/TxChannel.c rename BitEmitter/bitemitter.h => TxChannel/TxChannel.h (75%) create mode 100644 WSPRbeacon/WSPRbeacon.c create mode 100644 WSPRbeacon/WSPRbeacon.h create mode 100644 WSPRbeacon/thirdparty/WSPRutility.c create mode 100644 WSPRbeacon/thirdparty/WSPRutility.h create mode 100644 WSPRbeacon/thirdparty/nhash.c create mode 100644 WSPRbeacon/thirdparty/nhash.h create mode 100644 debug/logutils.c create mode 100644 debug/logutils.h create mode 100644 docs/wspr_coding_process.pdf diff --git a/BitEmitter/bitemitter.c b/BitEmitter/bitemitter.c deleted file mode 100644 index 41feace..0000000 --- a/BitEmitter/bitemitter.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "bitemitter.h" - -BitEmitterContext *pBEC = NULL; - -void __not_in_flash_func (BitEmitterISR)(void) -{ - if(!pBEC) - { - pBEC->_tm_future_call += 500LL; - goto EXIT; - } - - pBEC->_pf_modulator(pBEC->_bits_per_sample, - (pBEC->_u8byte_buffer[pBEC->_ix_output] >> pBEC->_ixbit_output) - & ((1 << pBEC->_bits_per_sample) - 1)); - - pBEC->_ixbit_output += pBEC->_bits_per_sample; - if(8 == pBEC->_ixbit_output) - { - ++pBEC->_ix_output; - } - - pBEC->_tm_future_call += pBEC->_bit_period_us; - -EXIT: - hw_clear_bits(&timer_hw->intr, 1U<_timer_alarm_num); - timer_hw->alarm[pBEC->_timer_alarm_num] = (uint32_t)pBEC->_tm_future_call; -} - -BitEmitterContext *BitEmitterInit(const uint32_t bit_period_us, uint8_t timer_alarm_num, - uint8_t bits_per_sample, void *pfmodulator) -{ - assert_(pfmodulator); - assert_(bit_period_us > 10); - - BitEmitterContext *p = calloc(1, sizeof(BitEmitterContext)); - assert_(p); - - p->_bit_period_us = bit_period_us; - p->_timer_alarm_num = timer_alarm_num; - p->_bits_per_sample = bits_per_sample; - p->_pf_modulator = pfmodulator; - - hw_set_bits(&timer_hw->inte, 1U << p->_timer_alarm_num); - irq_set_exclusive_handler(TIMER_IRQ_0, BitEmitterISR); - irq_set_priority(TIMER_IRQ_0, 0x00); - irq_set_enabled(TIMER_IRQ_0, true); - - p->_tm_future_call = timer_hw->timerawl + 500LL; - timer_hw->alarm[p->_timer_alarm_num] = (uint32_t)p->_tm_future_call; - - return p; -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 077a2ae..11b7072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,20 +31,27 @@ add_executable(pico-wspr-tx) target_sources(pico-wspr-tx PUBLIC ${CMAKE_CURRENT_LIST_DIR}/pico-hf-oscillator/lib/assert.c - ${CMAKE_CURRENT_LIST_DIR}/BitEmitter/bitemitter.c + ${CMAKE_CURRENT_LIST_DIR}/TxChannel/TxChannel.c + ${CMAKE_CURRENT_LIST_DIR}/WSPRbeacon/thirdparty/WSPRutility.c + ${CMAKE_CURRENT_LIST_DIR}/WSPRbeacon/thirdparty/nhash.c + ${CMAKE_CURRENT_LIST_DIR}/WSPRbeacon/WSPRbeacon.c + ${CMAKE_CURRENT_LIST_DIR}/debug/logutils.c ${CMAKE_CURRENT_LIST_DIR}/main.c ) pico_set_program_name(pico-wspr-tx "pico-wspr-tx") pico_set_program_version(pico-wspr-tx "0.1") -pico_enable_stdio_uart(pico-wspr-tx 1) -pico_enable_stdio_usb(pico-wspr-tx 0) +pico_enable_stdio_uart(pico-wspr-tx 0) +pico_enable_stdio_usb(pico-wspr-tx 1) # Add the standard include files to the build target_include_directories(pico-wspr-tx PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/pico-hf-oscillator/piodco + ${CMAKE_CURRENT_LIST_DIR}/TxChannel + ${CMAKE_CURRENT_LIST_DIR}/WSPRbeacon + ${CMAKE_CURRENT_LIST_DIR}/WSPRbeacon/thirdparty ${CMAKE_CURRENT_LIST_DIR}/.. ) diff --git a/TxChannel/TxChannel.c b/TxChannel/TxChannel.c new file mode 100644 index 0000000..ca5ed08 --- /dev/null +++ b/TxChannel/TxChannel.c @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// TxChannel.c - Produces a time-accurate `bit` stream. +// Invokes a `modulator` function. +// DESCRIPTION +// Receives data asynchronously. Calls low level modulator function +// synchronously according to params. +// +// 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 "TxChannel.h" + +TxChannelContext *pTX = NULL; + +void __not_in_flash_func (TxChannelISR)(void) +{ + if(!pTX) + { + pTX->_tm_future_call += 500LL; + goto EXIT; + } + + pTX->_pf_modulator(FREQ_STEP_MILLIHERTZ, pTX->_u8byte_buffer[pTX->_ix_output++]); + pTX->_tm_future_call += pTX->_bit_period_us; + +EXIT: + hw_clear_bits(&timer_hw->intr, 1U<_timer_alarm_num); + timer_hw->alarm[pTX->_timer_alarm_num] = (uint32_t)pTX->_tm_future_call; +} + +/// @brief Initializes a TxChannel context. Starts ISR. +/// @param bit_period_us Period of data bits, BPS speed = 1e6/bit_period_us. +/// @param timer_alarm_num Pico-specific hardware timer resource id. +/// @param pfmodulator Ptr to low level real-time modulator function. +/// @return the Context. +TxChannelContext *TxChannelInit(const uint32_t bit_period_us, uint8_t timer_alarm_num, + void *pfmodulator) +{ + assert_(pfmodulator); + assert_(bit_period_us > 10); + + TxChannelContext *p = calloc(1, sizeof(TxChannelContext)); + assert_(p); + + p->_bit_period_us = bit_period_us; + p->_timer_alarm_num = timer_alarm_num; + p->_pf_modulator = pfmodulator; + + hw_set_bits(&timer_hw->inte, 1U << p->_timer_alarm_num); + irq_set_exclusive_handler(TIMER_IRQ_0, TxChannelISR); + irq_set_priority(TIMER_IRQ_0, 0x00); + irq_set_enabled(TIMER_IRQ_0, true); + + p->_tm_future_call = timer_hw->timerawl + 500LL; + timer_hw->alarm[p->_timer_alarm_num] = (uint32_t)p->_tm_future_call; + + return p; +} diff --git a/BitEmitter/bitemitter.h b/TxChannel/TxChannel.h similarity index 75% rename from BitEmitter/bitemitter.h rename to TxChannel/TxChannel.h index e097982..ec0d3cd 100644 --- a/BitEmitter/bitemitter.h +++ b/TxChannel/TxChannel.h @@ -6,22 +6,23 @@ /////////////////////////////////////////////////////////////////////////////// // // -// BitEmitter.h - Produces a time-accurate bit stream. -// +// TxChannel.h - Produces a time-accurate stream. +// Invokes a `modulator` function. // DESCRIPTION -// Receives data asynchronously. Calls low level bit tx funcs synchronously -// in time according to params. +// Receives data asynchronously. Calls low level modulator function +// synchronously according to params. // // HOWTOSTART -// . +// - // // PLATFORM // Raspberry Pi pico. // // REVISION HISTORY -// -// Rev 0.1 18 Nov 2023 -// Initial release. +// - +// +// PROJECT PAGE +// https://github.com/RPiks/pico-WSPR-tx // // LICENCE // MIT License (http://www.opensource.org/licenses/mit-license.php) @@ -55,24 +56,25 @@ #include "pico/stdlib.h" #include "../pico-hf-oscillator/lib/assert.h" +#define FREQ_STEP_MILLIHERTZ 1465 + typedef struct { uint64_t _tm_future_call; uint32_t _bit_period_us; - uint8_t _bits_per_sample; uint8_t _timer_alarm_num; uint8_t _u8byte_buffer[256]; uint8_t _ix_input, _ix_output; - uint8_t _ixbit_output; - int (*_pf_modulator)(uint8_t bits_per_sample, uint8_t sample_val); + int (*_pf_modulator)(uint32_t frq_step, uint8_t shift_val); + int (*_pf_setPTT)(uint8_t bptt_state); -} BitEmitterContext; +} TxChannelContext; -BitEmitterContext *BitEmitterInit(const uint32_t bit_period_us, uint8_t timer_alarm_num, - uint8_t bits_per_sample, void *pfmodulator); -void __not_in_flash_func (BitEmitterISR)(void); +TxChannelContext *TxChannelInit(const uint32_t bit_period_us, uint8_t timer_alarm_num, + void *pfmodulator); +void __not_in_flash_func (TxChannelISR)(void); #endif diff --git a/WSPRbeacon/WSPRbeacon.c b/WSPRbeacon/WSPRbeacon.c new file mode 100644 index 0000000..34b83d4 --- /dev/null +++ b/WSPRbeacon/WSPRbeacon.c @@ -0,0 +1,32 @@ +#include "WSPRbeacon.h" + +#include + +WSPRbeaconContext *WSPRbeaconInit(const char *pcallsign, const char *pgridsquare, int txpow_dbm, + void *pfsk4modulator) +{ + WSPRbeaconContext *p = calloc(1, sizeof(WSPRbeaconContext)); + assert_(p); + + strncpy(p->_pu8_callsign, pcallsign, sizeof(p->_pu8_callsign)); + strncpy(p->_pu8_locator, pgridsquare, sizeof(p->_pu8_locator)); + p->_u8_txpower = txpow_dbm; + + p->_pTX = TxChannelInit(682667, 0, pfsk4modulator); + assert_(p->_pTX); + + return p; +} + +void WSPRbeaconSetDialFreq(WSPRbeaconContext *pctx, uint32_t freq_hz) +{ + assert_(pctx); + pctx->_u32_dialfreqhz = freq_hz; +} + +int WSPRbeaconCreatePacket(WSPRbeaconContext *pctx) +{ + assert_(pctx); + + wspr_encode(pctx->_pu8_callsign, pctx->_pu8_locator, pctx->_u8_txpower, pctx->_pu8_outbuf); +} diff --git a/WSPRbeacon/WSPRbeacon.h b/WSPRbeacon/WSPRbeacon.h new file mode 100644 index 0000000..004a62c --- /dev/null +++ b/WSPRbeacon/WSPRbeacon.h @@ -0,0 +1,29 @@ +#ifndef WSPRBEACON_H_ +#define WSPRBEACON_H_ + +#include +#include +#include + +typedef struct +{ + uint8_t _pu8_callsign[12]; + uint8_t _pu8_locator[7]; + uint8_t _u8_txpower; + + uint8_t _pu8_outbuf[256]; + + TxChannelContext *_pTX; + uint32_t _u32_dialfreqhz; + +} WSPRbeaconContext; + +WSPRbeaconContext *WSPRbeaconInit(const char *pcallsign, const char *pgridsquare, int txpow_dbm, + void *pfsk4modulator); +void WSPRbeaconSetDialFreq(WSPRbeaconContext *pctx, uint32_t freq_hz); +int WSPRbeaconCreatePacket(WSPRbeaconContext *pctx); +int WSPRbeaconSendPacket(const WSPRbeaconContext *pctx); + +//int WSPRbeaconFSK4mod(uint8_t bits_per_sample, uint8_t sample_val); + +#endif diff --git a/WSPRbeacon/thirdparty/WSPRutility.c b/WSPRbeacon/thirdparty/WSPRutility.c new file mode 100644 index 0000000..c72c22f --- /dev/null +++ b/WSPRbeacon/thirdparty/WSPRutility.c @@ -0,0 +1,517 @@ +/* + * JTEncode.cpp - JT65/JT9/WSPR/FSQ encoder library for Arduino + * + * Copyright (C) 2015-2021 Jason Milldrum + * + * Based on the algorithms presented in the WSJT software suite. + * Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding + * process that helped me to understand all of this. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "WSPRutility.h" + +static char callsign[12]; +static char locator[7]; +static int8_t power; + +/* + * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) + * + * Takes a callsign, grid locator, and power level and returns a WSPR symbol + * table for a Type 1, 2, or 3 message. + * + * call - Callsign (12 characters maximum). + * loc - Maidenhead grid locator (6 characters maximum). + * dbm - Output power in dBm. + * symbols - Array of channel symbols to transmit returned by the method. + * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. + * + */ +void wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols) +{ + char call_[13]; + char loc_[7]; + uint8_t dbm_ = dbm; + strcpy(call_, call); + strcpy(loc_, loc); + + // Ensure that the message text conforms to standards + // -------------------------------------------------- + wspr_message_prep(call_, loc_, dbm_); + + // Bit packing + // ----------- + uint8_t c[11]; + wspr_bit_packing(c); + + // Convolutional Encoding + // --------------------- + uint8_t s[WSPR_SYMBOL_COUNT]; + convolve(c, s, 11, WSPR_BIT_COUNT); + + // Interleaving + // ------------ + wspr_interleave(s); + + // Merge with sync vector + // ---------------------- + wspr_merge_sync_vector(s, symbols); +} + +void wspr_message_prep(char * call, char * loc, int8_t dbm) +{ + // Callsign validation and padding + // ------------------------------- + + // Ensure that the only allowed characters are digits, uppercase letters, slash, and angle brackets + uint8_t i; + for(i = 0; i < 12; i++) + { + if(call[i] != '/' && call[i] != '<' && call[i] != '>') + { + call[i] = toupper(call[i]); + if(!(isdigit(call[i]) || isupper(call[i]))) + { + call[i] = ' '; + } + } + } + call[12] = 0; + + strncpy(callsign, call, 12); + + // Grid locator validation + if(strlen(loc) == 4 || strlen(loc) == 6) + { + for(i = 0; i <= 1; i++) + { + loc[i] = toupper(loc[i]); + if((loc[i] < 'A' || loc[i] > 'R')) + { + strncpy(loc, "AA00AA", 7); + } + } + for(i = 2; i <= 3; i++) + { + if(!(isdigit(loc[i]))) + { + strncpy(loc, "AA00AA", 7); + } + } + } + else + { + strncpy(loc, "AA00AA", 7); + } + + if(strlen(loc) == 6) + { + for(i = 4; i <= 5; i++) + { + loc[i] = toupper(loc[i]); + if((loc[i] < 'A' || loc[i] > 'X')) + { + strncpy(loc, "AA00AA", 7); + } + } + } + + strncpy(locator, loc, 7); + + // Power level validation + // Only certain increments are allowed + if(dbm > 60) + { + dbm = 60; + } + //const uint8_t VALID_DBM_SIZE = 28; + const int8_t valid_dbm[VALID_DBM_SIZE] = + {-30, -27, -23, -20, -17, -13, -10, -7, -3, + 0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40, + 43, 47, 50, 53, 57, 60}; + for(i = 0; i < VALID_DBM_SIZE; i++) + { + if(dbm == valid_dbm[i]) + { + power = dbm; + } + } + // If we got this far, we have an invalid power level, so we'll round down + for(i = 1; i < VALID_DBM_SIZE; i++) + { + if(dbm < valid_dbm[i] && dbm >= valid_dbm[i - 1]) + { + power = valid_dbm[i - 1]; + } + } +} + +void wspr_bit_packing(uint8_t * c) +{ + uint32_t n, m; + + // Determine if type 1, 2 or 3 message + char* slash_avail = strchr(callsign, (int)'/'); + if(callsign[0] == '<') + { + // Type 3 message + char base_call[13]; + memset(base_call, 0, 13); + uint32_t init_val = 146; + char* bracket_avail = strchr(callsign, (int)'>'); + int call_len = bracket_avail - callsign - 1; + strncpy(base_call, callsign + 1, call_len); + uint32_t hash = nhash_(base_call, &call_len, &init_val); + hash &= 32767; + + // Convert 6 char grid square to "callsign" format for transmission + // by putting the first character at the end + char temp_loc = locator[0]; + locator[0] = locator[1]; + locator[1] = locator[2]; + locator[2] = locator[3]; + locator[3] = locator[4]; + locator[4] = locator[5]; + locator[5] = temp_loc; + + n = wspr_code(locator[0]); + n = n * 36 + wspr_code(locator[1]); + n = n * 10 + wspr_code(locator[2]); + n = n * 27 + (wspr_code(locator[3]) - 10); + n = n * 27 + (wspr_code(locator[4]) - 10); + n = n * 27 + (wspr_code(locator[5]) - 10); + + m = (hash * 128) - (power + 1) + 64; + } + else if(slash_avail == (void *)0) + { + // Type 1 message + pad_callsign(callsign); + n = wspr_code(callsign[0]); + n = n * 36 + wspr_code(callsign[1]); + n = n * 10 + wspr_code(callsign[2]); + n = n * 27 + (wspr_code(callsign[3]) - 10); + n = n * 27 + (wspr_code(callsign[4]) - 10); + n = n * 27 + (wspr_code(callsign[5]) - 10); + + m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) + + (10 * (locator[1] - 'A')) + (locator[3] - '0'); + m = (m * 128) + power + 64; + } + else if(slash_avail) + { + // Type 2 message + int slash_pos = slash_avail - callsign; + uint8_t i; + + // Determine prefix or suffix + if(callsign[slash_pos + 2] == ' ' || callsign[slash_pos + 2] == 0) + { + // Single character suffix + char base_call[7]; + memset(base_call, 0, 7); + strncpy(base_call, callsign, slash_pos); + for(i = 0; i < 7; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); + + char x = callsign[slash_pos + 1]; + if(x >= 48 && x <= 57) + { + x -= 48; + } + else if(x >= 65 && x <= 90) + { + x -= 55; + } + else + { + x = 38; + } + + m = 60000 - 32768 + x; + + m = (m * 128) + power + 2 + 64; + } + else if(callsign[slash_pos + 3] == ' ' || callsign[slash_pos + 3] == 0) + { + // Two-digit numerical suffix + char base_call[7]; + memset(base_call, 0, 7); + strncpy(base_call, callsign, slash_pos); + for(i = 0; i < 6; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); + + // TODO: needs validation of digit + m = 10 * (callsign[slash_pos + 1] - 48) + callsign[slash_pos + 2] - 48; + m = 60000 + 26 + m; + m = (m * 128) + power + 2 + 64; + } + else + { + // Prefix + char prefix[4]; + char base_call[7]; + memset(prefix, 0, 4); + memset(base_call, 0, 7); + strncpy(prefix, callsign, slash_pos); + strncpy(base_call, callsign + slash_pos + 1, 7); + + if(prefix[2] == ' ' || prefix[2] == 0) + { + // Right align prefix + prefix[3] = 0; + prefix[2] = prefix[1]; + prefix[1] = prefix[0]; + prefix[0] = ' '; + } + + for(uint8_t i = 0; i < 6; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); + + m = 0; + for(uint8_t i = 0; i < 3; ++i) + { + m = 37 * m + wspr_code(prefix[i]); + } + + if(m >= 32768) + { + m -= 32768; + m = (m * 128) + power + 2 + 64; + } + else + { + m = (m * 128) + power + 1 + 64; + } + } + } + + // Callsign is 28 bits, locator/power is 22 bits. + // A little less work to start with the least-significant bits + c[3] = (uint8_t)((n & 0x0f) << 4); + n = n >> 4; + c[2] = (uint8_t)(n & 0xff); + n = n >> 8; + c[1] = (uint8_t)(n & 0xff); + n = n >> 8; + c[0] = (uint8_t)(n & 0xff); + + c[6] = (uint8_t)((m & 0x03) << 6); + m = m >> 2; + c[5] = (uint8_t)(m & 0xff); + m = m >> 8; + c[4] = (uint8_t)(m & 0xff); + m = m >> 8; + c[3] |= (uint8_t)(m & 0x0f); + c[7] = 0; + c[8] = 0; + c[9] = 0; + c[10] = 0; +} + +void convolve(uint8_t * c, uint8_t * s, uint8_t message_size, uint8_t bit_size) +{ + uint32_t reg_0 = 0; + uint32_t reg_1 = 0; + uint32_t reg_temp = 0; + uint8_t input_bit, parity_bit; + uint8_t bit_count = 0; + uint8_t i, j, k; + + for(i = 0; i < message_size; i++) + { + for(j = 0; j < 8; j++) + { + // Set input bit according the MSB of current element + input_bit = (((c[i] << j) & 0x80) == 0x80) ? 1 : 0; + + // Shift both registers and put in the new input bit + reg_0 = reg_0 << 1; + reg_1 = reg_1 << 1; + reg_0 |= (uint32_t)input_bit; + reg_1 |= (uint32_t)input_bit; + + // AND Register 0 with feedback taps, calculate parity + reg_temp = reg_0 & 0xf2d05351; + parity_bit = 0; + for(k = 0; k < 32; k++) + { + parity_bit = parity_bit ^ (reg_temp & 0x01); + reg_temp = reg_temp >> 1; + } + s[bit_count] = parity_bit; + bit_count++; + + // AND Register 1 with feedback taps, calculate parity + reg_temp = reg_1 & 0xe4613c47; + parity_bit = 0; + for(k = 0; k < 32; k++) + { + parity_bit = parity_bit ^ (reg_temp & 0x01); + reg_temp = reg_temp >> 1; + } + s[bit_count] = parity_bit; + bit_count++; + if(bit_count >= bit_size) + { + break; + } + } + } +} + +void wspr_interleave(uint8_t * s) +{ + uint8_t d[WSPR_BIT_COUNT]; + uint8_t rev, index_temp, i, j, k; + + i = 0; + + for(j = 0; j < 255; j++) + { + // Bit reverse the index + index_temp = j; + rev = 0; + + for(k = 0; k < 8; k++) + { + if(index_temp & 0x01) + { + rev = rev | (1 << (7 - k)); + } + index_temp = index_temp >> 1; + } + + if(rev < WSPR_BIT_COUNT) + { + d[rev] = s[i]; + i++; + } + + if(i >= WSPR_BIT_COUNT) + { + break; + } + } + + memcpy(s, d, WSPR_BIT_COUNT); +} + +void wspr_merge_sync_vector(uint8_t * g, uint8_t * symbols) +{ + uint8_t i; + const uint8_t sync_vector[WSPR_SYMBOL_COUNT] = + {1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, + 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, + 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0}; + + for(i = 0; i < WSPR_SYMBOL_COUNT; i++) + { + symbols[i] = sync_vector[i] + (2 * g[i]); + } +} + +uint8_t wspr_code(char c) +{ + // Validate the input then return the proper integer code. + // Change character to a space if the char is not allowed. + + if(isdigit(c)) + { + return (uint8_t)(c - 48); + } + else if(c == ' ') + { + return 36; + } + else if(c >= 'A' && c <= 'Z') + { + return (uint8_t)(c - 55); + } + else + { + return 36; + } +} + +void pad_callsign(char * call) +{ + // If only the 2nd character is a digit, then pad with a space. + // If this happens, then the callsign will be truncated if it is + // longer than 6 characters. + if(isdigit(call[1]) && isupper(call[2])) + { + // memmove(call + 1, call, 6); + call[5] = call[4]; + call[4] = call[3]; + call[3] = call[2]; + call[2] = call[1]; + call[1] = call[0]; + call[0] = ' '; + } + + // Now the 3rd charcter in the callsign must be a digit + // if(call[2] < '0' || call[2] > '9') + // { + // // return 1; + // } +} diff --git a/WSPRbeacon/thirdparty/WSPRutility.h b/WSPRbeacon/thirdparty/WSPRutility.h new file mode 100644 index 0000000..8f37969 --- /dev/null +++ b/WSPRbeacon/thirdparty/WSPRutility.h @@ -0,0 +1,45 @@ +/* + * JTEncode.cpp - JT65/JT9/WSPR/FSQ encoder library for Arduino + * + * Copyright (C) 2015-2021 Jason Milldrum + * + * Based on the algorithms presented in the WSJT software suite. + * Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding + * process that helped me to understand all of this. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef WSPR_UTILITY_H_ +#define WSPR_UTILITY_H_ + +#include +#include +#include + +#include "nhash.h" + +#define WSPR_SYMBOL_COUNT 162 +#define WSPR_BIT_COUNT 162 +#define VALID_DBM_SIZE 28 + +void wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols); +void wspr_message_prep(char * call, char * loc, int8_t dbm); +void wspr_bit_packing(uint8_t * c); +void convolve(uint8_t * c, uint8_t * s, uint8_t message_size, uint8_t bit_size); +void wspr_interleave(uint8_t * s); +void wspr_merge_sync_vector(uint8_t * g, uint8_t * symbols); +uint8_t wspr_code(char c); +void pad_callsign(char * call); + +#endif diff --git a/WSPRbeacon/thirdparty/nhash.c b/WSPRbeacon/thirdparty/nhash.c new file mode 100644 index 0000000..8aabb4f --- /dev/null +++ b/WSPRbeacon/thirdparty/nhash.c @@ -0,0 +1,384 @@ +/* + *------------------------------------------------------------------------------- + * + * This file is part of the WSPR application, Weak Signal Propagation Reporter + * + * File Name: nhash.c + * Description: Functions to produce 32-bit hashes for hash table lookup + * + * Copyright (C) 2008-2014 Joseph Taylor, K1JT + * License: GPL-3 + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Files: lookup3.c + * Copyright: Copyright (C) 2006 Bob Jenkins + * License: public-domain + * You may use this code any way you wish, private, educational, or commercial. + * It's free. + * + *------------------------------------------------------------------------------- +*/ + +/* +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +*/ + +#define SELF_TEST 1 + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#ifdef Win32 +#include "win_stdint.h" /* defines uint32_t etc */ +#else +#include /* defines uint32_t etc */ +#endif +//#include /* attempt to define endianness */ +//#ifdef linux +//# include /* attempt to define endianness */ +//#endif + +#define HASH_LITTLE_ENDIAN 1 + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; /* fall through */ + case 11: c+=((uint32_t)k[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k[9])<<8; /* fall through */ + case 9 : c+=k[8]; /* fall through */ + case 8 : b+=((uint32_t)k[7])<<24; /* fall through */ + case 7 : b+=((uint32_t)k[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k[5])<<8; /* fall through */ + case 5 : b+=k[4]; /* fall through */ + case 4 : a+=((uint32_t)k[3])<<24; /* fall through */ + case 3 : a+=((uint32_t)k[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k[1])<<8; /* fall through */ + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + +//uint32_t __stdcall NHASH(const void *key, size_t length, uint32_t initval) diff --git a/WSPRbeacon/thirdparty/nhash.h b/WSPRbeacon/thirdparty/nhash.h new file mode 100644 index 0000000..d0b1aee --- /dev/null +++ b/WSPRbeacon/thirdparty/nhash.h @@ -0,0 +1,14 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NHASH_H_ +#define NHASH_H_ + +uint32_t nhash_( const void *, int *, uint32_t *); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/debug/logutils.c b/debug/logutils.c new file mode 100644 index 0000000..e936d91 --- /dev/null +++ b/debug/logutils.c @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Roman Piksaykin [piksaykin@gmail.com], R2BDY +// https://www.qrz.com/db/r2bdy +// +/////////////////////////////////////////////////////////////////////////////// +// +// +// BitEmitter.h - Produces a time-accurate bit stream. Invokes a modulator +// Invokes a `modulator` function. +// DESCRIPTION +// Receives data asynchronously. Calls low level modulator function +// synchronously according to params. +// +// HOWTOSTART +// - +// +// PLATFORM +// Raspberry Pi pico. +// +// REVISION HISTORY +// - +// +// 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 e69de29..b79cc40 100644 --- a/defines.h +++ b/defines.h @@ -0,0 +1,12 @@ +#ifndef DEFINESWSPR_H +#define DEFINESWSPR_H + +#define DEBUG + +#ifdef DEBUG +#define DEBUGPRINTF(x) StampPrintf(x); +#else +#define DEBUGPRINTF(x) { } +#endif + +#endif diff --git a/docs/wspr_coding_process.pdf b/docs/wspr_coding_process.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5794b0098ff5476ca1d27fe99de458b5526aea40 GIT binary patch literal 46431 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

YJU78nlSeWms?_{rjIHi~V36WE1 z?^}@SEZCV?a=rm~v-vyKehI4TC*+=0?jL?RXr>QSz?#Z+CJ^f`YGf1%^;izHo{NCZBx{g1CK)u)Tjf`YjoPA}5fG&!^!!em0Dm zV&N4xceq2lz&vYZ2H(TUk^Ty5Z=SNf1u|k1i#PP`=(+OjYl{1P`RFw)US!`rJQ+Qn zf%xKmj*?bxJ|$G@R_Ic>y@2z!Ua9Eo9OAdm89c4|w5`zEUW#ps&Rt4UEw)Onta1Bs z<&cvUHCJd>s4ql*GJE7V?039{KXp9s!T7YykF&X~6RNJ~?|LBPf=&&c)LUY-4(xiM z939UaWFDjPZty|`uA;jm;dGKo!^rXDxAvGGPMq3S5lLCPSgOiMlZC0~I5U?&r~Jz( z5q5AtkMQ)&{GMr#d-0W@0XjM@@E@1M8TdJ$U$CGl5yt7MnHSx&ugDXNDpGmACgsHo zd+6xhl4^U)R?utKAHyhkZDu+eOICm~l<0jlCiN?E8hhoWLrz3QEWBXU<3O9jx$?y} z29NXa^g9y{rRlNz!97o8nGJl3U}HKE9I8D_@#6WQ=9197b0C^vT(dk-j+i>FXWXBi z<#8e<`jC+tM#@Oag-N;nGi^uBICIJ_H<-YA2HjQ%YG2qc_Gfzn2ws+P#yspgWJ__r&JeJ zi*HChwMXse#j%c-@;HiDGrmRbT<8;@$1=3c{Ok7gj~$^en>)^|sB zwu@*>NGQJM&pj#Y9T!WvawxMri?!s2ylH9{eA-UZjVx6rHJSJ6!f3la-V=B4_KV4} zN4?K2kL2>X*X&3=$53~9AfG!?vomGy4T{m~<24=gFrCDM6Mk=LKa*_}SbG2ZHv7JQ zd!6bgMorzC)XKr3wp;_X?pA5t3;NEd%z}A2nOb7?N^-uN(b(m5br%g= zHSIF0><$gw&#m+M&y&t62hxVxXloD~xs@+&=_+NyKE^QA@+#mT;# zqhk*yLetKZ#Xq0@-efu&tL-0u7>i)8_NXy`>7@JgOY0{&^E=gFhvm$IhF!Q593%(N zG*yY9+bvI>yC0OdaN{8F*N1&h?&6O{>PLj85B(aWV7C($?P`B7VD~m@iRlcrY(3y1 z!F@=u{*~0l8S93Km*Wd;VQd9{Vo#g--_|71v|?*t55`VRX#@p6#8SLJp{k}FA05au zEmF1E{N<{s%*xO=5&ny581_#m#)O7<--DgwLepJ+r#Spm2p)6zOLn%@?JJ+k z9(ZZ1=2pslnG%ygPle$xw^iw3TQYZ%HFEatopc{FEmrX2d>H)M;2C28@*ue+110}J##L;FOm@MKf;Rv zG~*eLI{VG21is?2VH9hyZC-o+e!?eWLdk^5{t;~1n z%`ba?1u)1#wOHHB^3e05JIF5c6?p2)tFywh8LTbvhX`P;nE<)qMy**~euC`HC- zl>0RHC_hVlz%yj;GV;0H!)oc}J&Sr-3Xg3jHHi7NqdcfJPS}5PI68J%!ou)&RXbx_3N{#$yrJlG|O3)nSK#6&fK z3RqL&npz}4-VhC{1HLaoEC=ykQze-HugZk@fVFfy-6#)zU!w)P%Y8J8_=m#;~Qf?ii zEqPGsf5}IQOa2cwM})rEY;)SWO7d!oLWUSeG(lJ&=V;-y+3Lig81gq(C%Vq+h)-Vq zhq6nk@D6;0m+S5%Yz&d zV2FU)gpQ>BniW7)sC`l&pgm;0t39P9^%E)!g@mr6tU(tBpxKgAoGyK%6Bxf)xa*1QJ1zBn#(N*d^jA zG;4zLpqjLR8i@^mS`0?SG+KxLMVvzXjhy~*EB)W0uG!!uhR_y3zg6ZQ~(z@oGY5x9an&WcEpghhXFj%cpRuaRZxBQ z00`B!szkJln*|nBrR#YdsVb=->cDwWx2|@0G#b>fGlYam<_f zs&NuxYG;A9*#%^i}tU+}eG;8yf66k%fN@jsq=BwJ zOo6zNg+V}xXaZr4EeG|vq*xET0f(JeGdk*zdQ@y+896&IJ>QVzVs zv{^22F-hcR{em>qR+<iaUH_;FXG4R^9u`U9RfL5s+`5{Ch2*^z| z1h_WbOcQ~yb~ltmAVBBbOasmIAL=5v$OR#?b-W1BIyUhGtr@wsf2gf8MWD9$0$c)g z!p;07kX!5-bQ#Ibau$OlA-L@g;}sK!R{mRPTYN?g1&5GuH`Yag3^&n0zl6|i*UGu# zExm3l0R|XPG#GPWAcQy$1pbDmJ}^Md$p!}lD?mWZYKkyZLAK8K5$~=ylY3KL lmFnOB>Dr;K6f+1LoZxDKcO?cg@Iw>er$Pk;lysD7{}117_!0mB literal 0 HcmV?d00001 diff --git a/main.c b/main.c index a94f897..2deab71 100644 --- a/main.c +++ b/main.c @@ -47,16 +47,67 @@ // THE SOFTWARE. /////////////////////////////////////////////////////////////////////////////// #include +#include #include #include "defines.h" #include "pico/multicore.h" -#include "pico-hf-oscillator/defines.h" -#include "pico-hf-oscillator/piodco/piodco.h" #include "pico-hf-oscillator/lib/assert.h" +#include "pico-hf-oscillator/defines.h" +#include + +#include + +#include "debug/logutils.h" + +int FSK4mod(uint32_t frq_step_millihz, uint8_t shift_index); int main() { + DEBUGPRINTF("\n"); + sleep_ms(1000); + DEBUGPRINTF("Pico-WSPR-tx start."); + DEBUGPRINTF("WSPR beacon init..."); + WSPRbeaconContext *pWB = WSPRbeaconInit("R2BDY", "KO85", 6, FSK4mod); + DEBUGPRINTF("OK"); + + DEBUGPRINTF("Create packet..."); + WSPRbeaconCreatePacket(pWB); + DEBUGPRINTF("OK"); + + sleep_ms(100); + + int row = 0; + do + { + for(int i = 0; i < 16; ++i) + { + const int j = i + row * 16; + printf("%X ", pWB->_pu8_outbuf[j]); + if(161 == j) + { + row = -1; + break; + } + } + printf("\n"); + if(-1 == row) + break; + ++row; + + } while (true); + + for(;;) + { + DEBUGPRINTF("tick."); + sleep_ms(1000); + } } + +int FSK4mod(uint32_t frq_step_millihz, uint8_t shift_index) +{ + + return 0; +} \ No newline at end of file diff --git a/pico-hf-oscillator b/pico-hf-oscillator index 37dbc57..494ca2f 160000 --- a/pico-hf-oscillator +++ b/pico-hf-oscillator @@ -1 +1 @@ -Subproject commit 37dbc57481edd722a719de7c8ac4c83b1843cc65 +Subproject commit 494ca2fb2f922366921c3deea0c2b03432c9b867