学习一个东西最好的办法就是自己动手做一遍,很多细节的问题会在这个过程中暴露出来,这是单纯看书或文档很难做到的。
5G兴起,为了学习5G,看3gpp文档实在太痛苦,而自己写协议栈?似乎有点不切实际。于是想到找个协议栈的源码来研究研究。而开源协议栈源码中最著名的似乎是openairinterface,正好OAI也已经实现了5G的一些基本功能,就拿它来练手了。本篇所用的是2019.3.27日下载的代码。后续如果有更新再做修改。
由于原来一直看的是终端侧的协议,所以OAI也从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对象实例。这个定义很简单,但是用于定义等结构体却值得研究。
这个结构体是干啥用的呢?
我们先跳到这个结构体的定义看看。由于这个结构体的定义太长,我们只从源码截开头的一段:
/// 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帧默认参数
跳过用于系统设置的函数,我们看最后一个设置默认帧参数的函数。
打开这个函数看看具体做了哪些设置:
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 ()函数主要是通过解析命令行参数来对系统做配置。
与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];
先看第一个函数:
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频域的起始位置。
它的正确的获取姿势应该是:
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_parms_ue()里面调用的这个nr_init_frame_parms0()函数。
这个函数主要是根据不同的numerology来显式计算对应的scs, slot数,symbol数,甚至采样点数。
由于OAI默认只定义了numerology0, 我们就把这个函数中numerology0相关的部分截取两段出来看看:
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;
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():
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(); //初始化射频参数
其中比较重要的就是我后面加注释的部分。
还有就是最后这个初始射频参数的函数,我们再次跳进去瞅瞅。
首先是根据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全面初始化
果然是越简单的越重要,一定不要对其貌不扬的人掉以轻心,说不定就是一个可以开启你另一番人生的入口。
让我们看看这个其貌不扬的小家伙都带来了什么吧:
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函数中调用了,但是没有介绍的部分再仔细研究完成一下。