从OAI学习5G

  • 从OAI代码学习5G
    • UE的主函数
      • PHY_VARS_NR_UE结构体
    • 跳回主函数:
      • set_default_frame_parms(frame_parms) 函数
    • 重新跳回主函数:
      • get_options ()函数
    • 返回主函数
      • nr_init_frame_parms_ue()函数
        • nr_init_frame_parms0()函数
      • init_nr_ue_vars()函数
    • 主函数继续
      • init_openair0()函数
    • 再次回到主函数
      • init_UE(1)函数

从OAI代码学习5G

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

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

Created with Raphaël 2.2.0maindefine UE instanceset_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;
#endifint 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;
#endifPHY_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 instanceuint8_t Mod_id;/// \brief Component carrier ID for this PHY instanceuint8_t CC_id;/// \brief Mapping of CC_id antennas to cardsopenair0_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 scanningint UE_scan;/// \brief Indicator that UE should perform coarse scanning around carrierint UE_scan_carrier;/// \brief Indicator that UE should enable estimation and compensation of frequency offsetint UE_fo_compensation;/// \brief Indicator that UE is synchronized to an eNBint is_synchronized;/// Data structure for UE process schedulingUE_nr_proc_t proc;/// Flag to indicate the UE shouldn't do timing correction at allint 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概念也没有直接关系,暂时passexit_fun("[SOFTMODEM] Error, configuration module init failed\n");}CONFIG_SETRTFLAG(CONFIG_NOEXITONHELP);    //这句和下面的也都是对系统做配置#ifdef DEBUG_CONSOLEsetvbuf(stdout, NULL, _IONBF, 0);setvbuf(stderr, NULL, _IONBF, 0);
#endifset_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中初始化为1frame_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; //FDDconfig[CC_id]->subframe_config.dl_cyclic_prefix_type.value = 0; //NORMALconfig[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;    //帧类型,设定为FDDframe_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 0frame_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 paramsframe_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 logginglogInit();//解析命令行参数,对系统进行配置// get options and fill parameters from configuration fileget_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_TRACERT_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 ITTIMSC_INIT(MSC_E_UTRAN, THREAD_MAX+TASK_MAX);
#endifif (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_NETLINKnetlink_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 pressedsignal(SIGSEGV, signal_handler);signal(SIGINT, signal_handler);
#endifcheck_clock();   //检查时钟精度#ifndef PACKAGE_VERSION
#  define PACKAGE_VERSION "UNKNOWN-EXPERIMENTAL"
#endifLOG_I(HW, "Version: %s\n", PACKAGE_VERSION);

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

  //下面是一段重复定义,没有意义// init the parametersfor (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 laterLOG_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);

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

 //重要的初始化函数1nr_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);//重要的初始化函数2PHY_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 scsfp->subcarrier_spacing = nr_subcarrier_spacing[NR_MU_0];   //15kfp->slots_per_subframe = nr_slots_per_subframe[NR_MU_0];   //1 slotbreak;
  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 formatsfp->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 FR2fp->freq_range = (fp->dl_CarrierFreq < 6e9)? nr_FR1 : nr_FR2;//BWP设置为全频段,因此不需要关心它了// Initial bandwidth part configuration -- full carrier bandwidthfp->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 inue = (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 buffersinit_nr_ue_signal(ue,1,abstraction_flag);// intialize transportinit_nr_ue_transport(ue,abstraction_flag);return(ue);
}

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

主函数继续

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

    if (phy_test==1)       //是否是物理层only测试,可以通过命令行配置,默认值为0UE[CC_id]->mac_enabled = 0;elseUE[CC_id]->mac_enabled = 1;if (UE[CC_id]->mac_enabled == 0) {  //set default UL parameters for testing modefor (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;   //哦,提前配置好了CRNTIelseUE[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+0UE[CC_id]->tx_power_max_dBm = tx_max_power[CC_id];    //最大发射功率初始为0if (frame_parms[CC_id]->frame_type==FDD) {    //nta offset  38.133 7.1.2UE[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.pdfcpuf=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参数配置,默认值为0openair0_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 //FDDopenair0_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];elseopenair0_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];elseopenair0_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;
#elseUE[CC_id]->hw_timing_advance = 160;
#endif}while (oai_exit==0)rt_sleep_ns(100000000ULL);

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

