从OAI代码学习5G

从OAI代码学习5G

学习一个东西最好的办法就是自己动手做一遍,很多细节的问题会在这个过程中暴露出来,这是单纯看书或文档很难做到的。
5G兴起,为了学习5G,看3gpp文档实在太痛苦,而自己写协议栈?似乎有点不切实际。于是想到找个协议栈的源码来研究研究。而开源协议栈源码中最著名的似乎是openairinterface,正好OAI也已经实现了5G的一些基本功能,就拿它来练手了。本篇所用的是2019.3.27日下载的代码。后续如果有更新再做修改。

由于原来一直看的是终端侧的协议,所以OAI也从UE的代码入手。
先放一张主函数的流程图:

Created with Raphaël 2.2.0 main define UE instance set_default_frame_parms(frame_parms) get_options() netlink_init()&pdcp_netlink_init() nr_init_frame_parms_ue() init_nr_ue_vars() init_openair0() init_UE(1) end

UE的主函数

UE的主函数在nr-uesoftmodem.c中:

int main( int argc, char **argv ) {    //这里就是终端的入口啦,接下来定义了几个不知所云的变量,OAI的代码注释真的有些业余啊
    int i;//j,k,aa,re;
#if defined (XFORMS)
    void *status;
#endif

    int CC_id;    //这个据说是compoent carrier ID,如果没有载波聚合,应该就是0吧
    uint8_t  abstraction_flag=0;
    //uint8_t beta_ACK=0,beta_RI=0,beta_CQI=2;

#if defined (XFORMS)
    int ret;
#endif

    PHY_VARS_NR_UE *UE[MAX_NUM_CCs];     //定义UE结构体数组,就相当于UE对象实例。这个定义很简单,但是用于定义等结构体却值得研究。

PHY_VARS_NR_UE结构体

这个结构体是干啥用的呢?
我们先跳到这个结构体的定义看看。由于这个结构体的定义太长,我们只从源码截开头的一段:

/// Top-level PHY Data Structure for UE
typedef struct {
  /// \brief Module ID indicator for this instance
  uint8_t Mod_id;
  /// \brief Component carrier ID for this PHY instance
  uint8_t CC_id;
  /// \brief Mapping of CC_id antennas to cards
  openair0_rf_map      rf_map;
  //uint8_t local_flag;
  /// \brief Indicator of current run mode of UE (normal_txrx, rx_calib_ue, no_L2_connect, debug_prach)
  runmode_t mode;
  /// \brief Indicator that UE should perform band scanning
  int UE_scan;
  /// \brief Indicator that UE should perform coarse scanning around carrier
  int UE_scan_carrier;
  /// \brief Indicator that UE should enable estimation and compensation of frequency offset
  int UE_fo_compensation;
  /// \brief Indicator that UE is synchronized to an eNB
  int is_synchronized;
  /// Data structure for UE process scheduling
  UE_nr_proc_t proc;
  /// Flag to indicate the UE shouldn't do timing correction at all
  int no_timing_correction;
  /// \brief Total gain of the TX chain (16-bit baseband I/Q to antenna)
  uint32_t tx_total_gain_dB;
  /// \brief Total gain of the RX chain (antenna to baseband I/Q) This is a function of rx_gain_mode (and the corresponding gain) and the rx_gain of the card.
  uint32_t rx_total_gain_dB;
  /// \brief Total gains with maximum RF gain stage (ExpressMIMO2/Lime)
  uint32_t rx_gain_max[4];

看第一行注释,直译过来就是用于UE的最高等级物理层数据结构,其实就是定义了UE物理层相关的各个参数,比如:终端的接收功率增益,发射功率增益,是否同步,射频通路的映射关系等等等等。
说等等等等,是因为后面确实还有很多相关参数,感兴趣的可以参阅源码。

跳回主函数:

定义来UE结构体数组之后,主函数调用了几个用于初始化的函数,如下:

    start_background_system();    //新起一个进程,用于运行system()系统调用,根据注释这是为了UE设置IP地址用的,因为与理解5G无关,暂时先不关心它
    if ( load_configmodule(argc,argv) == NULL) {   //解析命令行参数,对系统做一些配置,比如debug level之类,看起来跟5G概念也没有直接关系,暂时pass
      exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
    }
    CONFIG_SETRTFLAG(CONFIG_NOEXITONHELP);    //这句和下面的也都是对系统做配置
 
#ifdef DEBUG_CONSOLE
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
#endif

    set_default_frame_parms(frame_parms);   //重点来了,设置5G帧默认参数

跳过用于系统设置的函数,我们看最后一个设置默认帧参数的函数。

set_default_frame_parms(frame_parms) 函数

打开这个函数看看具体做了哪些设置:

void set_default_frame_parms(NR_DL_FRAME_PARMS *frame_parms[MAX_NUM_CCs]) {

  int CC_id;

  for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {       //MAX_NUM_CCs在CMakelists.txt中初始化为1
        frame_parms[CC_id] = (NR_DL_FRAME_PARMS*) malloc(sizeof(NR_DL_FRAME_PARMS));   //为每一个component carrier的帧分配参数存储空间
        /* Set some default values that may be overwritten while reading options */
        frame_parms[CC_id] = (NR_DL_FRAME_PARMS*) malloc(sizeof(NR_DL_FRAME_PARMS));

		//为全局变量config赋予相同的初始化参数,用于其他模块的参数传递
        config[CC_id] = (nfapi_nr_config_request_t*) malloc(sizeof(nfapi_nr_config_request_t));
        config[CC_id]->subframe_config.numerology_index_mu.value =1;        //这个值似乎与下面numerology的设置矛盾
        config[CC_id]->subframe_config.duplex_mode.value = 1; //FDD
        config[CC_id]->subframe_config.dl_cyclic_prefix_type.value = 0; //NORMAL
        config[CC_id]->rf_config.dl_carrier_bandwidth.value = 106;
        config[CC_id]->rf_config.ul_carrier_bandwidth.value = 106;
        config[CC_id]->sch_config.physical_cell_id.value = 0;

		//来看看初始的帧参数设定
        frame_parms[CC_id]->frame_type          = FDD;    //帧类型,设定为FDD
        frame_parms[CC_id]->tdd_config          = 3;       //TDD子帧配置,在LTE中有7种子帧配置,默认值为3,但是在NR中,对于普通的循环前缀就有62种时隙配置。这里应该是从LTE copy过来还没有修正。
        //frame_parms[CC_id]->tdd_config_S        = 0;
        frame_parms[CC_id]->N_RB_DL             = 106;      //下行RB设置为106,如果按照下面numerology设置为0,即子载波间隔为15k的情况下,系统带宽为106×12×15=19080k,也就是20M带宽
        frame_parms[CC_id]->N_RB_UL             = 106;      //上行同样设置为20M带宽
        frame_parms[CC_id]->Ncp                 = NORMAL;    //循环前缀设置为普通前缀
        //frame_parms[CC_id]->Ncp_UL              = NORMAL;
        frame_parms[CC_id]->Nid_cell            = 0;    //小区id设置为0
        //frame_parms[CC_id]->num_MBSFN_config    = 0;
        frame_parms[CC_id]->nb_antenna_ports_eNB  = 1;    //应该是gNB的天线端口数
        frame_parms[CC_id]->nb_antennas_tx      = 1;      //发送天线数
        frame_parms[CC_id]->nb_antennas_rx      = 1;      //接收天线数

        //frame_parms[CC_id]->nushift             = 0;

        ///frame_parms[CC_id]->phich_config_common.phich_resource = oneSixth;
        //frame_parms[CC_id]->phich_config_common.phich_duration = normal;

	// UL RS Config
        /*frame_parms[CC_id]->pusch_config_common.ul_ReferenceSignalsPUSCH.cyclicShift = 1;//n_DMRS1 set to 0
        frame_parms[CC_id]->pusch_config_common.ul_ReferenceSignalsPUSCH.groupHoppingEnabled = 1;
        frame_parms[CC_id]->pusch_config_common.ul_ReferenceSignalsPUSCH.sequenceHoppingEnabled = 0;
        frame_parms[CC_id]->pusch_config_common.ul_ReferenceSignalsPUSCH.groupAssignmentPUSCH = 0;

	frame_parms[CC_id]->pusch_config_common.n_SB = 1;
	frame_parms[CC_id]->pusch_config_common.hoppingMode = 0;
	frame_parms[CC_id]->pusch_config_common.pusch_HoppingOffset = 0;
	frame_parms[CC_id]->pusch_config_common.enable64QAM = 0;
		
        frame_parms[CC_id]->prach_config_common.rootSequenceIndex=22;
        frame_parms[CC_id]->prach_config_common.prach_ConfigInfo.zeroCorrelationZoneConfig=1;
        frame_parms[CC_id]->prach_config_common.prach_ConfigInfo.prach_ConfigIndex=0;
        frame_parms[CC_id]->prach_config_common.prach_ConfigInfo.highSpeedFlag=0;
        frame_parms[CC_id]->prach_config_common.prach_ConfigInfo.prach_FreqOffset=0;*/

        // NR: Init to legacy LTE 20Mhz params
        frame_parms[CC_id]->numerology_index	= 0;    //numerology设为0,即15k子载波间隔
        frame_parms[CC_id]->ttis_per_subframe	= 1;    //每个子帧的tti数,5G中仍然规定tti长度为1ms,等于一个子帧的长度
        frame_parms[CC_id]->slots_per_tti		= 2;    //每个tti的时隙数,这是LTE中的情况,对于5G numerolog 0, 一个tti对应1个slot

    }

}

可以看到,这个函数主要是设置了默认的帧类型,numerology,带宽,gNB的天线端口数,以及UE的收发天线数等。
不过,这个函数应该也是从LTE那边拿过来的,很多地方都还没有修改成5G的参数。

重新跳回主函数:

接下来,在主函数中继续做一些系统参数的初始化配置:

    mode = normal_txrx;          //设置运行模式,默认就是正常收发模式
    memset(&openair0_cfg[0],0,sizeof(openair0_config_t)*MAX_CARDS);    //初始化射频前端配置参数

    memset(tx_max_power,0,sizeof(int)*MAX_NUM_CCs);         //初始化最大发射功率

    set_latency_target();   //锁定cup cstate,避免进入深睡模式

    // initialize logging
    logInit();


    //解析命令行参数,对系统进行配置
    // get options and fill parameters from configuration file
    get_options (); //Command-line options, enb_properties

这里最后的get_options ()我们需要稍微注意一下。

get_options ()函数

get_options ()函数主要是通过解析命令行参数来对系统做配置。
与UE相关的设置主要是下行频点的初始化和扫频指示的初始化,如下:

  for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {
    frame_parms[CC_id]->dl_CarrierFreq = downlink_frequency[0][0];     //可以通过-C参数来设置下行频点,默认值为2680000000
  }
  UE_scan=0;    //UE是否需要扫频的标志位
  

返回主函数

接下来又是一堆初始化工作,但跟协议原理相关性不大,先pass:

#if T_TRACER
    T_Config_Init();    //T tracer配置初始化,https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/T
#endif

    //randominit (0);
    set_taus_seed (0);   //随机数初始化

    printf("configuring for UE\n");

    //if (ouput_vcd)
    //    VCD_SIGNAL_DUMPER_INIT("/tmp/openair_dump_UE.vcd");

    //if (opp_enabled ==1) {
    //    reset_opp_meas();
    //}
    cpuf=get_cpu_freq_GHz();   //获取cpu频率

#if defined(ENABLE_ITTI)
    //log_set_instance_type (LOG_INSTANCE_UE);

    itti_init(TASK_MAX, THREAD_MAX, MESSAGES_ID_MAX, tasks_info, messages_info);   //itti log任务初始化,https://gitlab.eurecom.fr/oai/openairinterface5g/wikis/IttiAnalyzer

    // initialize mscgen log after ITTI
    MSC_INIT(MSC_E_UTRAN, THREAD_MAX+TASK_MAX);
#endif

    if (opt_type != OPT_NONE) {      //pcap log相关
        if (init_opt(in_path, in_ip) == -1)
            LOG_E(OPT,"failed to run OPT \n");
    }

下面netlink的初始化等后面分析上层数据流程的时候再详细分析吧

//netlink以及pdcp与netlink的接口初始化,用于pdcp与内核ip报文的交互
#ifdef PDCP_USE_NETLINK
    netlink_init();
#if defined(PDCP_USE_NETLINK_QUEUES)
    pdcp_netlink_init();
#endif
#endif

继续一些系统相关设置:

//定义信号处理函数
#if !defined(ENABLE_ITTI)
    // to make a graceful exit when ctrl-c is pressed
    signal(SIGSEGV, signal_handler);
    signal(SIGINT, signal_handler);
#endif


  check_clock();   //检查时钟精度

#ifndef PACKAGE_VERSION
#  define PACKAGE_VERSION "UNKNOWN-EXPERIMENTAL"
#endif

  LOG_I(HW, "Version: %s\n", PACKAGE_VERSION);

还有一些冗余代码没有精简:

  //下面是一段重复定义,没有意义
  // init the parameters
  for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {

      frame_parms[CC_id]->nb_antennas_tx     = nb_antenna_tx;
      frame_parms[CC_id]->nb_antennas_rx     = nb_antenna_rx;
      frame_parms[CC_id]->nb_antenna_ports_eNB = 1; //initial value overwritten by initial sync later

      LOG_I(PHY,"Set nb_rx_antenna %d , nb_tx_antenna %d \n",frame_parms[CC_id]->nb_antennas_rx, frame_parms[CC_id]->nb_antennas_tx);
  
    //init_ul_hopping(frame_parms[CC_id]);
    //phy_init_nr_top(frame_parms[CC_id]);
  }

  for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {
        //init prach for openair1 test
        // prach_fmt = get_prach_fmt(frame_parms->prach_config_common.prach_ConfigInfo.prach_ConfigIndex, frame_parms->frame_type);
        // N_ZC = (prach_fmt <4)?839:139;
  }

定义一些全局变量:

  //初始化UE和gNB的实例个数
  NB_UE_INST=1;
  NB_INST=1;
  //为UE物理层参数的全局变量分配空间
  PHY_vars_UE_g = malloc(sizeof(PHY_VARS_NR_UE**));
  PHY_vars_UE_g[0] = malloc(sizeof(PHY_VARS_NR_UE*)*MAX_NUM_CCs);

然后是两个重要等初始化函数,这两个函数我们需要跳进去仔细看看:

	//重要的初始化函数1
    nr_init_frame_parms_ue(frame_parms[CC_id],numerology,NORMAL,frame_parms[CC_id]->N_RB_DL,(frame_parms[CC_id]->N_RB_DL-20)>>1,0);
	//重要的初始化函数2
    PHY_vars_UE_g[0][CC_id] = init_nr_ue_vars(frame_parms[CC_id], 0,abstraction_flag);
    UE[CC_id] = PHY_vars_UE_g[0][CC_id];

nr_init_frame_parms_ue()函数

先看第一个函数:

int nr_init_frame_parms_ue(NR_DL_FRAME_PARMS *fp,
			   int mu, 
			   int Ncp,
			   int N_RB_DL,
			   int n_ssb_crb,
			   int ssb_subcarrier_offset) 
{
  /*n_ssb_crb and ssb_subcarrier_offset are given in 15kHz SCS*/
  nr_init_frame_parms0(fp,mu,Ncp,N_RB_DL);
  fp->ssb_start_subcarrier = (12 * n_ssb_crb + ssb_subcarrier_offset)/(1<<mu);
  return 0;
}

这里的参数需要解释一下:
fp: 是之前我们已经初始化了的帧参数指针;
mu: 实际接受的值是全局变量 numerology,而 numerology 可以通过命令行参数 -numerology 来设置,如果没有设置,默认值是0, 也就是 scs(subcarrier spacing)=15k;
Ncp: 明确为普通循环前缀;
N_RB_DL: 下行RB数,根据之前的设置应该是106;
n_ssb_crb: 在5G中表示SSB相对于pointA的偏移量,以RB数计算,根据频段不同,所采用的scs可能是15k或60k。它应该是从上层参数offsetToPointA获得,但是在上面主函数中调用时是按照如下方式计算的:
n_ssb_crb=(frame_parms[CC_id]->N_RB_DL-20)>>1
这里明显是按照LTE系统的方式进行计算了。实际上,SSB不一定是系统带宽的最中间,所以不能以系统带宽-20/2来获取。
n_ssb_crb计算方法不对,那么ssb_start_subcarrier的结果也就不会正确。
ssb_start_subcarrier: 表示SSB频域的起始位置。
它的正确的获取姿势应该是:

  1. 对于NSA系统,应该按照LTE的RRC Reconfiguration消息指示去配置NR的搜索频点;
  2. 对于SA系统,应该根据当前频段,按照GSCN(Global Synchronization Channel Number)来搜索频点。

ssb_subcarrier_offset: 对应k_ssb,表示SSB相对于其所在CRB的偏移量,以子载波数计算,所采用的scs可能为15k或者由上层参数决定。在scs固定为15k的情况下,ssb_subcarrier_offset设为0倒是正确的。
关于这几个参数之间的关系,盗用一张大神图镇在这里(侵删):

因为5G采用了不同的numerology,存在不同的子载波间隔,为了频域定位方便而引入了CRB(common resource block的和pointA的概念,CRB就是以pointA为起点的频率标尺,这个标尺根据频段不同,可能以15k或60k为单位。)

再放一张与实际带宽及带宽中心关系的图:
在这里插入图片描述

nr_init_frame_parms0()函数

解释完这几个参数,我们再来看nr_init_frame_parms_ue()里面调用的这个nr_init_frame_parms0()函数。
这个函数主要是根据不同的numerology来显式计算对应的scs, slot数,symbol数,甚至采样点数。
由于OAI默认只定义了numerology0, 我们就把这个函数中numerology0相关的部分截取两段出来看看:

  1. 确定SCS和slot数:
  switch(mu) {

    case NR_MU_0: //15kHz scs
      fp->subcarrier_spacing = nr_subcarrier_spacing[NR_MU_0];   //15k
      fp->slots_per_subframe = nr_slots_per_subframe[NR_MU_0];   //1 slot
      break;
  1. 确定采样点数以及频段范围,BWP等
  fp->slots_per_frame = 10* fp->slots_per_subframe;   //每帧的slot数

  fp->nb_antenna_ports_eNB = 1; // default value until overwritten by RRCConnectionReconfiguration

  //每slot的symbol数,以及每帧,每子帧,每slot的采样点数
  fp->symbols_per_slot = ((Ncp == NORMAL)? 14 : 12); // to redefine for different slot formats
  fp->samples_per_subframe_wCP = fp->ofdm_symbol_size * fp->symbols_per_slot * fp->slots_per_subframe;
  fp->samples_per_frame_wCP = 10 * fp->samples_per_subframe_wCP;
  fp->samples_per_slot_wCP = fp->symbols_per_slot*fp->ofdm_symbol_size; 
  fp->samples_per_slot = fp->nb_prefix_samples0 + ((fp->symbols_per_slot-1)*fp->nb_prefix_samples) + (fp->symbols_per_slot*fp->ofdm_symbol_size); 
  fp->samples_per_subframe = (fp->samples_per_subframe_wCP + (fp->nb_prefix_samples0 * fp->slots_per_subframe) +
                                      (fp->nb_prefix_samples * fp->slots_per_subframe * (fp->symbols_per_slot - 1)));
  fp->samples_per_frame = 10 * fp->samples_per_subframe;
  //频段范围,FR1 or FR2
  fp->freq_range = (fp->dl_CarrierFreq < 6e9)? nr_FR1 : nr_FR2;

  //BWP设置为全频段,因此不需要关心它了
  // Initial bandwidth part configuration -- full carrier bandwidth
  fp->initial_bwp_dl.bwp_id = 0;
  fp->initial_bwp_dl.scs = fp->subcarrier_spacing;
  fp->initial_bwp_dl.location = 0;
  fp->initial_bwp_dl.N_RB = fp->N_RB_DL;
  fp->initial_bwp_dl.cyclic_prefix = fp->Ncp;
  fp->initial_bwp_dl.ofdm_symbol_size = fp->ofdm_symbol_size;

init_nr_ue_vars()函数

然后我们来看第二个重要函数init_nr_ue_vars():

PHY_VARS_NR_UE *init_nr_ue_vars(NR_DL_FRAME_PARMS *frame_parms,
                                uint8_t UE_id,
                                uint8_t abstraction_flag)

{
  PHY_VARS_NR_UE *ue;

  if (frame_parms!=(NR_DL_FRAME_PARMS *)NULL) { // if we want to give initial frame parms, allocate the PHY_VARS_UE structure and put them in
    ue = (PHY_VARS_NR_UE *)malloc(sizeof(PHY_VARS_NR_UE));
    memset(ue,0,sizeof(PHY_VARS_NR_UE));
    memcpy(&(ue->frame_parms), frame_parms, sizeof(NR_DL_FRAME_PARMS));
  } else ue = PHY_vars_UE_g[UE_id][0];

  ue->Mod_id      = UE_id;
  ue->mac_enabled = 1;
  // initialize all signal buffers
  init_nr_ue_signal(ue,1,abstraction_flag);
  // intialize transport
  init_nr_ue_transport(ue,abstraction_flag);
  return(ue);
}

这个函数主要是通过调用init_nr_ue_signal()和init_nr_ue_transport()生成UE用到的各种信号和定义传输数据用到的各种参数。
由于这里面涉及的东西比较多,就放到后面另开一篇介绍吧。

主函数继续

跳回主函数,接下来又是一大堆初始化:

    if (phy_test==1)       //是否是物理层only测试,可以通过命令行配置,默认值为0
      UE[CC_id]->mac_enabled = 0;
    else
      UE[CC_id]->mac_enabled = 1;
    
    if (UE[CC_id]->mac_enabled == 0) {  //set default UL parameters for testing mode
      for (i=0; i<NUMBER_OF_CONNECTED_eNB_MAX; i++) {
	//UE[CC_id]->pusch_config_dedicated[i] = malloc(sizeof(PUSCH_CONFIG_DEDICATED));
	
	//UE[CC_id]->scheduling_request_config[i] = malloc(sizeof(SCHEDULING_REQUEST_CONFIG));
	
	/*UE[CC_id]->pusch_config_dedicated[i].betaOffset_ACK_Index = beta_ACK;
	  UE[CC_id]->pusch_config_dedicated[i].betaOffset_RI_Index  = beta_RI;
	  UE[CC_id]->pusch_config_dedicated[i].betaOffset_CQI_Index = beta_CQI;
	  
	  UE[CC_id]->scheduling_request_config[i].sr_PUCCH_ResourceIndex = 0;
	  UE[CC_id]->scheduling_request_config[i].sr_ConfigIndex = 7+(0%3);
	  UE[CC_id]->scheduling_request_config[i].dsr_TransMax = sr_n4;*/
      }
    }
    
    UE[CC_id]->UE_scan = UE_scan;      //指示UE是否扫频
    UE[CC_id]->UE_scan_carrier = UE_scan_carrier;     //指示UE是否在当前载频做粗扫
    UE[CC_id]->UE_fo_compensation = UE_fo_compensation;     //指示UE是否做频率偏移估计和补偿
    UE[CC_id]->mode    = mode;
    printf("UE[%d]->mode = %d\n",CC_id,mode);
    
    for (uint8_t i=0; i<RX_NB_TH_MAX; i++) {
      //UE[CC_id]->pdcch_vars[i][0]->agregationLevel = agregation_Level;
      //UE[CC_id]->pdcch_vars[i][0]->dciFormat     = dci_Format;
    
    /*compute_prach_seq(&UE[CC_id]->frame_parms.prach_config_common,
      UE[CC_id]->frame_parms.frame_type,
      UE[CC_id]->X_u);*/
    
    if (UE[CC_id]->mac_enabled == 1)
	UE[CC_id]->pdcch_vars[i][0]->crnti = 0x1234;   //哦,提前配置好了CRNTI
    else
	UE[CC_id]->pdcch_vars[i][0]->crnti = 0x1235;
    }
    
    UE[CC_id]->rx_total_gain_dB =  (int)rx_gain[CC_id][0] + rx_gain_off;   //总接收增益,初始值设置为110+0
    UE[CC_id]->tx_power_max_dBm = tx_max_power[CC_id];    //最大发射功率初始为0
    
    if (frame_parms[CC_id]->frame_type==FDD) {    //nta offset  38.133 7.1.2
      UE[CC_id]->N_TA_offset = 0;
    } else {
      if (frame_parms[CC_id]->N_RB_DL == 100)
	UE[CC_id]->N_TA_offset = 624;
      else if (frame_parms[CC_id]->N_RB_DL == 50)
	UE[CC_id]->N_TA_offset = 624/2;
      else if (frame_parms[CC_id]->N_RB_DL == 25)
	UE[CC_id]->N_TA_offset = 624/4;
    }
    
  }
  
  //  printf("tx_max_power = %d -> amp %d\n",tx_max_power[0],get_tx_amp(tx_max_poHwer,tx_max_power));
  
  
  fill_modeled_runtime_table(runtime_phy_rx,runtime_phy_tx);    //似乎是为了试验用的,
  																//Processing Radio Access Network Functions in the Cloud: Critical Issues and Modeling
  																//http://www.eurecom.fr/fr/publication/4640/download/cm-publi-4640.pdf
  cpuf=get_cpu_freq_GHz();
  
  
  //dump_frame_parms(frame_parms[0]);
  
  init_openair0();   //初始化射频参数

其中比较重要的就是我后面加注释的部分。
还有就是最后这个初始射频参数的函数,我们再次跳进去瞅瞅。

init_openair0()函数

首先是根据numerology设置采样率和带宽:

    if(frame_parms[0]->N_RB_DL == 106) {
	  if (numerology==0) {
            if (frame_parms[0]->threequarter_fs) {    //是否使用3/4采样率,可以通过-E参数配置,默认值为0
                openair0_cfg[card].sample_rate=23.04e6;
                openair0_cfg[card].samples_per_frame = 230400;
                openair0_cfg[card].tx_bw = 10e6;
                openair0_cfg[card].rx_bw = 10e6;
            } else {
                openair0_cfg[card].sample_rate=30.72e6;         //采样速率
                openair0_cfg[card].samples_per_frame = 307200;    //采样数
                openair0_cfg[card].tx_bw = 10e6;    //发送带宽
                openair0_cfg[card].rx_bw = 10e6;    //接收带宽
            }

然后设置双工方式和收发频点:

	if (frame_parms[0]->frame_type==TDD)     //设置双工方式
	  openair0_cfg[card].duplex_mode = duplex_mode_TDD;
	else //FDD
	  openair0_cfg[card].duplex_mode = duplex_mode_FDD;
	
	printf("HW: Configuring card %d, nb_antennas_tx/rx %d/%d\n",card,
	       PHY_vars_UE_g[0][0]->frame_parms.nb_antennas_tx,
	       PHY_vars_UE_g[0][0]->frame_parms.nb_antennas_rx);
	openair0_cfg[card].Mod_id = 0;
	
	openair0_cfg[card].num_rb_dl=frame_parms[0]->N_RB_DL;
	
	openair0_cfg[card].clock_source = clock_source;   //设置时钟源
	

	//设置收发信道数
	openair0_cfg[card].tx_num_channels=min(2,PHY_vars_UE_g[0][0]->frame_parms.nb_antennas_tx);
	openair0_cfg[card].rx_num_channels=min(2,PHY_vars_UE_g[0][0]->frame_parms.nb_antennas_rx);

	//设置收发频点及增益
	for (i=0; i<4; i++) {
	  
	  if (i<openair0_cfg[card].tx_num_channels)
	    openair0_cfg[card].tx_freq[i] = downlink_frequency[0][i]+uplink_frequency_offset[0][i];
	  else
	    openair0_cfg[card].tx_freq[i]=0.0;
	  
	  if (i<openair0_cfg[card].rx_num_channels)
	    openair0_cfg[card].rx_freq[i] = downlink_frequency[0][i];
	  else
	    openair0_cfg[card].rx_freq[i]=0.0;
	  
	  openair0_cfg[card].autocal[i] = 1;
	  openair0_cfg[card].tx_gain[i] = tx_gain[0][i];
	  openair0_cfg[card].rx_gain[i] = PHY_vars_UE_g[0][0]->rx_total_gain_dB - rx_gain_off;

最后关联到底层的射频硬件:

	if (usrp_args) openair0_cfg[card].sdr_addrs = usrp_args;   //设置usrp设备
	//设置usrp时钟源类型
	if (usrp_clksrc) {
	  if (strcmp(usrp_clksrc, "internal") == 0) {
	    openair0_cfg[card].clock_source = internal;
	    LOG_D(PHY, "USRP clock source set as internal\n");
	  } else if (strcmp(usrp_clksrc, "external") == 0) {
	    openair0_cfg[card].clock_source = external;
	    LOG_D(PHY, "USRP clock source set as external\n");
	  } else if (strcmp(usrp_clksrc, "gpsdo") == 0) {
	    openair0_cfg[card].clock_source = gpsdo;
	    LOG_D(PHY, "USRP clock source set as gpsdo\n");
	  } else {
	    openair0_cfg[card].clock_source = internal;
	    LOG_I(PHY, "USRP clock source unknown ('%s'). defaulting to internal\n", usrp_clksrc);	
	  }
	} else {
	  openair0_cfg[card].clock_source = internal;
	  LOG_I(PHY, "USRP clock source not specified. defaulting to internal\n");	
	}

再次回到主函数

回到主函数后,接下来是一堆对线程相关的设置,暂时不关心它。
下一个重要的函数,也是main函数中最重要的调用,隆重登场:

    init_UE(1);   // 对UE全面初始化

果然是越简单的越重要,一定不要对其貌不扬的人掉以轻心,说不定就是一个可以开启你另一番人生的入口。

init_UE(1)函数

让我们看看这个其貌不扬的小家伙都带来了什么吧:

void init_UE(int nb_inst) {
  int inst;
  NR_UE_MAC_INST_t *mac_inst;

  for (inst=0; inst < nb_inst; inst++) {
    //    UE->rfdevice.type      = NONE_DEV;
    //PHY_VARS_NR_UE *UE = PHY_vars_UE_g[inst][0];
    LOG_I(PHY,"Initializing memory for UE instance %d (%p)\n",inst,PHY_vars_UE_g[inst]);
	//这个函数在前面已经调用过一次,只不过前面是针对inst 0的每一个component carrier进行初始化,这次是针对不同inst进行初始化
    PHY_vars_UE_g[inst][0] = init_nr_ue_vars(NULL,inst,0);    
    PHY_VARS_NR_UE *UE = PHY_vars_UE_g[inst][0];
	//初始化UE接口模块实例,包括:当前帧号,时隙号,设置上下行指示函数等
    AssertFatal((UE->if_inst = nr_ue_if_module_init(inst)) != NULL, "can not initial IF module\n");
    nr_l3_init_ue();   //初始化UE的RRC实例
    nr_l2_init_ue();   //初始化UE的mac实例
    mac_inst = get_mac_inst(0);
    mac_inst->if_module = UE->if_inst;
    UE->if_inst->scheduled_response = nr_ue_scheduled_response;   //设置接口实例的调度响应函数和物理层配置请求函数
    UE->if_inst->phy_config_request = nr_ue_phy_config_request;
    LOG_I(PHY,"Intializing UE Threads for instance %d (%p,%p)...\n",inst,PHY_vars_UE_g[inst],PHY_vars_UE_g[inst][0]);
    //init_UE_threads(inst);
    //UE = PHY_vars_UE_g[inst][0];
    AssertFatal(0 == pthread_create(&UE->proc.pthread_ue,   //启动UE线程,这是UE实际工作的地方,后面开篇另述
                                    &UE->proc.attr_ue,
                                    UE_thread,
                                    (void *)UE), "");
  }

嗯,初始化了UE实例的接口模块,初始化了RRC实例,初始化了MAC实例,最后,也是最激动人心的发现,它开启了一个新的UE线程。
这么重要的东西我肯定要留到以后再讲啦。
可以透露的是,新启动的UE的工作线程,是UE主要工作的地方,包括搜网,接收系统消息,传输数据等。
启动UE线程之后,主函数基本就完成了工作。
再次设置一些参数后进入来无限等待,直到用户中断程序或触发其他中断条件。

    number_of_cards = 1;   //设置卡数量
    
    for(CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {   //设置射频通路和卡的对应关系
      PHY_vars_UE_g[0][CC_id]->rf_map.card=0;
      PHY_vars_UE_g[0][CC_id]->rf_map.chain=CC_id+chain_offset;
    }


    for (CC_id=0; CC_id<MAX_NUM_CCs; CC_id++) {   //设置硬件时间提前量
      
#if defined(OAI_USRP) || defined(OAI_ADRV9371_ZC706)
      UE[CC_id]->hw_timing_advance = timing_advance;
#else
      UE[CC_id]->hw_timing_advance = 160;
#endif
    }
    while (oai_exit==0)
        rt_sleep_ns(100000000ULL);

到这里,UE的main函数就介绍完了,应该是学习完了。
后面会把main函数中调用了,但是没有介绍的部分再仔细研究完成一下。

猜你喜欢

转载自blog.csdn.net/qq_44113393/article/details/89221086
5G