万字警告,建议收藏后食用。

目录

一、AD9361概述

1.1  AD9361芯片结构

1.2  AD9361性能特点

1.3 AD-FMCOMMS2-EBZ性能特点

二、Zynq-7000概述

2.1  Zynq的芯片结构

2.2 ZC702简介和结构

三、AD9361和ZC702之间的数据通路

四、AD9361参考设计说明(PL侧硬件部分)

4.1 IP核的概念

4.2 硬件设计

五、AD9361 参考设计说明(PS侧软件部分)

5.1 AD9361 no-OS Software 概述

5.2 AD9361 no-OS Software 顶层目录说明

5.3 main.c文件

5.4 dac_init函数

5.5 adc_capture函数

 

一、AD9361概述

1.1  AD9361芯片结构

AD9361是一款面向3G和4G基站应用的高性能、高集成度的射频(RF)Agile Transceiver™捷变收发器。芯片采用了零中频架构,将整个射频以及中频信号电路集成在一个芯片中,包括射频放大器、 模拟滤波器、混频器、解调器、 12 位的 ADC 和 DAC 的 RF 2x2(接收和发送通道各两路) 收发器, 另外还集成了收发通道的频率合成器, 同时为每个接收子系统集成了独立的自动增益控制( AGC) 、直流失调校正、正交校正和数字滤波电路,消除了数字基带中提供这些功能的必要性,每个通道搭载两个高动态范围 ADC, 先将收到的I 信号和 Q 信号进行数字化处理,然后将其传给可配置抽取滤波器和抽头有限脉冲响应( FIR) 滤波器,以相应的采样率生成 12 路输出信号。

图 1 AD9361芯片结构

1.2  AD9361性能特点

AD9361 的工作频率为 70 MHz ~ 6 GHz,涵盖了大部分特许执照和免执照频段,支持可调谐 200 kHz ~ 56 MHz 的通道带宽,且具有高度的可编程能力, 发射器采用了直接变频架构,可实现较高的调制精度和较低的噪声,在接收通道,接收噪声系数可以做到小于 2. 5 dB,此外,该芯片的 EVM 可以做到小于-40 dB,可为外部功率放大器的选择留出客观的系统裕量,并且,芯片还支持 AGC 自动增益和更加灵活的手动增益模式,支持外部控制。公众号:OpenFPGA

1.3 AD-FMCOMMS2-EBZ性能特点

AD-FMCOMMS2-EBZ是一款高速模拟模块,设计用于展示AD9361的硬件评估板。AD-FMCOMMS2-EBZ使射频工程师能够将AD9361连接到射频测试平台(矢量信号分析仪、信号发生器等)并测量性能。

图 2为AD-FMCOMMS2-EBZ的外观:

图 2  AD-FMCOMMS2-EBZ外观

通俗来讲,AD9361就是一个集成度很高的信号收发器,用户不需要向普通收发器那样准备放大器,混频器等等部件;它可以通过搭载它的评估板上的FMC接口与各种母板连接,连接后,在母板上用户可以使用ADI公司提供的API编程来配置AD9361的一些参数例如滤波器系数等等,达到灵活控制收发器的目的,而且工作频率宽,设计也很简单。

AD-FMCOMMS2-EBZ就是是搭载AD9361的评估板,用于外围设备(例如天线和FMC接口)与AD9361的连接。

二、Zynq-7000概述

2.1  Zynq的芯片结构

Zynq-7000 AP SOC是Zynq-7000全可编程片上系统的缩写(Zynq-7000 All Programmable System on Chip),它通过将一个双核ARM Cortex-A9处理系统(Processing System,简称PS)和Xilinx 7-Series 28nm 可编程逻辑(Programmable Logic,简称PL)及各种接口等周边设备集成到一个芯片上组成一个片上系统(SOC),来减少系统的复杂性。图 3为一个最简单的Zynq结构模型:

图 3  Zynq简单结构模型

Zynq最为简单的结构模型就只有两部分:PS和PL,PS和PL之间通过AXI接口进行数据通信,这样,使用Zynq SOC既可以单独使用ARM来实现嵌入式系统的设计,又可以使用FPGA来实现各种时序和逻辑的设计,最为关键的是可以同时使用二者来进行更为灵活的系统设计。公众号:OpenFPGA

