Сообщество Engee

PDU в DMR

Автор
avatar-yurevyurev
Notebook

PDU в DMR

В современных профессиональных системах радиосвязи, таких как DMR (Digital Mobile Radio), взаимодействие между базовой станцией (БС) и абонентской радиостанцией (РС) является строго регламентированным и высокоорганизованным процессом. Это необходимо для обеспечения надежной, помехоустойчивой и эффективной связи множества абонентов в рамках одного канала.

БС выступает в роли координатора и управляющего центра в зоне своего покрытия (соте). Она непрерывно транслирует служебную информацию, синхронизируя все РС в сети, управляя доступом к каналу и предоставляя услуги связи. Радиостанция (РС), в свою очередь, для любого своего действия (начало вызова, передача голоса или данных, регистрация в сети) должна взаимодействовать с БС, запрашивая ресурсы и следуя ее инструкциям.

Это взаимодействие осуществляется путем обмена специальными пакетами данных, строго соответствующими формату кадров DMR. Одной из ключевых структур для организации такого диалога является слот данных, основным элементом которого является PDU (Protocol Data Unit — блок данных протокола).

SLOT PDU — это информационная часть слота, предназначенная для передачи сигнальной информации или данных пользователя. Его структура включает в себя несколько критически важных информационных элементов, которые позволяют БС и РС корректно интерпретировать передаваемую информацию. Состав SLOT PDU для сигнального сообщения можно представить следующей таблицей:

Информационный элемент (IE) Длина (бит) Примечание
Цветной код (СС) 4 Уникальный идентификатор зоны обслуживания (соты). Позволяет радиостанциям отличать свою базовую станцию от других, работающих на той же частоте (например, в соседней соте). РС игнорирует сообщения с неправильным CC.
Тип данных 4 Определяет тип и назначение передаваемой в PDU информации. Например, является ли сообщение запросом на установление соединения, пакетом пользовательских данных или служебной командой.
Паритет типа слота 12 Служебное поле, представляющее собой FEC код Голлея (20,8). Используется для контроля и коррекции ошибок в первых двух полях (CC и Тип данных). Позволяет получателю обнаружить и исправить возможные ошибки, допущенные при передаче, обеспечивая высокую достоверность приема критически важной сигнальной информации.

Принцип работы на примере:

  1. Инициация вызова (РС -> БС):

    • Радиостанция (РС) желает установить соединение. Она формирует SLOT PDU, в котором устанавливает:
      • Цветной код (СС) в соответствии с кодом целевой базовой станции.
      • Тип данных в значение, соответствующее запросу на подключение (например, [0,0,1,0]).
    • К этим 8 битам (4+4) добавляется 12-битный Паритет типа слота (FEC Голлея), формируя защищенный 20-битный блок.
    • Этот пакет передается на БС.
  2. Обработка запроса (БС):

    • Базовая станция принимает сигнал. Первым делом она декодирует 20-битный блок, используя код Голлея для проверки и исправления ошибок в полях CC и Тип данных.
    • БС проверяет Цветной код. Если он совпадает с ее собственным, она продолжает обработку.
    • БС анализирует Тип данных. Значение [0,0,1,0] указывает ей, что это запрос на установление соединения.
    • Далее БС, в зависимости от занятости канала, либо выделяет ресурсы для этой РС и отправляет ей ответный подтверждающий PDU с другим Типом данных, либо отклоняет запрос.
  3. Передача данных:

    • После установления соединения для передачи голоса или пользовательских данных РС и БС будут использовать в SLOT PDU Тип данных, зарезервированный для этой цели (например, [0,0,0,1]).

Таким образом, содержимое SLOT PDU, а именно комбинация защищенных кодом Голлея полей Цветной код и Тип данных, является фундаментальным механизмом, который обеспечивает начало, поддержание и завершение любого сеанса связи между базовой станцией и радиостанцией в сети DMR.

Как и во всех предыдущих демонстрациях по теме DMR, мы продолжаем использовать нашу пользовательскую библиотеку функций:

