nearlink_sdr.phy.uwb_pulse 源代码

"""超宽带脉冲波形与调制 (标准 6.2.1.4)。

实现 Kaiser 参考脉冲波形、码片载波调制和归一化互相关验证。
"""

from __future__ import annotations

__all__ = [
    "UWBPulseConfig",
    "chip_modulate",
    "kaiser_pulse",
    "normalized_cross_correlation",
    "validate_pulse",
]


import math
from dataclasses import dataclass

import numpy as np
from numpy.typing import NDArray


def _i0(x: float) -> float:
    """第一类零阶修正贝塞尔函数。"""
    return float(np.i0(x))


[文档] @dataclass class UWBPulseConfig: """UWB 脉冲配置参数。 :ivar tp_ns: 带宽参数 Tp (ns) :ivar tw_ns: 归一化互相关函数参数 Tw (ns) :ivar beta: Kaiser 波形形状参数 :ivar max_prf_mhz: 最大脉冲重复频率 (MHz) :ivar sample_rate_ghz: 采样率 (GHz) """ tp_ns: float = 2.0 tw_ns: float = 0.5 beta: float = 10.0 max_prf_mhz: float = 499.2 sample_rate_ghz: float = 4.0
[文档] @classmethod def bw_500mhz(cls) -> UWBPulseConfig: """创建 -10dB 带宽 >= 500MHz 的配置。""" return cls(tp_ns=2.0, tw_ns=0.5)
[文档] @classmethod def bw_1300mhz(cls) -> UWBPulseConfig: """创建 -10dB 带宽 > 1300MHz 的配置。""" return cls(tp_ns=0.75, tw_ns=0.2)
@property def pulse_duration_ns(self) -> float: """脉冲持续时间 L = 3 * Tp (ns)。""" return 3.0 * self.tp_ns @property def chip_duration_ns(self) -> float: """码片持续时间 Tc = 1 / max_prf (ns)。""" return 1e3 / self.max_prf_mhz @property def samples_per_chip(self) -> int: """每个码片的采样点数。""" return round(self.chip_duration_ns * self.sample_rate_ghz)
[文档] def kaiser_pulse( cfg: UWBPulseConfig, num_samples: int | None = None, ) -> NDArray[np.float64]: """生成 Kaiser 参考脉冲波形 r(t)。 :param cfg: UWB 脉冲配置 :param num_samples: 采样点数, 默认根据脉冲持续时间和采样率计算 :returns: 归一化的 Kaiser 脉冲波形采样值 """ l_ns = cfg.pulse_duration_ns if num_samples is None: num_samples = math.ceil(l_ns * cfg.sample_rate_ghz) + 1 t = np.linspace(-l_ns / 2, l_ns / 2, num_samples) i0_beta = _i0(cfg.beta) r = np.zeros_like(t) mask = np.abs(t) <= l_ns / 2 arg = cfg.beta * np.sqrt(np.maximum(1.0 - (2.0 * t[mask] / l_ns) ** 2, 0.0)) r[mask] = np.i0(arg) / (l_ns * i0_beta) return r
[文档] def chip_modulate( chips: NDArray[np.int8], cfg: UWBPulseConfig, fc_ghz: float, ) -> NDArray[np.float64]: """码片载波调制。 每个码片 c(n) ∈ {-1, 0, +1} 的调制信号为: c(n) · r(t - n·Tc) · cos[2π·fc·(t - n·Tc)] :param chips: 码片序列, 值为 -1, 0 或 +1 :param cfg: UWB 脉冲配置 :param fc_ghz: 载波中心频率 (GHz) :returns: 调制后的时域信号 """ pulse = kaiser_pulse(cfg) pulse_len = len(pulse) spc = cfg.samples_per_chip total_len = spc * len(chips) + pulse_len - 1 signal = np.zeros(total_len, dtype=np.float64) dt_ns = 1.0 / cfg.sample_rate_ghz for n, c in enumerate(chips): if c == 0: continue start = n * spc t_offset_ns = n * cfg.chip_duration_ns t_local = np.arange(pulse_len) * dt_ns carrier = np.cos(2.0 * np.pi * fc_ghz * (t_local + start * dt_ns - t_offset_ns)) signal[start:start + pulse_len] += float(c) * pulse * carrier return signal
[文档] def normalized_cross_correlation( r: NDArray[np.float64], p: NDArray[np.float64], ) -> NDArray[np.float64]: """计算归一化互相关函数 |φ(τ)|。 :param r: 参考脉冲波形 :param p: 发射脉冲波形 :returns: 归一化互相关函数的幅度 """ e_r = np.sum(r ** 2) e_p = np.sum(p ** 2) if e_r == 0 or e_p == 0: return np.zeros(len(r) + len(p) - 1) corr = np.correlate(r, p, mode="full") norm = math.sqrt(e_r * e_p) return np.abs(corr / norm)
[文档] def validate_pulse( p: NDArray[np.float64], cfg: UWBPulseConfig, main_lobe_threshold: float = 0.92, side_lobe_threshold: float = 0.1, ) -> tuple[bool, float, float]: """验证发射脉冲波形是否满足标准要求。 :param p: 发射脉冲波形 :param cfg: UWB 脉冲配置 :param main_lobe_threshold: 主瓣最低强度阈值 (默认 0.92) :param side_lobe_threshold: 旁瓣最高强度阈值 (默认 0.1) :returns: (是否通过, 主瓣最小强度, 旁瓣最大强度) """ r = kaiser_pulse(cfg, num_samples=len(p)) phi = normalized_cross_correlation(r, p) peak_idx = np.argmax(phi) peak_val = phi[peak_idx] # 主瓣: 围绕峰值, 持续时间 >= Tw 的区域 tw_samples = math.ceil(cfg.tw_ns * cfg.sample_rate_ghz) half_tw = tw_samples // 2 start = max(0, peak_idx - half_tw) end = min(len(phi), peak_idx + half_tw + 1) main_lobe_min = float(np.min(phi[start:end])) # 旁瓣: 标准定义为 d|φ(τ)|/dτ = 0 的临界点 (局部极值) # 先找主瓣的连续区域 (从峰值向两侧单调递减直到第一个局部极小值) left_boundary = peak_idx while left_boundary > 0 and phi[left_boundary - 1] <= phi[left_boundary]: left_boundary -= 1 right_boundary = peak_idx while right_boundary < len(phi) - 1 and phi[right_boundary + 1] <= phi[right_boundary]: right_boundary += 1 # 在主瓣以外查找局部极大值 side_lobe_max = 0.0 for region in (phi[:left_boundary], phi[right_boundary + 1:]): if len(region) < 3: continue for i in range(1, len(region) - 1): if region[i] >= region[i - 1] and region[i] >= region[i + 1]: side_lobe_max = max(side_lobe_max, float(region[i])) # 边界值也作为候选 if left_boundary > 0: side_lobe_max = max(side_lobe_max, float(phi[0])) if right_boundary < len(phi) - 1: side_lobe_max = max(side_lobe_max, float(phi[-1])) main_ok = main_lobe_min >= main_lobe_threshold side_ok = side_lobe_max <= side_lobe_threshold _ = peak_val # 避免未使用警告 return (main_ok and side_ok, main_lobe_min, side_lobe_max)