Engee 文档
Notebook

DMR:LC和A–E分组的形成。

数字移动无线电协议(DMR)仍然是可靠的专业通信的标准. 对其行为进行建模对于测试和分析非常重要。 在[原始示例](https://engee.com/community/ru/catalogs/projects/sistemnaia-model-dmr 数据生成和同步的基本逻辑已经奠定。 然而,它没有包括控制信道和分组类型的传输的逻辑的详细处理。

在这个修订版本中,我们增加了以下几点。

*形成LC数据包(逻辑信道数据包)的完整逻辑。

A、B、C、D和E类型的数据包的正确形成*:这些数据包由服务头、信号字段组成,包括同步序列(A)或短格式LC(B、C、D、E),还包含控制位。

*使用xcorr的流的帧同步的机制也在这里得到了改进。

该扩展适用于协议的全面分析,时间延迟的模拟和在真实硬件上的测试。

我们将通过分析编写的数据包生成函数并在脚本中测试此函数来开始实施此项目,以便随后使用Engee功能块将其传输到模型。

首先,我们将连接一个带有辅助函数的文件。 每个函数在与编码、错误检查或消息结构形成相关的数据处理中执行特定任务。 下面是一个功能列表,简要说明了它们的用途。

  1. de2bi(x,n)
    转换十进制数 x 到长度的二进制数组 n (右侧的低阶位)。

  2. bi2de(位)
    转换二进制数组 bits 到十进制数(将其解释为位字符串)。

  3. 编码(味精)
    计算消息的控制位(CRC msg 使用多项式 POLY 和Reed-Solomon编码算法。

  4. lc_header_mask(奇偶校验)
    敷面膜 START_MASK 到奇偶校验位 parity 使用XOR操作。

  5. log_mult(a,b)
    使用对数表在Galois字段(GF(256))中执行乘法 LOG_TABLEEXP_TABLE.

  6. CS5bit(LCcrc)
    计算72位数据块的5位校验和 LCcrc (将字节求和,并将除法的剩余部分除以31)。

  7. HemR(l)
    计算矩阵的水平(逐行)汉明验证位 l 尺寸为9x11。

  8. HemC(l,HR)
    计算矩阵的垂直(列)汉明验证位 l 考虑到横向检查 HR.

  9. 打字机(CC)
    基于4位代码生成20位消息类型 CC 从表中添加校验和 ENCODE_2087.

In [ ]:
path = "$(@__DIR__)/dmr_lib.jl"
println("Путь до библиотеки: $path")
include(path)
Путь до библиотеки: /user/start/examples/communication/dmr_v2/dmr_lib.jl
Out[0]:
typegen (generic function with 1 method)

以下是我们实现的功能 Gen_Pkg 处理输入参数,生成并返回数据以比特序列的形式传输,控制它们的发送,以及
生成用于在通信系统中传输的数据帧。 以下是该功能的主要阶段。

  1. 服务领域的形成(FLCO,FID);

  2. 地址处理;

  3. 编码和校验和;

  4. 抗噪声编码的数据准备;

  5. 传输帧的形成;

  6. 传输顺序控制;

  7. 完成的数据包或LC块的返回。

In [ ]:
# Инициализация глобальных переменных для отслеживания состояния
global E = 0       # Счетчик кадров
global l_block = 0  # Текущий блок данных

function Gen_Pkg(block, AFLCO, BFID, Switch, AdrP, AdrI, Ecstro, Shiroko, OVCM, Pr, ELC, CC)
    global E, l_block
    
    # 1. Формирование FLCO (Functional Logical Channel Organization)
    FLCO = get(Dict(
        1 => [0,0,0,0,0,0],  # Тип 1
        2 => [0,0,0,0,1,1],  # Тип 2
        3 => [0,0,0,1,0,0],  # Тип 3
        4 => [0,0,0,1,0,1],  # Тип 4
        5 => [0,0,0,1,1,0],  # Тип 5
        6 => [0,0,0,1,1,1],  # Тип 6
        7 => [0,0,1,0,0,0]   # Тип 7
    ), AFLCO, zeros(Int,6))  # По умолчанию - нули

    # 2. Формирование FID (Feature ID) в зависимости от Switch
    FID = Switch == 0 ? get(Dict(
        1 => [0,0,0,0,0,0,0,0],  # Профиль 1
        2 => [0,0,0,0,0,1,0,0],  # Профиль 2
        3 => [0,1,1,1,1,1,1,1]   # Профиль 3
    ), BFID, zeros(Int,8)) : zeros(Int,8)  # Если Switch=1 - нули

    # 3. Преобразование адресов в битовые векторы
    AdrP_vec = (AdrP == 0) ? de2bi(1, 24) : de2bi(AdrP, 24)  # Адрес получателя
    AdrI_vec = (AdrI == 0) ? de2bi(1234, 24) : de2bi(AdrI, 24)  # Адрес источника

    # 4. Формирование полного LC-блока (72 бита)
    FullLC = vcat([0, 0], FLCO, FID, [Ecstro], [0, 0, 0], [Shiroko], [OVCM], 
                  de2bi(Pr - 1, 2), AdrP_vec, AdrI_vec)

    # 5. Кодирование и вычисление контрольных сумм
    FullLCdec = [bi2de(FullLC[i:i+7]) for i in 1:8:72]  # Разбивка на байты
    parity = encode(FullLCdec)  # Кодирование Рида-Соломона
    CRC = lc_header_mask(parity)  # Применение маски к контрольной сумме
    LCcrcDec = vcat(FullLCdec[1:9], CRC)  # Объединение данных и CRC
    LCcrc = vcat([reverse(digits(b, base=2, pad=8)) for b in LCcrcDec]...)  # В биты

    # 6. Подготовка данных для BPTC (Block Product Turbo Code)
    R = [0, 0, 0, 0]  # Резервные биты
    I = vcat(reverse(R[1:3]), LCcrc)  # Формирование информационного блока
    l = reshape(I[1:99], 11, 9)'  # Матрица 9x11
    CS = CS5bit(LCcrc)  # 5-битная контрольная сумма
    HR = HemR(l)  # Горизонтальные проверки Хэмминга
    HC = HemC(l, HR)  # Вертикальные проверки Хэмминга
    type20bit = typegen(CC)  # Генерация 20-битного типа сообщения

    # 7. Управление передачей кадров
    E = E == 0 ? ELC * 12 + 1 : E + 1  # Обновление счетчика кадров

    Enabled = mod(E, 2) == 0 ? 1 : 0  # Флаг активности передачи
    LCs = 0  # Флаг LC-блока
    LC_next = false  # Флаг следующего LC-блока
    
    # Логика управления передачей
    if E == ELC * 12 + 1
        LC_next = true
    elseif E == ELC * 12 + 2
        Enabled = 0
        LCs = 1
    elseif E == ELC * 12 + 3
        E = 1
        Enabled = 0
    end

    # 8. Формирование LC-блока (288 бит)
    LC_block = zeros(Int, 288)
    LC_block[121:168] = [1,1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1]  # Синхропоследовательность
    LC_block[111:120] = type20bit[1:10]  # Первые 10 бит типа
    LC_block[169:178] = type20bit[11:20]  # Последние 10 бит типа

    # 9. Формирование BPTC матрицы (13x15)
    BPTC = zeros(Int, 13, 15)
    BPTC[1:9, 1:11] .= l  # Основные данные
    BPTC[10:13, 1:15] .= HC  # Вертикальные проверки
    BPTC[1:9, 12:15] .= HR  # Горизонтальные проверки
    BPTCl = vec(permutedims(BPTC))  # Преобразование в вектор

    # 10. Перемежение данных
    LCper = zeros(Int, 195)
    for i in 1:195
        idx = mod(i * 181, 196)
        idx == 0 && (idx = 196)
        LCper[idx] = BPTCl[i]  # Алгоритм перемежения
    end

    # 11. Заполнение LC-блока
    LC_block[14:110] .= LCper[1:97]  # Первая часть данных
    LC_block[179:276] .= LCper[98:195]  # Вторая часть данных
    LC_block[13] = 0  # Резервный бит

    # 12. Формирование быстрых данных
    FullLC = LCcrc[1:72]
    CC = reverse([1, 0, 0, 0])  # Код коррекции
    QR = [1, 0, 0, 0, 1, 0, 1, 1, 1]  # Быстрые данные
    Pi = 0  # Флаг PI
    QR_rev = reverse(QR[1:8])  # Обратные быстрые данные

    # 13. Формирование BPTC для быстрых данных (8x16)
    BPTC = zeros(Int, 8, 16)
    BPTC[1:2, 1:11] = reshape(reverse(FullLC[51:72]), 2, 11)  # Часть данных
    BPTC[3:7, 1:10] = reshape(reverse(FullLC[1:50]), 5, 10)  # Основные данные
    BPTC[1:7, 12:16] = [1 0 1 1 1; 0 0 0 0 0; 0 1 0 0 0; 0 0 0 0 0; 1 1 1 1 0; 0 0 0 0 0; 0 0 0 0 1]  # Контрольные биты
    BPTC[8, :] = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]  # Чередующиеся биты
    BPTC[3:7, 11] = CS  # Контрольная сумма

    # 14. Формирование битового блока из входных данных
    bit_block = zeros(Int, 288)
    for i in 1:27
        bits = reverse(digits(block[i], base=2, pad=8))  # Байт в биты
        if i < 14
            bit_block[(1:8) .+ ((i-1)*8) .+ 12] = bits  # Первые 13 байт
        elseif i == 14
            bit_block[117:120] = bits[1:4]  # Особый случай для 14-го байта
            bit_block[169:172] = bits[5:8]
        else
            bit_block[(1:8) .+ ((i-1)*8) .+ 12 .+ 48] = bits  # Остальные байты
        end
    end

    # 15. Формирование кадров данных
    DataFrames = [
        # Синхрокадр
        vcat(zeros(120), [0,1,1,1,0,1,0,1,1,0,0,1,0,0,0,1,1,0,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,0,0,1,0,1,0,0,1,1,1,1,1,0,0], zeros(120)), 
        # Кадры данных 1-4
        vcat(zeros(120), CC, [Pi], [0,1], [QR[9]], vec(BPTC[:, 1:4]), QR_rev, zeros(120)),
        vcat(zeros(120), CC, [Pi], [1,1], [QR[9]], vec(BPTC[:, 5:8]), QR_rev, zeros(120)),
        vcat(zeros(120), CC, [Pi], [1,1], [QR[9]], vec(BPTC[:, 9:12]), QR_rev, zeros(120)),
        vcat(zeros(120), CC, [Pi], [1,0], [QR[9]], vec(BPTC[:, 13:16]), QR_rev, zeros(120))
    ]

    # 16. Формирование итогового пакета
    package = zeros(Int, 288)
    if Enabled == 1
        l_block += 1
        package = copy(DataFrames[l_block])
        package[13:120] .= bit_block[13:120]  # Данные первой части
        package[169:276] .= bit_block[169:276]  # Данные второй части
        if l_block == 5
            l_block = 0  # Сброс счетчика блоков
        end
    end
    
    # Возврат LC-блока или пакета данных и флага LC_next
    return LCs == 1 ? copy(LC_block) : package, LC_next
end
Out[0]:
Gen_Pkg (generic function with 1 method)

接下来,我们将测试所描述的功能。 测试由三个主要部分组成。

  1. 数据准备:

    *正在创建一个2670字节(等于1)的测试数组

    *输入数组以零字节添加到27的倍数(块大小)

  2. 分区和处理:

    *将数据分成27字节的块。

    *为每个块调用函数 Gen_Pkg(),其形成数据帧,由此我们基本上检查我们的函数在仿真条件下的操作。

  3. 结果分析:

    *收集所有生成的位块

    *计算每个块中的位的总和(用于验证)

    *仅过滤和输出非零量(我们丢弃每第二帧)

In [ ]:
bytes = Int.(ones(2670))
remainder = length(bytes) % 27
bytes = vcat(bytes, remainder == 0 ? Int[] : zeros(Int, 27 - remainder))
bit_blocks = Vector{Vector{Int}}()
buffer = Int[]
pending_block = nothing
i = 1
while i <= length(bytes)
    block = bytes[i:min(i+26, length(bytes))]
    data_bits, LC_next = Gen_Pkg(block, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, [0, 0, 0, 1])
    push!(bit_blocks, data_bits)
    i += 27
end
sum_bit = sum.(bit_blocks)
filtered_vector = filter(x -> x != 0, sum_bit)
println(filtered_vector)
[104, 52, 41, 38, 41, 44, 52, 41, 38, 41, 44, 52, 41, 104, 38, 41, 44, 52, 41, 38, 41, 44, 52, 41, 38, 41, 104, 44, 52, 41, 38, 41, 44, 52, 41, 38, 41, 44, 52, 104, 41, 38, 41, 44, 52, 41, 38, 41, 44]

现在让我们检查LC数据包是否以正确的频率发生。 基于ELC=2设置,我们将每2个超帧看到它,即每第十三帧应该是104。

In [ ]:
indices = ((findall(x -> x == 104, filtered_vector)).-1)/13
Out[0]:
4-element Vector{Float64}:
 0.0
 1.0
 2.0
 3.0

现在让我们删除LC并检查其余帧是否交替。

In [ ]:
# Удаляем все 104(LC)
filtered_vector = filter(x -> x != 104, filtered_vector)
# Искомая комбинация
pattern = [52, 41, 38, 41, 44] # A, B, C, D, E

function check_pattern(vec, pat)
    # Обрезаем вектор до ближайшей подходящей длины
    pattern_length = length(pat)
    suitable_length = div(length(vec), pattern_length) * pattern_length
    trimmed_vec = vec[1:suitable_length]
    # Проверяем соответствие шаблону
    for i in 1:pattern_length:length(trimmed_vec)
        if trimmed_vec[i:i+pattern_length-1] != pat
            return false
        end
    end
    return true
end

# Проверяем filtered_vector
is_correct = check_pattern(filtered_vector, pattern)
println("Повторяется ли комбинация $pattern? ", is_correct ? "Да" : "Нет")
Повторяется ли комбинация [52, 41, 38, 41, 44]? Да

正如我们所看到的,该功能正常工作。 现在让我们测试模型。

image.png

让我们运行为函数创建绑定的模型。

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
Out[0]:
run_model (generic function with 1 method)

根据下面的图表,我们看到,首先,LC_next控制信号警告我们下一帧正常工作,其次,我们看到该函数在模型和脚本中的行为相同。 另外,为了方便在Engee函数中定义包参数,我们使用参数声明,从而减少了块的输入端口数。![图像。png](附件:图片。png)

In [ ]:
display(run_model("test_new_function"))
gr()
display(plot(vcat(collect(simout["test_new_function/Переключатель.1"]).value...)))
plot(sum_bit, seriestype = :steppre, linewidth=3, legend = false)
plot!(vcat(collect(simout["test_new_function/Сумма элементов.1"]).value...), seriestype = :steppre, linewidth=3, legend = false)
Building...
Progress 0%
Progress 8%
Progress 27%
Progress 41%
Progress 55%
Progress 70%
Progress 83%
Progress 98%
Progress 100%
SimulationResult(
    "Сумма элементов.1" => WorkspaceArray{Int64}("test_new_function/Сумма элементов.1")
,
    "Переключатель.1" => WorkspaceArray{Vector{Int64}}("test_new_function/Переключатель.1")

)
Out[0]:

现在我们已经测试了功能本身,我们可以继续改进我们的DMR系统模型。

image.png

从上面的截图中我们可以看到,该块 Gen_Pkg 控制块内的数据输入 DMR Physical Layer 在此示例的先前版本中描述的逻辑位于,并且在块 Frame_Synchronization 有两个函数执行我们的流的帧同步。 让我们来看看这些块,让我们从 Xcorr.

``'茱莉亚

用于存储延迟的全局变量

全局延迟=0

处理数据块的主要功能

函数(c::块)(T::Real,BitSignal)
全球延迟
US=false#成功同步标志

#参考同步序列(48位)
数据同步= [-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, -1, 1, -1, -1, 1, 1, 
             -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1]

#计算输入信号和同步序列之间的互相关
MS=xcorr_simple((2*BitSignal.-1),Data_Sync)

#搜索相关峰(阈值>46)

峰=findall(MS.>46)

如果!isempty(峰值) 
    P=Peak[1]#取第一个显着峰
    
    #检查峰值是否在可接受的范围内
    如果P>120&&P<456
        #根据峰值的位置计算延迟
        如果P>288
            延迟=P-408
        其他
            延迟=P-120    
        结束
    结束
    US=true#设置成功同步标志

结束

D=Delay#当前延迟值
返回我们,D,MS#我们返回同步状态,延迟和

端相关阵列。


现在让我们进入街区 Selector.

# 用于管理处理状态的全局变量:
信号处理活动的全局U=false#标志
全局j=0#时间间隔计数器

# 处理数据块的主要功能
函数(c::块)(T::Real,BitSignal,US,延迟)
全局U,j#访问全局变量
    
    #输出信号的初始化(216位)
    Sig=零(216)
    
    #处理权限标志
    启用=错误
    
    #同步信号处理(US):
#如果接收到同步信号(我们!=0),重置状态
    我们!= 0 ? (U=US;j=0):无
    
    #如果处理处于活动状态(U==1)
如果U==1
        j+=1#递增计数器
        
        #我们只处理奇数间隔
        如果isodd(j)
#我们通过从BitSignal中选择位来生成输出信号,同时考虑到延迟:
            #-块1:位13-120(108位)
            #-块2:位169-276(108位)
            #总长度:216位
            西格=BitSignal[Int。([收集(13:120);收集(169:276)]。+延迟)]
Enable=true#激活权限标志
        结束
    结束
    
    #12间隔后重置状态
    j==12? (U=false;j=0):无
    
     我们回来了:
    #-Enable:有效数据的标志
    #-Sig:已处理信号(216位)
    返回启用,Sig
结束

现在,让我们运行模型并通过计算输出处相对于输入的错误数来检查其正确性。
更有趣的测试在模型的第一个版本中呈现。 如果您愿意,您也可以将它们应用到这个模型中,但我们决定不在这里这样做,以免人为地增加在这个例子中被分析的材料的体积。

In [ ]:
display(run_model("DMR_V2"))
println("Суммарное количество ошибок по байтам равно: ",sum(vcat(collect(simout["DMR_V2/err_symbol"]).value...)))
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 62%
Progress 67%
Progress 72%
Progress 77%
Progress 82%
Progress 88%
Progress 93%
Progress 98%
Progress 100%
Progress 100%
SimulationResult(
    "err_symbol" => WorkspaceArray{Vector{Int64}}("DMR_V2/err_symbol")
,
    "Inp" => WorkspaceArray{Vector{Int64}}("DMR_V2/Inp")
,
    "Out" => WorkspaceArray{Vector{UInt32}}("DMR_V2/Out")

)
Суммарное кол-во ошибок по байтам равно: 0

输出

在这个例子中,分析了DMR协议的相当重要的部分。 下次,如果您对此主题感兴趣,我们将分析MAC层,即对信道帧和控制帧的响应逻辑。