In [ ]:
include("/user/start/examples/communication/dmr_v3/dmr_lib.jl")
Out[0]:
decode_slot_pdu (generic function with 1 method)

Давайте посмотрим, что в ней нового.

1. Функция typegen (расширенная версия)
Была модифицирована и теперь принимает два входных параметра:

  • CC (Color Code) — 4-битный вектор, представляющий цветовой код системы.
  • data_type — 4-битный вектор, определяющий тип передаваемых данных (например, [0,0,1,0] для сигнала подключения или [0,0,0,1] для пользовательских данных).

Функция объединяет эти два вектора в один 8-битный блок и применяет к нему помехоустойчивое кодирование с помощью кода Голлея (20,8). Этот код не только обнаруживает, но и исправляет ошибки, возникшие при передаче. Результатом работы функции является 20-битный защищенный блок, который готов к передаче в эфир.

2. Функция encode_slot_pdu
Эта функция является высокоуровневой оболочкой для typegen. Она принимает те же аргументы (цветовой код и тип данных), вызывает typegen для генерации 20-битного блока и наглядно выводит в консоль получившуюся битовую последовательность. Её основная задача — упростить процесс кодирования и обеспечить визуальный контроль результата.

3. Функция decode_slot_pdu
Выполняет обратную операцию. Она принимает на вход 20-битный вектор (принятый SLOT PDU) и разделяет его на исходные компоненты:

  • Первые 4 бита интерпретируются как цветовой код (CC_decoded).
  • Следующие 4 бита — как тип данных (data_type_decoded).

Функция не проводит проверку на ошибки с помощью кода Голлея, а просто извлекает информационные биты из соответствующей позиции в структуре слота.

