官方发布的软件包中,带了一份example code,用于向客户展示API的调用方法以及基于官方的验证版ADRV902X最简单的bring up的流程。
该 example 位于软件包的路径下:“\Adi.Adrv9025.Api\src\c_src\app\example\”。
代码组成:
-
initdata.c / initdata.h 定义了一组默认的初始化配置参数和多片同步后初始化(PostMCS)的配置。这两个文件可以通过官方提供的GUI生成得到。在example code的工程中,initdata.c 源文件定义的 `adrv9025InitInst` 初始化配置结构体,并不会被用到,在后续的章节中会进行说明。
-
main.c 文件给出了example 工程的具体实现,包括单板的初始化,FPGA/时钟芯片的初始化,以及ADRV902X的bringup,这些流程都是通过调用官方提供的API来实现的。
-
makefile文件给出了构建example工程的方法,在Linux的环境中makefile较为通用,同时该makefile文件也给出了API的构建集成方式。
下面的章节会重点介绍这几个文件中的代码实现以及如何在官方提供的验证板上构建并运行example code
main函数的开头,就是单板的一些初始化准备工作:
int main()
{
uint32_t rxChannelMaskGet = 0x00;
uint32_t txChannelMaskGet = 0x00;
uint32_t rxChannelMaskSet = 0x0F;
uint32_t txChannelMaskSet = 0x0F;
int inputSelection = 0x00;
recoveryAct = discoverPlatform();
if (recoveryAct != ADI_COMMON_ACT_NO_ACTION)
{
printf("\nERROR: : %s:%u has failed.\n", __func__, __LINE__);
}
官方给的函数名称也比较有实际意义,`discoverPlatform`的字面意思是发现平台,也就是说在正式开始各种初始化工作前,需要先确定单板信息以及做一些软件层面的准备工作。
ADRV902X官方指定的验证板是ADS9-V2EBZ,但由于该数字板的价格偏高,且单板的bringup时间较长,所以一般会选用价格较为便宜的ADS8作为其数字验证板使用,但是官方的example code例程并不支持ADS8 验证板。话题回到软件层面,`discoverPlatform`函数就是用来识别当前使用的验证板平台。
在软件层面,ADI把数字板抽象为母板(motherboard),而ADRV902X 射频验证板抽象为子板(daughterboard),motherboard和daughterboard各有一个抽象出来的结构体,分别是 `adi_motherboard_trx_t` 和 `adi_daughterboard_trx_t`,这两个结构体采用了面向对象的思想,其中包含了单板的相关属性和操作方法。motherboard结构体中包含了daughterboard的结构体,这个也比较好理解,对比到硬件上,ADRV902X的验证平台,是由数字母版和扣在母版上的子板一并构成的。
函数`discoverPlatform`的实现如下:
{
printf("\n*** Begin Platform Discover *** ");
/********************************************************************************************/
/**************** Discover motherboard ******************************************************/
/********************************************************************************************/
/* This API creates a generic structure to hold all the necessary motherboard information ***/
recoveryAct = adi_motherboard_Discover(&motherboard);
if ((recoveryAct != ADI_COMMON_HAL_OK) || (motherboard == NULL))
{
printf("ERROR: adi_Motherboard_Discover has failed.\n");
return ADI_COMMON_ACT_ERR_RESET_FULL;
}
/**************** END: Discover motherboard *************************************************/
/********************************************************************************************/
/**************** Discover daughterboard ****************************************************/
/********************************************************************************************/
/* This API creates a generic structure to hold all the necessary daughterboard information */
recoveryAct = adi_daughterboard_Discover(motherboard);
if ((recoveryAct != ADI_COMMON_HAL_OK) || (motherboard == NULL))
{
printf("ERROR: ERPC-SERVER: %s:%u has failed.\n", __func__, __LINE__);
return failureReturn;
}
...
}
可以看到这个函数的主要作用是完成母版和子板的识别以及完成相关的硬件初始化的预备工作,比如配置结构体的初始化,hal层接口的挂接,硬件设备的资源配置等。
adi_motherboard_Discover
该函数的主要功能包括:
-
实例化子板结构体 `adi_daughterboard_trx_t`,并初始化其成为0;
-
实例化母板的设备层抽象,包括LOG和TIMER这两个模块;
-
识别母版类型;
-
完成platform 的hal层函数钩子注册挂接;
-
调用hal层设备打开接口,打开log模块;
注意这里很重要的一步是第4步,也是实际开发人员在集成的时候需要注意的步骤。
adi_daughterboard_Discover
在完成motherboard 的发现之旅后,便开始启动子板的发现之旅。该函数的主要功能包括:
1. 实例化子版的设备抽象,包括I2C/LOG/TIMER 这三个模块;
2. 挂接daughterboard对象中的方法(钩子函数们);
3. 通过daughterBoard->DeviceCreate,完成Clock/FPGA/TrxBoard 的Dispatch方法注册,用于后续的Clock/FPGA/TrxBoard的硬件初始化和配置操作,然后实例化Clock/FPGA/ADRV902X设备,在代码中对应结构体`adi_ad9528_Device_t`/`adi_adrv9025_Device_t`/`adi_fpga9010_Device_t`;
4. 通过daughterBoard->DeviceInit,实例化Clock/FPGA/TrxBoard的设备层抽象,这其中包含各个设备的SPI/I2C 总线接口,LOG 和 TIMER 模块,并调用 `adi_hal_HwOpen` 完成设备的打开。
完成上述所有工作后,会将子板中实例化的Clock/FPGA/TrxBoard设备保存到三个全局对象中,分别为 `ad9528Device`/`adrv9025Device`和`fpga9025Device`中,方便后续的代码使用。
总而言之,在真正program ADRV902X之前,官方示例代码通过子母板的抽象,完成了底层钩子函数的挂接、设备软件资源的分配、设备对应的控制总线(SPI/I2C)以及LOG 的基本配置和打开,为后续ADRV902X的初始化和Program做准备。
这部分内容对用户做代码集成的时候有一定的指导作用。
ADRV902X 初始化及API使用方法
回到示例main函数的主流程中,完成子母板以及子母板上设备的实例化和HAL抽象层初始化后,正式开始对ADRV902X 芯片进行初始化和配置。
配置加载
首先需要从UseCase的profile文件中加载配置,在实际的应用项目上,这一步可以跳过,如前文所说,可以使用GUI生成的initdata.h/initdata.c 文件,initdata.c 中保存了GUI根据实际修改配置所生成的所有关于ADRV902X的配置。在示例代码中使用了 `adi_adrv9025_ConfigFileLoad` 这个API来完成的这个任务,这也是在示例工程中第一个显式地调用ADRV902X API的例子:
static void configFileLoad(adi_adrv9025_Device_t *adrv9025_Device,
adi_adrv9025_Init_t *adrv9025_Init)
{
recoveryAct = ADI_COMMON_ACT_ERR_RESET_FULL;
/* Load ADRV9010 Init structure */
/* adiProfileFilePath is defined as const global at top of this file */
recoveryAct = adi_adrv9025_ConfigFileLoad(adrv9025_Device, adiProfileFilePath, adrv9025_Init);
if (recoveryAct != ADI_COMMON_ACT_NO_ACTION)
{
printf("ERROR: : %s:%u has failed.\n", __func__, __LINE__);
exit(EXIT_FAILURE);
}
}
这个例子也很好地说明了如何调用官方提供的API,ADI提供的API命名格式均为 `adi_adrv9025_functionName`,至少有一个入参 `adi_adrv9025_Device_t *adrv9025_Device`,返回值为 init32_t 类型的运行结果;所以调用时一般会先申明一个返回值变量,用来保存API的执行结果。API的第一个参数即为上一章节中提到的实例化的设备结构指针 adrv9025Device 。
函数 `configFileLoad` 执行成功后会将加载的配置保存在全局变量 `adrv9025InitInst`中,也即initdata.c 中的配置结构体变量,这也是前面提到的,example code中initdata.c 的配置不会被用到的原因。关于ADRV902X的配置结构,以后有时间会更新专门的文章进行描述。
在加载配置完成后,会通过usecase中的device clock/vcxo/reference clock 的值生成时钟的配置参数并保存。
随后的操作是单板的PreProgram,其中会检查设备结构体是否实例化,并再次加载配置,感觉有点多余。
最后的一部分是FPGA和clock的硬件初始化与配置,主要包括FPGA的AXI总线配置,MMCM配置,JESD配置等。至此完成了配置文件的加载和FPGA以及时钟的配置信息,历程运行到这里,可以看到ADRV902X子板上的时钟指示灯处于锁定状态。
ADRV902X初始化
在完成上述的基本配置加载后,正式开始ADRV902X的初始化配置,配置的主要流程如下:
1. HWReset:ADRV902X芯片要求在上电配置之前,需要对芯片做一次硬件复位,API `adi_adrv9025_HwReset`即完成这个任务,但这个API同时也会检查hal层钩子函数的挂接状态以及硬件SPI的校验。
2. PreMcsInit:这一步是ADRV902X的具体初始化的第一步,通过调用API `adi_adrv9025_PreMcsInit_v2`来完成,这一步会初始化ADRV902X相关外设,加载ADRV902X的相关资源,比如固件版本、stream 二进制文件,增益补偿表等,然后尝试启动ADRV902X的arm芯片。
这里注意,API `adi_adrv9025_PreMcsInit_v2`需要提供初始化配置结构体,固件版本在BBIC侧单板操作系统中的路径,stream 二进制文件在BBIC侧单板操作系统中的路径,RX和TX的增益表。
3. NonBroadCast: 这一步主要是在上一步的基础上,等待ADRV902X 中的arm启动成功,并对TX和RX的通道参数进行配置,生成JESD的参数,通过调用API `adi_adrv9025_PreMcsInit_NonBroadCast` 实现。
4. MCS:这一步是完成多片ADRV902X的同步,需要时钟芯片的参与,MCS的控制是通过API `adi_adrv9025_MultichipSyncSet` 来实现,具体代码实现可参考example code。
5. PostMcsInit: 在完成多片同步后,还有一些初始化工作,比如TRX通道的使能,TXORX mapping,ADRV902X的InitialCal 的使能,这些工作都是在PostMcs阶段完成,通过调用API `adi_adrv9025_PostMcsInit` 实现。
6. JESD Link setup: 截止到上一步,芯片相关的基本配置和初始化已经完成,此时需要进行JESD建链操作,这个步骤稍微复杂一些,因为涉及到FPGA,clock和ADRV902X三个芯片的配合和设置,在example code中可以查看 `jesdBringup`这个函数。
7. Pa protection init: 在建链完成后,为了保护后继的PA,需要打开ADRV902X的PA 保护功能,通过调用 API `adi_adrv9025_TxRampDownInit` 来实现。
8. GP Int init: 这一步是打开GP INT的状态监控,这样我们就可以通过ADRV902X的两个GPIO管脚来监控芯片内部的事件,通过API `adi_adrv9025_GpIntInit` 来实现。
上述的所有流程在main.c 中都被封装到了接口`programMadura`中,具体实现可以参考main.c 中该函数的源码。
截止到这里,整个ADRV902X的初始化和板级的初始化已经结束,后续可以调用ADRV902X相关的功能API对其进行测试和控制,比如打开芯片的tracking calibration,TRX通道开关控制,ATT设置等。
上述的流程涵盖了最基本的ADRV902X 设备的初始化流程,用户或开发者可以按照该流程将ADRV902X的初始化集成到自己的软件工程中。
代码编译与环境部署
ADI官方提供的构建工具为make,在example code的目录下可以看到Makefile,通过该makefile,可以直接在交叉编译环境,或者是官方的EVB板(ADS9/8) 上对代码进行编译构建。
先说一下官方软件包中的makefile结构,除了device文件夹外,其他项目的次顶层文件夹下均有一个makefile,device文件夹下除了share_utils外的文件夹下,也均有makefile,这些makefile的构建目标,均为静态编译库,也就是说,这些文件夹内的文件相对独立,只会以接口形式调用其他文件夹下的函数。
这些库的构建,均可以通过./c_src/boards下的makefile触发。这些库都会被example code工程构建所使用。对于example code 的makefile,通过调用上述./c_src/boards 下的makefile触发所有的静态库的编译,并链接到最终的输出应用的方式,构建完成整个example code工程:
需要注意的一点是,官方提供的makefile中,是不包含debug编译选项的,如果所以如果是移植makefile并包含调试符号表时,可以增加debug编译选项为g1。作为编译的演示,使用指令 `make all -j4`即可启动编译,其中生成的'main'即为example code工程的应用程序:
在构建完成后,我们需要在构建平台的文件系统上上传ADRV902X初始化需要的资源,如 固件版本,增益表,usecase profile等,存放的位置可以参考example code中的这两个变量:
const char adiProfileFilePath[] = "/home/analog/adrv9025_c_example/resources/adrv9025/profiles/public/ADRV9025Init_StdUseCase13_nonLinkSharing.profile";
static adi_adrv9025_PlatformFiles_t platformFiles = { { "/home/analog/adrv9025_server/resources/ADRV9025_FW.bin;/home/analog/adrv9025_server/resources/ADRV9025_DPDCORE_FW.bin" },
{ "/home/analog/adrv9025_c_example/resources/adrv9025/arm_firmware/stream_image.bin" },
{ { { "/home/analog/adrv9025_c_example/resources/adrv9025/gain_tables/RxGainTable.csv" }, 0xFF } },
1,
{ { { "/home/analog/adrv9025_c_example/resources/adrv9025/gain_tables/TxAttenTable.csv" }, 0x0F } },
1 };
运行结果