3. UWB MAC层广播机制详解:广播帧格式、地址过滤、序列号管理
说到UWB定位,很多人一上来就盯着物理层那点带宽和脉冲。但我做了这么多年嵌入式,实话告诉你——丢包问题的根,十有八九在MAC层。广播机制要是没吃透,你天线调得再好、功率开得再大,该丢还是丢。
这一章,咱们就把UWB MAC层的广播机制掰开揉碎。我会从帧格式讲起,再到地址过滤和序列号管理。嗯,都是实战里天天打交道的东西。
3.1 广播帧格式:比你想象的要“瘦”
UWB的广播帧,说白了就是一根“瘦竹竿”。它不像数据帧那样带一堆负载,结构非常精简。我当年第一次看协议栈源码时还纳闷——就这么点东西?后来才明白,广播帧的核心使命是“快”和“准”,不是“多”。
标准的IEEE 802.15.4广播帧结构如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 帧控制(FCF) | 2 | 帧类型、寻址模式、安全使能等 |
| 序列号 | 1 | 用于去重和排序 |
| 目的PAN ID | 2 | 广播时通常为0xFFFF |
| 目的地址 | 2或8 | 广播地址0xFFFF或0xFFFFFFFFFFFFFFFF |
| 源地址 | 2或8 | 发送节点的ID |
| 辅助安全头 | 0-14 | 可选,广播帧一般不用 |
| 数据负载 | 可变 | 定位数据、时间戳等 |
| FCS | 2 | 帧校验序列 |
你看,广播帧的头部其实就那几样东西。但有个细节我特别想强调——目的地址字段。广播地址是0xFFFF(短地址模式)或0xFFFFFFFFFFFFFFFF(扩展地址模式)。这个值所有节点都能识别,不需要配对。
关键点:广播帧的FCF字段中,帧类型必须设置为“数据帧”(0b001),且寻址模式不能是“仅源地址”。我见过有人把寻址模式配错了,结果广播帧发出去,接收端直接丢弃——因为MAC层解析不到目的地址。
3.2 地址过滤:不是所有帧都值得处理
地址过滤,是MAC层的第一道防线。你想想看,一个UWB定位网络里可能有几十个基站,每个基站每秒发几百个广播帧。如果不做过滤,接收端的中断处理程序会被活活撑死。
IEEE 802.15.4的MAC层提供了三种过滤机制:
- PAN ID过滤:只接收与自己PAN ID匹配的帧。广播帧的PAN ID通常是0xFFFF,表示“所有PAN”。但如果你把接收节点的PAN ID设成特定值,它就会忽略广播帧——这是个坑。
- 地址类型过滤:短地址(2字节)还是扩展地址(8字节)?必须一致。我遇到过一种情况:基站用短地址广播,标签用扩展地址接收,结果标签死活收不到数据。
- 目的地址过滤:只有目的地址是广播地址或本机地址的帧才会被接收。
我曾经踩过的坑:有一次调试定位系统,发现某个标签总是收不到某个基站的广播。查了半天,原来是那个基站的PAN ID配成了0x0001,而标签的PAN ID是0x0002。广播帧的PAN ID是0xFFFF没错,但接收端MAC层会先检查PAN ID是否匹配——不匹配直接丢弃。所以,所有参与广播通信的节点,PAN ID必须一致,或者接收端关闭PAN ID过滤。
地址过滤的流程,我画了张图,你看一眼就明白了:
3.3 序列号管理:去重与排序的命根子
序列号(Sequence Number)只有1个字节,取值范围0~255。你别看它小,作用可大了。广播帧没有ACK确认机制,发出去就完了。那接收端怎么知道一个帧是不是重复的?靠的就是序列号。
我个人的习惯是,每个基站的序列号独立维护。也就是说,基站A和基站B可以同时发序列号为1的帧,这不冲突——因为接收端会根据源地址+序列号来去重。
实战技巧:序列号管理要处理“回绕”问题。当序列号从255增加到0时,你不能简单地认为这是一个旧帧。我一般会设置一个“窗口机制”——比如,如果新收到的序列号比上次收到的序列号大50以上(考虑回绕),就认为是新帧。具体窗口大小取决于你的发包频率和网络规模。
序列号去重的逻辑,我写了个伪代码,你感受一下:
// 伪代码:广播帧去重逻辑
#define SEQ_WINDOW 64
bool is_duplicate(uint8_t src_addr, uint8_t new_seq) {
uint8_t last_seq = seq_table[src_addr];
// 计算差值,考虑回绕
int8_t diff = (int8_t)(new_seq - last_seq);
if (diff > 0 && diff < SEQ_WINDOW) {
// 正常递增,新帧
seq_table[src_addr] = new_seq;
return false;
} else if (diff <= 0 && diff > -SEQ_WINDOW) {
// 旧帧或重复帧
return true;
} else {
// 超出窗口,可能是回绕或乱序
// 我建议保守处理:当作新帧
seq_table[src_addr] = new_seq;
return false;
}
}
这段代码我用了好多年,基本没出过问题。但有个前提——你的时钟要准。如果两个基站的时钟偏差太大,序列号递增速度不一样,接收端就会误判。我曾经在一个项目里遇到过:基站A的时钟快,序列号已经跑到200了;基站B的时钟慢,序列号才到50。接收端一看,基站B的50比上次记录的200小,直接当成旧帧丢了——结果就是基站B的数据永远上不了位。
解决方案:后来我加了一个“时间戳辅助去重”机制。不光看序列号,还看帧里的时间戳。如果时间戳比上次新,就算序列号回绕了也接收。这样就把时钟偏差的影响降到了最低。
3.4 广播帧的发送与接收流程
最后,我把广播帧的完整收发流程串一下。你对照着代码看,会更清楚。
发送端流程:
- 构造帧控制字段(FCF),设置帧类型为数据帧,寻址模式为短地址+目的地址。
- 获取当前序列号,写入帧头。序列号每次发送后自增1。
- 设置目的PAN ID为0xFFFF,目的地址为0xFFFF。
- 填入源地址(本机ID)。
- 填充数据负载(定位数据、时间戳等)。
- 计算并写入FCS。
- 调用物理层发送接口,直接发出去——不等待ACK。
接收端流程:
- 物理层收到帧,检查FCS。如果校验失败,直接丢弃。
- MAC层解析FCF,确认是广播帧。
- 执行地址过滤(PAN ID、地址类型、目的地址)。
- 提取源地址和序列号,执行去重判断。
- 如果判定为新帧,提交给上层(定位算法层)。
- 更新序列号表。
你看,整个流程其实不复杂。但每个环节都有坑。地址过滤配错了,丢包;序列号管理没处理好,丢包;FCS校验不过,还是丢包。说白了,UWB定位广播丢包,很多时候不是物理层的问题,而是MAC层这些“小细节”没做到位。