值得注意的是,Zynq的处理系统(PS)并不是只包含ARM处理器,还包含应用处理单元(Application Processing Unit,APU)和外围接口,缓存区,内存接口和时钟电路;Zynq可以单独使用PS部分,但无法单独使用PL部分,要想使用PL部分必须得启动PS侧,通过PS来配置PL。

图 4为较为详细的Zynq结构图,上半部分PS侧结构,其中为绿色部分为APU;下半部分为PL侧结构。

图 4  Zynq结构模型

2.2 ZC702简介和结构

Xilinx ZC702是一个评估板,为Zynq SoC提供一个开发和评估的硬件环境。ZC702和一些嵌入式处理系统有共同的特性,包括DDR3内存,三模式以太网,通用I/O和两个URAT接口。另外ZC702还支持FMC。使用ZC702作为平台连接AD-FMCOMMS2-EBZ板子可以生成需要发送的信号、对采集到的信号进行处理等功能。图 5为ZC702的外观结构,图 6为ZC702的简略的框架结构

图 5  ZC702外观结构

图 6  ZC702结构框图

三、AD9361和ZC702之间的数据通路

ADI公司提供了基于ZC702的硬件(PL侧)和软件(PS侧)设计,作为AD9361和Zynq之间的连接和使用的基础。在其提供的硬件设计的基础上,AD9361和ZC702之间的数据通路如所示:

图 7  AD9361和ZC702之间数据通路

右边为AD9361部分,左边为ZC702部分;二者通过FMC接口传输数据。在ZC702部分的蓝色区域为zynq的PL部分,命名为AD9361Core;上方为ARM部分,其余为接口部分,为PS和PL及AD9361之间建立数据通道。

ADI公司也提供了代码用于配置AD9361的参数和一些API用于数据的传输和接收;

与DDR进行传输数据的传输方式为DMA传输方式:在DMAC(DMA控制器)的操作下,数据直接由源地址传输到目的地址,(DDR<->AD9361)不需CPU的干预,因此可以极大的提高了CPU的效率。

注意:如果使用PL侧来产生用户数据而是直接通过PS侧来产生数据并发送的话,用户数据应该使用DMA传输方式和DDR。

在PFGA部分中DDS(直接数字式频率合成器)产生的信号数据和从AD9361接收到的信号数据都需要进行IQ调制和解调后,通过DMA方式传输到DDR中存储,这样方便ARM读取数据,并进行处理。

以上的数据通路是通过硬件设计来实现的,若想真正配置和使用AD9361,还需通过运行在ARM上的软件部分。

四、AD9361参考设计说明(PL侧硬件部分)

4.1 IP核的概念

IP(Intellectual Property)内核模块是一种预先设计好的甚至已经过验证的具有某种确定功能的集成电路、器件或部件。它有几种不同形式。IP内核模块有行为 (behavior)、结构(structure)和物理(physical)3级不同程度的设计,对应有主要描述功能行为的“软IP内核(soft IP core)”、完成结构描述的“固IP内核(firm IP core)”和基于物理描述并经过工艺验证的“硬IP内核(hard IP core)”3个层次。这相当于集成电路(器件或部件)的毛坯、半成品和成品的设计技术。

软核是用VHDL等硬件描述语言描述的功能块,但是并不涉及用什么具体电路元件实现这些功能。软IP通常是以硬件描述 语言HDL源文件的形势出现,应用开发过程与普通的HDL设计也十分相似,只是所需的开发硬软件环境比较昂贵。软IP的设计周期短,设计投入少。由于不涉 及物理实现,为后续设计留有很大的发挥空间,增大了IP的灵活性和适应性。其主要缺点是在一定程度上使后续工序无法适应整体设计,从而需要一定程度的软 IP修正,在性能上也不可能获得全面的优化。

因此,在AD9361参考设计中给出的IP核全为软IP内核

4.2 硬件设计

打开Vivado,打开…\hdl-hdl_2014_r2\projects\fmcomms2\zc702路径下的AD9361的Vivado工程文件fmcomms2_zc702.xpr

点击右侧导航栏里的IP Integrator—Open Block Design

打开ADI公司给的AD9361的参考设计IP核框图,每个蓝色方块即为硬件部分的各个IP核,如图 8所示的部分(可放大观看),该部分为AD9361 Core和DMA控制器部分(其余可在Vivado中查看)。

