DSP tutorial: RTTY decoder using IIR filters

This example uses IIR filters instead of FFTing chunks, it works well even on a noisy channel.

It uses the signal flow discussed in this paper:

Note that this example only implements the demodulator, UART emulator and Baudot terminal blocks from the diagram.

After the first bandpass filter you get the following signal on a clean channel:

On a noisy channel it looks like this:

After calculating the RMS (squaring the signal) on the clean channel:

On a noisy channel:

After adding the two signal lines and lowpass filtering them on a clean channel:

On a noisy channel:

The ticks mark the points where the DPLL algorithm returned (so that’s the point where the bit decision was made).

The DPLL algorithm

The DPLL algorithm is used for bit syncing, when it’s used, the bit decision can be made in the middle of a bit. The algorithm uses a counter called DPLLBitPhase. If it’s more than a bit’s length in samples, the function returns. The counter gets increased by every sample the DPLL reads from the demodulator input.

The sync correction works like this: if a bit change is detected, it checks if it happened at the half of one bit time. If the change was before the half, the DPLLBitPhase counter gets increased because it’s late, so the function will return earlier. If the change was after the half, the counter gets decreased, so the function will return later (reads more from the demodulator input before returning). The correction is only made once per DPLL function call.

Other resources on RTTY / FSK decoding

  1. RTTY decoder application and paper describing the method.
  2. RTTY diddles, about the protocol
  3. DPLL theory
  4. Bit banging, simple bit synchronization
  5. UART character recovery
  6. FSK signals and demodulation
  7. FSK demodulation theory
  8. Receiver sync theory

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// for filter designing, see http://www-users.cs.york.ac.uk/~fisher/mkfilter/
    // order 2 Butterworth, freqs: 865-965 Hz
    public double bandPassFreq0(double sampleIn) {
        xvBP0[0] = xvBP0[1]; xvBP0[1] = xvBP0[2]; xvBP0[2] = xvBP0[3]; xvBP0[3] = xvBP0[4];
        xvBP0[4] = sampleIn / 2.356080041e+04;
        yvBP0[0] = yvBP0[1]; yvBP0[1] = yvBP0[2]; yvBP0[2] = yvBP0[3]; yvBP0[3] = yvBP0[4];
        yvBP0[4] = (xvBP0[0] + xvBP0[4]) - 2 * xvBP0[2]
                    + (-0.9816582826 * yvBP0[0]) + (3.9166274264 * yvBP0[1])
                    + (-5.8882201843 * yvBP0[2]) + (3.9530488323 * yvBP0[3]);
        return yvBP0[4];
    }

    // order 2 Butterworth, freqs: 1035-1135 Hz
    public double bandPassFreq1(double sampleIn) {
        xvBP1[0] = xvBP1[1]; xvBP1[1] = xvBP1[2]; xvBP1[2] = xvBP1[3]; xvBP1[3] = xvBP1[4];
        xvBP1[4] = sampleIn / 2.356080365e+04;
        yvBP1[0] = yvBP1[1]; yvBP1[1] = yvBP1[2]; yvBP1[2] = yvBP1[3]; yvBP1[3] = yvBP1[4];
        yvBP1[4] = (xvBP1[0] + xvBP1[4]) - 2 * xvBP1[2]
                    + (-0.9816582826 * yvBP1[0]) + (3.9051693660 * yvBP1[1])
                    + (-5.8653953990 * yvBP1[2]) + (3.9414842213 * yvBP1[3]);
        return yvBP1[4];
    }

    // order 2 Butterworth, freq: 50 Hz
    public double lowPass(double sampleIn) {
        xvLP[0] = xvLP[1]; xvLP[1] = xvLP[2];
        xvLP[2] = sampleIn / 9.381008646e+04;
        yvLP[0] = yvLP[1]; yvLP[1] = yvLP[2];
        yvLP[2] = (xvLP[0] + xvLP[2]) + 2 * xvLP[1]
                   + (-0.9907866988 * yvLP[0]) + (1.9907440595 * yvLP[1]);
        return yvLP[2];
    }

    // this function returns the bit value of the current sample
    public int demodulator() {
        double sample = getSample();

        double line0 = bandPassFreq0(sample);
        double line1 = bandPassFreq1(sample);
        // calculating the RMS of the two lines (squaring them)
        line0 *= line0;
        line1 *= line1;

        // inverting line 1
        line1 *= -1;

        // summing the two lines
        line0 += line1;

        // lowpass filtering the summed line
        line0 = lowPass(line0);

        baos.write(getBytesFromDouble(line0), 0, 2); // writing to the output wav for debugging purposes

        if (line0 > 0)
            return 0;
        else
            return 1;
    }

    // this function returns at the half of a bit with the bit's value
    public int getBitDPLL() {
        boolean phaseChanged = false;
        int val = 0;

        while (DPLLBitPhase < oneBitSampleCount) {
            val = demodulator();

            if (!phaseChanged && val != DPLLOldVal) {
                if (DPLLBitPhase < oneBitSampleCount/2)
                    DPLLBitPhase += oneBitSampleCount/8; // early
                else
                    DPLLBitPhase -= oneBitSampleCount/8; // late
                phaseChanged = true;
            }
            DPLLOldVal = val;
            DPLLBitPhase++;
        }
        DPLLBitPhase -= oneBitSampleCount;

        // putting a tick to the output wav signing the moment when the DPLL returned
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        baos.write(100);
        return val;
    }

    // this function returns only when the start bit is successfully received
    public void waitForStartBit() {
        int bitResult;

        while (!Thread.interrupted()) {
            // waiting for a falling edge
            do {
                bitResult = demodulator();
            } while (bitResult == 0 && !Thread.interrupted());
           
            do {
                bitResult = demodulator();
            } while (bitResult == 1 && !Thread.interrupted());

            // waiting half bit time
            for (int i = 0; i < oneBitSampleCount/2; i++)
                bitResult = demodulator();

            if (bitResult == 0)
                break;
        }
    }
   
    @Override
    public void run() {
        tdl.start();

        int byteResult = 0;
        int byteResultp = 0;
        int bitResult;
       
        while (!Thread.interrupted()) {
            waitForStartBit();

            System.out.print("0 "); // first bit is the start bit, it's zero

            // reading 7 more bits
            for (byteResultp = 1, byteResult = 0; byteResultp < 8; byteResultp++) {
                bitResult = getBitDPLL();

                switch (byteResultp) {
                    case 6: // stop bit 1
                        System.out.print(" " + bitResult);
                        break;
                    case 7: // stop bit 2
                        System.out.print(bitResult);
                        break;
                    default:
                        System.out.print(bitResult);
                        byteResult += bitResult << (byteResultp-1);
                }
            }

            switch (byteResult) {
                case 31:
                    mode = RTTYMode.letters;
                    System.out.println(" ^L^");
                    break;
                case 27:
                    mode = RTTYMode.symbols;
                    System.out.println(" ^F^");
                    break;
                default:
                    switch (mode) {
                        case letters:
                            System.out.println(" *** " + RTTYLetters[byteResult] + "(" + byteResult + ")");
                            break;
                        case symbols:
                            System.out.println(" *** " + RTTYSymbols[byteResult] + "(" + byteResult + ")");
                            break;
                    }
            }
        }

        tdl.stop();
        tdl.close();
    }

download (mirror2) (80.3 kb, 264 dls, today: 0)

leo 2012-07-31 13:55:41

hi,your work is awesome.
But I test the program in 14(encoder), and the 18(decoder).
It can not decode correctly.
the following is the encoder string and decoder string:
encoder:
LTRS: 0 11111 11
H (20): 1 00101 11
A (3): 1 11000 11
FIGS: 0 11011 11
2 (19): 1 11001 11
LTRS: 0 11111 11
N (12): 1 00110 11
O (24): 1 00011 11
N (12): 1 00110 11
LTRS: 0 11111 11
H (20): 1 00101 11
A (3): 1 11000 11
FIGS: 0 11011 11
2 (19): 1 11001 11
LTRS: 0 11111 11
N (12): 1 00110 11

decoder:

0 01111 11 *** V(30)
0 01011 11 *** G(26)
0 11111 11 ^L^
0 11001 11 *** W(19)
0 00111 01 *** M(28)
0 01111 11 *** V(30)
0 01111 10 *** V(30)
0 01111 11 *** V(30)
0 11111 11 ^L^
0 11111 00 ^L^
0 11111 10 ^L^
0 01111 11 *** V(30)
0 11111 01 ^L^
0 11101 11 *** Q(23)
0 01101 11 *** P(22)
0 11111 11 ^L^
0 01111 11 *** V(30)
0 11111 11 ^L^
0 01111 10 *** V(30)
0 11111 11 ^L^
0 11111 00 ^L^
0 11111 00 ^L^
0 01011 11 *** G(26)
0 00111 11 *** M(28)
0 11111 11 ^L^
0 11111 10 ^L^
0 11111 11 ^L^
0 11111 11 ^L^
0 01101 10 *** P(22)
0 11111 10 ^L^
0 11111 00 ^L^
0 00111 11 *** M(28)
0 01111 11 *** V(30)

I do not know why.

 
Rob 2013-08-18 00:03:46

Hi Nonoo,

thank you for publicizing your research and explanation.
I have a question, do you think is is possible to run this type of rtty decoder on a mbed lpc1768 (cortex M3) .. or do you need more processing power , like maybe an raspberry pi ?

kind regards

r o b

 
Jaehwa Han 2013-09-28 11:26:06

Thanks for sharing great article! I’m now reading DPPL theory, and finally got it.

Your video is quite impressive. I’m now trying to implement simple iOS app which can transmit text or simple image through RTTY for just inspiring tele-communication nostalgia. Hope it will be ready soon!

 
FRank 2014-01-13 11:54:32

Hi, I’d run the code in ARDUINO DUE board. Have you any source code compatible with “semplfied C for ARDUINO”. I got confused with “@override” instruction.
thank you
ciao Frank Italy

 
Name (required)
E-mail (required - never shown publicly)
Webpage URL
Comment:
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> in your comment.

Trackback responses to this post

About me

Nonoo
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with. ... »

Twitter

Listening now

My favorite artists