自主设计一个DDS信号发生器
DDS发生器
DDS信号发生器是直接数字频率合成技术,采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采用这种方法设计的信号源可工作于调制状态,可对输出电平进行调节,也可输出各种波形。
工作原理
相位累加 :在基准时钟的驱动下,频率控制字与累加器输出的累加相位数据相加,结果送至相位寄存器的数据输入端,相位寄存器将累加器在上一个时钟作用后产生的新相位数据反馈到累加器的输入端,使相位累加器进行线性相位累加,当累加满时产生一次溢出,完成一个周期性动作,溢出频率即为输出信号频率。
波形查找与转换 :相位寄存器的输出与相位控制字相加,结果作为正弦查找表的地址,查找表由 ROM 构成,存有一个完整周期正弦波的数字幅度信息,每个地址对应正弦波的一个相位点,把输入地址信息映射成正弦波幅度信号,输出到 D/A 转换器,经转换成模拟量形式信号,再通过低通滤波器衰减和滤除不需要的取样分量,输出频谱纯净的正弦波信号。
基本结构
DDS 主要由相位累加器、相位调制器、波形数据表以及 D/A 转换器构成。
相位累加器:相位寄存器同样由FPGA内部的寄存器资源构成,用于暂存相位累加器的输出结果。其位数与相位累加器相匹配,以便准确地反馈相位累加结果。
在相位累加器完成一次累加后,其输出结果会被送入相位寄存器。在下一个时钟周期,相位寄存器将存储的相位数据反馈到相位累加器的输入端,以实现连续的相位累加操作。
波形存储器(ROM):在FPGA中,波形存储器通常由只读存储器(ROM)实现。可利用FPGA内部的ROM资源,预先将波形的一个周期内的幅度信息以数字形式存储在ROM中。例如,对于正弦波,可以预先计算出一个周期内各个相位点的幅度值,并将其存储到ROM中。
相位寄存器的输出与相位控制字相加后,结果作为波形存储器的地址。FPGA根据该地址从ROM中读取预先存储的波形幅度值,并将其输出到数模转换器(DAC)。波形存储器的容量和精度决定了输出信号的精度和质量,容量越大,可存储的波形点数越多,输出信号的波形越平滑;存储精度越高,输出信号的幅度精度越高。
数模转换器(DAC):FPGA本身不直接包含数模转换器,通常需要外接DAC芯片。通过FPGA的I/O引脚与DAC芯片的接口相连,将数字波形数据传输给DAC进行数模转换。
FPGA按照一定的时序控制,将波形存储器输出的数字幅度信号通过I/O引脚发送到DAC芯片。DAC芯片根据接收到的数字信号,将其转换为对应的模拟信号。转换后的模拟信号通常是一个阶梯状的波形,需要经过后续的低通滤波处理。
低通滤波器(LPF):低通滤波器一般由外部的无源或有源滤波电路实现,FPGA主要负责控制和配置滤波器的相关参数。例如,通过FPGA的I/O引脚输出控制信号来调整滤波器的截止频率等参数。
DAC输出的阶梯状模拟信号包含丰富的高频分量,低通滤波器会滤除这些高频分量,使输出信号变得更加平滑,接近理想的正弦波或其他目标波形。
DFS信号发生器中,相位累加器是核心部件之一,它由N位加法器和N位寄存器组成。每当有一个时钟脉冲到来时,加法器就会把频率控制字和寄存器里当前存着的相位数据加起来,得出的结果又会送回寄存器的输入端。这样,在下一个时钟脉冲来的时候,加法器会再次把频率控制字和寄存器里的新数据相加。这个过程不断重复,相位累加器就在时钟信号的驱动下,持续地对频率控制字进行相加操作。每次相加后,累加器输出的数据就代表了合成信号的相位信息,而且相位累加器发生溢出的频率,就是DFS输出信号的频率。简单来说,相位累加器的作用就是在时钟的作用下,不断累加频率控制字,从而得到合成信号的相位数据,并且这个相位数据会被用作波形存储器的地址,去查出对应的波形采样值,实现从相位到幅度的转换,最后波形存储器输出的数据会被送到D/A转换器,转换成模拟信号输出。
特点
频率分辨率高:输出信号的频率与相位累加器的增量值有关,可以实现非常细微的频率调节。
波形种类丰富:能够生成正弦波、方波、三角波等多种波形,通过改变查找表中的数据即可快速切换。
频率稳定度和准确度高:频率稳定度和准确度可提高到与基准频率相同的水平。
快速转换时间:信号频率转换时间短。
可调制性:可工作于调制状态,对输出电平进行调节。
自己动手设计一个DDS信号发生器
思路
通过查阅相关资料,我们可以从以下几个步骤实现DDS信号发生器的设计:
一、系统设计
确定设计目标
输出信号类型:正弦波和方波。
频率范围:10Hz~5MHz。
最小频率分辨率:小于1kHz。
系统架构划分
相位累加器模块:负责生成相位信息。
波形查找表模块:存储正弦波和方波的波形数据。
数模转换器(DAC)接口模块:将数字信号转换为模拟信号。
控制模块:用于设置频率控制字、波形选择等参数。
时钟模块:提供系统时钟信号。
二、模块设计
相位累加器模块
选择合适的相位累加器位数(如32位)。
根据频率范围和分辨率要求,计算频率控制字的范围。
波形储存器模块
使用ROM IP核存储正弦波和方波的波形数据。
初始化查找表,填充正弦波和方波的数字样本。
控制模块
提供用户接口,用于设置频率控制字、选择波形类型(正弦波或方波)等参数。
将用户输入的参数传递给相位累加器和波形查找表模块。
三、仿真验证
使用ModelSim或其他仿真工具搭建仿真环境。将设计的各个模块集成到仿真环境中,设置仿真时钟信号和测试激励。
实现步骤
根据实验指导,我们可以根据以下结构设计以便理解:
所需正弦波生成
在仿真设计之前,我们需要通过MATLAB生成一个正弦波文件添加到我们的项目中,以便我们后续的操作。
clc, clear, close allF1=1; %信号频率Fs=10^2; %采样频率P1=0; %信号初始相位N=10^2; %采样点数t=[0:1/Fs:(N-1)/Fs]; %采样时刻ADC=2^7 - 1; %直流分量A=2^7; %信号幅度%生成正弦信号s=A*sin(2*pi*F1*t + pi*P1/180) + ADC;plot(s); %绘制图形%创建 coe 文件fild = fopen('sin_wave_100x8.coe','wt');%写入 coe 文件头%固定写法,表示写入的数据是 10 进制表示fprintf(fild, '%s\n','memory_initialization_radix=10;');%固定写法,下面开始写入数据fprintf(fild, '%s\n\n','memory_initialization_vector ='); for i = 1:Ns2(i) = round(s(i)); %对小数四舍五入以取整if s2(i) <0 %负 1 强制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %数据写入if i==Nfprintf(fild, '%s\n',';'); %最后一个数据用;elsefprintf(fild,',\n'); % 其他数据用,endendfclose(fild); % 写完了,关闭文件F1 = 1; %信号频率Fs = 10^2; %采样频率P1 = 0; %信号初始相位N = 10^2; %采样点数t = [0:1/Fs:(N-1)/Fs]; %采样时刻ADC = 2^7 - 1; %直流分量A = 2^7; %信号幅度%生成方波信号s = A*square(2*pi*F1*t + pi*P1/180) + ADC;plot(s); %绘制图形%创建 coe 文件fild = fopen('squ_wave_100x8.coe','wt');%写入 coe 文件头%固定写法,表示写入的数据是 10 进制表示fprintf(fild, '%s\n','memory_initialization_radix=10;');%固定写法,下面开始写入数据 fprintf(fild, '%s\n\n','memory_initialization_vector =');for i = 1:Ns2(i) = round(s(i)); %对小数四舍五入以取整if s2(i) <0 %负 1 强制置零s2(i) = 0endfprintf(fild, '%d',s2(i)); %数据写入if i==Nfprintf(fild, '%s\n',';'); %最后一个数据用分号elsefprintf(fild,',\n'); % 其他数据用 ,endendfclose(fild); % 写完,关闭文件
1、相位累加器模块
// 模块声明,名称为squwave// CPi为时钟输入信号,RSTn为复位输入信号,Address为17位地址输入,Qsquare为12位输出寄存器module squwave(CPi, RSTn, Address, Qsquare);input CPi; // 时钟信号输入input RSTn; // 复位信号输入(低电平有效)input [16:0] Address; // 17位地址输入output reg [11:0] Qsquare; // 12位输出寄存器// 时序逻辑块,检测CPi的上升沿触发always @(posedge CPi) begin// 当复位信号RSTn为低电平时,Qsquare输出全0if (!RSTn) beginQsquare = 12'h000; // 复位时输出12位全0end// 正常工作时,根据Address的值设置Qsquare的输出else begin// 如果Address小于等于17'h0FFFF(即十进制的131071),Qsquare输出全1if (Address <= 17'h0FFFF) beginQsquare = 12'hFFF; // 输出12位全1end// 否则,Qsquare输出全0else beginQsquare = 12'h000; // 输出12位全0endendendendmodule
该代码定义了一个名为squwave的数字电路模块,该模块的主要功能是根据输入地址Address的值来生成一个12位的输出信号Qsquare。模块接收一个时钟信号CPi和一个低电平有效的复位信号RSTn。当复位信号RSTn被激活(即处于低电平状态)时,输出Qsquare会被立即清零,以确保模块从一个已知的初始状态开始工作。
在正常工作状态下,模块会在每个时钟周期的上升沿检查Address输入的值。如果Address的值小于或等于0xFFFF(即十进制的65535),则输出Qsquare会被设置为全1(0xFFF),这通常表示一个高电平状态或激活状态。相反,如果Address的值大于0xFFFF,则输出Qsquare会被设置为全0(0x000),表示一个低电平状态或非激活状态。
2、ROM模块
// 定义一个名为rom_sine的模块,用于生成正弦波形的只读存储器(ROM)module rom_sine(input [10:0] address, // 11位地址输入,用于访问ROM中的波形数据input clock, // 时钟信号输入,用于同步数据读取output reg [11:0] q // 12位输出,用于输出ROM中存储的正弦波形数据);// 声明一个12位宽、2048个元素的存储器数组,用于存储正弦波形数据reg [11:0] mem [0:2047];// 初始化块,用于从MIF文件中读取正弦波形数据到存储器数组initial begin$readmemb("Sine1024.mif", mem); // 从Sine1024.mif文件中读取数据到mem数组end// 时序逻辑块,用于在时钟上升沿更新输出q的值always @(posedge clock) beginq <= mem[address]; // 根据输入地址,从存储器数组中读取对应的正弦波形数据,并赋值给输出qendendmodule// 定义一个名为rom_square的模块,用于生成方波形的只读存储器(ROM)module rom_square(input [10:0] address, // 11位地址输入,用于访问ROM中的波形数据input clock, // 时钟信号输入,用于同步数据读取output reg [11:0] q // 12位输出,用于输出ROM中存储的方波形数据);// 声明一个12位宽、2048个元素的存储器数组,用于存储方波形数据reg [11:0] mem [0:2047];// 初始化块,用于从MIF文件中读取方波形数据到存储器数组initial begin$readmemb("Square1024.mif", mem); // 从Square1024.mif文件中读取数据到mem数组end// 时序逻辑块,用于在时钟上升沿更新输出q的值always @(posedge clock) beginq <= mem[address]; // 根据输入地址,从存储器数组中读取对应的方波形数据,并赋值给输出qendendmodule
这两个模块分别实现了正弦波和方波的ROM生成器。它们都使用了一个12位宽、2048个元素的存储器数组来存储波形数据,并在时钟信号的上升沿根据输入地址读取对应的波形数据并输出。初始化块使用readmemb系统任务从MIF文件中读取波形数据到存储器数组中。这种设计可以用于数字信号处理应用中,生成所需的波形信号。
3、顶层模块
module DDS_top (input CLOCK_50, // 输入50MHz时钟信号input RSTn, // 输入复位信号,低电平有效input [1:0] WaveSel, // 输入波形选择信号,2位宽,用于选择输出的波形类型input [12:0] K, // 输入频率控制字,13位宽,用于控制输出波形的频率output reg [11:0] WaveValue, // 输出波形值,12位宽,输出实际的波形数据wire [9:0] ROMaddr, // 定义一个10位宽的ROM地址信号wire [16:0] Address, // 定义一个17位宽的地址信号wire [11:0] Qsine, // 定义一个12位宽的正弦波形数据信号wire [11:0] Qsquare,// 定义一个12位宽的方波形数据信号output [0:0] LEDG, // 输出LED指示信号,1位宽,用于指示PLL锁定状态output CLOCK_100 // 输出100MHz时钟信号);// 定义内部时钟信号CPi,等于输出的100MHz时钟wire CPi = CLOCK_100;// 实例化PLL模块,将50MHz时钟倍频至100MHz,并输出锁定指示LEDGPLL100M_CP PLL100M_CP_inst (.inclk0(CLOCK_50),.c0(CLOCK_100),.locked(LEDG[0]));// 实例化地址计数器模块,根据频率控制字K和内部时钟CPi生成ROM地址ROMaddr和地址Addressaddr_cnt U0_instance (.CPi(CPi),.K(K),.ROMaddr(ROMaddr),.Address(Address));// 实例化正弦波ROM模块,根据ROM地址ROMaddr和内部时钟CPi输出正弦波形数据QsineSineROM ROM_inst (.address(ROMaddr),.clock(CPi),.q(Qsine));// 实例化方波模块,根据地址Address和内部时钟CPi输出方波形数据Qsquaresquwave U1 (.CPi(CPi),.RSTn(RSTn),.Address(Address),.Qsquare(Qsquare));// 根据波形选择信号WaveSel选择输出的波形类型always @(posedge CPi) begincase (WaveSel)2'b01: WaveValue = Qsine; // 如果选择正弦波,则输出Qsine2'b10: WaveValue = Qsquare; // 如果选择方波,则输出Qsquaredefault: WaveValue = Qsine; // 默认输出正弦波endcaseendendmodule
部分Ip核配置
配置输出位宽、存储容量和存储器类型等:
相关文章:

自主设计一个DDS信号发生器
DDS发生器 DDS信号发生器是直接数字频率合成技术,采用直接数字频率合成(Direct Digital Synthesis,简称DDS)技术,把信号发生器的频率稳定度、准确度提高到与基准频率相同的水平,并且可以在很宽的频率范围内进行精细的频率调节。采…...

鸿蒙UI(ArkUI-方舟UI框架)- 使用弹框
返回主章节 → 鸿蒙UI(ArkUI-方舟UI框架) 文章目录 弹框概述使用弹出框(Dialog)弹出框概述不依赖UI组件的全局自定义弹出框(openCustomDialog)(推荐)生命周期自定义弹出框的打开与关闭更新自定义弹出框内容更新自定义弹出框的属性完整示例 基础自定义弹…...

学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2]
学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2] 学习机器学习,需要学习如何预处理原始数据,这里用到pandas,将原始数据转换为张量格式的数据。 学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1]-CSDN博客 下面…...

