第2章:参数编码基础——二进制与十六进制表示法、位与字节的概念、大端与小端字节序、有符号与无符号整数

各位同学,欢迎来到《FDR飞行数据参数编码与解码实战课程》的第二讲。

说实话,很多刚入行的工程师,一上来就盯着ARINC 717或者429的协议手册看,结果被那些密密麻麻的二进制位给整懵了。我当年也是这样,第一次看到FDR原始数据流时,满屏的0和1,完全不知道从哪下手。

这一章,咱们就把地基打牢。你想想看,飞机上几百个参数——高度、空速、航向、发动机转速——最终都要被编码成一串串二进制数,存进黑匣子。不懂这些基础,后面解码就是空中楼阁。

2.1 二进制与十六进制:飞行数据的“母语”

飞机上的计算机,说白了只认两种状态:高电平和低电平。这就是二进制的物理基础——0和1。

但人不一样。让你看一串“1011010110101101”,你肯定头晕。所以工程师们发明了十六进制,作为二进制的人性化“翻译”。

为什么是16?因为2的4次方等于16。每4个二进制位,正好对应1个十六进制位。这个对应关系,我建议你刻在脑子里:

二进制 十六进制 二进制 十六进制
0000 0 1000 8
0001 1 1001 9
0010 2 1010 A
0011 3 1011 B
0100 4 1100 C
0101 5 1101 D
0110 6 1110 E
0111 7 1111 F

举个例子。FDR里经常出现的参数“气压高度”,原始数据可能是这样的:

二进制:1100 0011 1010 0101
十六进制:0xC3A5

你看,用十六进制写,是不是清爽多了?我个人习惯,在调试FDR数据时,永远先用十六进制看原始帧。一眼就能看出数据有没有明显的异常——比如全是0xFF或者0x00,那多半是传感器掉线了。

小技巧:Python里用 hex() 函数可以把整数转成十六进制字符串。用 bin() 转成二进制。调试时非常方便。

2.2 位与字节:数据的最小单元

位(bit)是计算机里最小的数据单位,非0即1。字节(byte)呢?8个位组成一个字节。

为什么是8位?历史原因,IBM System/360定的规矩,后来就成了行业标准。FDR数据帧里,所有参数的长度都是字节的整数倍——至少我见过的都是这样。

一个字节能表示多少种状态?2的8次方,256种。范围是0到255(无符号),或者-128到127(有符号)。

咱们来看个实际例子。FDR里记录“襟翼位置”这个参数,通常只用1个字节。为什么?因为襟翼角度就那么几个档位:0、1、2、3、4、5。一个字节绰绰有余。

# Python演示:一个字节能存什么
import struct

# 襟翼位置:假设5档
flap_position = 3
# 编码成一个字节
encoded = struct.pack('B', flap_position)  # 'B' 表示无符号字节
print(f"编码后:{encoded.hex()}")  # 输出:03

# 解码
decoded = struct.unpack('B', encoded)[0]
print(f"解码后:{decoded}")  # 输出:3

嗯,这里要注意。有些老旧的FDR系统,参数长度可能不是整字节。比如某个参数只用了10个位。这时候就要用到“位操作”了,咱们后面章节会细讲。

2.3 大端与小端字节序:谁先谁后的问题

这个问题,我当年踩过坑。有一次解码一个发动机参数,怎么算都不对。折腾了半天,发现是字节序搞反了。

什么叫字节序?就是一个多字节数据,在内存里是怎么排列的。

  • 大端(Big-Endian):高位字节在前,低位字节在后。就像我们写数字“1234”,先写千位1,再写百位2……
  • 小端(Little-Endian):低位字节在前,高位字节在后。Intel的CPU就是这种。

举个例子。十六进制数 0x12345678,在内存里:

地址 大端存储 小端存储
0x00 0x12 0x78
0x01 0x34 0x56
0x02 0x56 0x34
0x03 0x78 0x12

FDR数据里,大部分采用大端字节序。为什么?因为航空电子系统讲究“直观”。你读原始数据时,大端排列跟人类阅读习惯一致。但也不绝对——有些机载计算机用PowerPC架构,它天生就是大端。而x86架构的PC机是小端。所以你在PC上解析FDR数据时,必须做字节序转换。

避坑指南:我曾经在解析某型飞机的QAR数据时,发现空速参数总是差一个数量级。查了两天,最后发现是字节序搞反了。FDR文档里写的是“Motorola格式”,其实就是大端。而我的解析代码默认用了小端。从那以后,我拿到任何FDR数据,第一件事就是确认字节序。

