第四章 编码与解码技术:NRZ、曼彻斯特编码、汉明码、CRC校验

各位工程师朋友,大家好。我是老张,干遥测这行快十五年了。今天咱们聊聊编码与解码技术。说实话,这章内容看着基础,但我在项目里见过太多人在这上面栽跟头。你想想看,信号发出去收不回来,十有八九是编码没选对,或者校验没做好。

咱们先看一张图,把今天要讲的知识体系理清楚。

编码与解码技术知识体系 编码与解码技术 线路编码 NRZ(不归零码) 曼彻斯特编码 纠错编码 汉明码 CRC校验 核心目标:保证数据在传输过程中的正确性与可靠性 线路编码解决信号同步问题,纠错编码解决数据完整性问题

4.1 NRZ编码:最简单的,也是最容易出问题的

NRZ,全称是Non-Return-to-Zero,不归零码。说白了,就是高电平代表1,低电平代表0,就这么简单。我刚开始做遥测时,第一个项目用的就是NRZ。当时觉得这玩意儿太简单了,结果调试时发现接收端老是丢数据。

为什么会这样?因为NRZ有个致命弱点——没有时钟同步。你想想看,如果发送一串连续的1,电平一直保持高,接收端怎么知道发了多少个1?它只能靠自己的时钟去猜。一旦收发双方的时钟有偏差,数据就全乱了。

⚠️ 注意: NRZ编码在长串连续相同比特时,会丢失时钟同步信息。这是它的先天缺陷,不是靠调参数能解决的。

NRZ的优缺点我列个表,大家看得清楚些:

特性 优点 缺点
带宽效率 高,每个码元携带1比特信息
时钟同步 长串连续比特时丢失同步
直流分量 有直流分量,不利于变压器耦合
实现复杂度 极低,硬件实现简单

NRZ的编码规则很简单,看代码就明白了:

// NRZ编码示例
// 输入:data[] = {1, 0, 1, 1, 0, 0, 1}
// 输出:高电平表示1,低电平表示0

void nrz_encode(uint8_t *data, int len) {
    for (int i = 0; i < len; i++) {
        if (data[i] == 1) {
            set_pin_high();  // 输出高电平
        } else {
            set_pin_low();   // 输出低电平
        }
    }
}
💡 我的经验: 如果项目对带宽要求高,且数据本身有足够的跳变(比如经过扰码处理),NRZ还是可以用的。我在一个高速遥测项目中就这么干过,配合扰码器效果不错。

4.2 曼彻斯特编码:自带时钟的聪明方案

曼彻斯特编码解决了NRZ的同步问题。它的思路很巧妙:每个比特中间都有一次电平跳变。从高到低表示1,从低到高表示0。这样接收端就能从跳变中提取时钟信号。

我记得有一次做无人机遥测链路,信道干扰特别大,NRZ完全没法用。换成曼彻斯特编码后,问题迎刃而解。虽然带宽翻了一倍,但可靠性上去了,值!

// 曼彻斯特编码示例
// 规则:1 -> 高到低跳变,0 -> 低到高跳变

void manchester_encode(uint8_t *data, int len) {
    for (int i = 0; i < len; i++) {
        if (data[i] == 1) {
            set_pin_high();  // 前半周期高电平
            delay_half_bit();
            set_pin_low();   // 后半周期低电平
            delay_half_bit();
        } else {
            set_pin_low();   // 前半周期低电平
            delay_half_bit();
            set_pin_high();  // 后半周期高电平
            delay_half_bit();
        }
    }
}

关键点: 曼彻斯特编码每个比特都有跳变,所以接收端可以轻松恢复时钟。但代价是带宽需求翻倍——原来1Hz能传1比特,现在需要2Hz。

曼彻斯特编码还有个变种叫差分曼彻斯特编码。它看的是比特起始处有没有跳变:有跳变表示0,没有跳变表示1。这种编码抗干扰能力更强,我在一些工业遥测设备上见过。