在不同型号的手机或平板上后台运行Aidlux
在不同型号的手机或平板上后台运行Aidlux 一、鸿蒙/HarmonyOS手机与平板 二、小米手机与平板 三、OPPO手机与平板 四、vivo手机与平板 一、鸿蒙/HarmonyOS手机与平板 (系统版本有差异,但操作原理相通) 第一步:点击设置——应用和…...

【SSM】SpringBoot学习笔记1:SpringBoot快速入门
前言: 文章是系列学习笔记第9篇。基于黑马程序员课程完成,是笔者的学习笔记与心得总结,供自己和他人参考。笔记大部分是对黑马视频的归纳,少部分自己的理解,微量ai解释的内容(ai部分会标出)。 …...

1.企业可观测性监控三大支柱及开源方案的横评对比
[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(De…...
Neo4j图数据库管理:原理、技术与最佳实践
Neo4j作为领先的图数据库,其高效管理是发挥图计算潜力的关键。本文基于官方技术文档,深入探讨其管理原理、核心操作及生产环境最佳实践。 一、 管理架构与核心原理 多数据库架构 系统数据库 (system):管理元数据(用户、角色、权限、其他数据库信息)。标准数据库:存储实际…...
Elasticsearch中的地理空间(Geo)数据类型介绍
在Elasticsearch中,地理空间(Geo)数据类型用于存储和处理与地理位置相关的数据,支持基于地理坐标的查询、过滤和分析。这类数据类型允许用户在分布式环境中高效地处理地理空间相关的搜索、聚合和可视化需求,广泛应用于地图应用、物流追踪、位置服务(LBS)等场景。 一、核…...
[论文阅读] 软件工程 | 如何挖掘可解释性需求?三种方法的深度对比研究
如何挖掘可解释性需求?三种方法的深度对比研究 研究背景:当软件变复杂,我们需要“说明书” 想象你买了一台智能家电,却发现它的运行逻辑完全看不懂,按钮按下后毫无反应,故障时也不提示原因——这就是现代…...

双空间知识蒸馏用于大语言模型
Dual-Space Knowledge Distillation for Large Language Models 发表:EMNLP 2024 机构:Beijing Key Lab of Traffic Data Analysis and Mining 连接:https://aclanthology.org/2024.emnlp-main.1010.pdf 代码:GitHub - songmz…...

OpenCV CUDA模块特征检测------角点检测的接口createMinEigenValCorner()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 该函数创建一个 基于最小特征值(Minimum Eigenvalue)的角点响应计算对象,这是另一种经典的角点检测方法&…...
Git 提交备注应该如何规范
Git 提交备注应该如何规范 在软件开发过程中,Git 作为版本控制系统被广泛使用,而规范的提交备注对于代码的可维护性、团队协作以及项目的长期发展都有着至关重要的意义。良好的提交备注能够清晰地记录代码变更的原因、范围和影响,方便团队成…...
青少年编程与数学 02-020 C#程序设计基础 17课题、WEB与移动开发
青少年编程与数学 02-020 C#程序设计基础 17课题、WEB与移动开发 一、C#语言Web和移动项目开发1. Web项目开发2. 移动项目开发 二、ASP.NET Core1. ASP.NET Core 基础架构1.1 请求处理管道1.2 主机模型1.3 服务器选项 2. 核心新特性2.1 NativeAOT 支持2.2 增强的身份验证方案2.…...
Qt OpenGL 实现交互功能(如鼠标、键盘操作)
一、基本概念 1. Qt 事件系统与 OpenGL 渲染的协同 Qt 提供了完善的事件处理机制,而 OpenGL 负责图形渲染。交互的实现本质上是: 事件捕获:通过 Qt 的事件系统(如 mousePressEvent、keyPressEvent)捕获用户输入。 状态更新:根据输入事件更新场景状态(如相机位置、模型…...
【Go语言基础【3】】变量、常量、值类型与引用类型
文章目录 一、值(Value)与字面量(Literal)1. 值2. 字面量 二、变量(Variable)1. 声明方式2. 赋值方式3. 变量默认值4. 类型与值的匹配 三、常量(Constant)1. 声明方式2. 常量的特性3…...

8天Python从入门到精通【itheima】-69~70(字符串的常见定义和操作+案例练习)
目录 69节-字符串的定义和操作 1.学习目标 2.数据容器视角下的字符串 3.字符串的下标索引 4.字符串是一个无法修改的数据容器 5.字符串的常用操作 【1】index方法 【2】replace方法:进过替换,得到一个新的字符串 【3】split方法:将字…...
在 Linux 中查看文件并过滤空行
在 Linux 中查看文件并过滤空行 在 Linux 中查看文件内容时过滤掉空行有多种方法,以下是几种常用的方法: 方法 1:使用 grep grep -v ^$ filename-v:反转匹配,只显示不匹配的行^$:表示空行的正则表达式&a…...

GC1809:高性能音频接收与转换芯片
GC1809 是一款高性能音频接收与转换芯片,适用于多种音频设备,如 A/V 接收器、多媒体音响设备、机顶盒等。本文将简要介绍该芯片的主要特性、性能参数及应用。 主要特性 多协议兼容:兼容 IEC60958、S/PDIF、EIAJ CP1201 和 AES3 协议。 多种…...

项目实战——C语言扫雷游戏
这是一款9*9的扫雷游戏 扫雷游戏 1.需求分析2.程序框架设计3.分函数实现打印游戏菜单界面游戏主逻辑函数程序主入口初始化游戏棋盘随机布置地雷显示当前棋盘状态计算指定位置周围的地雷数量玩家排雷主逻辑 4.分文件实现(1)test.c(2࿰…...

【Java】CopyOnWriteArrayList
一,概述 CopyOnWriteArrayList作为List接口的实现之一,它区分于ArrayList在于它是线程安全的。如它名字一样,所有的写操作均复制了原数组的值,虽说代价较大,但读多写少的环境下,是可接受的。笔者在此简单看…...
【JS进阶】ES6 实现继承的方式
ES6 实现继承的方式 基本语法 class Parent {constructor(name) {this.name name;this.colors [red, blue];}sayName() {console.log(this.name);} }class Child extends Parent {constructor(name, age) {super(name); // 必须调用super(),且在使用this之前thi…...
mac 电脑Pycharm ImportError: No module named pip
这个错误表明 PyCharm 在尝试使用 pip 时找不到该模块,通常是由于 Python 环境中的 pip 未正确安装或损坏引起的。以下是针对 Mac 系统的完整解决方案: 解决方案步骤: 检查终端中的 pip 状态 打开终端(Terminal),执行以下命令: bash python3 -m ensurepip --upgrade pi…...

C#入门学习笔记 #8(委托)
欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。 笔记内容会持续更新~~ 本章介绍C#中的委托,本章难度较大... 委托 C#中的委托是C语言、C++中函数指针的升级版。接下来介绍一个概念—…...

CSS 3D 变换中z-index失效问题
CSS 3D 变换中 z-index 失效问题 1. z-index 失效了 在 CSS 中,z-index 通常用于控制元素的层叠顺序,数值越大,元素越靠前显示。在 3D 变换(如 rotateX、translateZ) 中使用 z-index 时,可能会发现z-inde…...
Vue3 中使用 i18n
Vue3 中使用i18n 在 Vue 3 中使用国际化(i18n)可以通过官方的 vue-i18n 库来实现。vue-i18n 是专门为 Vue 应用设计的国际化解决方案,支持多语言切换、动态内容插值等功能。 以下是如何在 Vue 3 中使用 vue-i18n 的详细步骤: 1.…...
vue:当前对象添加对应值
this.$set(this.dynamicValidateForm.domains[index], // 目标对象item.roundProperty, // 要添加的属性名row[item.roundProperty] ? row[item.roundProperty].split(",") : [] // 属性值 ); 说明: 第一个参数:th…...

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(七):消息框交互功能添加
Tailwind CSS 实战,基于Kooboo构建AI对话框页面(一) Tailwind CSS 实战,基于Kooboo构建AI对话框页面(二):实现交互功能 Tailwind CSS 实战,基于 Kooboo 构建 AI 对话框页面&#x…...
JavaScript 核心原理深度解析-不停留于表面的VUE等的使用!
一、解释 JavaScript 作为当今最流行的编程语言之一,广泛应用于 Web 开发、移动端开发、后端开发等多个领域。然而,许多开发者在使用 JavaScript 时,往往只关注其表面的语法和 API,而对其底层原理和核心机制了解甚少。深入理解 J…...

【计算机网络】网络层IP协议与子网划分详解:从主机通信到网络设计的底层逻辑
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:计算机网络 🌹往期回顾🌹: 【计算机网络】传输层TCP协议——协议段格式、三次握手四次挥手、超时重传、滑动窗口、流量控制、 &…...

基于WSL搭建Ubnutu 20.04.6 LTS(二)-部署Docker环境
Docker是一组平台即服务(PaaS)的产品。它基于操作系统层级的虚拟化技术,将软件与其依赖项打包为容器。托管容器的软件称为Docker引擎。Docker能够帮助开发者在轻量级容器中自动部署应用程序,并使得不同容器中的应用程序彼此隔离&a…...