In [ ]:
function typegen(CC::Vector{Int}, data_type::Vector{Int})
    data = vcat(CC, data_type)
    
    ENCODE_2087 = [
        0x0000, 0xB08E, 0xE093, 0x501D, 0x70A9, 0xC027, 0x903A, 0x20B4, 0x60DC, 0xD052, 0x804F, 0x30C1, 0x1075, 0xA0FB, 0xF0E6,
        0x4068, 0x7036, 0xC0B8, 0x90A5, 0x202B, 0x009F, 0xB011, 0xE00C, 0x5082, 0x10EA, 0xA064, 0xF079, 0x40F7, 0x6043, 0xD0CD,
        0x80D0, 0x305E, 0xD06C, 0x60E2, 0x30FF, 0x8071, 0xA0C5, 0x104B, 0x4056, 0xF0D8, 0xB0B0, 0x003E, 0x5023, 0xE0AD, 0xC019,
        0x7097, 0x208A, 0x9004, 0xA05A, 0x10D4, 0x40C9, 0xF047, 0xD0F3, 0x607D, 0x3060, 0x80EE, 0xC086, 0x7008, 0x2015, 0x909B,
        0xB02F, 0x00A1, 0x50BC, 0xE032, 0x90D9, 0x2057, 0x704A, 0xC0C4, 0xE070, 0x50FE, 0x00E3, 0xB06D, 0xF005, 0x408B, 0x1096,
        0xA018, 0x80AC, 0x3022, 0x603F, 0xD0B1, 0xE0EF, 0x5061, 0x007C, 0xB0F2, 0x9046, 0x20C8, 0x70D5, 0xC05B, 0x8033, 0x30BD,
        0x60A0, 0xD02E, 0xF09A, 0x4014, 0x1009, 0xA087, 0x40B5, 0xF03B, 0xA026, 0x10A8, 0x301C, 0x8092, 0xD08F, 0x6001, 0x2069,
        0x90E7, 0xC0FA, 0x7074, 0x50C0, 0xE04E, 0xB053, 0x00DD, 0x3083, 0x800D, 0xD010, 0x609E, 0x402A, 0xF0A4, 0xA0B9, 0x1037,
        0x505F, 0xE0D1, 0xB0CC, 0x0042, 0x20F6, 0x9078, 0xC065, 0x70EB, 0xA03D, 0x10B3, 0x40AE, 0xF020, 0xD094, 0x601A, 0x3007,
        0x8089, 0xC0E1, 0x706F, 0x2072, 0x90FC, 0xB048, 0x00C6, 0x50DB, 0xE055, 0xD00B, 0x6085, 0x3098, 0x8016, 0xA0A2, 0x102C,
        0x4031, 0xF0BF, 0xB0D7, 0x0059, 0x5044, 0xE0CA, 0xC07E, 0x70F0, 0x20ED, 0x9063, 0x7051, 0xC0DF, 0x90C2, 0x204C, 0x00F8,
        0xB076, 0xE06B, 0x50E5, 0x108D, 0xA003, 0xF01E, 0x4090, 0x6024, 0xD0AA, 0x80B7, 0x3039, 0x0067, 0xB0E9, 0xE0F4, 0x507A,
        0x70CE, 0xC040, 0x905D, 0x20D3, 0x60BB, 0xD035, 0x8028, 0x30A6, 0x1012, 0xA09C, 0xF081, 0x400F, 0x30E4, 0x806A, 0xD077,
        0x60F9, 0x404D, 0xF0C3, 0xA0DE, 0x1050, 0x5038, 0xE0B6, 0xB0AB, 0x0025, 0x2091, 0x901F, 0xC002, 0x708C, 0x40D2, 0xF05C,
        0xA041, 0x10CF, 0x307B, 0x80F5, 0xD0E8, 0x6066, 0x200E, 0x9080, 0xC09D, 0x7013, 0x50A7, 0xE029, 0xB034, 0x00BA, 0xE088,
        0x5006, 0x001B, 0xB095, 0x9021, 0x20AF, 0x70B2, 0xC03C, 0x8054, 0x30DA, 0x60C7, 0xD049, 0xF0FD, 0x4073, 0x106E, 0xA0E0,
        0x90BE, 0x2030, 0x702D, 0xC0A3, 0xE017, 0x5099, 0x0084, 0xB00A, 0xF062, 0x40EC, 0x10F1, 0xA07F, 0x80CB, 0x3045, 0x6058, 0xD0D6
    ]
    byte = bi2de(data)
    cksum = ENCODE_2087[byte+1]
    type_val = (byte << 12) | ((cksum & 0xFF) << 4) | (cksum >> 8)
    type20bit = de2bi(type_val, 20)
    return type20bit
end

function encode_slot_pdu(CC::Vector{Int}, data_type::Vector{Int})
    slot_pdu = typegen(CC, data_type)
    println("Полный SLOT PDU: $(join(slot_pdu))")
    return slot_pdu
end

function decode_slot_pdu(slot_pdu::Vector{Int})
    CC_decoded = slot_pdu[1:4]
    data_type_decoded = slot_pdu[5:8]
    return CC_decoded, data_type_decoded
end
Out[0]:
decode_slot_pdu (generic function with 1 method)

Давайте проведём простой тест наших новых функций, чтобы убедиться в их корректной работе.

Логика теста:

  1. Задаём исходные данные: Мы определяем тестовые значения цветового кода (CC) и типа данных. В данном случае оба параметра установлены в [0, 0, 0, 1].
  2. Кодируем данные: Функция encode_slot_pdu принимает наши исходные данные. Внутри она вызывает функцию typegen, которая:
    • Объединяет CC и тип данных в 8-битную последовательность.
    • Применяет к ней сложный алгоритм помехоустойчивого кодирования (код Голлея).
    • Этот блок выводится на экран, и мы можем увидеть, как наши исходные 8 бит превратились в 20.
  3. Декодируем данные: Мы имитируем процесс приёма данных, передавая полученный 20-битный блок функции decode_slot_pdu. Её задача — извлечь из этой защищённой структуры исходные информационные биты, то есть первые 4 бита (CC) и следующие 4 бита (тип данных).
  4. Сравниваем результаты.
