22 - OAI rfsimulator 射频仿真- 博一

最近有朋友问我OAI的rfsimulator怎么用,我就简单举个例子分享一下:包括如何使用rfsimulator、如何新建一个自己定义的信道。

  • OAI 的rfsimulator顾名思义是用来替代真实的射频的,即当你没有USRP的时候可以在本地跑一个基站和UE来进行仿真。

用rfsimulator能做的事情:

  1. 使用noS1模式,仅使用接入网进行仿真,通过gNB/eNB 和 UE的“oaitun” IP隧道,进行实时通信。也可以用写好的IQ数据文件来测试收发特定的数据,详见第二节。
  2. 使用phy-test模式,在gNB/eNB和UE之间产生随机流量。
  3. 自行定义空口信道参数,包括各种衰落、时延扩展、噪声等,详见第三节。

网络拓扑:
[OAI核心网] --网线-- [OAI gNB/eNB ---- 127.0.0.1::4043 ---- OAI UE]
或部署在不同机器上:
[OAI gNB/eNB] 10.0.1.1–网线-- [OAI UE] 10.0.1.2

1、build OAI rfsimulator

和一般的接入网build步骤类似,开始build前的步骤可以参考03 - OAI接入网&OAIUE搭建过程 - 研0

区别在这一步:

./build_oai -I --eNB -x --install-system-files -w SIMU

即-w USRP 换成 -w SIMU 即可。(更多编译选项可-h查看)

2、运行rfsimulator

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

2.1 基站部分:

跟运行带RF的时候只有命令行中要加上--rfsim选项的区别,所以需要一个有效的配置文件。配置文件中和rfsimulator相关的参数:
①serveraddr:UE要连接的或者基站要用作server的IP地址
②serverport:端口
③option:用逗号分隔的运行时的选项,目前支持两个:chanmod 启用特定信道模型,saviq 启用将传输的I/Q采样写入文件。默认情况下都不启用。
④modelname:信道模型,在chanmod选项启用的时候,要指明使用的信道模型的名字,默认是AWGN信道。定义/修改信道模性详见第三节。
⑤IQfile:启用saviq选项时,用来指定存储iq的文件的路径。不指定则默认是/tmp/rfsimulator.iqs

下面举两个例子,

  • 如果使用5G gNB:
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
  • 如果使用4G eNB:
sudo RFSIMULATOR=enb ./lte-softmodem -O <config file> --rfsim

2.2 UE部分:

  • 如果使用5G UE:
sudo RFSIMULATOR=<TARGET_GNB_INTERFACE_ADDRESS> ./nr-uesoftmodem --rfsim --phy-test --rrc_config_path ../../../ci-scripts/rrc-files 

其中,如果gNB和UE运行在一个机器上, 就是127.0.0.1,如果不在一个机器上,那就该配置为gNB所在的IP地址。如果运行在一个机器上gNB会给UE必要的RRC配置,--rrc_config_path ../../../ci-scripts/rrc-files是可以省略的。此外,运行在一台机器上的话务必确保机器性能OK。

  • 如果使用4G UE:

添加一个RFSIMULATOR= ,其他跟运行4G UE一样。

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

3、使用rfsimulator

3.1 Replay

启用saviq选项的时候rfsimulator会存储收发到的I/Q文件,想要replay的话,需要额外编译make replay_node
然后就可以使用replay_node这个程序来作为数据源向基站或者UE发送。(我还没用过

3.2 信道仿真

到这里,才是涉及到你自己的仿真需要定义的地方。
首先,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后面加上你要用的信道名字,比如:
22 - OAI rfsimulator 射频仿真- 博一_第1张图片
注:只有输入噪声可以在命令行通过加上-s选项来更改,其他参数的修改都需要重新编译(是有点反人类嗷……)。当路径损耗= 0时,-s 5 为可见一丢丢噪声。 -s是channelmod.s的快捷方式。 希望EURECOM以后在channelmod里添加更多参数来增强信道建模仿真的灵活性。

4、扩展

  • 4.1 信道绝对时延

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再给基站去处理。那么就需要:

  1. ① 最偷懒的做法,给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

  1. ②需要把我们的信道绝对时延参数传递过来,才能sleep

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);

除了上述添加绝对时延支持以外,添加任何其他信道特征的修改方法也是一样/类似的。


你可能感兴趣的:(博一)