在我的职业生涯中,我一直从事模拟和混合信号系统、FPGA架构、I/O和信号完整性方面的工作。所以我真的是一个搞硬件的人,让我有时有一点负罪感的是我曾认为嵌入式软件适合其他人来做。
随着射频(RF)数据转换器的问世,情况发生了变化。我们现在已经将世界一流的RF ADC和DAC集成到Zynq UltraScale +架构中了。因此,传统的射频和模拟工程师不可避免地以前所未有的方式接触到了嵌入式系统。
射频数据转换器解决方案
如果您对数据转换器解决方案很熟悉,那您就会知道它是以IP核的形式被封装到Vivado Design Suite中的。这让您可以通过赛灵思提供的软件驱动来管理射频模数转换器(RF-ADC)和射频数模转换器(RF-DAC)块的状态和控制。
《Zynq UltraScale + RFSoC RF数据转换器IP产品指南》(PG269)提供了有关此IP的所有详情,并且还提供了有关该驱动的详细附录。
开始在RF-ADC和RF-DAC上进行调试
RF分析仪工具是一个不错的起点。
RF分析仪是基于MicroBlaze的设计,具有通信层,可以部署到任何电路板上的任何器件上。它还带有一个GUI,让您可以将RF-ADC接收的内容可视化,并可通过RF-DAC来实现激励生成和发射函数。至关重要的是,该应用是通过软件驱动构建的。
如果您试图追踪RF系统中的问题,RF分析仪非常强大,而且由于它独立工作,不依赖于设计或电路板,因此可以用来验证系统的RF部分。
一个常见的用例是您希望在系统中调试RF-ADC和RF-DAC,并需要编写一个小应用以在运行时进行测试。鉴于RF分析仪和定制设计都需要使用软件驱动,我决定编写一个博客帮助您了解驱动,并展示一下如何开始用它来进行调试。在下一个博客中,我会展示一次拆箱,带您一起来看看RF分析仪工具。
有可能您对RF数据转换器系统已经很熟悉了,那就把了解驱动想成是在您的知识基础上再增添一点知识,而不是去了解完全未知的事物吧。
在这篇博客中,我会介绍以下内容:
如何构建驱动
数据结构
我们现在还是来制作一个Baremetal应用吧。在稍后的博客中,我们会在此基础上介绍如何制作Linux应用。
构建驱动:
RFDC驱动的一个优点是它是使用Libmetal构建的。Libmetal是赛灵思开发的开源软件堆栈,提供用来访问器件的、处理器件中断、请求跨Linux、Realtime OS和baremetal内存的通用用户API。
这对我们来说意味着什么?嗯,这意味着我们真正感兴趣的驱动部分是在用户空间实现的,因此我们不必担心与硬件交谈的机制。这还意味着API在Linux和Baremetal应用中很常见,因此您不需要了解两组API调用,也不用担心如何将代码从Baremetal移植到Linux。
XRFdc驱动程序源代码在下图中显示得更详细。驱动的源代码可以在赛灵思的SDK安装中找到,也可以在Github上单击这里找到。
在这个图中,在底部您会看到xrfdc_hw.h文件。这个报头文件包含您可能会认为是RF ADC和DAC块的寄存器地址映射。这个文件并不是编写来供用户使用的,而是用于驱动的内部机制。应该不需要用到此文件中的标识符,甚至不需要研究它们,因为它们在编写应用时无法真正帮到您。
其他报头文件(例如xrfdc.h和xrfdc_mts.h)更重要,因为它们内含以下内容:
API调用所需的所有数据结构。
用于实现API调用的代码中的内联(辅助)函数。
用于应用的宏(这很有用,因为您可以在适当的时候使用宏XRFDC_ADC_TILE/XRFDC_DAC_TILE,而不是将ADC块的值0或DAC块的值1传递给API。这让读取和理解代码变得易如反掌。)
最重要的是,这些文件显示的是将用于您的应用的API调用原型。
接下来,您将看到的是API调用的源代码。这是我们实现用户API调用功能的地方。通常,不需要非常详细地研究各个API调用是如何实现的。
xrfdc.c:这是用于应用的API的主体。
xrfdc_intr.c:用于实现管理RF数据转换器中断所需的API调用。
xrfdc_mixer.c:包含XRFdc驱动中混频器设置的接口函数。
xrfdc_mts.c:包含XRFdc驱动的多块同步函数。
XRFdc软件驱动的工作原理
我们已经看到了驱动源中包含的内容,现在我们来谈谈您需要用它来做什么。
如上所述,xrfdc.h报头文件应该是您最常引用的文件。这个文件中提供了数据结构和API函数原型。数据结构和API调用在(PG269)的附录D中也有详细记录。
我们先来谈谈数据结构。
数据结构是一种信息组织方法,用于将有关RF数据转换器的信息组织成有意义的组。我喜欢将数据结构看成是一个“容器”。一个全职软件开发者可能会说这是一个C ++术语,不应该这样使用,但这个比喻对我有用。
我想指向的示例数据结构是RF-ADC或RF-DAC块中的锁相环(PLL)。看一下,我们可以看到它描述了我们可能需要知道的关于PLL的一切,例如,它是否已启用、它的输入时钟、它输出的示例时钟等。
一旦这样的结构存在,我们就可以在API之间轻松地来回传递它,并且可能还可以从一个API中读取,或在另一个API中进行修改。
您还可以在另一个结构中找到结构。例如,XRFdc_PLL_Settings是XRFdc_DAC_Tile的一个成员。
由于结构是透明的,因此您可以在代码中单独对它的一个成员进行修改。一个例子是复杂混频器中数控振荡器频率的变化。
MixerSettings结构有一个称为Freq(表示频率)的成员,因此我们可以按如下方式在代码中对它进行更改。
MixerSettings.Freq = 2000;//MHz
一旦理解了数据结构的基础知识,我们就需要掌握可以使用的API调用了。这些是应用的构建块。
各个API调用机制被抽取并传递给用户。它们在XRFdc.c文件中被实现。如果您想使用调用,只需要知道三件事。
要使用的ADC或DAC块中的函数。
您需要将其作为输入传递。
这通常意味着您可以告诉它您在块中需要的RF块类型、块ID和单个块。
API可能需要被传递一个结构才能使用。
您会得到什么作为输出。
例如,它可能会返回结构的内容。
此驱动中只使用了几种不同类型的API:
有些管理调用可以用来控制块,例如XRFdc_StartUp/XRFdc_Shutdown。这些块用来启动或关闭单个RF-ADC或RF-DAC块。
有些API调用可以启用高级状态报告,例如,XRFdc_GetIPStatus/XRFdc_GetBlockStatus。
还有针对各个子块的Get和Set API调用。例如,您可以分别使用XRFdc_GetMixerSettings和XRFdc_SetMixerSettings同时用get和set来进行复杂的混频器设置。
请务必注意,在IP中也会配置一些Get和Set调用,例如复杂的混频器设置。有些调用只能在运行时完成。一个例子是RF-ADC阈值标志和正交调制校正(QMC)。
最后,其中一些更改将需要重新启动块。更改PLL设置就是一个这样的例子。
你好,RFDC
我们现在对驱动已经有所了解,可以为Zynq UltraScale + RFSoC ZCU111评估板制作第一个非常简单的应用了。
在本示例中,我打算只捕获一些ADC数据。我们可以随时扩展对以后的博客的设计。我正在发送一些IQ数据模式,我将用混频器将其混合到基带并将其输出到设计中的系统集成逻辑分析器块。
在本示例中,我们只想展示一些高级状态和管理API以及我们先前讨论过的一些get和set API。我们只检查IP的状态,并确保启动块的方法正确无误。然后我们将回读数据路径的状态。最后,我们要检查一下混频器的设置。
在我们实现此设计并获得比特流之后,我们可以导出硬件切换文件(HDF)并启动SDK。如果您想了解有关嵌入式设计和SDK的更多信息,也许值得花点时间查看本教程。
HDF文件在SDK中创建硬件平台,因此它知道设计中使用的所有外设及其地址。接下来要做的是制作一个电路板支持包。这需要硬件定义,并提供您需要的所有相关驱动和库。
操作演示
单击“File > New > Board Support Package”。
选择刚刚创建的硬件平台,然后单击“下一步”。
系统会提示您包含某些库。确保您勾选包含libmetal,然后单击“OK”。在这个阶段,我们拥有制作我们的应用所需的一切。
现在,您可以单击“File > New > Application Project”创建示例。
确保指向硬件平台和刚刚创建的BSP。
单击“Next”,然后选择一个空白工程。
我在这篇博客中已提供了我的应用的源代码。您可以导入我的应用并将其用作模板。
我们一起来看看一些主要功能。
我们需要先包含xparameters.h文件(这个文件中有我们需要的所有硬件参数)和我们前面提到的xrfdc.h文件(这个文件中有API调用的驱动结构和函数原型)。
在本示例中,我有一个ZCU111电路板,而且我需要在启动时对时钟进行编程。为了实现这一点,我添加了一些文件(这些文件来自驱动程序源中的“examples”文件夹)。
您将看到我创建XRFdc顶层结构的静态实例。这里的想法是我们有一个结构实例,而且我们可以从任何我们可能需要的API或函数指向它。
static XRFdc RFdcInst; /* RFdc driver instance */
在main函数中,我们声明了我们需要的所有结构:
int Status;
XRFdc_Config *ConfigPtr;
XRFdc *RFdcInstPtr = &RFdcInst;
XRFdc_BlockStatus BlockStatus;
XRFdc_IPStatus myIPStatus;
XRFdc_Mixer_Settings MixerSettings = {0};
请注意,我们只是指向我们刚刚创建的驱动的静态实例。
下一步是初始化驱动。这一步每次都必须做。简单说来,我们将获得xrfdc_g文件中的配置表,并通过XRFdc_LookupConfig函数使用xparameters中的值和设置来填充表。然后我们将其存储在配置指针ConfigPtr中。完成此操作后,我们调用XRFdc_CfgInitialize API并通过配置填充RFdcInstPtr。
现在我们就可以在我们的应用中使用该驱动了。
您会看到我将输入时钟编程到ADC块。
我用XRFdc_GetIPStatus来检查我启用的ADC块的状态。
Status = XRFdc_GetIPStatus(RFdcInstPtr, &myIPStatus);
if (Status != XRFDC_SUCCESS) {
return XRFDC_FAILURE;
}
int powerup_status;
int tile_state;
powerup_status = myIPStatus.ADCTileStatus[0].PowerUpState;
tile_state = myIPStatus.ADCTileStatus[0].TileState;
printf("ADC PowerUp Status: %u\n", powerup_status);
printf("ADC Tile State: %u\n", tile_state);
在本示例中,我希望看到块状态为15。这表示该块已到达其启动状态机的末尾,并且加电状态为1,这意味着它已启用并处于活动状态。
我使用的下一个API调用是XRFdc_GetBlockStatus。这应该告诉我们采样频率的设置,以及数字数据路径的配置方式。请注意,我现在在用XRFDC_ADC_TILE抽取此API调用的块类型。
Status = XRFdc_GetBlockStatus(RFdcInstPtr, XRFDC_ADC_TILE, 0, 0, &BlockStatus);
if (Status != XRFDC_SUCCESS) {
return XRFDC_FAILURE;
}
最后,我会使用XRFdc_GetMixerSettings并在混频器上打印一些细节。
我进行了更改,然后用XRFdc_SetMixerSettings来写入新设置。
在此之后,我会生成一个块事件并将所做的更改应用到硬件。
随后,XRFdc_GetMixerSettings应该会显示我们已在硬件中将混频器比例从0更改为2或从AUTO更改为1.0,并且我的混频器设置也已更改。
那现在我们在SDK中的调试器里来运行一下这个应用。
右键单击应用程序,然后选择“运行方式...”,然后选择“运行配置”。我使用系统调试器,然后也选择对FPGA进行编程的选项。
您也可以选择“Debug As”,这将启用调试透视图并适应代码的步进等。
我们现在来看看UART串行控制台出来的结果。
(我使用内置的SDK终端进行连接,但可以使用任何终端仿真器。)
您可以看到它执行以下操作:
说“Hello!”
对ZCU111电路板上的时钟进行编程。
将块状态设置为15,这意味着块已完全启动,并且AXI流上有有效数据出来。
报告数字数据路径的阻止状态。
显示混频器设置的读取,并确认修改。
最后一次检查显示RF-ADC的数据正传递给系统ILA。
总结语
现在您可以开始编写一些应用了。我已经附上了用来构建我的硬件块设计的Tcl脚本和用于我的ZCU111工程的XDC文件。而且,还附上了该应用的C代码。
如果您有可能想要编写来与RF数据转换器通信的任何应用,它应该是一个很好的起点。我会鼓励您用自己的裸机应用进行练习。
我打算在这篇博客的基础添加一些内容,并突出显示RFSoC的其他一些不错的设计和调试功能。接下来,我打算展示拆箱并浏览RF分析仪工具(这个工具允许您在任何平台上对任何RFSoC器件进行调试)。
下次见!
-
射频
+关注
关注
104文章
5579浏览量
167715 -
嵌入式系统
+关注
关注
41文章
3589浏览量
129453 -
模拟工程师
+关注
关注
1文章
19浏览量
13561
发布评论请先 登录
相关推荐
评论