本帖最后由 傅沈骁 于 2025-11-14 22:48 编辑
开发板上的雷达芯片为XENSIV 60GHz radar sensor,相关介绍可以参见https://www.infineon.com/part/BGT60TR13C
为了便于快速开发,本文尝试基于MicroPython实现板载雷达的人体存在检测
首先在英飞凌的MicroPython固件包中选择对应开发板固件
接着在ModusToolbox Programmer中烧录对应的MicroPython固件
打开Thonny,选择解释器为MicroPython(通用)
可以看到芯片已经识别成功
接着开始编写代码根据User Guide,可以看到雷达相关引脚如下
定义引脚,定义雷达帧长度、ADC分频、起始频率、带宽、检测最小/最大距离 - ========= 引脚 =========
- PIN_RSPI_MOSI = 'P12_0'
- PIN_RSPI_MISO = 'P12_1'
- PIN_RSPI_SCK = 'P12_2'
- PIN_RSPI_CS = 'P12_3'
- PIN_RSPI_IRQ = 'P11_0'
- PIN_RXRES_L = 'P11_1'
- PIN_LED_MACRO = 'P5_3'
- PIN_LED_MICRO = 'P5_4'
- # ========= 参数(现场可调) =========
- WORD_SIZE = 512
- ADC_DIV = 16
- START_FREQ_KHZ = 61_000_000
- BANDWIDTH_KHZ = 1_000_000
- MIN_DIST_M = 0.30
- MAX_DIST_M = 6.00
定义宏存在判定(SNR阈值+滞后+保持时间),定义微存在判定(短窗能量的变异系数CV),定义基线(背景能量)更新与冻结,以及其他一些参数定义 - # 宏存在:用 SNR(dB) 滞后
- PRESENCE_ON_DB = 5.5 # <-- 下调
- PRESENCE_OFF_DB = 3.5
- HOLD_MACRO_SEC = 8.0
- # 微存在:用短窗变异系数 CV = std/mean
- MICRO_ON_CV = 0.10 # 进入微存在
- MICRO_OFF_CV = 0.04 # 退出微存在
- HOLD_MICRO_SEC = 10.0
- # 基线学习率(可冻结)
- ALPHA_ABSENT = 0.02
- ALPHA_PRESENT = 0.002
- FREEZE_BASELINE_WHEN_PRESENT = True
- # 短窗/循环
- SHORT_N = 10
- LOG_INTERVAL_MS = 80
- USE_FFT = True
- # 周期性 FIFO 清理
- FIFO_RESET_PERIOD = 200
- # 汉宁窗(抑制旁瓣)
- USE_HANN_WINDOW = True
- # ROI 跟随(峰值 ±bins)
- USE_ROI_FOLLOW = True
- ROI_HALF_BINS = 4 # ≈ ±0.6 m @ 0.151 m/bin
- # 峰值距离滑动平均
- PEAK_MA_N = 5
定义一些基础工具函数,用于计算复数幅度以及构建SPI、构建雷达对象以及做硬复位脉冲 - def mag(re, im):
- return (re*re + im*im) ** 0.5
- def build_spi():
- return SPI(
- baudrate=50_000_000, polarity=0, phase=0, bits=8,
- sck=Pin(PIN_RSPI_SCK), mosi=Pin(PIN_RSPI_MOSI), miso=Pin(PIN_RSPI_MISO)
- )
- def build_radar(spi):
- return BGT60TRxxModule(
- word_size=WORD_SIZE, spi_interface=spi, chip_select=PIN_RSPI_CS,
- reset=PIN_RXRES_L, interrupt_pin=PIN_RSPI_IRQ, interrupt_handler=None
- )
- def hw_reset_pulse(radar):
- radar.reset_radar.value(0); time.sleep_ms(2)
- radar.reset_radar.value(1); time.sleep_ms(10)
写位的控制操作 - def start_frame_direct(radar):
- radar.write_reg(BGTCONST.MAIN_ADDR, BGTCONST.START_FRAME)
- time.sleep_ms(1)
- def fifo_reset_direct(radar):
- radar.write_reg(BGTCONST.MAIN_ADDR, BGTCONST.FIFO_RESET)
- time.sleep_ms(1)
- radar.write_reg(BGTCONST.MAIN_ADDR, 0x00)
雷达初始化,其中需要设定ADC分频/chirp长度/频率参数,调节前端增益,初始化寄存器表,然后复位FIFO并启动第一帧 - def init_radar_full(radar, n_fsu, n_rtu, n_rsu):
- hw_reset_pulse(radar)
- radar.reset(); time.sleep_ms(5) # 软复位(直接写)
- radar.set_adc_div(ADC_DIV)
- radar.set_chirp_len(WORD_SIZE)
- radar.configure_chirp(n_fsu, n_rtu, n_rsu)
- radar.set_vga_gain_ch1(3)
- radar.set_vga_gain_ch2(3)
- radar.set_vga_gain_ch3(3)
- radar.setCompareValue(75)
- radar.initSensor(); time.sleep_ms(5) # 整表写
- fifo_reset_direct(radar)
- start_frame_direct(radar)
计算能量与峰值
- def compute_metrics_fft(radar, i0, i1):
主检测循环与状态机 - def run_detection(radar, rng_res, i0, i1, n_fsu, n_rtu, n_rsu, use_fft=True):
- led_macro = Pin(PIN_LED_MACRO, Pin.OUT, value=0)
- led_micro = Pin(PIN_LED_MICRO, Pin.OUT, value=0)
- STATE_ABSENT, STATE_PRESENT_MACRO, STATE_PRESENT_MICRO = 0, 1, 2
- state = STATE_ABSENT
- baseline = 0.0
- # ROI 能量短窗缓冲
- short_buf = [0.0] * SHORT_N; short_i = 0
- hold_macro_sec = 0.0; hold_micro_sec = 0.0
- prev_ms = time.ticks_ms()
- frame_count = 0
- # 峰值距离滑动平均
- peak_ma_buf = [0.0] * PEAK_MA_N; peak_ma_i = 0
预热阶段(假设无人),这一步用于快速建立背景基线 - # 预热(无人)
- for _ in range(30):
- try:
- start_frame_direct(radar)
- e_full, e_roi, _, _ = (compute_metrics_fft(radar, i0, i1) if use_fft
- else compute_metrics_nfft(radar, i0, i1))
- except Exception as e2:
- safe_recover(radar, e2, n_fsu, n_rtu, n_rsu); e_roi = 0.0
- baseline = (1 - ALPHA_ABSENT) * baseline + ALPHA_ABSENT * e_roi
- time.sleep_ms(10)
每帧的处理中,需要定期清FIFO,启动新帧,计算能量与峰值,并计算峰值距离与滑动平均 - # 峰值距离与滑动平均
- peak_dist = peak_idx * rng_res
- peak_ma_buf[peak_ma_i] = peak_dist
- peak_ma_i = (peak_ma_i + 1) % PEAK_MA_N
- peak_ma = sum(peak_ma_buf) / PEAK_MA_N
计算SNR(dB)(对ROI能量相对基线) - # SNR(dB) = 10*log10(E_roi/baseline)
- eps = 1e-9
- snr_db = 10.0 * math.log(e_roi / max(baseline, eps)) / math.log(10.0) if baseline > 0 else 99.0
短窗CV(用于微存在) - # ---- 宏存在:SNR(dB) 滞后 + 边沿计时 ----
- macro_enter = (snr_db > PRESENCE_ON_DB)
- macro_exit = (snr_db < PRESENCE_OFF_DB)
- if macro_enter:
- if state != STATE_PRESENT_MACRO:
- state = STATE_PRESENT_MACRO; hold_macro_sec = 0.0
- else:
- hold_macro_sec += dt_sec
- elif state == STATE_PRESENT_MACRO:
- hold_macro_sec += dt_sec
- if macro_exit and hold_macro_sec > HOLD_MACRO_SEC:
- state = STATE_ABSENT; hold_macro_sec = 0.0
微存在状态机(CV 滞后 + 保持时间) - # ---- 微存在:CV 滞后 + 边沿计时 ----
- micro_enter = (cv > MICRO_ON_CV)
- micro_exit = (cv < MICRO_OFF_CV)
- if state == STATE_ABSENT and micro_enter:
- state = STATE_PRESENT_MICRO; hold_micro_sec = 0.0
- elif state == STATE_PRESENT_MICRO:
- hold_micro_sec += dt_sec
- if micro_exit and hold_micro_sec > HOLD_MICRO_SEC:
- state = STATE_ABSENT; hold_micro_sec = 0.0
最后更新LED和打印日志 - # LED
- led_macro.value(1 if state == STATE_PRESENT_MACRO else 0)
- led_micro.value(1 if state == STATE_PRESENT_MICRO else 0)
- # 日志
- print("res={:.3f}m/bin, idx=[{}..{}], E_full={:.2f}, E_roi={:.2f}, base={:.2f}, "
- "SNR={:.2f}dB, short_mean={:.2f}, CV={:.3f}, state={}, holdM={:.1f}s, holdm={:.1f}s, "
- "peak={:.2f}m(ma:{:.2f}m, val:{:.2f})".format(
- rng_res, i0, i1, e_full, e_roi, baseline,
- snr_db, short_mean, cv, state, hold_macro_sec, hold_micro_sec,
- peak_dist, peak_ma, peak_val
- ))
其中各个字段的含义如下:
res:距离分辨率
idx[i0..i1]:参与能量/峰值计算的bin索引范围
E_full:在[i0..i1]全串口上加窗后的平均能量(单位位归一化幅度的平均)
E_roi:在ROI以当前峰值为中心、半宽ROI_HALF_BINS)的平均能量
base:基线能量
SNR(dB):ROI能量相对基线的信噪比
short_mean:短窗中ROI能量的均值
CV:短窗ROI能量的变异系数,用于微存在判定
status:当前存在状态,0为无人,1为宏存在,2为微存在
holdM:宏存在状态下累计的保持时间
holdm:微存在状态下累计的保持时间
peak:当前帧的峰值距离
ma:峰值距离的滑动平均
val:峰值bin的幅度/能量值(加窗后),反映该距离处的反射强度
最终main函数如下 - def main():
- spi = build_spi()
- radar = build_radar(spi)
- n_fsu = calculateFSU(START_FREQ_KHZ)
- n_rtu = calculateRTU(ADC_DIV, WORD_SIZE)
- n_rsu = calculateRSU(BANDWIDTH_KHZ, n_rtu)
- init_radar_full(radar, n_fsu, n_rtu, n_rsu)
- rng_res = radar.get_range_resolution() # m/bin
- i0 = max(0, int(MIN_DIST_M / rng_res))
- i1 = min(WORD_SIZE - 1, int(MAX_DIST_M / rng_res))
- run_detection(radar, rng_res, i0, i1, n_fsu, n_rtu, n_rsu, use_fft=USE_FFT)
最终输出的效果如下,有些数据处理得不太好,导致这些值都不怎么变化
|