4. SECS 消息帧格式:Message Header、Data Block、Block Count 与长度字段解析

SECS-I 数据链路层以固定长度的 Message Header(消息头) 作为每个数据块的起始标识,其后可跟随可变长度的 Data Block(数据块)。理解帧格式是分析 CRC 校验错误与链路层异常的基础——任何字段的错位、长度不匹配或块计数异常,都会直接导致 CRC 计算范围错误或接收端解析失败。

4.1 消息帧整体结构

一个完整的 SECS-I 消息帧由以下三部分组成:

组成部分 长度(字节) 说明
Message Header 10 固定长度,包含设备ID、控制位、块计数、系统字节等
Data Block 0~N 可变长度,承载 SECS-II 消息内容(仅多块消息的后续块可省略)
CRC 校验码 2 覆盖 Header + Data Block 的 CRC-16 校验值
关键点: CRC 校验范围包括整个 Message Header 和 Data Block,但不包括 CRC 字段本身。接收端在计算 CRC 时必须使用与发送端相同的多项式(0x1021)和初始值(0xFFFF)。
SECS-I 消息帧结构 Message Header (10 字节) 设备ID | 控制位 | 块计数 | 系统字节 Data Block (0~N 字节) SECS-II 消息内容(可选) CRC (2 字节) CRC-16 CRC 校验覆盖范围(Header + Data Block) 注意:CRC 字段本身不参与校验计算

4.2 Message Header 字段详解

10 字节的 Message Header 是 SECS-I 帧的核心控制结构,每个字段都有明确的语义和约束:

字节偏移 字段名称 长度(bit) 说明
0 Device ID(高字节) 8 设备标识符高 8 位
1 Device ID(低字节) 8 设备标识符低 8 位,共 16 位设备地址
2 Control Byte 8 控制位:bit7=R-bit, bit6=W-bit, bit5~0=保留
3 Block Count(高字节) 8 消息总块数高 8 位
4 Block Count(低字节) 8 消息总块数低 8 位,共 16 位块计数
5 System Byte(高字节) 8 系统字节高 8 位
6 System Byte(中高字节) 8 系统字节中高 8 位
7 System Byte(中低字节) 8 系统字节中低 8 位
8 System Byte(低字节) 8 系统字节低 8 位,共 32 位系统 ID
9 Length Byte 8 Data Block 长度(字节数),0~254 有效
常见陷阱: Length Byte 字段值为 0 时表示该帧不包含 Data Block(仅用于多块消息的中间块或控制帧)。但 CRC 校验仍然覆盖整个 Header(10 字节),此时 Data Block 长度为 0,CRC 仅对 Header 计算。

4.3 Control Byte 与 R/W 位

Control Byte 的第 7 位(R-bit)和第 6 位(W-bit)决定了消息的传输方向与应答要求:

  • R-bit(Reply Expected):置 1 表示发送方期望接收方回复确认消息;置 0 表示无需回复。
  • W-bit(Wait):置 1 表示发送方在收到回复前将保持等待状态;置 0 表示不等待。

R-bit 与 W-bit 的组合定义了四种消息类型:

R-bit W-bit 消息类型 典型场景
0 0 无回复、不等待 单向通知、心跳
0 1 无回复、但等待 较少使用
1 0 期望回复、不等待 查询请求
1 1 期望回复、等待 标准事务消息
Control Byte 位结构 bit7 R-bit bit6 W-bit bit5 ~ bit0 保留位(必须为 0) R-bit=1 且 W-bit=1:标准事务消息,发送方等待回复并期望确认 R-bit=0 且 W-bit=0:单向消息,无需回复也不等待

4.4 Block Count 与多块消息

Block Count 字段(2 字节,16 位无符号整数)表示整个消息被分割成的总块数。对于单块消息,Block Count = 1;对于多块消息,Block Count > 1。

多块消息的传输规则:

  1. 第一块:必须包含完整的 Message Header 和 Data Block(Data Block 包含 SECS-II 消息头)。
  2. 中间块:仅包含 Message Header,Data Block 长度为 0(Length Byte = 0)。
  3. 最后一块:包含 Message Header 和剩余的 Data Block 数据。