Python里怎么处理?用 struct 模块:

import struct

# 假设FDR原始数据里,空速是2字节大端
raw_data = bytes([0x01, 0x2C])  # 十六进制:01 2C

# 大端解码
airspeed_big = struct.unpack('>H', raw_data)[0]  # '>H' 表示大端无符号短整型
print(f"大端解码:{airspeed_big}")  # 输出:300

# 小端解码(错误示范)
airspeed_little = struct.unpack('<H', raw_data)[0]  # '<H' 表示小端
print(f"小端解码:{airspeed_little}")  # 输出:11265,完全不对!

2.4 有符号与无符号整数:正与负的博弈

飞行数据里,有些参数天生就是非负的。比如高度(海平面以上)、空速、发动机转速。这些用无符号整数就够了。

但有些参数可能是负的。比如垂直速度——飞机下降时就是负值。还有迎角、侧滑角,都可能出现负值。这时候就需要有符号整数。

计算机里怎么表示负数?用的是“补码”表示法。简单说:

  • 正数的补码就是它本身
  • 负数的补码:绝对值取反,再加1

举个例子。用1个字节表示-5:

  1. 5的二进制:0000 0101
  2. 取反:1111 1010
  3. 加1:1111 1011(这就是-5的补码)

你想想看,为什么用补码?因为CPU做减法时,可以直接用加法器完成。比如计算 3 - 5,CPU会算 3 + (-5) 的补码。省电路,效率高。

FDR数据里,有符号整数最常见的是16位和32位。咱们看个实际例子:

import struct

# 垂直速度,单位英尺/分钟,有符号16位
# 假设原始数据是 0xFF38
raw = bytes([0xFF, 0x38])

# 当作无符号解码
unsigned_val = struct.unpack('>H', raw)[0]
print(f"无符号解读:{unsigned_val}")  # 输出:65336

# 当作有符号解码
signed_val = struct.unpack('>h', raw)[0]  # '>h' 表示大端有符号短整型
print(f"有符号解读:{signed_val}")  # 输出:-200

# 这才是真实的垂直速度:下降率200英尺/分钟
重要提醒:解码FDR参数时,一定要搞清楚文档里定义的是有符号还是无符号。搞错了,数据就全乱了。我见过有人把下降率-200英尺/分钟,解码成65336英尺/分钟——这都快到平流层了,明显不对。

2.5 实战:用Python验证这些概念

光说不练假把式。咱们写个小程序,把这些概念串起来:

def decode_fdr_parameter(raw_bytes, is_signed, byte_order='big'):
    """
    解码FDR参数
    :param raw_bytes: 原始字节数据,bytes类型
    :param is_signed: 是否有符号,bool
    :param byte_order: 字节序,'big' 或 'little'
    :return: 解码后的整数值
    """
    if byte_order == 'big':
        if is_signed:
            # 大端有符号
            return int.from_bytes(raw_bytes, byteorder='big', signed=True)
        else:
            # 大端无符号
            return int.from_bytes(raw_bytes, byteorder='big', signed=False)
    else:
        if is_signed:
            # 小端有符号
            return int.from_bytes(raw_bytes, byteorder='little', signed=True)
        else:
            # 小端无符号
            return int.from_bytes(raw_bytes, byteorder='little', signed=False)

# 测试:气压高度,2字节,无符号,大端
altitude_raw = bytes([0x0F, 0xA0])  # 十六进制:0FA0
altitude = decode_fdr_parameter(altitude_raw, is_signed=False, byte_order='big')
print(f"气压高度:{altitude} 英尺")  # 输出:4000

# 测试:垂直速度,2字节,有符号,大端
vs_raw = bytes([0xFF, 0x38])  # 十六进制:FF38
vs = decode_fdr_parameter(vs_raw, is_signed=True, byte_order='big')
print(f"垂直速度:{vs} 英尺/分钟")  # 输出:-200

这段代码,我建议你保存下来。后面章节里,我们会不断扩展它。

好了,这一章的内容就到这里。二进制和十六进制是基础中的基础,位和字节是数据的最小单元,字节序决定了数据怎么排列,有符号无符号决定了数值范围。这四个概念,是解码FDR数据的四根柱子。下一章,咱们开始讲真正的FDR数据帧结构。

记住,搞航空电子,细节决定成败。一个位搞错,可能就导致错误的飞行数据记录。咱们慢慢来,把基础打扎实。