MSK144 — цифровой протокол, разработанный Джо Тейлором (K1JT) и его командой в 2016 году для проведения связей через метеорное рассеивание. В этой статье будут рассмотрены подробности работы протокола.

Статья может быть интересна радиолюбителям, как знакомым, так и не знакомым с MSK144 и связью через метеорное рассеивание, а также тем, кто хочет понять устройство этого протокола.

Предыстория

Связь через метеорные потоки — вид связи, основанный на отражении радиоволны от ионизированных следов метеоров, сгорающих в атмосфере Земли. Ионизационный след от метеора существует от нескольких миллисекунд, до нескольких секунд, в зависимости от размеров метеора.

Проведение связей через метеорные потоки начало обретать популярность в 50-х годах прошлого столетия, тогда впервые их проводили на 15 и 20 метровых диапазонах. Также было установлено, что еще более впечатляющих результатов можно добиться на 6 и 2 метровых диапазонах, где шумовая картина значительно лучше, а большего усиления можно добиться с помощью направленных антенн, занимающих намного меньшие габариты.

Так как ионизационный след в достаточной мере эфемерен, радиолюбителям приходилось использовать телеграф со скоростью от 10 до 40 знаков в секунду. Сигналы записывались на магнитофон, отправлялись и воспроизводились с сильным ускорением и замедлением. С применением компьютеров удалось увеличить скорость до 150 знаков в секунду.

Введение

В начале нулевых Джо Тейлор представил протокол FSK441 (использовавший частотную манипуляцию), давший большой толчок проведению метеорных УКВ связей на дальние расстояния. С ростом мощностей компьютеров в 2016 году Тейлор с командой представили протокол с более эффективной модуляцией — MSK144 (от Minimum Shift Keying, с длиной пакета в 144 бита), включающий в себя коды коррекции ошибок и проверку целостности данных, о котором пойдет дальнейшая речь.

Общие технические характеристики

  • Цикл передачи: от 5 до 30 секунд;

  • Тип модуляции: MSK (Minimum Shift Keying) на частотах 1000 и 2000 Гц;

  • Скорость передачи: 2000 бод;

  • Механизм коррекции ошибок LDPC (128,80);

  • Ширина полосы: 2.5 КГц;

  • Размер сообщения 83 бит + 13-бит CRC-13;

Структура протокола

На прикладном уровне структура протокола во многом заимствует логику работы протоколов FT8/FT4, описанных ранее в соответствующей статье, в связи с этим настоятельно рекомендуется с ней ознакомиться.

MSK144, как и любой протокол передачи данных, представляет собой "матрешку", в которой данные на каждом уровне претерпевают изменения перед передачей на уровень ниже.

Общая схема протокола приведена на рисунке 1. Разделение на уровни модели OSI условное.

Рисунок 1: Общая схема протокола (абстракция по уровням OSI условная).
Рисунок 1: Общая схема протокола (абстракция по уровням OSI условная).

На рисунке 1.А приведена схема взаимодействия алгоритмов кодирования при формировании исходящего сигнала; на рисунке 1.B — схема обработки и декодирования принятого сигнала.

Как видно, отличия от FTX заключаются в использовании контрольных сумм CRC-13, иных маркеров синхронизации и, собственно, в типе модуляции сигнала.

Прикладной радиообмен

Аналогично протоколам семейства FTX, участники разделяются на передающих и принимающих, чьи роли меняются каждый цикл, длящийся от 5 до 30 секунд (в зависимости от прохождения).

Передаваемые сообщения аналогичны сообщениям в протоколах семейства FTX:

  1. CQ — общий вызов с передачей позывного и QTH-квадрата;

  2. обмен SNR-рапортами;

  3. завершение сессии посылками Roger-сигналов.

CRC-13

В отличие от протоколов семейства FTX, использующих CRC-14 для проверки целостности сообщений, протокол MSKX использует еще более компактную версию циклического избыточного кода — CRC-13, который, формирует 13-и битную контрольную сумму.

MSKX_CRC_POLYNOMIAL = 0x15D7
MSKX_CRC_WIDTH = 13
MSKX_CRC_TOP_BIT = 1 << (MSKX_CRC_WIDTH - 1)
MSKX_PAYLOAD_BITS = 96
MSKX_MESSAGE_BITS = MSKX_PAYLOAD_BITS - MSKX_CRC_WIDTH