各IP核的源Verilog HDL代码在hdl-hdl_2014_r2\library中,这些设计就是图 7中左半部分的设计

图 8  AD9361参考设计IP核框图(DMA和AD9361部分)

五、AD9361 参考设计说明(PS侧软件部分)

5.1 AD9361 no-OS Software 概述

AD9361 no-OS Software是ADI公司提供的AD9361的软件部分,运行在CPU(也就是Zynq的ARM)中,该程序为裸机程序(即无操作系统的程序),可以AD9361各个参数进行配置,对PL中的一些寄存器进行读写,控制发送数据源,控制DMAC(DMA控制器)对发送和接收的数据进行传输,从而实现AD9361的基本功能:对数据的接收、处理和发送。整个程序是使用C语言来完成的。

5.2 AD9361 no-OS Software 顶层目录说明

在ADI官网可以下载到no-OS-Software的源码,下载到的源码包含了很多ADI的收发器, 表 1列出的是AD9361的no-OS-Software中的文件和文件夹目录结构

目录

子文件

解释说明

console_commands

command.h 、command.c

包含用于控制AD9361的命令行的文件

console.h、console.c

包含用于命令行操作、显示等的与平台串口相关的文件

platform_altera

Altera平台的相关文件(使用Xilinx平台无需该文件)

platform_generic

通用平台的相关文件(使用Xilinx平台无需该文件)

platform_linux

Linux平台相关的文件(使用Xilinx平台无需该文件)

platform_xilinx

adc_core.h、adc_core.c

模数转换模块控制文件,包括模块的初始化和数据传输等

dac_core.h、dac_core.c

模数转换模块控制文件,包括模块的初始化和数据传输等

Platform.c、platform.h

Xilinx平台一些驱动文件

parameters.h

以上文件所用到的参数的宏定义文件

ad9361.c

AD9361的驱动文件,比如增益控制函数等

ad9361.h

ad9361_api.c

AD9361应用编程接口驱动文件,比如AD9361的初始化函数

ad9361_api.h

common.h

通用驱动文件,包含时钟结构体和通用宏定义

config.h

AD9361和 AD9361 API的配置文件

main.c

整个软件部分的main函数文件

util.c

util驱动文件

util.h

表 1AD9361 no-OS-Software源码目录结构

5.3 main.c文件

main.c文件是main函数所在文件,是整个程序的入口。

main.c文件的开头是需要条件编译的头文件和宏定义。

/*****************************************************************************
                              Include Files *****************************************************************************/9
#include"config.h"
#include"ad9361_api.h"
#include"parameters.h"
#include"platform.h"
#ifdef CONSOLE_COMMANDS
#include"command.h"
#include"console.h"
#endif
#ifdef XILINX_PLATFORM
#include<< span="">xil_cache.h>
#endif
#ifdefined XILINX_PLATFORM ||defined LINUX_PLATFORM
#include"adc_core.h"
#include"dac_core.h"
#include"adc_interrupt.h"
#include"SD_card.h"
#endif

因此,在使用时需要根据情况在程序最开始对一些参数进行宏定义,

使用ZC702需要添加语句:

#define XILINX_PLATFORM

如需使用命令行控制AD9361,需要添加语句:

#define CONSOLE_COMMANDS

如需使用ADC的数据捕获功能,需要添加语句:

#defineCAPTURE_SCRIPT

然后是命令行函数所用到的一些变量的定义、对AD9361初始化所需要参数的变量定义和AD9361接收和发射端滤波器的定义。