从OAI代码学习5G相关推荐

  1. 5G学习-OAI代码架构分析

    文章目录 1 OAI代码架构分析 1.1 简介 1.2 硬件部分 1.3 软件部分 1.3.2 代码结构 1.3.3 代码结构说明 1.4 参考文献 1 OAI代码架构分析 1.1 简介 官方网站: ...

  2. [OpenAirInterface实战-9] :OAI代码的运行与常规测试(ping、Iperf)之RF Simulator

    作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...

  3. Apollo代码学习(六)—模型预测控制(MPC)_follow轻尘的博客-CSDN博客_mpc代码

    Apollo代码学习(六)-模型预测控制(MPC)_follow轻尘的博客-CSDN博客_mpc代码

  4. 2016年大数据Spark“蘑菇云”行动代码学习之AdClickedStreamingStats模块分析

    2016年大数据Spark"蘑菇云"行动代码学习之AdClickedStreamingStats模块分析     系统背景:用户使用终端设备(IPAD.手机.浏览器)等登录系统,系 ...

  5. 超好用的自信学习:1行代码查找标签错误,3行代码学习噪声标签

    十三 发自 凹非寺 量子位 报道 | 公众号 QbitAI 你知道吗?就连ImageNet中也可能至少存在10万个标签问题. 在大量的数据集中去描述或查找标签错误本身就是挑战性超高的任务,多少英雄豪杰 ...

  6. YOLO 卷积层代码学习

    YOLO 卷积层代码学习 卷积层的初始化 void im2col_cpu(float* data_im,int channels, int height, int width,int ksize, i ...

  7. python代码学习-数据处理图片加遮挡、噪声、模糊

    python代码学习-数据处理图片加遮挡.噪声.模糊 (一)python代码学习-数据处理图片加遮挡 代码: from matplotlib import pyplot as plt from PIL ...

  8. Keras版Faster-RCNN代码学习(IOU,RPN)1

    最近开始使用Keras来做深度学习,发现模型搭建相较于MXnet, Caffe等确实比较方便,适合于新手练手,于是找来了目标检测经典的模型Faster-RCNN的keras代码来练练手,代码的主题部分 ...

  9. java绘制图形代码_ImagePy_Learn | 图形学绘制代码学习:core\draw\polygonfill.py

    最近在学图形学绘制,想到了ImagePy框架的ROI涂抹交互很方便,于是啃起了绘制代码. 这里主要对ImagePy中一个填充工具进行难点讲解. 让我们好好学习Python中的图形学绘制吧. 例子代码来 ...

最新文章

  1. Java 7中的Try-with-resources
  2. Python 通过all()判断列表(list)中所有元素是否都包含某个字符串(string)
  3. kafka报错打开文件数过多导致kafka关闭
  4. linx vim 文件操作 ubuntu server 软件源
  5. 在HTML中使用WCF RESTful上传文件
  6. maven ${path.separator}
  7. 关于JS !!flag 语法
  8. dell 服务器r410装系统,dell r410安装windows2003系统
  9. 【ArcGIS|空间分析|网络分析】0 网络分析总结
  10. HDU1286 找新朋友
  11. 【路径规划】基于matlab蚁群优化遗传算法求解机器人栅格地图最短路径规划问题【含Matlab源码 1581期】
  12. linux运维要掌握哪些,学习Linux运维需要掌握哪些技能?Linux运维
  13. 身份证验证程序(一)
  14. 【人工智能】王飞跃教授讲述可解释的神经元网络发展历程
  15. XPIR : Private Information Retrieval for Everyone论文阅读笔记
  16. 计算机专业应届研究生面试自我介绍,计算机专业应届生面试自我介绍
  17. 爱班级电脑端下载|二维码签到
  18. 陕西守护星-智慧井口综合安检
  19. iOS Bilibili/ijkplayer 集成与使用
  20. 超静音 无振动 | TRINAMIC的3D打印机解决方案

热门文章

  1. html代码中radio,HTML DOM Input Radio用法及代码示例
  2. 前端学习点滴留痕1: bgcolor+background
  3. 数组sort排序方法,数字从小到大排序,汉字拼音音序排序
  4. 易优cms忘记后台登陆密码?解决方法 Eyoucms快速入门
  5. 【Never Stop】联赛集训记录
  6. 数据分析--01:数据分析的介绍与基本图例的绘画
  7. 佳能hdr_不要忘了相机自带的HDR功能_佳能 6D_数码影像评测-中关村在线
  8. Centos7 添加网卡
  9. 串口服务器网页显示iis,生命在等待中延续:汉枫Wi-Fi串口服务器HF2211S应用案例...
  10. pycharm里有android虚拟设备吗,已解决!PyCharm打开一直出现Reloading generated skeletons问题...