def mskx_compute_crc(message: typing.ByteString, num_bits: int) -> int:
   remainder = 0
   idx_byte = 0


   for idx_bit in range(num_bits):
       if idx_bit % 8 == 0:
           remainder ^= message[idx_byte] << (MSKX_CRC_WIDTH - 8)
           idx_byte += 1
       if remainder & MSKX_CRC_TOP_BIT != 0:
           remainder = (remainder << 1) ^ MSKX_CRC_POLYNOMIAL
       else:
           remainder = remainder << 1
   return remainder & ((MSKX_CRC_TOP_BIT << 1) - 1)




def mskx_add_crc(payload: typing.ByteString) -> typing.ByteString:
   message = payload + (b"\x00" * (FTX_LDPC_K_BYTES - len(payload)))
   message[-3] &= 0xfc
   message[-2] = 0


   checksum = mskx_compute_crc(message, MSKX_MESSAGE_BITS)


   message[-3] |= byte(checksum >> 10)
   message[-2] = byte(checksum >> 2)
   message[-1] = byte(checksum << 6)


   return message

Для CRC-13 используется полином 0x15D7 (MSKX_CRC_POLYNOMIAL).

msg = bytearray(b'\x00\x00\x00 Y\xac\xff\x94\xc9\xc8')
msg_crc = mskx_add_crc(msg)

К примеру, сообщение "CQ R9FEU LO87" после упаковки бит примет вид "0x00 0x00 0x00 0x20 0x59 0xac 0xff 0x94 0xc9 0xc8" в HEX-представлении, CRC-13 этих данных "0x5e5". В функции mskx_add_crc после конкатенации сообщения с контрольной суммой, итоговое сообщение принимает вид "0x00 0x00 0x00 0x20 0x59 0xac 0xff 0x94 0xc9 0xc9 0x79 0x40".

LDPC (низкоплотностный код)

Протоколы семейства MSKX используют низкоплотностные коды проверки на четность меньших размеров, по сравнению с протоколами семейства FTX. В отличии от FTX, где используется LDPC (174, 87), в MSKX применен LDPC (128, 80).

Параметры LDPC-кодировщика для MSKX сообщений:

