这是一篇来自于旧站的重置文章。

第四篇无敌了,1篇人工智能、1篇信息安全、2篇电子工程,我真的无敌了。

前言

加油!明天写!

2022年4月5日 1:45 am

2025年8月20日 0:47 am 开始补全!

这其实是一个很久之前的痛,我直到硕士毕业完成,我都没有弄明白这个 dump1090 是怎么操作的,我看着我的IQ信号不知所措(如果当时有AI、有ChatGPT估计一切都会大不相同。果然AI改变时代啊。) 我甚至发现这个 Github仓库现在有新的版本了,有人在维护:

  1. 最原始的版本: dump1090

  2. 有人维护的新版本: Dump1090

正文

1. 如何探测 ADS-B 报文?

一个一直围绕着我的问题就是,我拿到的是IQ信号,我要去转换成二进制的01信号,但是问题是,我这一段IQ信号,我怎么知道哪一段是一个完整的ADS-B报文呢?一段IQ信号里面应该有很多才对,但是如何去找开头、如何去找结束呢?

1.1 前导(preamble)检测

这个问题的核心就是 前导检测:靠前导检测来判定一帧的开始

Mode S/1090ES 的前导是4个脉冲,每个0.5 µs,后3个相对第一个脉冲的时间间隔分别是1.0 µs、3.5 µs、4.5 µs;数据块起点在第一个前导脉冲后8 µs。检测思路就是在幅度序列里找这个时间指纹

简单判据(幅度/相关法都行):

高:t=0、1.0、3.5、4.5 µs 有脉冲峰;

低:t=0.5、1.5、2.0、2.5、3.0、4.0 µs 等“应该没有脉冲”的位置能量较低;

通过阈值和相对幅度比值过滤伪检出。这一步常用匹配滤波/相关来做更稳。

1.2 位时钟与PPM判决(确定每个比特)

数据块从前导起点+8 µs对齐后,按照1 µs一位划窗;每位内前半0.5 µs与后半0.5 µs做能量比较:

前半有脉冲→比特1;后半有脉冲→比特0。相邻位若出现“后半(0)紧接前半(1)”时,两脉冲会合并成1 µs宽的单脉冲,积分比较同样适用。

1.3 用“长度”确定“结束”

结束信号的核心是 固定长度切断:ADS-B总长112帧(112 µs的数据块),加上8 µs前导检测,总计120 µs,然后这 0-120 µs就是整个报文,然后通过最后的(89-112 PI)做 CRC验证,看看是不是一段合法的ADS-B报文。

2. ADS-B 二进制报文结构化

一旦获取到ADS-B的二进制报文,其实结构化就很简单了

广泛意义上,参考这张表格就可以了:

Bit

No. bits

Abbreviation

Information

1–5

5

DF

Downlink Format

6–8

3

CA

Transponder capability

9–32

24

ICAO

ICAO aircraft address

33–88

56

ME

Message, extended squitter

(33–37)

(5)

(TC)

(Type code)

89–112

24

PI

Parity/Interrogator ID

更具体的 dump1090 里面都有

/* Capability table. */
char *ca_str[8] = {
    /* 0 */ "Level 1 (Survillance Only)",
    /* 1 */ "Level 2 (DF0,4,5,11)",
    /* 2 */ "Level 3 (DF0,4,5,11,20,21)",
    /* 3 */ "Level 4 (DF0,4,5,11,20,21,24)",
    /* 4 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on ground)",
    /* 5 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on airborne)",
    /* 6 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7)",
    /* 7 */ "Level 7 ???"
};

/* Flight status table. */
char *fs_str[8] = {
    /* 0 */ "Normal, Airborne",
    /* 1 */ "Normal, On the ground",
    /* 2 */ "ALERT,  Airborne",
    /* 3 */ "ALERT,  On the ground",
    /* 4 */ "ALERT & Special Position Identification. Airborne or Ground",
    /* 5 */ "Special Position Identification. Airborne or Ground",
    /* 6 */ "Value 6 is not assigned",
    /* 7 */ "Value 7 is not assigned"
};

/* ME message type to description table. */
char *me_str[] = {
};

char *getMEDescription(int metype, int mesub) {
    char *mename = "Unknown";

    if (metype >= 1 && metype <= 4)
        mename = "Aircraft Identification and Category";
    else if (metype >= 5 && metype <= 8)
        mename = "Surface Position";
    else if (metype >= 9 && metype <= 18)
        mename = "Airborne Position (Baro Altitude)";
    else if (metype == 19 && mesub >=1 && mesub <= 4)
        mename = "Airborne Velocity";
    else if (metype >= 20 && metype <= 22)
        mename = "Airborne Position (GNSS Height)";
    else if (metype == 23 && mesub == 0)
        mename = "Test Message";
    else if (metype == 24 && mesub == 1)
        mename = "Surface System Status";
    else if (metype == 28 && mesub == 1)
        mename = "Extended Squitter Aircraft Status (Emergency)";
    else if (metype == 28 && mesub == 2)
        mename = "Extended Squitter Aircraft Status (1090ES TCAS RA)";
    else if (metype == 29 && (mesub == 0 || mesub == 1))
        mename = "Target State and Status Message";
    else if (metype == 31 && (mesub == 0 || mesub == 1))
        mename = "Aircraft Operational Status Message";
    return mename;
}

3. 具体dump1090实现

3.1 信号幅度计算

首先,将原始的I/Q样本转换为幅度向量:

Modes.maglut = malloc(129*129*2);
for (i = 0; i <= 128; i++) {
    for (q = 0; q <= 128; q++) {
            Modes.maglut[i*129+q] = round(sqrt(i*i+q*q)*360);
    }
}

...

void computeMagnitudeVector(void) {
    // 计算幅度向量:SQRT(I^2 + Q^2)并重标定到0-255范围
    for (j = 0; j < Modes.data_len; j += 2) {
        int i = p[j]-127;
        int q = p[j+1]-127;
        if (i < 0) i = -i;
        if (q < 0) q = -q;
        m[j/2] = Modes.maglut[i*129+q];
    }
}

3.2 前导码检测

detectModeS 函数中,通过检查特定的幅度模式来识别Mode S前导码:

void detectModeS(uint16_t *m, uint32_t mlen) {
    unsigned char bits[MODES_LONG_MSG_BITS];
    unsigned char msg[MODES_LONG_MSG_BITS/2];
    uint16_t aux[MODES_LONG_MSG_BITS*2];
    uint32_t j;
    int use_correction = 0;

    /* The Mode S preamble is made of impulses of 0.5 microseconds at
     * the following time offsets: (Mode S前导码由0.5微秒的脉冲组成,时间偏移如下:)
     *
     * 0   - 0.5 usec: first impulse.(0 - 0.5 usec: 第一个脉冲)
     * 1.0 - 1.5 usec: second impulse.(1.0 - 1.5 usec: 第二个脉冲)
     * 3.5 - 4   usec: third impulse.(3.5 - 4 usec: 第三个脉冲)
     * 4.5 - 5   usec: last impulse. (4.5 - 5 usec: 最后一个脉冲)
     * 
     * Since we are sampling at 2 Mhz every sample in our magnitude vector
     * is 0.5 usec, so the preamble will look like this, assuming there is
     * an impulse at offset 0 in the array:
     *
     * 0   -----------------
     * 1   -
     * 2   ------------------
     * 3   --
     * 4   -
     * 5   --
     * 6   -
     * 7   ------------------
     * 8   --
     * 9   -------------------
     */

前导码检测的关键步骤:

  1. 检查前10个样本之间的关系 (这个模式检查确保信号具有Mode S前导码的特征波形。):

/* First check of relations between the first 10 samples
* representing a valid preamble. We don't even investigate further
* if this simple test is not passed. */
if (!(m[j] > m[j+1] &&
    m[j+1] < m[j+2] &&
    m[j+2] > m[j+3] &&
    m[j+3] < m[j] &&
    m[j+4] < m[j] &&
    m[j+5] < m[j] &&
    m[j+6] < m[j] &&
    m[j+7] > m[j+8] &&
    m[j+8] < m[j+9] &&
    m[j+9] > m[j+6]))
{
    if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
        m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
        dumpRawMessage("Unexpected ratio among first 10 samples",
            msg, m, j);
    continue;
}
  1. 检查脉冲之间的样本 :确保脉冲之间的样本幅度足够低

/* The samples between the two spikes must be < than the average
* of the high spikes level. We don't test bits too near to
* the high levels as signals can be out of phase so part of the
* energy can be in the near samples. */
high = (m[j]+m[j+2]+m[j+7]+m[j+9])/6;
if (m[j+4] >= high ||
    m[j+5] >= high)
{
    if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
        m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
        dumpRawMessage(
            "Too high level in samples between 3 and 6",
            msg, m, j);
    continue;
}
  1. 检查前导码后的空间 :确保前导码和实际数据之间的空间幅度足够低

/* Similarly samples in the range 11-14 must be low, as it is the
* space between the preamble and real data. Again we don't test
* bits too near to high levels, see above. */
if (m[j+11] >= high ||
    m[j+12] >= high ||
    m[j+13] >= high ||
    m[j+14] >= high)
{
    if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
        m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
        dumpRawMessage(
            "Too high level in samples between 10 and 15",
            msg, m, j);
    continue;
}

3.3 比特解码

一旦检测到有效的前导码,程序会解码后续的112个比特:

/* Decode all the next 112 bits, regardless of the actual message
* size. We'll check the actual message type later. */
errors = 0;
for (i = 0; i < MODES_LONG_MSG_BITS*2; i += 2) {
    low = m[j+i+MODES_PREAMBLE_US*2];
    high = m[j+i+MODES_PREAMBLE_US*2+1];
    delta = low-high;
    if (delta < 0) delta = -delta;

    if (i > 0 && delta < 256) {
        bits[i/2] = bits[i/2-1];
    } else if (low == high) {
        /* Checking if two adiacent samples have the same magnitude
            * is an effective way to detect if it's just random noise
            * that was detected as a valid preamble. */
        bits[i/2] = 2; /* error */
        if (i < MODES_SHORT_MSG_BITS*2) errors++;
    } else if (low > high) {
        bits[i/2] = 1;
    } else {
        /* (low < high) for exclusion  */
        bits[i/2] = 0;
    }
}

3.4 消息类型识别

解码比特后,程序会将比特打包成字节,并确定消息类型:

// 打包比特为字节
for (i = 0; i < MODES_LONG_MSG_BITS; i += 8) {
    msg[i/8] =
        bits[i]<<7 | 
        bits[i+1]<<6 | 
        bits[i+2]<<5 | 
        bits[i+3]<<4 | 
        bits[i+4]<<3 | 
        bits[i+5]<<2 | 
        bits[i+6]<<1 | 
        bits[i+7];
}

// 确定消息类型和长度
int msgtype = msg[0]>>3;
int msglen = modesMessageLenByType(msgtype)/8;

3.5 信号质量验证

程序会进行最终的幅度差异检查,以过滤掉随机噪声:

/* Last check, high and low bits are different enough in magnitude
* to mark this as real message and not just noise? */
// 计算平均幅度差异
delta = 0;
for (i = 0; i < msglen*8*2; i += 2) {
    delta += abs(m[j+i+MODES_PREAMBLE_US*2]-
                    m[j+i+MODES_PREAMBLE_US*2+1]);
}
delta /= msglen*4;

/* Filter for an average delta of three is small enough to let almost
* every kind of message to pass, but high enough to filter some
* random noise. */
// 过滤掉幅度差异太小的信号
if (delta < 10*255) {
    use_correction = 0;
    continue;
}

3.6 消息解码

最后,如果消息通过了前面的检查,程序会解码消息

/* If we reached this point, and error is zero, we are very likely
* with a Mode S message in our hands, but it may still be broken
* and CRC may not be correct. This is handled by the next layer. */
if (errors == 0 || (Modes.aggressive && errors < 3)) {
    struct modesMessage mm;

    /* Decode the received message and update statistics */
    // 解码消息
    decodeModesMessage(&mm,msg);

    ...

3.7 CRC验证

最后,验证CRC

/* Update statistics. */
if (mm.crcok || use_correction) {
    if (errors == 0) Modes.stat_demodulated++;
    if (mm.errorbit == -1) {
        if (mm.crcok)
            Modes.stat_goodcrc++;
        else
            Modes.stat_badcrc++;
    } else {
        Modes.stat_badcrc++;
        Modes.stat_fixed++;
        if (mm.errorbit < MODES_LONG_MSG_BITS)
            Modes.stat_single_bit_fix++;
        else
            Modes.stat_two_bits_fix++;
    }
}

总结

完成了,远古时期的不明白,虽然还有测试文件,但是由于我现在的Windows系统,这个是linux环境,就先不测试他提供的modes1.bin了。

参考

[1] dump1090
[2] Dump1090

立志做一个有趣的碳水化合物。