nearlink_sdr.phy.psk 源代码


__all__ = [
    "BPSK_MAP",
    "BPSK_NOROT_MAP",
    "BPSK_ROTATION",
    "QPSK_MAP",
    "QPSK_ROTATION",
    "SLE_RRC_BETA",
    "PSKDemodulator",
    "PSKModulator",
    "rrc_filter",
]


from functools import lru_cache

import numpy as np

# TXS-10002-2025 6.2.1.2 PSK调制
# 滚降系数 beta = 0.4

SLE_RRC_BETA = 0.4


[文档] @lru_cache(maxsize=8) def rrc_filter(beta: float, sps: int, span: int = 10) -> np.ndarray: """生成平方根升余弦脉冲成型滤波器。 :param beta: 滚降系数 :param sps: 每符号采样数 :param span: 单侧符号跨度 :returns: 滤波器系数, 归一化使得匹配滤波后采样点增益为1 """ N = 2 * span * sps + 1 t = np.arange(-(N - 1) / 2, (N - 1) / 2 + 1) / sps h = np.zeros(len(t)) for i, ti in enumerate(t): if abs(ti) < 1e-10: h[i] = 1.0 - beta + 4 * beta / np.pi elif abs(abs(ti) - 1.0 / (4 * beta)) < 1e-10: h[i] = (beta / np.sqrt(2)) * ( (1 + 2 / np.pi) * np.sin(np.pi / (4 * beta)) + (1 - 2 / np.pi) * np.cos(np.pi / (4 * beta)) ) else: num = np.sin(np.pi * ti * (1 - beta)) + 4 * beta * ti * np.cos(np.pi * ti * (1 + beta)) den = np.pi * ti * (1 - (4 * beta * ti) ** 2) h[i] = num / den # 归一化 h = h / np.sqrt(np.sum(h ** 2)) return h
# 标准中的PSK星座映射表 # BPSK: 0->90°, 1->-90°, 偶数符号额外顺时针旋转90° BPSK_MAP = { 0: np.exp(1j * np.pi / 2), 1: np.exp(-1j * np.pi / 2), } BPSK_ROTATION = np.exp(-1j * np.pi / 2) # 偶数位顺时针90° # QPSK: 00->45°, 01->135°, 11->-135°, 10->-45° QPSK_MAP = { 0b00: np.exp(1j * np.pi / 4), 0b01: np.exp(1j * 3 * np.pi / 4), 0b11: np.exp(-1j * 3 * np.pi / 4), 0b10: np.exp(-1j * np.pi / 4), } QPSK_ROTATION = np.exp(-1j * np.pi / 4) # 偶数位顺时针45° # 8PSK: 000->22.5°, 001->67.5°, 011->112.5°, 010->157.5°, # 110->-157.5°, 111->-112.5°, 101->-67.5°, 100->-22.5° _8PSK_MAP = { 0b000: np.exp(1j * np.deg2rad(22.5)), 0b001: np.exp(1j * np.deg2rad(67.5)), 0b011: np.exp(1j * np.deg2rad(112.5)), 0b010: np.exp(1j * np.deg2rad(157.5)), 0b110: np.exp(-1j * np.deg2rad(157.5)), 0b111: np.exp(-1j * np.deg2rad(112.5)), 0b101: np.exp(-1j * np.deg2rad(67.5)), 0b100: np.exp(-1j * np.deg2rad(22.5)), } _8PSK_ROTATION = np.exp(-1j * np.deg2rad(22.5)) # 偶数位顺时针22.5° # 无相位旋转BPSK: 0->0°, 1->180° BPSK_NOROT_MAP = { 0: np.exp(1j * 0), 1: np.exp(1j * np.pi), }
[文档] class PSKModulator: """TXS-10002-2025 6.2.1.2 PSK调制器。 支持 BPSK, QPSK, 8PSK 及无相位旋转BPSK。 """ def __init__(self, mod_type: str = "QPSK", sps: int = 4, beta: float = SLE_RRC_BETA, rrc_span: int = 10): self.mod_type = mod_type.upper() self.sps = sps self.beta = beta self.rrc_span = rrc_span self._rrc = rrc_filter(beta, sps, rrc_span) if self.mod_type == "BPSK": self._map = BPSK_MAP self._bits_per_symbol = 1 self._rotation = BPSK_ROTATION elif self.mod_type == "QPSK": self._map = QPSK_MAP self._bits_per_symbol = 2 self._rotation = QPSK_ROTATION elif self.mod_type == "8PSK": self._map = _8PSK_MAP self._bits_per_symbol = 3 self._rotation = _8PSK_ROTATION elif self.mod_type == "BPSK_NOROT": self._map = BPSK_NOROT_MAP self._bits_per_symbol = 1 self._rotation = None else: raise ValueError(f"不支持的调制方式: {mod_type}") @property def bits_per_symbol(self) -> int: return self._bits_per_symbol
[文档] def map_symbols(self, bits: np.ndarray) -> np.ndarray: """比特到星座符号的映射(含相位旋转)。""" bps = self._bits_per_symbol n_pad = (-len(bits)) % bps if n_pad: bits = np.concatenate([bits, np.zeros(n_pad, dtype=int)]) n_symbols = len(bits) // bps symbols = np.zeros(n_symbols, dtype=complex) for i in range(n_symbols): idx = 0 for b in range(bps): idx = (idx << 1) | int(bits[i * bps + b]) symbols[i] = self._map[idx] # 偶数符号位额外旋转(标准中第一个符号处于奇数位,索引0为奇数位) if self._rotation is not None and (i % 2 == 1): symbols[i] *= self._rotation return symbols
[文档] def modulate(self, bits: np.ndarray) -> np.ndarray: """PSK调制并进行RRC脉冲成型。 :returns: 复基带IQ信号 """ symbols = self.map_symbols(bits) # 上采样 upsampled = np.zeros(len(symbols) * self.sps, dtype=complex) upsampled[::self.sps] = symbols # RRC脉冲成型 signal = np.convolve(upsampled, self._rrc, mode='same') return signal
[文档] class PSKDemodulator: """PSK解调器(匹配滤波+最近邻判决)。""" def __init__(self, mod_type: str = "QPSK", sps: int = 4, beta: float = SLE_RRC_BETA, rrc_span: int = 10): self.mod_type = mod_type.upper() self.sps = sps self._rrc = rrc_filter(beta, sps, rrc_span) if self.mod_type == "BPSK": self._map = BPSK_MAP self._bits_per_symbol = 1 self._rotation = BPSK_ROTATION elif self.mod_type == "QPSK": self._map = QPSK_MAP self._bits_per_symbol = 2 self._rotation = QPSK_ROTATION elif self.mod_type == "8PSK": self._map = _8PSK_MAP self._bits_per_symbol = 3 self._rotation = _8PSK_ROTATION elif self.mod_type == "BPSK_NOROT": self._map = BPSK_NOROT_MAP self._bits_per_symbol = 1 self._rotation = None else: raise ValueError(f"不支持的调制方式: {mod_type}") # 构建星座参考 self._constellation_keys = sorted(self._map.keys()) self._constellation_vals = np.array([self._map[k] for k in self._constellation_keys])
[文档] def demodulate(self, signal: np.ndarray) -> np.ndarray: """PSK解调。 :param signal: 复基带IQ信号 :returns: 解调后的比特序列 """ # 匹配滤波 filtered = np.convolve(signal, self._rrc, mode='same') # 降采样(取峰值点) symbols = filtered[::self.sps] # 反旋转 + 最近邻判决 bps = self._bits_per_symbol bits = [] for i, sym in enumerate(symbols): # 反相位旋转 if self._rotation is not None and (i % 2 == 1): sym = sym * np.conj(self._rotation) # 最近邻判决 distances = np.abs(sym - self._constellation_vals) idx = self._constellation_keys[np.argmin(distances)] # 解码比特 bits.extend((idx >> b) & 1 for b in range(bps - 1, -1, -1)) return np.array(bits, dtype=int)