From d04f5672944423a01f08355846a79a8706aeb82c Mon Sep 17 00:00:00 2001 From: LongHairedHacker Date: Sun, 25 Apr 2021 17:33:35 +0200 Subject: [PATCH] Crudely ported my C wspr encoder --- src/application/gps.rs | 9 ++- src/application/mod.rs | 1 + src/application/setup.rs | 1 + src/lib.rs | 1 + src/loc.rs | 6 +- src/wspr.rs | 142 +++++++++++++++++++++++++++++++++++++++ testsuite/Cargo.toml | 4 ++ testsuite/tests/wspr.rs | 33 +++++++++ 8 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 src/wspr.rs create mode 100644 testsuite/tests/wspr.rs diff --git a/src/application/gps.rs b/src/application/gps.rs index ee0c520..d06939b 100644 --- a/src/application/gps.rs +++ b/src/application/gps.rs @@ -7,6 +7,7 @@ use stm32f1xx_hal::{ }; use crate::application::App; +use crate::loc; use crate::time; impl App { @@ -18,8 +19,12 @@ impl App { match result { Ok(ParseResult::GGA(Some(gga))) => { time::set(&gga.time); - defmt::info!("Got GGA") - } // Got GGA sentence + self.locator = loc::locator_from_coordinates( + gga.latitude.as_f64(), + gga.longitude.as_f64(), + ); + defmt::info!("Got GGA. New locator: {}", self.locator.as_str()); + } Ok(_) => {} // Some other sentences.. Err(_) => {} // Got parse error } diff --git a/src/application/mod.rs b/src/application/mod.rs index 3fd26d5..17da48a 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -29,6 +29,7 @@ pub struct App { ), >, gps_parser: Parser, + locator: arrayvec::ArrayString<6>, } impl App { diff --git a/src/application/setup.rs b/src/application/setup.rs index 33e300d..42f57ef 100644 --- a/src/application/setup.rs +++ b/src/application/setup.rs @@ -66,5 +66,6 @@ pub fn setup(cp: cortex_m::peripheral::Peripherals, dp: stm32::Peripherals) -> A board_led, serial, gps_parser, + locator: arrayvec::ArrayString::new(), } } diff --git a/src/lib.rs b/src/lib.rs index 6c837d2..93ae761 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use stm32f1xx_hal as _; pub mod application; pub mod loc; pub mod time; +pub mod wspr; // same panicking *behavior* as `panic-probe` but doesn't print a panic message // this prevents the panic message being printed *twice* when `defmt::panic` is invoked diff --git a/src/loc.rs b/src/loc.rs index be8be0e..2a1b689 100644 --- a/src/loc.rs +++ b/src/loc.rs @@ -3,16 +3,16 @@ use arrayvec::ArrayString; const FIELD_SYMBOLS: &str = "ABCDEFGHIJKLMNOPQR"; const SUBSQUARE_SYMBOLS: &str = "abcdefghijklmnopqrstuvwx"; -pub fn locator_from_coordinates(lat: f32, long: f32) -> ArrayString<6> { +pub fn locator_from_coordinates(lat: f64, long: f64) -> ArrayString<6> { let mut target_buf = ArrayString::<6>::new(); let false_east = if long < 180.0 { long + 180.0 } else { long - 180.0 - }; + } as f64; - let false_north = if lat < 90.0 { lat + 90.0 } else { lat - 90.0 }; + let false_north = if lat < 90.0 { lat + 90.0 } else { lat - 90.0 } as f32; let long_field = (false_east / 20.0) as usize; let mut long_rest = false_east % 20.0; diff --git a/src/wspr.rs b/src/wspr.rs new file mode 100644 index 0000000..cefd7d6 --- /dev/null +++ b/src/wspr.rs @@ -0,0 +1,142 @@ +// Implemented by following http://www.g4jnt.com/Coding/WSPR_Coding_Process.pdf + +const WSPR_LENGTH: usize = 162; +const WSPR_POWER_LOC_BITS: usize = 22; + +const WSPR_SYNC: [u8; WSPR_LENGTH] = [ + 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, +]; + +fn encode_call_char(call_sign: &arrayvec::ArrayString<6>, idx: usize) -> u32 { + let c = call_sign.chars().nth(idx).unwrap().to_ascii_uppercase(); + + if c >= '0' && c <= '9' { + return (c as u32) - ('0' as u32); + } else if c >= 'A' && c <= 'Z' { + return 10 + (c as u32) - ('A' as u32); + } + // Turn everything else to space + else { + return 36; + } +} + +fn encode_loc_char(locator: &arrayvec::ArrayString<6>, idx: usize) -> u32 { + let c = locator.chars().nth(idx).unwrap().to_ascii_uppercase(); + + if c >= '0' && c <= '9' { + return (c as u32) - ('0' as u32); + } else if c >= 'A' && c <= 'R' { + return (c as u32) - ('A' as u32); + } + // Everthing else becomes 9/I + else { + return 9; + } +} + +fn encode_power(power: u8) -> u32 { + if power > 60 { + return 60 as u32; + } + return power as u32; +} + +fn message_to_bits( + call_sign: &arrayvec::ArrayString<6>, + locator: &arrayvec::ArrayString<6>, + power: u8, +) -> u64 { + let mut n = encode_call_char(call_sign, 0); + n = n * 36 + encode_call_char(call_sign, 1); + n = n * 10 + encode_call_char(call_sign, 2); + n = n * 27 + encode_call_char(call_sign, 3) - 10; + n = n * 27 + encode_call_char(call_sign, 4) - 10; + n = n * 27 + encode_call_char(call_sign, 5) - 10; + // lowest 28 bits of n contain the encoded callsign + + let mut m = 179 - 10 * encode_loc_char(locator, 0) - encode_loc_char(locator, 2); + m = m * 180 + 10 * encode_loc_char(locator, 1) + encode_loc_char(locator, 3); + m = m * 128 + encode_power(power) + 64; + // lowest 22 bits of m contain the encoded locator (15bit) and the encoded power (7 bits) + + // Concactenate to 50bit wspr message + return (n as u64) << WSPR_POWER_LOC_BITS | (m as u64); +} + +const FEC_TAPS1: u32 = 0xF2D05351; +const FEC_TAPS2: u32 = 0xE4613C47; + +fn parity32(mut x: u32) -> u8 { + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + x ^= x >> 2; + x ^= x >> 1; + + return (x & 1) as u8; +} + +fn generate_fec(msg: u64) -> [u8; WSPR_LENGTH] { + let mut output: [u8; WSPR_LENGTH] = [0; WSPR_LENGTH]; + + let mut reg: u32 = 0; + for i in 0..81 { + let bit = if i < 50 { + ((msg >> (50 - i - 1)) & 1) as u32 + } else { + 0 + }; + + reg = (reg << 1) | bit; + + let tabs1 = reg & FEC_TAPS1; + let tabs2 = reg & FEC_TAPS2; + + output[i * 2] = parity32(tabs1); + output[i * 2 + 1] = parity32(tabs2); + } + + return output; +} + +fn interleave_message(input: [u8; WSPR_LENGTH]) -> [u8; WSPR_LENGTH] { + let mut output: [u8; WSPR_LENGTH] = [0; WSPR_LENGTH]; + + let mut p: u8 = 0; + let mut i: u8 = 0; + while p < WSPR_LENGTH as u8 { + let rev_i = i.reverse_bits(); + if rev_i < WSPR_LENGTH as u8 { + output[rev_i as usize] = input[p as usize]; + p += 1; + } + i += 1; + } + + return output; +} + +fn add_sync(symbols: &mut [u8; WSPR_LENGTH]) { + for i in 0..WSPR_LENGTH { + symbols[i] = WSPR_SYNC[i] | symbols[i] << 1; + } +} + +pub fn endcode_message( + call_sign: &arrayvec::ArrayString<6>, + locator: &arrayvec::ArrayString<6>, + power: u8, +) -> [u8; WSPR_LENGTH] { + let bits = message_to_bits(call_sign, locator, power); + let msg_fec = generate_fec(bits); + let mut msg = interleave_message(msg_fec); + add_sync(&mut msg); + + return msg; +} diff --git a/testsuite/Cargo.toml b/testsuite/Cargo.toml index c3255d7..1480538 100644 --- a/testsuite/Cargo.toml +++ b/testsuite/Cargo.toml @@ -10,6 +10,10 @@ version = "0.1.0" name = "loc" harness = false +[[test]] +name = "wspr" +harness = false + [dependencies] wspr-beacon = { path = ".." } cortex-m = "0.7.1" diff --git a/testsuite/tests/wspr.rs b/testsuite/tests/wspr.rs new file mode 100644 index 0000000..e364062 --- /dev/null +++ b/testsuite/tests/wspr.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use wspr_beacon as _; // memory layout + panic handler + +// See https://crates.io/crates/defmt-test/0.1.0 for more documentation (e.g. about the 'state' +// feature) +#[defmt_test::tests] +mod tests { + use arrayvec::ArrayString; + use defmt::{assert, assert_eq, Debug2Format}; + use wspr_beacon::wspr; + + #[test] + fn test_encoding() { + let call_sign = ArrayString::<6>::from(" K1ABC").unwrap(); + let loc = ArrayString::<6>::from("FN42").unwrap(); + let symbols = wspr::endcode_message(&call_sign, &loc, 37); + + let expected_symbols: [u8; 162] = [ + 3, 3, 0, 0, 2, 0, 0, 0, 1, 0, 2, 0, 1, 3, 1, 2, 2, 2, 1, 0, 0, 3, 2, 3, 1, 3, 3, 2, 2, + 0, 2, 0, 0, 0, 3, 2, 0, 1, 2, 3, 2, 2, 0, 0, 2, 2, 3, 2, 1, 1, 0, 2, 3, 3, 2, 1, 0, 2, + 2, 1, 3, 2, 1, 2, 2, 2, 0, 3, 3, 0, 3, 0, 3, 0, 1, 2, 1, 0, 2, 1, 2, 0, 3, 2, 1, 3, 2, + 0, 0, 3, 3, 2, 3, 0, 3, 2, 2, 0, 3, 0, 2, 0, 2, 0, 1, 0, 2, 3, 0, 2, 1, 1, 1, 2, 3, 3, + 0, 2, 3, 1, 2, 1, 2, 2, 2, 1, 3, 3, 2, 0, 0, 0, 0, 1, 0, 3, 2, 0, 1, 3, 2, 2, 2, 2, 2, + 0, 2, 3, 3, 2, 3, 2, 3, 3, 2, 0, 0, 3, 1, 2, 2, 2, + ]; + + for i in 0..162 { + assert_eq!(expected_symbols[i], symbols[i]); + } + } +}