In [ ]:
CC = [0, 0, 0, 1]
data_type = [0, 0, 0, 1]

println("Исходные данные:")
println("Цветовой код: $(join(CC))")
println("Тип данных: $(join(data_type))")
encoded_pdu = encode_slot_pdu(CC, data_type)
decoded_CC, decoded_data_type = decode_slot_pdu(encoded_pdu)

println("\nРезультаты:")
println("Декодированный CC: $(join(decoded_CC))")
println("Декодированный тип данных: $(join(decoded_data_type))")

if (CC == decoded_CC) && (data_type == decoded_data_type)
    println("✅ УСПЕХ: Все данные совпали!")
else
    println("❌ ОШИБКА: Данные не совпали!")
end
Исходные данные:
Цветовой код: 0001
Тип данных: 0001
Полный SLOT PDU: 00010001101111000000

Результаты:
Декодированный CC: 0001
Декодированный тип данных: 0001
✅ УСПЕХ: Все данные совпали!

Проведённый тест и последующее моделирование служат важной, но промежуточной цели. Как мы можем убедиться из этого теста и поймём из дальнейшей работы, ключевая задача на текущем этапе — не в детальном и полномасштабном моделировании протокола DMR со всеми его нюансами.

Наша главная цель — создать и проверить корректность работы базового функционала, который послужит надёжным фундаментом для развития этой серии демонстраций. Мы абстрагируемся от излишней сложности, чтобы:

  1. Сфокусироваться на ключевых механизмах (кодирование, структура слота) без перегрузки деталями.
  2. Построить набор проверенных и работающих функций, которые можно будет с уверенностью использовать и наращивать в следующих частях.
  3. Этот модульный подход позволяет постепенно добавлять новые слои функциональности (например, голосовые кодеки, различные типы кадров, механизмы множественного доступа) в последующих демонстрациях, не переделывая базовую реализацию.

Таким образом, текущая реализация является концептуальной основой, подготовленной для будущего расширения и углубления в тему цифровой радиосвязи.

Реализованная модель системы DMR

image.png

Перейдём к описанию реализованной модели взаимодействия базовой станции (БС) и радиостанции (РС) в стандарте DMR. Модель построена по принципу обратной связи и включает несколько ключевых блоков, обеспечивающих установление соединения, передачу данных и управление состоянием системы.

Центральным управляющим элементом модели является блок Detect_mode_RS. Он реализует конечный автомат, управляющий состоянием радиостанции. Блок отслеживает два глобальных состояния: :disconnected (не подключен) и :connected (подключен).

