资讯详情

资讯详情

LoRa CAD信道活动检测:扫描与唤醒双端参数自协商实践

简介本资源为一件已公开的发明专利申请文本聚焦物联网LoRa无线模块的CAD空闲信道评估嗅探技术面向从事低功耗广域网通信、无线唤醒与射频参数自适应研究的工程师、专利撰写者及高校师生可用于解决双方射频参数不确定时难以快速建立连接、唤醒耗时过长导致功耗偏高的问题。包内共1个PDF文件即申请公布号为CN 110572843 A的专利全文约777KB包含权利要求书、说明书及附图完整记载了双模块在CAD检测模式与嗅探状态下的交互流程、射频参数序列帧的遍历匹配规则以及唤醒时刻窗口内探测帧与信标帧的收发时序。资源已有261人学习下载读者可从中获取方法权利要求与系统权利要求的撰写范式、CAD检测与CadDone/CadDelected信号的判断逻辑以及降低唤醒时间、提升通信能效的具体实现思路适合作为专利写作参考或LoRa低功耗通信方案设计的技术依据。1. 从一页专利到能跑的嗅探LoRa CAD 到底省下了哪一段等待一个很典型的现场几十个节点散在几百米范围内射频参数出厂就写死了换一个批次模组一多半就连不上。调试的人拿着频率和扩频因子一个组合一个组合地试试到第三个太阳都下山了。麻烦的根源在于 LoRa 的物理层约束非常硬——频率、扩频因子、带宽三个参数必须完全一致对端才可能解出前导码。而芯片本身给了另一条路信道活动检测也就是 CAD。它只需要一个符号周期就能判断信道上有没有相同参数的 LoRa 前导码而接收模式必须把整个包收完才能确定。这份资源是一份发明专利文本讲的是怎么把 CAD 从一个中断变成一套可执行的双端状态机扫描模式下两个模块互换探针帧一边轮询参数一边应答用 501000ms 的代价把参数对齐唤醒模式下用一串嗅探帧去撞对方极短的唤醒窗口撞上就回信标然后转入正常收发。它适合做 LoRa 私有组网、电池节点低占空比唤醒、现场参数自协商的从业者如果你已经在用成熟协议栈且不需要跨参数互通这套东西基本用不上。2. 参数空间与 CAD 中断先搞清楚什么条件下才可能撞上动手写代码之前得先把什么参数会影响 CAD、什么参数不影响这件事分清楚否则序列会做得又长又慢成功率还上不去。2.1 Freq/SF/BW 三元组与序列的笛卡尔积LoRa 解前导码靠的是频率、扩频因子、带宽三者的组合匹配。编码率 CR、显式/隐式包头、载荷 CRC 开关这些参数影响的是载荷能不能解开不影响前导码的相关运算。这个区分对嗅探设计非常关键CAD 检测的是前导码所以 CR 填错顶多让后面的数据帧解不出来但 CAD 照样会给出 CadDetected反过来SF 差一级、带宽差一档、中心频点偏几十 kHzCAD 就绝对不会响应。常见做法是把待扫描的参数写成三元组序列而不是只写频率参数是否影响 CAD说明中心频率 Freq影响偏一个信道带宽就检测不到扩频因子 SF影响SF 决定符号速率也决定 CAD 耗时带宽 BW影响125kHz 与 500kHz 的前导码互不相关编码率 CR不影响只影响载荷解码包头模式不影响显式/隐式只影响包结构同步字部分影响有些型号 CAD 不校验同步字需实测确认因为 CR 和包头模式被排除在外序列维度直接少了一半。假设现场只可能有 3 个频点、6 档 SF、1 档带宽全笛卡尔积是 18 个组合。听起来不多但每次 CAD 的耗时随 SF 指数增长SF12 在 125kHz 下单次就要 32ms 左右18 个组合走一轮接近半秒功耗和实时性都得重新算账。所以序列必须裁剪先用已知的频段范围和带宽范围砍掉一大半剩下的按最可能先命中的排在前面来排序。2.2 CAD 的七个动作与 CadDone/CadDetected 的先后关系芯片进入 CAD 之后并不是一直听而是一段固定流程锁 PLL、接收机抓取一个符号周期的信道样本、关掉接收机和 PLL、调制解调器在数字域做样本与理想前导码波形的相关运算、产生 CadDone 中断相关成功时同时置 CadDetected、芯片回到待机、软件根据结果决定是否切到接收模式。这段流程决定了软件层的判据顺序先等 CadDone再读 CadDetected。反过来写就会翻车——CadDetected 是伴随 CadDone 一起出现的标志位你在 CadDone 之前去读读到的永远是上一次的残留值。// 一次完整的 CAD 周期以 SX127x 系列为例具体位定义以对应型号手册为准 radio_set_params(px.freq, px.sf, px.bw); // 写 RegFrf / RegModemCfg1..3 radio_clear_irq(); // 清掉上一次的 CadDone / CadDetected radio_start_cad(); // RegOpMode 切到 CAD 模式 if (radio_wait_irq(IRQ_CAD_DONE, CAD_TIMEOUT_MS)) { if (radio_get_irq(IRQ_CAD_DETECTED)) { // 相关成功信道上存在同参数前导码 } }这段代码里最容易被忽略的是radio_clear_irq()。CadDone 和 CadDetected 是锁存型标志不清就会把上一轮的命中带进下一轮表现出来就是序列里某个参数永远成功但实际数据一帧都收不到。CAD_TIMEOUT_MS不能拍脑袋写死它至少要比一个符号周期长出一截SF11 在 125kHz 下符号周期大约 16ms加上 PLL 锁定和处理开销给 3050ms 才算稳妥如果序列里混了 SF12超时就得放到 60ms 以上。超时给太短的直接后果是软件以为没信号实际上是芯片还在算。2.3 为什么 CAD 比 Rx 便宜符号周期对整包时长把数量级摆出来就清楚了。符号周期 Tsym 2^SF / BWSFBW单符号周期单次 CAD 大致耗时7125kHz1.02ms24ms9125kHz4.10ms610ms11125kHz16.4ms2030ms12125kHz32.8ms3550ms12500kHz8.19ms1015ms接收一个普通数据包光前导码就要 8 个符号再加载荷、CRC、收尾SF9 下几十毫秒起步SF12 下轻松过百毫秒。也就是说一次 CAD 的代价大致等于收一个包的十分之一到三分之一而且不需要把包收完整。当你需要在 18 个参数组合里找出唯一正确的那一个时用 Rx 逐个蹲守是做不完的用 CAD 至少能在一秒量级内收工。还有一层工程上的好处CAD 期间接收机只开一个符号周期其余时间射频前端是关的。对电池节点来说这意味着守听这件事的占空比可以做到极低——唤醒模式那一章的所有时序设计本质上都是在利用这一点。3. 扫描模式复现两个状态机如何互换探针帧扫描模式解决的是双方都不知道对方具体参数只知道一个范围的场景。思路是把参数序列拆成两半监听方在自己的序列上轮询 CAD嗅探方在自己的序列上发探针帧并同时自检。谁先撞上谁就先开口。3.1 射频参数序列的构造与 S2 ⊆ S1 的硬约束两个序列的关系是这套方法的地基嗅探方的序列 S2 必须是监听方序列 S1 的子集。原因很直白——监听方的 CAD 只在 S1 里的参数上蹲守如果嗅探方某个参数不在 S1 里那个参数无论发多少次都不会有人应答白烧电。# 构造两个序列S2 必须严格取 S1 的子集且按命中概率排序 S1 [ (470_300_000, 9, 125_000), # P1 (470_300_000, 10, 125_000), # P2 (470_300_000, 11, 125_000), # P3 (471_500_000, 9, 125_000), # P4 (471_500_000, 10, 125_000), # P5 (471_500_000, 11, 125_000), # P6 ] # 现场统计里 P4 命中率最高所以排在嗅探序列第一位缩短平均耗时 S2 [S1[3], S1[1], S1[5]] assert all(p in S1 for p in S2), S2 必须是 S1 的子集 print(len(S1), len(S2)) # 6 3参数说明三维元组的顺序固定为频率, 扩频因子, 带宽三个值都要带上只写频率会导致带宽不一致的组合同样进序列白白拉长扫描轮次。子集断言不是形式主义现场改参数的时候最容易犯的错就是两边序列各改各的最后变成能通但很玄学。3.2 第一模块侧Px 自增、CadDetected 判定与 x 回绕监听方的逻辑是一个不会结束的循环取序列里的一个参数、初始化 CAD、等 CadDone、判 CadDetected。命中就回一个探针帧加一个信标帧然后切接收没命中就换下一个参数。序列走完了不是退出而是回绕到开头继续——因为对端可能才刚刚上电。// 第一模块监听方在 S1 上轮询 CAD命中后回 L1 探针帧 L2 信标帧 #define N_PARAM 6 static const rf_param_t S1[N_PARAM] { {470300000, 9, 125000}, {470300000, 10, 125000}, {470300000, 11, 125000}, {471500000, 9, 125000}, {471500000, 10, 125000}, {471500000, 11, 125000}, }; void module_a_scan(void) { int x 0; for (;;) { rf_param_t px S1[x]; radio_set_params(px.freq, px.sf, px.bw); radio_clear_irq(); radio_start_cad(); if (radio_wait_irq(IRQ_CAD_DONE, cad_timeout_ms(px.sf, px.bw))) { if (radio_get_irq(IRQ_CAD_DETECTED)) { radio_tx_frame(L1); // 回第一 CAD 嗅探帧 radio_tx_frame(L2); // 紧跟信标帧 radio_rx_continuous(FIRST_RX_TIMEOUT_MS); // 同参数进接收 if (radio_rx_ok()) return; // 收到应用数据收工 } } x (x 1) % N_PARAM; // x n 时回绕到 1继续下一轮 } }几个点值得说明。cad_timeout_ms()应该按 SF 和 BW 算出来而不是常量否则 SF12 那几档会稳定超时。命中后先发 L1 再发 L2 是原文的顺序L1 的作用是让嗅探方也能用 CAD 确认一次对方真的在L2 才是携带必要信息的信标。切接收之后必须有超时原文里对应第一预设时间这个值我一般给到 200500ms——比一次完整数据帧的时长略长即可太长会让命中失败的那一轮拖得很慢。3.3 第二模块侧嗅探帧节奏与 y 越界退出嗅探方的动作是设参数、发 K1、自检 CAD、命中就切接收等信标没命中就换下一个参数。走完整个 S2 还没结果就判定本次嗅探失败并结束这一点和监听方的无限回绕不同也是原文里明确区分的。// 第二模块嗅探方在 S2 上发 K1 并自检命中后切接收等信标 #define M_PARAM 3 static const rf_param_t S2[M_PARAM] { {471500000, 9, 125000}, // Q1 {470300000, 10, 125000}, // Q2 {470300000, 11, 125000}, // Q3 }; bool module_b_sniff(void) { for (int y 0; y M_PARAM; y) { rf_param_t qy S2[y]; radio_set_params(qy.freq, qy.sf, qy.bw); radio_tx_frame(K1); // 先发一帧嗅探帧 radio_clear_irq(); radio_start_cad(); // 再自检等对方回 L1 if (!radio_wait_irq(IRQ_CAD_DONE, cad_timeout_ms(qy.sf, qy.bw))) continue; if (!radio_get_irq(IRQ_CAD_DETECTED)) continue; radio_rx_continuous(SECOND_RX_TIMEOUT_MS); // 用 Qy 进接收 if (radio_rx_frame(beacon) beacon.type L2) { radio_tx_app_data(); // 嗅探成功发应用数据 return true; } } return false; // y m嗅探失败结束本次嗅探 }参数上要注意SECOND_RX_TIMEOUT_MS和监听方的FIRST_RX_TIMEOUT_MS的关系。嗅探方是在发出 K1 之后才开始等信标的而监听方要先收 K1 再发 L1 和 L2中间的帧间隔、处理延迟都算在嗅探方的等待窗口里。经验值上嗅探方的接收超时要比监听方短一些300ms 左右比较合适——等太久没有意义对方如果命中了信标几乎是在毫秒级内回来的。3.4 超时窗口第一/第二预设时间怎么给两个超时值不是随手填的常数它们对应的是这一轮判定失败要花多久。给短了本来能成功的轮次被判死给长了失败轮次拖慢整体。一个可用的算法是先算出该参数下的单包时长再乘以一个系数。def symbol_time(sf, bw): return (2 ** sf) / bw # 秒 def cad_timeout_ms(sf, bw): tsym symbol_time(sf, bw) return int(tsym * 1000 * 2) 10 # 两个符号周期 10ms 处理余量 def rx_timeout_ms(payload_bytes, sf, bw, preamble8): tsym symbol_time(sf, bw) n_sym preamble (payload_bytes * 8) / sf * 1.25 # 粗略估算 return int(tsym * 1000 * (n_sym 4)) print(cad_timeout_ms(11, 125_000)) # SF11/125k print(rx_timeout_ms(20, 9, 125_000)) # SF9/125k 收 20 字节第一组结果大概在 40ms 上下第二组在 300ms 上下。这两个数直接决定失败一轮的成本也就决定了序列长度还能不能撑得住。如果你把 S1 做到 18 个组合、每轮平均 100ms走完一轮就是 1.8 秒——这已经超出大多数低功耗场景能接受的范围了所以前面说的序列裁剪不是优化项是前提条件。4. 唤醒模式复现T1/T2/T3/T4 四个时刻怎么排唤醒模式和扫描模式的差别在于信息不对称的方向反过来了这次嗅探方知道对方大概什么时候会醒也知道唤醒后对方用什么参数来做检测。目标变成用最小的唤醒窗口把对方叫醒并让它收到数据。4.1 时序不等式的推导为什么信标必须晚于 T4Td原文给的约束是 T2 T1 T4 ≤ T3 T4 Td。逐个拆开看T2 到 T3 是嗅探方连续发射嗅探帧的窗口T3 时刻它切到接收模式。T1 是唤醒方开始做 E 次 CAD 检测的时刻必须落在 T2 之后否则唤醒方开机的时候嗅探方还没开始发第一轮检测必然空转。T4 是唤醒方实际检测到嗅探帧、产生 CadDetected 的时刻它天然满足 T1 T4并且不可能晚于 T3——晚于 T3 说明嗅探方已经停了那个前导码根本不存在。最关键的是最后一段T3 T4 Td。Td 是唤醒方从最后一次命中到发出信标之间的固定延迟。这个不等式的物理含义是——信标必须在嗅探方切到接收模式之后才发出去否则嗅探方还在发射收不到。提示这条不等式是整个唤醒模式的命门。Td 给短了信标发得比嗅探方切接收还早表现为CAD 能测到、数据永远收不到排查时极难定位。反推 Td 的取值范围因为 T4 ≤ T3所以 Td 至少要为 0实际必须留出保护余量也就是 Td ≥ (T3 − T4) 保护时间。而 T4 可能非常接近 T3嗅探方最后一帧撞上了所以工程上 Td 一般按嗅探方窗口长度来配而不是按剩余时间来配。4.2 嗅探端T2 到 T3 连续发射与 T3 切换接收嗅探端的动作很朴素在窗口内尽量密集地发到点就切接收。// 嗅探端在 [T2, T3] 内连发嗅探帧 JT3 时刻转入接收等信标 void module_b_wake_burst(uint32_t t2, uint32_t t3) { radio_set_params(P0.freq, P0.sf, P0.bw); wait_until(t2); uint32_t now millis(); uint16_t seq 0; while (now t3) { radio_tx_frame((frame_t){ .type J, .seq seq }); now millis(); } radio_rx_continuous(BEACON_WAIT_MS); // T3 起进入接收 if (radio_rx_frame(beacon) beacon.type L2) { radio_tx_app_data(); } }这里的参数 P0 是固定的不再是序列——唤醒模式下双方已经约好了同一个射频参数不需要扫描。seq字段用来让唤醒方区分不同的嗅探帧虽然 CAD 本身不关心载荷内容但调试时靠它判断到底收到了几帧。BEACON_WAIT_MS要比 Td 加一个包的时长更长否则信标还在路上就超时了这一条和 4.1 的不等式是配套的。还有一个容易忽略的细节窗口长度 T3 − T2 必须大于发一帧 J 的时长 一次切换的余量。如果窗口只够发半帧最后一帧会被截断截断的帧前导码可能不完整CAD 照样能测到因为 CAD 只看前导码但后面切接收时会因为包不完整而丢包——这就是典型的看起来成功了实际一帧数据都没收到。4.3 唤醒端E 次 CAD 检测、T4 刷新与 Td 到期发信标唤醒端的核心是检测到就续命每命中一次就把 T4 往后推并且再补 E 次检测这样只要嗅探端还在发唤醒时刻就一直在往后延。// 唤醒端T1 起做 E 次 CAD命中后刷新 T4 并再补 E 次T4Td 到点发信标 void module_a_wake_detect(uint32_t t1, uint32_t td, int e) { wait_until(t1); radio_set_params(P0.freq, P0.sf, P0.bw); uint32_t t4 0; bool hit false; for (int i 0; i e; i) { radio_clear_irq(); radio_start_cad(); if (radio_wait_irq(IRQ_CAD_DONE, cad_timeout_ms(P0.sf, P0.bw)) radio_get_irq(IRQ_CAD_DETECTED)) { t4 millis(); // 记录 CadDetected 时刻 hit true; i 0; // 命中就重新计数等价于“再补 E 次” } } if (!hit) return; // E 次全空本次嗅探结束 while ((millis() - t4) td) { /* 极浅休眠 */ } radio_tx_frame(L2); // T4 Td 到点以 P0 发信标 radio_rx_continuous(APP_RX_MS); }i 0这一行是原逻辑里再进行 E 次 CAD 检测的直译但它在实现上有个明显风险如果信道上持续有同参数前导码比如正好有另一个设备在发这个 for 循环永远退不出去。血泪经验是必须加一个总次数上限或总时长上限超出就强制跳出。const int MAX_CAD_ROUNDS 64; // 兜底上限防止死循环 int rounds 0; ... if (rounds MAX_CAD_ROUNDS) break;这个上限值不需要很精确它的作用只是把卡死变成失败退出。没有这一行的版本在现场一旦遇到干扰源整个节点就再也不发信标也不进休眠电池两小时耗光。4.4 一张参数表TS / E / Td / T3−T2 的取值区间符号含义典型取值约束与影响TS唤醒端 CAD 检测启动周期2001000ms越小越灵敏功耗越高E单次唤醒的 CAD 检测次数13超过 3 次基本是白烧电T3 − T2嗅探端连续发射窗口50200ms要容纳至少 35 帧完整嗅探帧Td命中到发信标的固定延迟≥ (T3 − T4) 1 包时长配小了信标必丢BEACON_WAIT_MS嗅探端等信标窗口Td 2 包时长与 Td 联动不能独立调表里最需要联调的是 Td 和 BEACON_WAIT_MS 这一对。实际调试时我的做法是把 Td 从大到小扫一遍先给一个明显偏大的值比如 300ms确认能通再以 20ms 为步长往下压压到开始丢包就回退两档。这个值对温度、晶振偏差敏感别指望一次调完能用一整年。E 的取值也要说清楚。E 加大确实能提高命中概率但每次 CAD 都是一次 PLL 锁定加一次相关运算SF12 下单次就要 30ms 以上E5 意味着一次唤醒里至少 150ms 的活跃时间。低占空比设计里这个数字直接决定电池寿命。我通常把 E 控制在 13靠 TS 拉长周期来省电而不是靠 E 大来抓信号。5. 避坑五个把嗅探成功率按到地板的细节5.1 前导码比 CAD 窗口短CadDetected 永远起不来现象嗅探方确定自己在发逻辑分析仪也看得到空中有波形但两边的 CadDetected 一次都没置起来。原因嗅探帧太短。CAD 的相关运算是对着一个符号周期的样本做的如果帧的前导码只有 4 个符号而对方 CAD 的采样窗口正好落在载荷段那一段里没有任何前导码波形相关必然失败。同理发送帧的前导码长度设得过短会显著降低被撞上的概率。解决把前导码长度拉到 8 个符号以上并且在嗅探帧的载荷后面留一小段填充让整个帧的空中时长至少覆盖两到三个 CAD 周期。改之前先量一下自己帧的实际空中时间别只看代码里的长度字段。5.2 双方驻留时间错拍参数明明在序列里也对不上现象S2 确实是 S1 的子集代码逐行对过没有错但成功率极低偶尔能通一次。原因监听方在 Px 上只停了一个 CAD 周期毫秒级测不到就立刻换下一个参数。而嗅探方发一帧 K1 本身就要几毫秒到几十毫秒发完还得再做一次 CAD。两边的节奏完全不在一个数量级上嗅探方发 K1 的时刻监听方往往已经在序列的另一头了。所谓命中变成了一个概率极低的事件。解决让监听方在每个参数上多停一会儿或者让嗅探方每个参数连发多帧。前者改for循环里的驻留计数后者在发 K1 的地方加个 35 次的小循环。我更倾向后者因为它不增加监听方的功耗只在嗅探方那一侧多花一点点时间。5.3 唤醒模式测得到前导码信标却永远收不到现象唤醒端的日志清楚显示 CadDetected 触发了、T4 也记上了但嗅探端从头到尾没收到 L2。原因Td 配小了信标在嗅探端还没切到接收模式的时候就发完了。这个坑在 4.1 的不等式里已经写过但现场特别容易踩因为 T4 每次都不一样——如果最后一次命中恰好发生在 T3 前一瞬间留给 Td 的余量就几乎为零。解决Td 不要按平均剩余时间算按最坏情况算也就是按 T3 − T4 0 来算取 Td ≥ 一个完整嗅探窗口。宁可多等几十毫秒也不要出现测得到收不到这种最难查的半成功状态。5.4 只对齐 SF 不对齐 BW换台设备就失联现象实验室里同一个批次的两块板子怎么都能通现场换成不同批次的模组参数序列完全一样成功率直接掉到零。原因序列里只写了频率和扩频因子带宽那一维是隐含的默认值。不同批次的模组出厂默认带宽可能不同或者封装库里的初始化顺序把带宽覆盖掉了。带宽不一致时前导码的符号速率不同相关运算直接不成立CAD 一点反应都没有。解决把带宽显式写进三元组并且在每次切参数之后回读配置寄存器确认写入生效。这一步看起来多余但它能把配了但没生效这类问题挡在调试阶段而不是留到现场。5.5 为了省电把 E 调大功耗反而翻倍现象按多检测几次更容易被叫醒的思路把 E 从 1 调到 8结果待机电流没降平均功耗还涨了一大截。原因E 次 CAD 是串行执行的每次都要重新锁 PLL、重新采样、重新做相关运算。E8 在 SF11 下意味着一次唤醒里超过 200ms 的射频活跃时间而唤醒周期本身可能才 1 秒。占空比从 2% 飙到 20%省电设计直接失效。解决E 保持小值13靠拉长 TS 和优化序列顺序来提升整体命中率。真正影响成功率的不是单次检测几次而是对方发的时候你到底在不在听——这个问题由时序设计解决不由 E 解决。6. 把能通变成可度量嗅探成功率与首包时延的量化方法这套东西最容易出问题的地方是它有时能通。断断续续的成功会让人误以为是干扰然后花几天时间去查天线和电源。我的做法是先把它变成两个可测的数字再谈优化。6.1 成功率与首包时延的两个统计口径第一个指标是嗅探成功率固定一组已知参数重复 N 次N 至少 200统计从嗅探方发出第一帧 K1 开始到收到应用数据首字节为止的成功次数占比。这个数字要在 90% 以上才算可用低于 80% 基本说明时序有余量不足的地方。第二个指标是首包时延成功那部分的耗时分布看的是中位数和 P95而不是平均值。平均值会被少数极端值带偏而低功耗场景真正在意的是 P95——最慢的那 5% 决定了你的唤醒窗口要开多大。import statistics def summarize(latencies_ms, attempts): ok len(latencies_ms) s sorted(latencies_ms) return { success_rate: ok / attempts, median_ms: statistics.median(s), p95_ms: s[int(len(s) * 0.95) - 1], max_ms: s[-1], } print(summarize([85, 120, 96, 210, 143, 98, 130], 10))参数说明attempts是总尝试次数而不是成功次数成功率的分母必须用总次数否则会把失败样本悄悄丢掉。p95_ms的取值方式在样本量小时会偏保守样本少于 100 的时候建议直接看max_ms。这套统计我在每次改完时序参数后都跑一遍前后对比比感觉好像好了一点可靠得多。6.2 参数兜底从全量穷举到按 SNR 排序的热缓存序列顺序对平均耗时的影响比想象中大。全量穷举时平均要扫一半序列但如果把上次成功过的参数排到最前面第二次上电往往第一发就命中。具体做法是留一小块非易失存储存最近若干次成功的三元组按成功次数排序每次嗅探开始时先按缓存顺序试缓存全失败再回落到完整序列。缓存不用大4 条就够写次数也能控制住。这个改动不涉及任何时序调整纯粹是把上次的答案当作先验实测能把首包时延的中位数压掉一大截。另外值得加的是失败计数。每一轮嗅探结束时把走到第几个参数才成功记下来连续多轮都停在序列尾部说明序列的前半段全是无效组合该裁剪了。这个计数比任何日志都好用因为它直接告诉你是谁在拖慢平均时间。最后说一个教训。早期我搭这类双端对拍的东西习惯直接上代码边跑边调觉得时序参数试试就知道了。结果有一次在现场卡了整整两天最后发现是 Td 比 T3 − T4 小了 15ms——一个在任何日志里都不会出现的数字。从那以后我每次做这类东西都强制先在纸上把时间轴画出来把 T1、T2、T3、T4、T4Td 五个点挨个标上保护余量单独写成一行确认不等式成立之后才允许自己打开编辑器。这个习惯救过我很多次也希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →