Introduction to pulse formats

A guide on common FSK and OOK pulse formats with some real world examples.

Structure of FSK and OOK transmissions

A burst of data transmitted by a device will be called "transmission". A transmission can be made up of multiple separate data blocks we'll call "packets". If the packets contain the same data (for reliability) we'll call that "repeats".

In the most general form a transmission or packet is made up of

  • an optional "warmup" to wake up the receiver, likely just a long pulse
  • an optional "preamble" to train the timing, like a long fixed pattern of 101010...
  • an optional "sync word" to identify the start of payload data, a very common sync word is 2dd4
  • alternatively a "desync" might be used, a symbol not otherwise used and invalid to the used coding
  • the payload data, likely with a checksum as last one or two bytes

The data is made up of "symbols" which decode to bits. We'll look at some common codings from symbols to bits in the following. All pulse formats here refer to the discrete binary type, e.g. pulse-width modulation will be assumed to only have two distinct lengths.

We don't distinguish between FSK and OOK modulations here. The two states of the FSK transmission, mark frequency and space frequency, are understood to map to pulse and gap for the OOK modulation. The mark or pulse state are also referred to as high-level, the space or gap states referred to as low-level.

We'll usually assign high levels or high frequency (fast) level changes a bit value of 1, low levels or low frequency (slow) level changes a bit value of 0. This not a commonly accepted convention though and needs to be figured out for each protocol.

flowchart LR
  subgraph Transmission
    direction LR
    subgraph Packet
        direction LR
        Preamble --- Sync --- Data --- Postamble
    end
    Warmup --- Packet --- Trailer
  end

PWM — Pulse-width modulation

The pulses encode the data and the gaps are either a fixed length (for a variable bit width) or a variable (complementary to the pulse width) length (for a fixed bit width). S.a. https://en.wikipedia.org/wiki/Pulse-width_modulation

Fixed bit-width PWM

;slicer PWM:10:20:0:30
10 20
20 10
10 20
10 20
20 10
20 10

Fixed gap-width PWM

;slicer PWM:10:20:0:30
10 15
20 15
10 15
10 15
20 15
20 15

Fixed bit-width PWM with leading pulses

In some few protocols, as a special case, the gaps matching the pulses will preceed the pulses: (this can easily be mistaken as gaps carrying data, e.g. Manchester coding)

;slicer PWM:10:20:0:30
 0 20
10 10
20 20
10 20
10 10
20 10
20 0

Here is an example from the "Bresser Thermo-/Hygro-Sensor 3CH", there is a preamble/sync with extra wide pulses, a fixed bit width is used:

;slicer PWM:250:500:750:625
268 704
756 708
760 704
756 716
260 464
272 464
268 460
512 224
268 460
512 224
508 228
500 228
264 472
264 468
504 228
264 472
256 476
504 232
496 232
260 480
256 472
256 480
256 480
252 472
508 228
508 216
268 468
268 468
256 468
512 224
268 460
268 468
264 464
264 472
264 472
256 476
504 232
260 468
260 476
260 476
500 232
256 480
256 472
504 232
256 956

PPM — Pulse-position modulation

The pulses have fixed width and the data is contained in the distance between pulses (gaps). Generally the pulses will be rather short in comparison to the gaps. S.a. https://en.wikipedia.org/wiki/Pulse-position_modulation

Example PPM

;slicer PPM:10:20:0:30
3 10
3 20
3 10
3 10
3 20
3 20

Here is an example from the "Acurite 609TXC Temperature and Humidity Sensor" which also has a preamble of invalid shorter gaps:

;slicer PPM:1000:2000:500:2500
516 484
504 492
504 8940
504 1984
504 1992
504 988
504 1000
504 1980
504 1000
504 1988
500 992
504 996
500 996
504 1988
508 988
508 992
504 992
504 996
504 1988
504 996
504 992
504 992
508 992
508 988
504 1992
500 1988
504 996
508 984
508 1988
504 992
504 996
500 1992
504 1984
504 996
504 996
500 996
504 996
504 1980
504 1992
508 1984
508 1984
504 988
504 1996
500 10004

NRZ(L) — Non-return-to-Zero level

The data is encoded in the signal level at a fixed clock rate. The clock generally can't be recovered and must be known. Usually a preamble of toggling bits (101010...) is used to train and sync the clock. Note that for OOK signals the end of transmission can't be distinguished from low-level bits.

Together with the similar RZ we'll refer to this as PCM. S.a. https://en.wikipedia.org/wiki/Non-return-to-zero

Here is an example from the "Jasco/GE Choice Alert Security Devices", which does not have a preamble:

;slicer PCM:250:250:0:1800
1508 1552
516 796
264 256
256 308
208 276
268 252
524 536
496 560
496 304
232 272
248 284
236 284
252 272
248 272
248 548
256 288
232 280
492 556
232 296
248 280
232 280
268 276
496 300
228 540
516 528
516 540
256 15084

RZ (PCM) — Return-to-Zero level

The data is encoded in the signal level at a fixed clock rate. High-level bits will return to the low-level, the clock can be recovered on high-level bits only. Usually a preamble of toggling high-level bits (111111...) is used to train and sync the clock.

Together with the similar NRZ we'll refer to this as PCM. S.a. https://en.wikipedia.org/wiki/Return-to-zero

Here is an example from the "Atech-WS308 temperature sensor":

;slicer PCM:1600:1832:2500:9000
1608 236
1596 236
1596 232
1596 236
1596 236
1596 236
1596 236
1596 7556
1596 2068
1596 2064
1596 2068
1596 2068
1596 232
1596 236
1596 2064
1600 232
1596 236
1596 2064
1600 2068
1592 2064
1600 2068
1592 2064
1600 2068
1596 2064
1596 2064
1600 2064
1596 2068
1596 232
1596 236
1596 2064
1600 232
1596 236
1596 2064
1600 2064
1596 2068
1596 2064
1596 2068
1596 236
1592 236
1596 2068
1596 2064
1596 2068
1596 2064
1600 2064
1596 2064
1596 236
1596 236
1596 1820
1596 236
1596 16084

MC — Manchester Code

This is basically a NRZ level code but the symbols are only 10 or 01. S.a. https://en.wikipedia.org/wiki/Manchester_code

Here is an example from the "Maverick ET-732/733 BBQ Sensor", which also uses a "warmup" of single pulses:

;slicer MC:230:0:0:400
256 5004
236 5008
244 5004
240 5008
240 5008
236 5012
236 5008
244 4832
472 524
468 528
468 524
472 532
468 280
216 528
220 272
472 288
212 540
208 280
464 280
220 524
472 524
472 524
472 528
472 524
220 284
468 276
220 524
220 276
472 280
216 536
464 528
464 528
220 284
468 272
220 532
220 284
464 520
476 524
472 284
216 520
224 280
468 276
224 532
460 532
468 524
216 280
472 276
220 532
220 280
464 532
468 528
464 276
220 532
464 532
216 280
468 528
468 276
220 544
456 524
476 532
460 528
216 280
468 532
468 280
216 528
220 272
480 276
216 528
468 528
220 276
476 524
472 284
212 524

DMC — Differential Manchester Code

Differential Manchester Encoding (DM) is also known as Biphase Mark Code (CC).

S.a. https://en.wikipedia.org/wiki/Differential_Manchester_encoding

Here is an example from the "WT450-TH weather sensor":

;slicer DM:1000:1000:0:2500
988 976
980 976
1956 1948
980 976
980 972
1956 972
980 1952
1952 980
976 972
980 1952
1956 1952
1952 980
976 976
976 976
976 1952
1956 976
976 1956
1952 976
972 1960
972 984
972 976
976 980
1948 1952
1960 1952
1948 1956
976 976
368 19604

PIWM — Pulse-interval-width modulation

tbd.

PIWM-DC — Differential Pulse-interval-width modulation

