Crudely ported my C wspr encoder

This commit is contained in:
Sebastian 2021-04-25 17:33:35 +02:00
parent 97c91a7e84
commit d04f567294
8 changed files with 192 additions and 5 deletions

View File

@ -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
}

View File

@ -29,6 +29,7 @@ pub struct App {
),
>,
gps_parser: Parser,
locator: arrayvec::ArrayString<6>,
}
impl App {

View File

@ -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(),
}
}

View File

@ -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

View File

@ -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;

142
src/wspr.rs Normal file
View File

@ -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;
}

View File

@ -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"

33
testsuite/tests/wspr.rs Normal file
View File

@ -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]);
}
}
}