use super::tables::{L2C_STATES, L5_PHASES};
use super::{bipolar, Modulation, SignalCode, SignalError};
use crate::iq::DataModulation;
use crate::sdr::CaCode;
pub const L1_HZ: f64 = 1_575_420_000.0;
pub const L2_HZ: f64 = 1_227_600_000.0;
pub const L5_HZ: f64 = 1_176_450_000.0;
pub const L5_CHIP_RATE_HZ: f64 = 10_230_000.0;
pub const L5_CODE_LEN: usize = 10_230;
pub const L2C_CHIP_RATE_HZ: f64 = 511_500.0;
pub const L2_CM_LEN: usize = 10_230;
pub const L2_CL_LEN: usize = 767_250;
pub const NH10: [u8; 10] = [0, 0, 0, 0, 1, 1, 0, 1, 0, 1];
pub const NH20: [u8; 20] = [0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0];
pub const L2C_POLY_OCTAL: u32 = 0o1112225171;
pub fn l1ca(prn: u16) -> Result<SignalCode, SignalError> {
let code = u8::try_from(prn)
.ok()
.and_then(CaCode::new)
.ok_or(SignalError::UnknownPrn {
signal: "GPS L1 C/A",
prn,
})?;
Ok(SignalCode::new(
format!("GPS L1 C/A PRN {prn}"),
crate::sdr::CA_CHIP_RATE_HZ,
L1_HZ,
bipolar(&code.chips),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Lnav))
}
type Reg13 = [u8; 13];
fn reg13_from_word(word: u16) -> Reg13 {
let mut r = [0u8; 13];
for (k, s) in r.iter_mut().enumerate() {
*s = ((word >> (12 - k)) & 1) as u8;
}
r
}
fn reg13_to_word(r: &Reg13) -> u16 {
r.iter().fold(0u16, |acc, &b| (acc << 1) | u16::from(b))
}
fn shift13(r: &mut Reg13, taps: &[usize]) {
let fb = taps.iter().fold(0u8, |acc, &t| acc ^ r[t - 1]);
r.copy_within(0..12, 1);
r[0] = fb;
}
const XA_TAPS: [usize; 4] = [9, 10, 12, 13];
const XB_TAPS: [usize; 8] = [1, 3, 4, 6, 7, 8, 12, 13];
pub fn l5_xb_state_after(advance: u32) -> u16 {
let mut r = [1u8; 13];
for _ in 0..advance {
shift13(&mut r, &XB_TAPS);
}
reg13_to_word(&r)
}
fn l5_primary_bits(xb_word: u16) -> Vec<u8> {
let mut xa = [1u8; 13];
let mut xb = reg13_from_word(xb_word);
let mut out = Vec::with_capacity(L5_CODE_LEN);
for k in 0..L5_CODE_LEN {
out.push(xa[12] ^ xb[12]);
if k % 8190 == 8189 {
xa = [1u8; 13];
} else {
shift13(&mut xa, &XA_TAPS);
}
shift13(&mut xb, &XB_TAPS);
}
out
}
fn l5_row(signal: &'static str, prn: u16) -> Result<&'static super::tables::L5Phase, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| L5_PHASES.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn l5_i5(prn: u16) -> Result<SignalCode, SignalError> {
let row = l5_row("GPS L5 I5", prn)?;
Ok(SignalCode::new(
format!("GPS L5 I5 PRN {prn}"),
L5_CHIP_RATE_HZ,
L5_HZ,
bipolar(&l5_primary_bits(row.xb_i5)),
bipolar(&NH10),
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.01 }))
}
pub fn l5_q5(prn: u16) -> Result<SignalCode, SignalError> {
let row = l5_row("GPS L5 Q5", prn)?;
Ok(SignalCode::new(
format!("GPS L5 Q5 PRN {prn}"),
L5_CHIP_RATE_HZ,
L5_HZ,
bipolar(&l5_primary_bits(row.xb_q5)),
bipolar(&NH20),
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Pilot))
}
pub fn l2c_run(state: u32, chips: usize) -> (Vec<u8>, u32) {
let fb = L2C_POLY_OCTAL >> 1;
let mut s = state;
let mut out = Vec::with_capacity(chips);
for _ in 0..chips {
let o = (s & 1) as u8;
out.push(o);
s >>= 1;
if o == 1 {
s ^= fb;
}
}
(out, s)
}
fn l2c_row(
signal: &'static str,
prn: u16,
) -> Result<&'static super::tables::L2cStates, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| L2C_STATES.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn l2c_cm(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2 CM", prn)?;
let (bits, _) = l2c_run(row.cm_initial, L2_CM_LEN);
Ok(SignalCode::new(
format!("GPS L2 CM PRN {prn}"),
L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.02 }))
}
pub fn l2c_cl(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2 CL", prn)?;
let (bits, _) = l2c_run(row.cl_initial, L2_CL_LEN);
Ok(SignalCode::new(
format!("GPS L2 CL PRN {prn}"),
L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Pilot))
}
pub fn l2c(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2C", prn)?;
let (cm, _) = l2c_run(row.cm_initial, L2_CM_LEN);
let (cl, _) = l2c_run(row.cl_initial, L2_CL_LEN);
let mut bits = Vec::with_capacity(2 * L2_CL_LEN);
for (k, &cl_bit) in cl.iter().enumerate() {
bits.push(cm[k % L2_CM_LEN]);
bits.push(cl_bit);
}
Ok(SignalCode::new(
format!("GPS L2C PRN {prn}"),
2.0 * L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::NotModelled))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iq::SpreadingCode;
#[test]
fn xb_state_word_round_trips() {
for w in [0u16, 1, 0x1FFF, 0b0101011100100] {
assert_eq!(reg13_to_word(®13_from_word(w)), w);
}
}
#[test]
fn out_of_range_prns_are_refused() {
assert!(l1ca(0).is_err());
assert!(l1ca(33).is_err());
assert!(l5_i5(0).is_err());
assert!(l5_q5(211).is_err());
assert!(l2c_cm(64).is_err());
}
#[test]
fn l1ca_wrapper_matches_sdr_code() {
let c = l1ca(7).unwrap();
let sdr = CaCode::new(7).unwrap();
assert_eq!(c.len_chips(), 1023);
for k in 0..1023 {
assert_eq!(c.value_at(k as f64 + 0.5), sdr.bipolar[k]);
}
}
}