最近有朋友问我OAI的rfsimulator怎么用,我就简单举个例子分享一下:包括如何使用rfsimulator、如何新建一个自己定义的信道。
用rfsimulator能做的事情:
网络拓扑:
[OAI核心网] --网线-- [OAI gNB/eNB ---- 127.0.0.1::4043 ---- OAI UE]
或部署在不同机器上:
[OAI gNB/eNB] 10.0.1.1–网线-- [OAI UE] 10.0.1.2
和一般的接入网build步骤类似,开始build前的步骤可以参考03 - OAI接入网&OAIUE搭建过程 - 研0
区别在这一步:
./build_oai -I --eNB -x --install-system-files -w SIMU
即-w USRP 换成 -w SIMU 即可。(更多编译选项可-h查看)
rfsimulator会在127.0.0.1的4043端口开启一个tcp server给gNB/eNB用,UE回去连接这个端口。可以在\targets\ARCH\rfsimulator\simulator.c 中可以修改这个默认端口:
#define PORT 4043 //default TCP port for this simulator
rfsimulator也使用接入网的配置文件(毕竟就是模拟RF的),配置文件中有一个叫“rfsimulator”的specific section是给仿真器的。
运行命令基本和运行带RF时候的一样,就是需要添加额外选项。不熟悉一般带RF时候运行命令的可以参考03 - OAI接入网&OAIUE搭建过程 - 研0
跟运行带RF的时候只有命令行中要加上--rfsim
选项的区别,所以需要一个有效的配置文件。配置文件中和rfsimulator相关的参数:
①serveraddr:UE要连接的或者基站要用作server的IP地址
②serverport:端口
③option:用逗号分隔的运行时的选项,目前支持两个:chanmod 启用特定信道模型,saviq 启用将传输的I/Q采样写入文件。默认情况下都不启用。
④modelname:信道模型,在chanmod选项启用的时候,要指明使用的信道模型的名字,默认是AWGN信道。定义/修改信道模性详见第三节。
⑤IQfile:启用saviq选项时,用来指定存储iq的文件的路径。不指定则默认是/tmp/rfsimulator.iqs
下面举两个例子,
sudo RFSIMULATOR=server ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --parallel-config PARALLEL_SINGLE_THREAD --rfsim --phy-test
命令行中gnb.band78.tm1.106PRB.usrpn300.conf即配置文件,--phy-test
即启用产生上下行随机流量。如果要启用noS1模式的话,需要再添加--noS1
和--nokrnmod 1
选项:
sudo RFSIMULATOR=server ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-LTE-EPC/CONF/gnb.band78.tm1.106PRB.usrpn300.conf --parallel-config PARALLEL_SINGLE_THREAD --rfsim --phy-test --noS1 --nokrnmod 1
sudo RFSIMULATOR=enb ./lte-softmodem -O <config file> --rfsim
sudo RFSIMULATOR=<TARGET_GNB_INTERFACE_ADDRESS> ./nr-uesoftmodem --rfsim --phy-test --rrc_config_path ../../../ci-scripts/rrc-files
其中,如果gNB和UE运行在一个机器上,--rrc_config_path ../../../ci-scripts/rrc-files
是可以省略的。此外,运行在一台机器上的话务必确保机器性能OK。
添加一个RFSIMULATOR=
sudo RFSIMULATOR=192.168.2.200 ./lte-uesoftmodem -C 2685000000 -r 50
注1:如果遇到“RA not active”的问题,要记得生成有效的SIM卡:
$OPENAIR_DIR/targets/bin/conf2uedata -c $OPENAIR_DIR/openair3/NAS/TOOLS/ue_eurecom_test_sfr.conf -o
注2:使用rfsimulator的时候UE也可以用-d选项正常启动软件示波器,论文出图必备
注3:只有在不同机器上运行gNB/eNB和UE的时候才能使用iperf灌包。一些测试命令的例子:
#from gNB mchine:
ping -I oaitun_enb1 10.0.1.2
#from nrUE mchine:
ping -I oaitun_ue1 10.0.1.1
#Server nrUE machine:
iperf -s -i 1 -u -B 10.0.1.2
#Client gNB machine:
iperf -c 10.0.1.2 -u -b 0.1M --bind 10.0.1.1
#Server gNB machine:
iperf -s -i 1 -u -B 10.0.1.1
#Client nrUE machine:
iperf -c 10.0.1.1 -u -b 0.1M --bind 10.0.1.2
启用saviq选项的时候rfsimulator会存储收发到的I/Q文件,想要replay的话,需要额外编译make replay_node
然后就可以使用replay_node这个程序来作为数据源向基站或者UE发送。(我还没用过
到这里,才是涉及到你自己的仿真需要定义的地方。
首先,rfsimulator的信道模型定义在\openair1\SIMULATION\TOOLS\sim.h 的SCM_t中,是一个enum类型。
typedef enum {
custom=0,
SCM_A, //SCM:Spatial Channel Model,3GPP的空间信道模性,A和B模型rfsimulator都没有定义(毕竟没啥用…)
//……
AWGN,
Rayleigh1_orthogonal,
//……
//已经包含了AWGN,瑞利信道,莱斯信道等等
//我们可以定义一个新的,也可以直接修改已经存在的,这里以新建一个高轨卫星HoSC信道为例
myHoSC,
} SCM_t;
要添加一个新的信道模性的话,需要先在SCM_t中添加上,比如上面我新建了一个叫HoSC的信道。紧接着,在下面映射表里也加上
#define CHANNELMOD_MAP_INIT \
{"custom",custom},\
//……
//加上我们定义的新的信道模性
{"myHoSC", myHoSC}
{NULL, -1}
然后,目前rfsimulator支持的参数全部定义在sim.h的channel_desc_t结构体中(desc是description的缩写),以下参数需要根据 你的模型 自己确定数值:
typedef struct {
///tx天线数 number of tx antennas
uint8_t nb_tx;
///rx天线数
uint8_t nb_rx;
///抽头数,通道模型的特征在于抽头数量、相对于第一次抽头的时间延迟、相对于最强抽头的平均功率和每个抽头的多普勒。在rfsimulator里可以理解为信道模型的多径数。
uint8_t nb_taps;
///抽头的线性幅度,抽头系数,数组。(用来表示每个多径的幅度系数)
double *amps;
///每个抽头的(相对)延迟,数组,以微秒为单位,数值在0到Td之间,Td定义在下面。
double *delays;
///信道冲激响应的length(和samplerate及Td相关)
uint8_t channel_length;
///表示信道状态向量,复数,二维数组. size(state) = nb_taps * (n_tx * n_rx);
struct complex **a;
///内插(采样间隔)的信道脉冲响应. size(ch) = (n_tx * n_rx) * channel_length. 注意:ch的维数是a的转置的维数。 这是为了在将相关矩阵应用于信道状态矩阵时使用BLAS库(basic linear algebra subroutine)。
struct complex **ch;
///Sampled frequency response (90 kHz resolution)
struct complex **chF;
///最大差分路径时延,单位为微秒。(原注释Maximum path delay in mus.
/*但我觉得不对,看random_channel.c里的代码这应该是Time difference,也就是最大路径时延差分。)
*我觉得虽然仿真多径的话Td就够了,但是如果要仿真卫星这种超大延迟通信链路的话还是有必要定义
//double *actual_path_delay;
*不然不能仿真在和核心网通信时候各种GTP加速、鉴权流程等各种操作。具体实现上,rfsimulator需要等待一个actual_path_delay再从TCP server发送数据,从UE收到的数据等待一个actual_path_delay再开始发给PHY处理。
*/
double Td;
///信道带宽,单位MHz(注意别设置离谱的数值)
double channel_bandwidth;
///系统采样率,单位Msps。
double sampling_rate;
///第一个抽头关于其他抽头的莱斯系数(0~1,其中0表示AWGN,1表示瑞利信道)。
double ricean_factor;
///AOA,波前到达角,以弧度为单位,仅适用于莱斯信道
double aoa;
///是否随机生成AOA,如果设置为1则aoa根据均匀随机分布进行随机化,你也可以到simulator.c里改成你想要的分布。
int8_t random_aoa;
///最大多普勒频移,单位Hz
double max_Doppler;
///Square root of the full correlation matrix size(R_tx) = nb_taps * (n_tx * n_rx) * (n_tx * n_rx).
struct complex **R_sqrt;
///路径损耗,包括阴影衰落,单位dB
double path_loss_dB;
///额外信道延迟 in samples.
int32_t channel_offset;
///工程性参数,allows for simple 1st order temporal variation. 0 means a new channel every call, 1 means keep channel constant all the time
double forgetting_factor;
///工程性的参数,第一次运行设置为1,否则为0。
uint8_t first_run;
/// 频偏仿真要用的初始相位,initial phase
double ip;
/// 信号传输的路径数,(原注释number of paths taken by transmit signal)
/*区别于nb_taps表示的多径数,这个是用来表示“路径数”的,MIMO的情况下针对每个天线的路径的。
*然后random_channel.c把这个参数固定为10,我感觉是默认了信道模型中的路径数都不超过10个,如果你要仿真更大规模的MIMO,要一并修改这个参数,否则超出nb_paths的路径不会进到multipath_tv_channel.c的循环里。
*/
uint16_t nb_paths;
/// 计时,测试用的
time_stats_t random_channel;
time_stats_t interp_time;
time_stats_t interp_freq;
time_stats_t convolution;
} channel_desc_t;
那么你的数值写在哪里呢?rfsimulator在运行时候会统一调用openair1\SIMULATION\TOOLS\random_channel.c 中的
channel_desc_t *new_channel_desc_scm(uint8_t nb_tx,
uint8_t nb_rx,
SCM_t channel_model,
double sampling_rate,
double channel_bandwidth,
double forgetting_factor,
int32_t channel_offset,
double path_loss_dB)
函数来加载信道模型,我们需要在这个函数的case中具体定义我们新加的信道的参数。
下面我分别用 单直射径莱斯信道模型 和 我们刚刚新定义的 myHoSC信道模型来举例:
case Rice1:
nb_tx = 1;nb_rx = 1;//SISO传输
nb_taps = 1;//一条路径
Td = 0;//没有多径,所以差分时延为0
channel_length = 1;
ricean_factor = 0.1;//莱斯信道,莱斯系数=0.1
aoa = 0.7854; //AOA
maxDoppler = 0; //没有多普勒频移
//用100 PRB,采样率是30.72MHz,带宽20MHz
sampling_rate = 30.72;channel_bandwidth = 20.00;
forgetting_factor = 0.0;//每次仿真生成新的信道
channel_offset = 0.0;//没有额外信道延迟
//最后调用fill_channel_desc函数把刚刚定义的参数写入channel_desc中
fill_channel_desc(chan_desc,nb_tx,
nb_rx,
nb_taps,
channel_length,
default_amp_lin,
NULL,
NULL,
Td,
sampling_rate,
channel_bandwidth,
ricean_factor,
aoa,
forgetting_factor,
maxDoppler,
channel_offset,
path_loss_dB,
0);
break;
我们其实不需要了解fill_channel_desc
做了些什么,但是如果你想“深度定制”你的信道模型,还是有必要了解一下的。下面我用myHoSC信道模型来举例不使用fill_channel_desc
时候该怎么定义信道参数。
#define M_PI 3.14159265358979323846
//…………
case myHoSC:
//设置九个抽头/九条多径
chan_desc->nb_taps = 9;
//在室内,没有直射径,那就瑞利信道
ricean_factor = 1;
//高速运动,有多普勒频移
maxDoppler = 10000
//最大路径差分时延
chan_desc->Td = 100;
//AOA木大木大
aoa = 0;
//偷个懒,用3GPP ETU的一些定义:
chan_desc->channel_length = (int) (2*chan_desc->sampling_rate*chan_desc->Td + 1 + 2/(M_PI*M_PI)*log(4*M_PI*chan_desc->sampling_rate*chan_desc->Td));
//设置每条多径的线性幅度系数
double myHoSC_amps_dB[] = {-1.0,-1.3,-1.8,0.0,0.5,0.2,-3.7,-5.2,-7.9};
sum_amps = 0;
chan_desc->amps = (double *) malloc(chan_desc->nb_taps*sizeof(double));
for (i = 0; i<chan_desc->nb_taps; i++) {
chan_desc->amps[i] = pow(10,.1*myHoSC_amps_dB[i]);
sum_amps += chan_desc->amps[i];
}
for (i = 0; i<chan_desc->nb_taps; i++)
chan_desc->amps[i] /= sum_amps;
//设置每条多径的时延
double myHoSC_delays[] = { 0.0,1.0,2.4,4.0,4.6,10.0,32.0,46.0,10.0};
chan_desc->delays = myHoSC_delays;
//工程性的部分
chan_desc->ricean_factor = ricean_factor;
chan_desc->aoa = aoa;
chan_desc->random_aoa = 0;
chan_desc->ch = (struct complex **) malloc(nb_tx*nb_rx*sizeof(struct complex *));
chan_desc->chF = (struct complex **) malloc(nb_tx*nb_rx*sizeof(struct complex *));
chan_desc->a = (struct complex **) malloc(chan_desc->nb_taps*sizeof(struct complex *));
for (i = 0; i<nb_tx*nb_rx; i++)
chan_desc->ch[i] = (struct complex *) malloc(chan_desc->channel_length * sizeof(struct complex));
for (i = 0; i<nb_tx*nb_rx; i++)
chan_desc->chF[i] = (struct complex *) malloc(1200 * sizeof(struct complex));
for (i = 0; i<chan_desc->nb_taps; i++)
chan_desc->a[i] = (struct complex *) malloc(nb_tx*nb_rx * sizeof(struct complex));
if (nb_tx==2 && nb_rx==2) {
//TM4的2*2 MIMO支持,其他MIMO需要再添加相应代码
chan_desc->R_sqrt = (struct complex **) malloc(6*sizeof(struct complex **));
for (i = 0; i<6; i++)
chan_desc->R_sqrt[i] = (struct complex *) &R22_sqrt[i][0];//需要自己定义2*2MIMO的R_sqrt
} else {
//SISO
chan_desc->R_sqrt = (struct complex **) malloc(6*sizeof(struct complex **));
for (i = 0; i<6; i++) {
chan_desc->R_sqrt[i] = (struct complex *) malloc(nb_tx*nb_rx*nb_tx*nb_rx * sizeof(struct complex));
for (j = 0; j<nb_tx*nb_rx*nb_tx*nb_rx; j+=(nb_tx*nb_rx+1)) {
chan_desc->R_sqrt[i][j].x = 1.0;
chan_desc->R_sqrt[i][j].y = 0.0;
}
}
}
break;
信道参数定义好之后需要重新编译,然后在运行的时候,配置文件的chanmod后面加上你要用的信道名字,比如:
注:只有输入噪声可以在命令行通过加上-s选项来更改,其他参数的修改都需要重新编译(是有点反人类嗷……)。当路径损耗= 0时,-s 5
为可见一丢丢噪声。 -s
是channelmod.s的快捷方式。 希望EURECOM以后在channelmod里添加更多参数来增强信道建模仿真的灵活性。
rfsimulator只模拟了多径的相对时延(Td和*delay),但没有模拟绝对时延。想要模拟大时延的信道就无法实现了。因此我们扩展一下rfsimulator。
在channel_desc_t
中添加参数:
//信道绝对时延,单位微秒
double abs_delay_dl;
double abs_delay_ul;
//方差,不停随机生成abs_delay,如果设置为非0则以(abs_delay,var_abs_delay)的高斯分布随机产生随机时延,你可以到simulator.c里改成你想要的分布。
//如果设置为0则不产生随机
double var_abs_delay_dl;
double var_abs_delay_ul;
按概率分布生成随机时延很简单就不说了。
然后,可以让基站的TCP server等待一个abs_delay_dl再发送,监听到UE的数据之后等待一个abs_delay_ul再给基站去处理。那么就需要:
targets\ARCH\rfsimulator\simulator.c
中的rfsimulator_read()
和rfsimulator_write()
函数各加一句sleep();
麻烦的是这两个函数是gNB/eNB和UE共用的,所以还要加个判断rfsimulator-> typeStamp == ENB_MAGICDL_FDD ? "(eg)NB" : "UE"
,从而确定使用abs_delay_ul
还是abs_delay_dl
。
A、我们可以选择在simulator.c中修改:
由于device->trx_read_func = rfsimulator_read;
,因此每次tx和rx需要read的时候rfsimulator_read()
会调用flushInput()
,flushInput()
和start_ue()
函数会调用allocCirBuf()
函数,allocCirBuf()
会调用new_channel_desc_scm()
和random_channel()
。
那么allocCirBuf()
就是我们该做①中修改的地方了。在这里修改我比较确定是不会影响rfsimulator以外其他任何功能的。
举例:usleep(ptr->channel_model->abs_delay_dl); //因为单位是微秒
B、我们也可以选择直接在random_channel.c的random_channel()里做①中的修改,我暂时还没看是否会影响到其他功能。
举例:usleep(desc->abs_delay_dl);
除了上述添加绝对时延支持以外,添加任何其他信道特征的修改方法也是一样/类似的。