MSKX_LDPC_K = 90
MSKX_LDPC_M = 38
MSKX_LDPC_N = MSKX_LDPC_K + MSKX_LDPC_M
MSKX_LDPC_N_BYTES = ((MSKX_LDPC_N + 7) // 8)
MSKX_LDPC_K_BYTES = ((MSKX_LDPC_K + 7) // 8)

Порождающая LDPC-матрица:

MSKX_LDPC_GENERATOR = [
   [0xa0, 0x8e, 0xa8, 0x08, 0x79, 0x05, 0x0a, 0x5e, 0x94, 0xda, 0x99, 0x40],
   [0x59, 0xf3, 0xb4, 0x80, 0x40, 0xca, 0x08, 0x9c, 0x81, 0xee, 0x88, 0x00],
...
   [0x7e, 0x79, 0x36, 0x2c, 0x16, 0x77, 0x3e, 0xfc, 0x64, 0x82, 0xe3, 0x00],
]

Матрицу целиком можно посмотреть здесь.

Алгоритм LDPC-кодирования идентичен алгоритму используемому в протоколах семейства FTX:

def parity8(x: int) -> int:
   for i in [4, 2, 1]:
       x ^= x >> i
   return byte(x % 2)




def mskx_encode(message: typing.ByteString) -> typing.ByteString:
   codeword = bytearray(message[i] if i < MSKX_LDPC_K_BYTES else 0 for i in range(MSKX_LDPC_N_BYTES))


   col_mask = 0x80 >> (MSKX_LDPC_K % 8)
   col_idx = MSKX_LDPC_K_BYTES - 1
   for i in range(MSKX_LDPC_M):
       nsum = 0
       for j in range(MSKX_LDPC_K_BYTES):
           bits = message[j] & MSKX_LDPC_GENERATOR[i][j]  
           nsum ^= parity8(bits)
       if nsum % 2:
           codeword[col_idx] |= col_mask
       col_mask >>= 1
       if col_mask == 0:
           col_mask = 0x80
           col_idx += 1


   return codeword
payload = mskx_encode(msg_crc)

Рассматриваемое ранее сообщение "CQ R9FEU LO87" с вычисленной контрольной суммой ("0x00 0x00 0x00 0x20 0x59 0xac 0xff 0x94 0xc9 0xc9 0x79 0x40") после LDPC-кодирования принимает вид "0x00 0x00 0x00 0x20 0x59 0xac 0xff 0x94 0xc9 0xc9 0x79 0x72 0x35 0x7c 0x80 0x91" (128 бит).

Метки синхронизации

Перед тем как начать формирование сигнала, готового к отправке в эфир, к исходным данным добавляются метки синхронизации, используемые при декодировании для определения сигнала и его выравниванию во временной области.

Маркером синхронизации в протоколе MSK144 используется двоичная последовательность 0,1,1,1,0,0,1,0 (0x72), располагаемая в самом начале и после 48 бит данных.

sync = b"\x72"  # 0,1,1,1,0,0,1,0
envelope = sync + payload[0:6] + sync + payload[6:16]

После добавления маркеров синхронизации, сообщение принимает вид "0x72 0x00 0x00 0x00 0x20 0x59 0xac 0x72 0xff 0x94 0xc9 0xc9 0x79 0x72 0x35 0x7c 0x80 0x91" (144 бит).

Примечание: в протоколе MSKMS для синхронизации используется последовательность бит 0,1,0,0,1,1,1,0, а в MSK40 — 1,0,1,1,0,0,0,1. Причины выбора таких значений констант неизвестны.

Minimum Shift Keying формирование сигнала

MSK (Minimum Shift Keying) — это подвид частотной манипуляции (FSK с непрерывной фазой, с индексом модуляции 0.5) с минимальным частотным сдвигом. Отличительной особенностью MSK является свойство, что разность частот между сигналами, формирующие нули и единицы, равняется половине скорости передачи символов, что, в том числе, с когерентностью фазы, обеспечивает ортогональность частот и высокую спектральную эффективность. 

Под свойствами ортогональности частот, понимается, что частоты независимы и не оказывают друг на друга взаимного влияния, которые проявляются в виде интерференций.

На рисунке 2 представлены спектры сигналов, отстоящих друг от друга достаточно далеко по частоте. На рисунке 3  спектры сигналов с ортогональными частотами, как можно заметить, спектры сигналов с ортогональными частотами имеют взаимные пересечения в точках перехода через ноль.

Рисунок 2: Спектры сигналов с разнесением по частотам.
Рисунок 2: Спектры сигналов с разнесением по частотам.
Рисунок 3: Спектры сигналов с ортогональными частотами.
Рисунок 3: Спектры сигналов с ортогональными частотами.

На рисунке 4 показаны графики функций с ортогональными частотами. В период T сигнала, выделенного красным, укладывается 2T сигнала выделенного синим, что является свойством, что частота синего сигнала имеет вдвое большую частоту относительно частоты красного сигнала, при этом интеграл от суммы двух сигналов равен нулю.

Рисунок 4: Графики сигналов с ортогональными частотами.
Рисунок 4: Графики сигналов с ортогональными частотами.

Исходя из этого, в отличие от разнесения сигналов далеко друг от друга в частотной области, сигналы с ортогональными частотами могут быть расположены достаточно близко в спектре, тем самым более эффективно использовать полосу частот.

При формировании MSK сигнала, результирующий сигнал можно рассматривать как разность синфазного и квадратурного сигналов (1).

s(t)=I(t)\text{cos}\omega_{c}t-Q(t)\text{sin}\omega_{c}t

Кодирование бит осуществляется поочередно, нечетные биты формируются квадратурным (Q) сигналом, а четные синфазным (I) (рисунок 5).

При этом для кодирования единиц используется положительная часть полуволны, для нулей — соответственно отрицательная.

Рисунок 5: Графики I(t) (синий) и Q(t) (красный) сигналов.
Рисунок 5: Графики I(t) (синий) и Q(t) (красный) сигналов.

На рисунке 5 изображены графики I и Q сигналов, сформированные согласно последовательности бит  01100110101101010.

Сообщение "720000002059ac72ff94c9c97972357c8091" в двоичном виде имеет вид "011100100000000000000000000000000010000001011001101011000111001011111111100101001100100111001001011110010111001000110101011111001000000010010001".

Для формирования I и Q сигналов, исходное битовое представление преобразуется в вид, где битам со значением 0 присваивается отрицательный знак, а битам со значением 1 — положительный соответственно.

signs = [2 * ((payload[i // 8] >> (7 - (i % 8))) & 1) - 1 for i in range(144)] + [0]

Результат: -1111-1-11-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-11-1-1-1-1-1-11-111-1-111-11-111-1-1-1111-1-11-1111111111-1-11-11-1-111-1-11-1-1111-1-11-1-11-11111-1-11-1111-1-11-1-1-111-11-11-111111-1-11-1-1-1-1-1-1-11-1-11-1-1-110.

На основе полученной последовательности и правил кодирования четных и нечетных бит формируются номера тонов, определяющие сдвиг частоты сигнала. Так как в MSK возможен только минимальный сдвиг частоты, или иными словами, 2 тона, то результатом является двоичная последовательность, где 0 — основной тон, 1 — тон с минимальным сдвигом.

tones = [0 for _ in range(144)]
for i in range(72):
   tones[2 * i - 0] = (signs[2 * i + 1] * signs[2 * i - 0] + 1) // 2
   tones[2 * i + 1] = -(signs[2 * i + 1] * signs[2 * i + 2] - 1) // 2


for i in range(144):
   tones[i] = -tones[i] + 1

Результат: 110000110101010101010101010101010011010110111111101000011100001001010101111010000000111100001110110111101100001100001010110100001101010011100111.

На финальной стадии, из полученной последовательности тонов формируется сигнал, с когерентной фазой.

def mskx_gen_signal(
       tones: typing.List[int], sample_rate: int,
       carrier_freq: int = 1000, delta_freq: int = 1000,
       sampling_factor: int = 1, sampling_rate_coef: int = 4):
   dt = 1.0 / (sampling_factor * sample_rate)
   samples_per_symbol = 6 * sampling_rate_coef


   phase = 0.0


   signal = np.zeros(len(tones) * samples_per_symbol)
   for i, tone in enumerate(tones):
       freq = carrier_freq + tone * delta_freq
       phase_delta = 2 * np.pi * freq * dt


       t_start = i * samples_per_symbol
       t_end = t_start + samples_per_symbol


       phases = np.fromiter(
           (np.fmod(phase_delta * i + phase, 2 * np.pi)
            for i in range(samples_per_symbol)),
           dtype=np.float32
       )


       signal[t_start:t_end] = np.sin(phases)


       phase = np.fmod(phase_delta * samples_per_symbol + phase, 2 * np.pi)


   return signal

Функция mskx_gen_signal принимает на вход список индексов ранее сформированных тонов, центральная частота в MSK144 составляет 1КГц, сдвиг частоты 1КГц. Значение в samples_per_symbol определяет количество семплов на один символ. Результатом является дискретный сигнал с частотой дискретизации sample_rate.

Синтезированный сигнал, готовый к отправке через радиоканал, можно записать в аудио-файл.

from scipy.io.wavfile import write


GEN_SAMPLE_RATE = 48000


signal = mskx_gen_signal(tones, sample_rate=GEN_SAMPLE_RATE)


amplitude = np.iinfo(np.int16).max
wave = np.concat([signal * amplitude] * 10)


write("signal.wav", GEN_SAMPLE_RATE, wave.astype(np.int16))

Длительность одного пакета данных в MSK144 составляет 0.072 секунды (72 мсек), для прослушивания примера сигнал был продублирован 10 раз.

При проведении сеанса связи через метеоры, один и тот же пакет данных многократно и непрерывно посылается в эфир в течении 15-30 секунд, тем самым компенсируя потери в канале связи избыточным количеством одних и тех же данных, что позволяет на принимающей стороне повысить вероятность принять и декодировать данные в условиях эфемерного канала связи.

На рисунках 6 и 7 представлены график части результирующего MSK-сигнала и спектр одного пакета данных.

Рисунок 6: График MSK-сигнала.
Рисунок 6: График MSK-сигнала.
Рисунок 7: Спектр MSK144-сигнала
Рисунок 7: Спектр MSK144-сигнала

Заключение

В статье были рассмотрены принципы формирования сигнала в протоколе MSK144 для проведения связей через метеорные потоки. Основными свойствами протокола является скорость передачи данных, размер передаваемого пакета, а также компенсация потерь в канале связи путем многократного повтора передаваемых данных.

Протоколы семейства MSKX во многом заимствует и использует архитектуру с принципами кодирования данных у протоколов семейства FTX. Также как и у протоколов семейства FTX, протоколам семейства MSKX присущи все те же недостатки, связанные с объемом передаваемых данных и сложностью адаптации его для передачи произвольных данных.

Технология также может предоставлять базис для радиолюбительских экспериментов с передачей данных любого типа. Так, например, изменяя механизм шквальной репликации пакетов в сессии, размер передаваемых данных и модульную привязку к времени, протокол может быть адаптирован для передачи датаграмм с высокой скоростью не только через метеорные потоки.

Ссылки

  1. Общий репозиторий с реализацией FTX и MSKX Python

  2. Исходный код примера генератора сигнала MSK144

  3. Статья с описанием протокола из журнала QEX

  4. Программа WSJT, реализующая протокол MSK144

  5. Программа MSHV, реализующая протоколы семейства MSKX

Комментарии (0)


  1. ArthPLM
    15.09.2025 08:03

    Следующий этап проведения QSO через метеоры ))