调试技巧: 当接收端检测到 Block Count 与已接收块数不一致时,应丢弃整个消息并触发链路层重传。CRC 校验错误常伴随块计数异常——例如中间块误传了非零 Data Block,导致 CRC 计算范围错误。
多块消息传输示例(Block Count = 3) 发送方 接收方 块1:Header + Data (含SECS-II头) 块2:Header 仅 (Length=0) 块3:Header + Data (剩余数据) 每个块独立计算 CRC,接收端按 Block Count 重组完整消息

4.5 Length Byte 与 Data Block 长度约束

Length Byte 位于 Message Header 的第 9 字节,其值表示紧随 Header 之后的 Data Block 的字节数。约束规则如下:

  • 取值范围:0 ~ 254(0x00 ~ 0xFE)。
  • 值 255(0xFF):保留值,不可用于正常消息。
  • 值 0:表示无 Data Block,常见于多块消息的中间块或控制帧。
  • 值 1 ~ 254:表示 Data Block 的实际长度。
CRC 校验与 Length Byte 的关联: 接收端首先读取 Length Byte 确定 Data Block 长度,然后从 Header 起始位置开始,读取 (10 + Length) 字节作为 CRC 校验的输入数据。如果 Length Byte 因传输错误被篡改,接收端将使用错误的长度计算 CRC,导致校验失败。这是数据链路层最常见的 CRC 错误根源之一。

4.6 帧格式与 CRC 校验的关联分析

理解帧格式后,可以归纳出以下与 CRC 校验错误直接相关的异常场景:

异常场景 帧格式表现 CRC 校验结果
Length Byte 被篡改 Data Block 长度与真实数据不匹配 CRC 计算范围错误,校验失败
Block Count 不一致 多块消息的块数声明与实际不符 接收端重组时 CRC 跨块校验失败
Control Byte 位翻转 R/W 位错误导致消息类型误判 CRC 校验通过但协议层异常
System Byte 重复 系统 ID 冲突导致消息混淆 CRC 校验通过但事务层异常
Header 长度偏移 接收端未按 10 字节解析 Header CRC 计算起点错误,校验失败
工程建议: 在实现 SECS-I 驱动时,应优先验证 Length Byte 的合法性(0~254),再计算 CRC。如果 Length Byte 超出范围,应直接丢弃帧并记录链路层错误,避免无效的 CRC 计算消耗 CPU 资源。

4.7 代码示例:解析 Message Header 并验证长度

以下 C 语言代码演示如何从原始字节流中解析 SECS-I Message Header,并验证 Length Byte 的合法性:

#include <stdint.h>
#include <stdbool.h>

#define SECS_HEADER_LEN 10
#define MAX_DATA_LEN   254

typedef struct {
    uint16_t device_id;
    uint8_t  control_byte;
    uint16_t block_count;
    uint32_t system_bytes;
    uint8_t  length_byte;
} SecsMessageHeader;

bool parse_secs_header(const uint8_t *buffer, SecsMessageHeader *header) {
    if (buffer == NULL || header == NULL) {
        return false;
    }

    // 解析 Device ID(大端序)
    header->device_id = (buffer[0] << 8) | buffer[1];

    // 解析 Control Byte
    header->control_byte = buffer[2];

    // 解析 Block Count(大端序)
    header->block_count = (buffer[3] << 8) | buffer[4];

    // 解析 System Bytes(大端序,4字节)
    header->system_bytes = ((uint32_t)buffer[5] << 24) |
                           ((uint32_t)buffer[6] << 16) |
                           ((uint32_t)buffer[7] << 8)  |
                           (uint32_t)buffer[8];

    // 解析 Length Byte
    header->length_byte = buffer[9];

    // 验证 Length Byte 合法性
    if (header->length_byte > MAX_DATA_LEN) {
        return false;  // 非法长度,触发 CRC 校验前拒绝
    }

    return true;
}
代码要点: 在调用 CRC 校验函数之前,先通过 parse_secs_header() 验证 Length Byte 的合法性。如果返回 false,应直接丢弃该帧并递增链路层错误计数器,避免无效的 CRC 计算。