AD9361_InitParam default_init_param ={

/* Identification number */

0,    //id_no;

/* Reference Clock */

40000000UL,//reference_clk_rate

/* Base Configuration */

0,    //two_rx_two_tx_mode_enable *** adi,2rx-2tx-mode-enable

1,    //one_rx_one_tx_mode_use_rx_num *** adi,1rx-1tx-mode-use-rx-num

1,    //one_rx_one_tx_mode_use_tx_num *** adi,1rx-1tx-mode-use-tx-num

1,    //frequency_division_duplex_mode_enable *** adi,frequency-division-duplex-mode-enable

0,    //frequency_division_duplex_independent_mode_enable *** adi,frequency-division-duplex-independent-mode-enable

0,    //tdd_use_dual_synth_mode_enable *** adi,tdd-use-dual-synth-mode-enable

0,    //tdd_skip_vco_cal_enable *** adi,tdd-skip-vco-cal-enable

0,    //tx_fastlock_delay_ns *** adi,tx-fastlock-delay-ns

0,    //rx_fastlock_delay_ns *** adi,rx-fastlock-delay-ns

0,    //rx_fastlock_pincontrol_enable *** adi,rx-fastlock-pincontrol-enable

0,    //tx_fastlock_pincontrol_enable *** adi,tx-fastlock-pincontrol-enable

0,    //external_rx_lo_enable *** adi,external-rx-lo-enable

0,    //external_tx_lo_enable *** adi,external-tx-lo-enable

5,    //dc_offset_tracking_update_event_mask *** adi,dc-offset-tracking-update-event-mask

6,    //dc_offset_attenuation_high_range *** adi,dc-offset-attenuation-high-range

之后的部分是整个软件部分的主函数,主函数的程序流程图如图 6所示(默认定义了XILINX_PLATFORM常量):

图 9 main函数流程图

DAC模块初始化使用的函数为dac_init函数

ADC数据捕获使用到的函数为adc_capture函数

这两个函数是控制数据传输的主要函数,下面的章节将会详细介绍这两个函数。

5.4 dac_init函数

dac_init为DAC模块初始化函数,也负责DMA传输部分,将DDR中的数据送给AD9361。

dac_init函数的函数声明为:

第一个参数struct ad9361_rf_phy *phy 为指向AD9361的射频设备结构体的指针。

第二个参数uint8_t data_sel为 需要发送的数据源的选择:

enum dds_data_select

{

DATA_SEL_DDS,

DATA_SEL_SED,

DATA_SEL_DMA,

DATA_SEL_ZERO,  /* OUTPUT 0 */

DATA_SEL_PN7,

DATA_SEL_PN15,

DATA_SEL_PN23,

DATA_SEL_PN31,

DATA_SEL_LB,  /* loopback data (ADC) */

DATA_SEL_PNXX,  /* (Device specific) */

USER_DATA,

};

0表示发送DDS生成的信号;

2表示通过DMA发送DDR中的信号数据,该数据在dac_core.c开头定义;

3表示发送全0信号;

4-7表示发送随机数信号;

8表示发送从ADC中接收到的数据信号;

9表示发送选定设备的信号;

10为用户数据(需要在函数中添加代码)

第三个参数为DMA设置的标准位,0表示设置;1表示不设置。

DMA传输支持2维传输(即按行列传输),但是目前只需要一维传输,因此,在源代码里向AXI_DMAC_REG_SRC_ADDRESS和AXI_DMAC_REG_Y_LENGTH写入0表示只使用一维传输。

在传输用户数据时,也应该在将用户数据进行调制后,参照以上代码,使用DMA方式发送数据

5.5 adc_capture函数

adc_capture函数为数据捕获函数,也负责DMA传输数据到      DDR

adc_init函数的函数声明为:

第一个参数size为要捕获的数据量(个);

第二个参数为start_adress存储捕获数据的目的地址。

图 9为dac_init函数的流程图:

图 12  adc_captur函数流程图

源代码如下:

int32_t adc_capture(uint32_t size, uint32_t start_address)

{

uint32_t reg_val;

if(adc_st.rx2tx2)

{

length =(size *8);

}

else

{

length =(size *4);

}

上图的源码为adc_capture函数的第一部分——数据单位转换部分,adc_capture函数的第一个参数size为用户想要捕获到的数据量,单位是“个”,但是在DMAC的很多寄存器中,比如AXI_DMAC_REG_X_LENGTH寄存器,其中的数值为传输的数据的总字节数,单位为“字节”,因此需要将size单位转换为“字节”。如果打开了双通道,那么捕获的数据数据会占用双倍的存储空间。

adc_dma_write(AXI_DMAC_REG_CTRL, 0x0); //初始化DMA通道

adc_dma_write(AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE);//DMA通道使能

adc_dma_write(AXI_DMAC_REG_IRQ_MASK,0x0);//取消屏蔽.

//adc_dma_read(AXI_DMAC_REG_TRANSFER_ID, &transfer_id);//读取下一个传输的ID号(5位)

adc_dma_read(AXI_DMAC_REG_IRQ_PENDING,&reg_val);    /*读取中断状态:一个传输完成后 END_OF_TRANSFER 即 [1]位 置 1,

一个传输加入队列后 START_OF_TRANSFER 即 [0]位 置 1 */

adc_dma_write(AXI_DMAC_REG_IRQ_PENDING, reg_val);//写入中断状态寄存器,使中断寄存器初始化

adc_dma_write(AXI_DMAC_REG_DEST_STRIDE,0x0);//设置目的地址中从一行的开始和下一行之间的字节数

adc_dma_write(AXI_DMAC_REG_X_LENGTH, length -1);//传输的字节数

adc_dma_write(AXI_DMAC_REG_Y_LENGTH,0x0);//传输的行数

adc_dma_write(AXI_DMAC_REG_DEST_ADDRESS, start_address);//设置传输的目的地址(destination address)

adc_dma_write(AXI_DMAC_REG_START_TRANSFER,0x1);//加入传输队列

上图为adc_capture函数的第二部分——DMA配置部分,其中与ADC模块相比不同的寄存器为:

AXI_DMAC_REG_IRQ_MASK:中断屏蔽寄存器,[1]位为EOT(End Of Transfer)IRQ,[0]位为SOT(Start Of Transfer) IRQ,哪一位置1,就表示那一位的中断请求被屏蔽;

AXI_DMAC_REG_IRQ_PENDING:读取中断状态:一个传输完成后 END_OF_TRANSFER 即 [1] 位 置 1,一个传输加入队列后 START_OF_TRANSFER 即 [0] 位 置 1 */

AXI_DMAC_REG_TRANSFER_ID:该寄存器的数值为下一次传输的ID号。

//Wait until the new transfer is queued.

do

{

adc_dma_read(AXI_DMAC_REG_START_TRANSFER,&reg_val);

}

while(reg_val ==1);*/

// Wait until the current transfer is completed.

do

{

adc_dma_read(AXI_DMAC_REG_IRQ_PENDING,&reg_val);

}

while(1);//reg_val !=0011b*/

//Wait until the transfer with the ID transfer_id is completed.

do

{

adc_dma_read(AXI_DMAC_REG_TRANSFER_DONE,&reg_val);//读取传输完成的ID号

}

while((reg_val &(1<<< span=""> transfer_id))!=(1<<< span=""> transfer_id));

上图为上图为adc_capture函数的第三部分——判断与等待

1.等待,直到一个新的传输加入传输队列

读取AXI_DMAC_REG_START_TRANSFER寄存器的值,值为1时循环,值为0时跳出循环。

之前已经向AXI_DMAC_REG_START_TRANSFER写入了1,在这时判断AXI_DMAC_REG_START_TRANSFER的值,若是1,表示新的传输仍然在排队,若是0,表示新的传输已经开始。

2.等待,直到目前的传输完成。

读取AXI_DMAC_REG_IRQ_PENDING的值,当传输进行时,AXI_DMAC_REG_IRQ_PENDING的[0]位SOT位始终为1,当传输完成时,[1]为EOT位由0置为1,之后两位都会被清0。因此,当AXI_DMAC_REG_IRQ_PENDING的值为3时,表示传输完成。

3.等待,直到ID为transfer_id的传输完成

这一步是为了验证之前设置的传输已经完成。

作者: 刘恒良

版权归作者所有

AD9361和Zynq及其参考设计说明相关推荐

  1. Xilinx的ZYNQ芯片软件设计说明

    1.概述 ZYNQ是xilinx推出一个款SOC,其亮点在于FPGA里包含了完整的ARM处理子系统(PS),每一颗Zynq系列的处理器都包含了Cortex-A9处理器,整个处理器的搭建都以处理器为中心 ...

  2. AD936x+ZYNQ搭建OpenWIFI

    之前推荐过GitHub上优秀的开源项目<Github 上有哪些优秀的 VHDL/Verilog/FPGA 项目>,OpenWIFI作为通信领域的"翘楚",自然很多人都会 ...

  3. AD936x+ZYNQ搭建收音机(二)含视频演示

    非官方列表中板卡应用 后续更新 收音机应用 书接上回<AD936x+ZYNQ搭建收音机(一)> 硬件:SDR硬件平台 开发平台:Windows 附件:收音机天线 注意:天线接口要选择和自己 ...

  4. FPGA时钟设计方案

    时钟设计方案 在复杂的FPGA设计中,设计时钟方案是一项具有挑战性的任务.设计者需要很好地掌握目标器件所能提供的时钟资源及它们的限制,需要了解不同设计技术之间的权衡,并且需要很好地掌握一系列设计实践知 ...

  5. 世界上第一个微处理器真的是Intel 4004吗?其实这是个很复杂的故事…

    发明于1947年的晶体管作为电子放大器和开关,是各种电子设备(从袖珍收音机到仓库规模的超级计算机)的核心部件.其早期版本被称为"双极晶体管",至今仍在使用.到了20世纪60年代,工 ...

  6. 6年等来的Win11,果里果气…兼容安卓App这是躺平了?

    全文共1291字,阅读大约需要4分钟 Windows11昨夜悄悄地来了! 没热搜,没热议.六年磨一升级,还真有点小悲凉. 新系统嘛......可以说看着像苹果,运行着安卓App,套得一手好娃,也难怪1 ...

  7. Vivado® ML 版,让设计更智能化

    赛灵思近日宣布推出 Vivado® ML 版,这是业内首个基于机器学习(ML )优化算法以及先进的面向团队协作的设计流程打造的 FPGA EDA 工具套件,可以显著节省设计时间与成本,与目前的 Viv ...

  8. 谈谈Xilinx FPGA设计的实现过程

    绪论 FPGA编译流程是指将一个FPGA设计从普通RTL描述转换为比特流所需要的一系列步骤.编译流程的顺序会有所不同,这取决于所使用的工具.然而,任何Xilinx FPGA的编译都将包含8个基本步骤: ...

  9. 无招胜有招-Vivado非工程模式下的FPGA设计流程

    参考:UG892 UG835 Vivado集成开发工具为设计者提供了非工程模式下的FPGA设计流程.在Vivado非工程模式下,FPGA开发人员可以更加灵活地对设计过程的每个阶段进行控制,从而进一步提 ...

最新文章

  1. word技巧 输入方框中带对勾的符号的快捷方式
  2. python使用numpy的np.float_power函数计算numpy数组中每个数值的指定幂次(例如平方、立方)、np.power函数默认返回整数格式、np.float_power函数返回浮点数
  3. 【Linux+Mono+Asp.net公开课】视频下载
  4. 从字符串中删除HTML标签
  5. 在Ubuntu下利用Eclipse调试FFmpeg
  6. 基于Java的RDMA高性能通信库(一):IBM jVerbs库
  7. Centos 6.5 64位双网卡绑定
  8. Win10 IoT 10 中文显示乱码或报错的问题
  9. 查看MySQL服务端版本
  10. java完全解耦_java-完全解耦 - osc_bc7dotjc的个人空间 - OSCHINA - 中文开源技术交流社区...
  11. linux ftp解压命令 cannot fid or open,Linux环境搭建及常用shell命令集锦
  12. python os.walk_Python os.walk() 简介
  13. 2018年技术上该怎样努力
  14. Linus 07年在 Google讲座介绍Git的特点和设计思路
  15. Arthas : 在线分析诊断工具Arthas(阿尔萨斯)
  16. python使用opencv库_python库(OpenCV的简单使用)
  17. 计算机sci审稿意见,【小木虫SCI秘籍】感悟之二——一个审稿意见的回复 - 论文投稿 - 小木虫 - 学术 科研 互动社区...
  18. 管理API访问令牌的最佳安全实践
  19. 【公开课】国内外公开课网址
  20. Redis 源码分析跳跃表(skiplist)

热门文章

  1. 米哈游贺甲:如何实现次世代卡通渲染效果?
  2. CentOS没有fonts字体文件夹
  3. python base64和png或jpg图片转换
  4. 大功率H桥电机驱动板电路设计方案 此大功率直流电机驱动板采用ir2103驱动芯片,可同时驱动两路电机
  5. cfa考试可以用计算机吗,CFA考试可以用哪种计算器?
  6. permission 文档 翻译 运行时权限
  7. “动吧“ - crud 练习 part7 - Shiro安全框架简介 - 58~60 - 、[扩展] - 动态菜单 - 60
  8. 古月居ROS入门21讲——9.创建工作空间与功能包
  9. 使用Gitolite实现分布式版本控制系统的权限管理
  10. 雨课堂卷子提前看_雨课堂怎么新建试卷?制作试题的具体方法