Логика работы заключается в следующем:

  • Если РС находится в состоянии :disconnected, она инициирует подключение, возвращая значение 2, что соответствует запросу на подключение.
  • В состоянии :connected блок анализирует входные данные. При наличии данных (any(in1 .!= 0) счетчик неудач (fail_counter) сбрасывается. Если данные отсутствуют, счетчик увеличивается.
  • При достижении счетчиком неудач порогового значения (например, 3 подряд неудачных кадра) радиостанция возвращается в состояние :disconnected и инициирует повторное подключение.
  • Если данные есть и соединение стабильно, блок возвращает 1 для разрешения передачи данных или 0 для передачи пустого кадра.

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

Далее идёт блок подачи текстовых данных (SMS), данный блок активируется при условии, что режим работы не является служебным (не 0 и не 2) и следующий кадр не является служебным кадром управления (LC). Для тестовых целей размер текстового блока установлен в 27 байт. Важно отметить, что часть этих данных в дальнейшем будет перезаписана при формировании SLOT PDU.

Блок Gen_Pkg_RS отвечает за формирование исходящих пакетов от радиостанции. Его работа включает несколько этапов:

  1. Формирование заголовка LC (Link Control): На основе входных параметров (таких как AFLCO, BFID, адреса отправителя AdrP и получателя AdrI) формируется структура управляющей информации. Затем к ней добавляется CRC для контроля ошибок.
  2. Помехоустойчивое кодирование: Данные LC проходят несколько этапов кодирования, включая вычисление контрольной суммы (CS5bit) и применение матричного кодирования Хемминга (HemR, HemC).
  3. Формирование SLOT PDU: С помощью функции typegen генерируется 20-битное поле типа слота, которое включает цветовой код (CC) и тип данных. Это поле защищается кодом Голлея.
  4. Упаковка данных в кадр: В зависимости от режима работы (Mode), в кадр помещается либо запрос на подключение ([0,0,1,0]), либо пользовательские данные ([0,0,0,1]). Данные упаковываются в структуру кадра DMR, которая включает синхропоследовательность (SYNC) и информационные биты.

Блок также управляет чередованием передаваемых кадров (LC и данные), обеспечивая корректное взаимодействие с базовой станцией.

Далее идёт блок Pkg_BS, он имитирует упрощенное поведение базовой станции. Его основная задача — отвечать на запросы от радиостанции. Этот блок обеспечивает минимальную обратную связь, необходимую для функционирования модели.

  • При получении запроса на подключение ([0,0,1,0]) он возвращает кадр с типом слота, указывающим на подтверждение подключения.
  • При получении кадра с данными ([0,0,0,1]) он возвращает кадр с подтверждением приема данных.
  • В остальных случаях возвращается пустой кадр.

Параллельно с блоком Pkg_BS работает блок Data_selector, который отвечает за извлечение полезных данных из принятого кадра(216 бит), включая SLOT PDU.

In [ ]:
# Подключение вспомогательной функции запуска модели.
function run_model( name_model)
    Path = (@__DIR__) * "/" * name_model * ".engee"
    if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
        model = engee.open( name_model ) # Открыть модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
    else
        model = engee.load( Path, force=true ) # Загрузить модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
        engee.close( name_model, force=true ); # Закрыть модель
    end
    sleep(0.1)
    return model_output
end
run_model("test_BS_RS") # Запуск модели.
Building...
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 41%
Progress 46%
Progress 51%
Progress 56%
Progress 61%
Progress 66%
Progress 72%
Progress 77%
Progress 82%
Progress 87%
Progress 92%
Progress 98%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    "Преобразование битов в целые числа-2.1" => WorkspaceArray{Vector{UInt32}}("test_BS_RS/Преобразование битов в целые числа-2.1")
,
    "Detect_mode_RS.1" => WorkspaceArray{UInt8}("test_BS_RS/Detect_mode_RS.1")
,
    "Pkg_BS.1" => WorkspaceArray{UInt8}("test_BS_RS/Pkg_BS.1")

)
In [ ]:
Mode_RS = collect(simout["test_BS_RS/Detect_mode_RS.1"]).value
Mode_BS = collect(simout["test_BS_RS/Pkg_BS.1"]).value
plot(Mode_RS, label="Mode RS", seriestype=:steppost)
plot!(Mode_BS, label="Mode BS", seriestype=:steppost)
Out[0]:

Как мы видим система работает корректно РС запрашивает подключение, БС отвечает, РС отправляет данные, БС отвечает.

In [ ]:
Data_out = reduce(vcat, (collect(simout["test_BS_RS/Преобразование битов в целые числа-2.1"]).value))
plot(Data_out, label="Data", seriestype=:steppost)
Out[0]:

Анализируя представленные графики данных, можно сделать вывод, что помимо текстовых символов, которые были знакомы нам по предыдущим демонстрациям, также присутствуют данные пакета логического канала (LC), а пики на графике соответствуют данным SLOT PDU.

Вывод

Данная модель реализует базовый цикл взаимодействия DMR, включая установление соединения, передачу данных и обработку ошибок. Важно подчеркнуть, что цель данной разработки — не детальное и полное моделирование протокола DMR со всеми его специфическими особенностями, а создание концептуальной основы и демонстрация ключевых принципов работы. Этот фундамент позволяет в дальнейшем наращивать функциональность.