4.3 汉明码:能纠错的"聪明码"

前面讲的都是线路编码,解决的是信号同步问题。但数据在传输过程中还可能出错,这就需要纠错编码了。汉明码就是其中经典的一种。

汉明码的核心思想是:在数据位中插入校验位,这些校验位能定位出错的位置。比如(7,4)汉明码,4位数据加3位校验位,能纠正1位错误。

我曾经在一个卫星遥测项目中用过汉明码。卫星信号经过大气层时会有闪烁,偶尔丢几个比特。用了汉明码后,大部分单比特错误都能自动纠正,省去了重传的麻烦。

// (7,4)汉明码编码示例
// 输入:4位数据 d1 d2 d3 d4
// 输出:7位码字 p1 p2 d1 p3 d2 d3 d4

void hamming_74_encode(uint8_t *data, uint8_t *code) {
    uint8_t d1 = data[0], d2 = data[1], d3 = data[2], d4 = data[3];
    
    // 计算校验位
    uint8_t p1 = d1 ^ d2 ^ d4;  // 校验位1
    uint8_t p2 = d1 ^ d3 ^ d4;  // 校验位2
    uint8_t p3 = d2 ^ d3 ^ d4;  // 校验位3
    
    // 组装码字
    code[0] = p1;
    code[1] = p2;
    code[2] = d1;
    code[3] = p3;
    code[4] = d2;
    code[5] = d3;
    code[6] = d4;
}
⚠️ 注意: 汉明码只能纠正1位错误,检测2位错误。如果信道误码率很高,汉明码就不够用了,得用更复杂的编码比如卷积码或LDPC码。

4.4 CRC校验:数据完整性的最后防线

CRC,循环冗余校验。这玩意儿在遥测系统里太常见了,几乎每个数据帧末尾都会带CRC校验码。它的原理是把数据看作一个多项式,用约定的生成多项式去除,余数就是CRC码。

CRC的检错能力很强,能检测出所有单比特错误、双比特错误、奇数个错误,以及长度不超过生成多项式阶数的突发错误。我习惯用CRC-16或CRC-32,具体看项目对误检率的要求。

// CRC-16计算示例(多项式 x^16 + x^15 + x^2 + 1)
uint16_t crc16_calculate(uint8_t *data, int len) {
    uint16_t crc = 0xFFFF;
    
    for (int i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;  // 0xA001是反转后的多项式
            } else {
                crc = crc >> 1;
            }
        }
    }
    
    return crc;
}
💡 避坑指南: 我曾经在一个项目中直接用软件计算CRC,结果发现速度跟不上遥测数据的码率。后来改用硬件CRC模块,速度提升了10倍。所以,如果码率超过1Mbps,建议用硬件实现。

CRC的生成多项式选择也有讲究。常用的有:

  • CRC-8:多项式0x07,用于小数据包
  • CRC-16:多项式0x8005,用于一般遥测帧
  • CRC-32:多项式0x04C11DB7,用于大数据块

我个人建议,遥测链路至少用CRC-16。CRC-8的检错能力在长数据帧面前有点不够看。

4.5 实际应用中的选择策略

讲了这么多,到底怎么选?我给大家一个参考:

  1. 信道质量好、带宽紧张:用NRZ + 扰码,配合CRC校验
  2. 信道有干扰、需要时钟同步:用曼彻斯特编码 + CRC
  3. 信道误码率较高、能容忍带宽开销:用汉明码 + 曼彻斯特编码
  4. 高速遥测、硬件资源充足:用NRZ + 硬件CRC + 卷积码

嗯,编码与解码技术就讲到这里。这些内容看着基础,但每个都是实战中绕不开的坎。下次调试遥测链路时,如果发现数据对不上,先检查编码方式选对了没有,再查CRC计算有没有问题。很多时候,问题就出在这些"简单"的地方。


专注资料整理