tbd.

NRZI — Non-return-to-zero inverted

  • Non-return-to-zero, inverted (NRZI) https://en.wikipedia.org/wiki/Non-return-to-zero#NRZI
  • NRZ(I) NRZI Non-return-to-zero inverted Refers to either an NRZ(M) or NRZ(S) code.
  • NRZ(M) NRZM Non-return-to-zero mark with mapping {0: constant, 1: toggle}.
  • NRZ(S) NRZS Non-return-to-zero space with mapping {0: toggle, 1: constant}.
  • A 1 is transmitted as a transition, and a 0 is transmitted as no transition.

Here is an example from the "Klimalogg sensor":

S.a. https://en.wikipedia.org/wiki/Non-return-to-zero#NRZS

;slicer NRZI:26:0:0:1000
74 14
36 16
12 13
11 14
12 14
12 14
12 12
12 15
12 12
14 13
12 12
14 11
15 11
14 13
12 12
14 13
12 14
13 12
14 12
14 12
14 12
14 12
14 11
14 12
14 12
14 11
14 12
13 12
14 12
13 12
14 11
15 12
13 12
14 12
15 10
14 12
14 11
14 12
14 11
14 13
12 12
14 12
12 12
15 12
13 11
15 12
13 12
14 12
14 12
14 11
14 11
15 12
12 14
13 12
12 12
14 12
14 12
14 12
14 12
14 12
14 11
14 12
13 12
14 12
13 13
12 12
14 11
14 12
14 11
16 10
14 12
13 12
14 12
14 12
14 11
14 11
14 12
13 12
14 12
14 12
14 12
14 12
13 13
14 10
14 12
13 13
14 11
14 10
14 12
15 10
14 12
14 13
14 10
15 12
13 12
14 12
14 12
14 12
14 11
14 12
14 12
14 12
14 12
14 11
14 12
14 10
14 12
14 11
14 12
14 11
14 12
14 11
14 11
15 12
14 10
15 12
14 10
15 12
14 11
14 11
13 13
14 10
15 12
14 12
13 12
14 10
16 11
14 12
14 11
15 12
12 12
14 11
13 13
14 11
14 12
15 10
15 11
14 12
14 12
13 12
15 10
16 10
14 12
14 11
15 11
14 12
14 12
14 12
14 12
14 12
13 13
14 11
14 12
14 10
16 12
13 12
14 12
14 12
14 10
14 12
14 12
14 14
12 12
14 12
40 12
63 15
36 14
11 14
12 14
12 13
38 17
35 14
114 15
9 17
10 16
10 14
12 14
13 12
14 14
62 14
12 12
63 15
36 16
36 15
11 13
12 15
11 15
36 14
11 14
13 14
12 12
12 14
38 13
12 14
12 12
13 14
37 14
63 14
10 14
12 14
11 16
11 13
13 14
12 12
64 14
10 16
10 13
12 14
11 13
14 13
13 12
14 13
12 14
13 10
65 14
37 14
36 17
35 15
37 13
12 14
12 14
12 14
37 14
12 14
12 12
14 12
13 14
12 12
14 13
13 11
15 12
12 12
13 14
13 11
14 13
14 10
14 12
13 11
15 11
15 11
14 12
13 12
14 161
14 11
14 12
14 1000

CMI — Coded Mark Inversion

Coded Mark Inversion (CMI) https://en.wikipedia.org/wiki/Coded_mark_inversion encodes zero bits as a half bit time of zero followed by a half bit time of one, and one bits are encoded as a full bit time of a constant level, the level used for one bits alternates each time one is coded.

Generally we'll observe pulses and gaps of 1/2 and 3/2 bit-widths and pulse gap runs of bit-width.

S.a. https://en.wikipedia.org/wiki/Coded_mark_inversion

;slicer CMI:10:0:0:40
;check 100001100001011110
20 10
10 10
10 10
10 10
10 20
20 10
10 10
10 10
10 10
10 30
30 20
20 30
10 0