简介:这份资源是一份面向通信工程、电子信息工程等专业学生的课程设计报告,围绕A/D与D/A转换器的仿真展开,采用Matlab/Simulink作为建模与验证环境,适合正在做通信原理课程设计、需要参考完整报告框架与仿真建模思路的读者。资源包内共1个docx文档,约78KB,内容为通信原理课程设计报告正文,涵盖设计目的与要求、开发环境介绍、A/D采样量化编码与D/A恢复滤波的原理说明,以及Constant、Zero-Order Hold、Quantizer、Integer to Bit Converter、Buffer、Bit to Integer Converter等模块的功能与参数设置。报告还给出仿真电路原理图、量化与并串转换的仿真结果分析,例如输入17.5经量化间隔1处理后输出18的过程,并附有设计总结与心得体会。目前已有338人学习下载,可作为课程设计报告的写作范本与Simulink建模的入门参照,帮助读者快速理清A/D、D/A转换的设计流程、模块选型与参数调试要点。
1. 从一次课程设计说起:AD 和 DA 转换器仿真到底在验证什么
课程设计里最容易被糊弄过去的一步,恰恰是 A/D 和 D/A 转换器的仿真。Constant 里输一个 17.5,量化器输出 18,编码成 8 位比特流串行发出,收端再并回来——链路上任何一个参数错位,示波器波形立刻给你难看的脸色。这类 AD 和 DA 转换器仿真真正的价值,是把采样定理、量化误差、串并转换这三块通信原理里的硬骨头,塞进一个能反复改参数、反复看结果的模型里。
适合谁:正在做通信原理课程设计、要交一份跑得通的 Simulink 报告的同学,关心模块怎么连、参数怎么填;也想用 Simulink 快速搭一条数据采集与回放链路、验证算法边界的工程师,更关心采样时间与量化位宽怎么取舍、Buffer 长度怎么定、仿真数据怎么导出到工作空间算误差。
2. 采样定理、量化间隔与比特映射:建模前的数学模型
动手拖模块之前,先把三件事算清楚:采样率取多少、量化间隔取多少、比特序怎么定。这三件事在 Simulink 里分别落到 Zero-Order Hold 的 Sample time、Quantizer 的 Quantization interval、Integer to Bit Converter 的 Output bit order 三个参数上,填错了不会报错,只会给你一组看似合理但完全错误的波形。
2.1 零阶保持器为什么能当采样保持电路
理想采样的数学模型是冲激串乘以模拟信号,频谱上表现为周期延拓。Simulink 里没有理想冲激采样模块,最接近的是 Zero-Order Hold:在每个采样时刻读取输入值,然后保持到这个采样周期结束,输出是阶梯波,也就是教科书里的平顶抽样。
ZOH 的冲激响应是一个宽度为 T 的矩形,对应的频率响应是 sinc 函数,在 1/T 的整数倍频率处有零点。这意味着平顶抽样相对理想冲激采样多了一个 sinc 包络的幅度衰减,高频分量被轻微压低。在课程设计这种量级下,这个包络不影响结论,但要心里有数:如果后面做的是宽带信号重构,这个衰减就得用补偿滤波器拉回来。
采样率的选择还是要回到采样定理:fs ≥ 2·fmax。Zero-Order Hold 的 Sample time 填 1s,等价于采样率 1Hz,理论上能无失真还原的最高信号频率是 0.5Hz。这个数字看着很慢,但配合 Integer to Bit Converter 每整数 8 比特的输出,仿真的信息量和耗时都控制在可接受的范围内。
提示:Constant 模块输出的是常数,经过 ZOH 之后波形仍然是直线,看不出任何阶梯。很多人因此以为采样模块没工作。想看采样效果,把 Constant 换成 Sine Wave,频率取 0.2Hz 左右,再把仿真时间拉长到 20s 以上。
2.2 Quantizer 的量化间隔与量化信噪比
Quantizer 模块做的事很简单:把输入值舍入到最近的、量化间隔的整数倍。Quantization interval 填 1,Sample time 填 -1,前者决定量化台阶的大小,后者表示采样时间继承上游模块,不要在这里另设一个采样率。
对输入 17.5、量化间隔 1 的情况:17.5 / 1 = 17.5,舍入到最近的整数是 18,量化误差 0.5,刚好等于 q/2,是均匀量化器能产生的最大误差。这个结果和底层浮点舍入规则一致,MATLAB 的 round 对 .5 采用远离零方向舍入,两边对齐,所以仿真不会出现 17 和 18 打架的情况。
均匀量化器的误差在 [-q/2, q/2] 内近似均匀分布,噪声功率是 q²/12。满量程正弦输入下,量化信噪比有个好记的近似式:SQNR ≈ 6.02n + 1.76 dB,n 是量化位宽。每加一位,信噪比提升约 6dB。下面这张表能帮你快速判断位宽和量化间隔该取多少:
| 量化位宽 n | 量化级数 2ⁿ | 满量程 SQNR 近似值 | 覆盖 0~255 时的量化间隔 |
|---|---|---|---|
| 4 | 16 | 25.8 dB | 16 |
| 8 | 256 | 49.9 dB | 1 |
| 12 | 4096 | 74.0 dB | 1/16 |
| 16 | 65536 | 98.1 dB | 1/256 |
本设计里 Constant 的取值范围是 0~255,8 位刚好能无溢出地覆盖这 256 个电平,量化间隔取 1 时既不浪费量化级数,也不产生截断。位宽换成 7 位,255 就会溢出;换成 12 位,则有一半以上的编码空间永远用不到。
2.3 整数与比特流的双向映射
A/D 的最后一步是编码。Integer to Bit Converter 把 0~2^M-1 的整数转成 M 位二进制,M 由 Number of bits per integer 决定,本设计取 8。Treat input values as 选 Unsigned,因为输入不会有负数;一旦选成 Signed,17.5 对应的整数会被当成补码解释,收端还原出来就是另一回事。
Output bit order 选 MSB first,意思是最高有效位排在输出向量的第一个位置。8 位无符号整数 18 的二进制是 00010010,MSB first 输出的向量就是 [0 0 0 1 0 0 1 0],拆开写是 0、0、0、1、0、0、1、0。收端的 Bit to Integer Converter 必须同样选 MSB first,否则得到的是位序反转的结果:01001000,也就是 72。
先把数学模型用几行脚本验证一遍,比在 Simulink 里盲调参数快得多:
q = 1; % 量化间隔,对应 Quantizer 的 Quantization interval nbits = 8; % 量化位宽,对应 bits per integer x = 17.5; % 待量化的模拟采样值 y = q * round(x / q); % 四舍五入到 q 的整数倍,与 Quantizer 行为一致 code = dec2bin(y, nbits); % 转成 nbits 位二进制串,MSB 在最左 sqnr = 6.02 * nbits + 1.76; % 满量程正弦输入下的理论量化信噪比 fprintf('量化输出 y = %d\n', y); fprintf('二进制码 code = %s\n', code); fprintf('理论 SQNR = %.1f dB\n', sqnr);这段脚本里,round承担的是 Quantizer 的角色,dec2bin承担 Integer to Bit Converter 的角色,sqnr那一行只是拿来对照,不参与转换。跑出来 y=18、code=00010010、SQNR≈49.9dB,这三个数就是后面判断 Simulink 波形对不对的基准。
3. 从 Constant 到 Bit to Integer:一条完整链路的搭法
模型分三段:前向的采样与量化、中间的编码与串行化、收端的还原与反量化。段与段之间靠数据类型和维度衔接,维度对不上是这类模型最常见的报错来源。
3.1 前向通道:Constant、Zero-Order Hold、Quantizer
Constant 的 Constant value 填 17.5,Sample time 填 1,让信号源本身也是离散的,避免模型里混入连续采样点。Zero-Order Hold 的 Sample time 同样填 1。Quantizer 的 Quantization interval 填 1,Sample time 填 -1,继承上游速率。
这三个模块串起来之后,链路上传递的还是一个标量序列,只是数值已经被约束到整数格点上。想验证采样保持确实起作用,把 Constant 换成 Signal Generator,波形选锯齿波,频率 0.2Hz,示波器上看前两级:ZOH 之前是斜线,之后是台阶。
3.2 编码与并串转换:Integer to Bit Converter 到 Buffer
Integer to Bit Converter 输出的是长度 8 的向量,但它默认按采样点逐个处理,一个采样点对应一个向量。Frame Status Conversion 的作用是把这 8 个并行比特声明成一帧,Sampling mode of output signal 选 Frame-based。这一步是很多人的盲区:不做帧声明,下游 Buffer 会按样本维度去缓冲,缓冲出来的东西根本不是你要的比特流。
Buffer 模块负责把帧内的比特按顺序推出去。报告里对模块 6 的描述是「串行化输出,Buffer 大小设置为 1」,但参数记录写的是 Output buffer size = 8,这两处是矛盾的。按串行化的语义,Output buffer size 应该填 1,表示每个采样时刻输出一个比特,Buffer overlap 填 0,Initial conditions 填 0。
注意:如果按记录里的 8 去填,Buffer 输出的仍然是一个 8 元素的并行帧,并没有串行化。收端 Buffer1 再设成 8,两级缓冲叠加,维度就会报错。这个坑在交报告前一定要自己跑一遍确认,别照抄参数表。
3.3 收端还原:Buffer1、Reshape 与 Bit to Integer Converter
收端第一步是把串行比特流重新攒成 8 位一组。Buffer1 的 Output buffer size 填 8,Buffer overlap 填 0,Initial conditions 填 0,输出是一个帧格式的 8 元素向量。这个向量要送进 Bit to Integer Converter,但后者期待的是数组格式而非帧格式,中间得插一个 Reshape,Output dimensionality 选 1-D array。
Bit to Integer Converter 的参数与编码端严格对称:Number of bits per integer 填 8,Input bit order 选 MSB first,After bit packing, treat resulting integer values as 选 Unsigned,Output data type 选 Inherit via internal rule。三处任一处与编码端不一致,还原出来的整数都是错的。
| 模块 | 关键参数 | 取值 | 作用 |
|---|---|---|---|
| Constant | Constant value / Sample time | 17.5 / 1 | 模拟直流输入源 |
| Zero-Order Hold | Sample time | 1 | 采样保持,平顶抽样 |
| Quantizer | Quantization interval / Sample time | 1 / -1 | 幅度离散化 |
| Integer to Bit Converter | Number of bits per integer / Treat input as / Output bit order | 8 / Unsigned / MSB first | 整数转并行比特 |
| Frame Status Conversion | Sampling mode of output signal | Frame-based | 声明并行比特为一帧 |
| Buffer | Output buffer size / Buffer overlap | 1 / 0 | 帧转串行比特流 |
| Buffer1 | Output buffer size / Buffer overlap | 8 / 0 | 串行比特流攒回并行帧 |
| Reshape | Output dimensionality | 1-D array | 帧格式转数组格式 |
| Bit to Integer Converter | Number of bits per integer / Input bit order | 8 / MSB first | 并行比特还原整数 |
3.4 用脚本重建模型,避免重复拖拽
模块多了以后,手工拖拽容易漏连、错连,改一版参数要重来一遍。可用 add_block 和 add_line 把建模过程写成脚本。不同 MATLAB 版本里通信模块的库路径会变,先按名字把路径搜出来再建模块:
% 库路径随版本变化,先按名字搜索,拿到路径再 add_block libs = {'simulink', 'simulink_extras', 'commlib', 'dsplib'}; names = {'Integer to Bit Converter', 'Frame Status Conversion', 'Reshape', 'Buffer'}; for i = 1:numel(libs) for j = 1:numel(names) p = find_system(libs{i}, 'SearchDepth', 4, 'Name', names{j}); if ~isempty(p) fprintf('%-28s -> %s\n', names{j}, p{1}); end end end拿到路径后,用 add_block 逐个实例化,参数通过名称-值对直接写死,比双击对话框填参数更不容易出错:
model = 'ad_da_demo'; if bdIsLoaded(model), close_system(model, 0); end new_system(model); open_system(model); % 前向通道:信号源 -> 采样保持 -> 量化 add_block('simulink/Sources/Constant', [model '/Const'], ... 'Value', '17.5', 'SampleTime', '1', 'Position', [30 100 70 130]); add_block('simulink/Discrete/Zero-Order Hold', [model '/ZOH'], ... 'SampleTime', '1', 'Position', [110 100 150 130]); add_block('simulink/Discrete/Quantizer', [model '/Quant'], ... 'QuantizationInterval', '1', 'SampleTime', '-1', ... 'Position', [190 100 230 130]); % 串联;discrete 模块的端口顺序是 1 进 1 出 add_line(model, 'Const/1', 'ZOH/1', 'autorouting', 'on'); add_line(model, 'ZOH/1', 'Quant/1', 'autorouting', 'on'); set_param(model, 'StopTime', '20', ... 'Solver', 'FixedStepDiscrete', 'FixedStep', '1');求解器必须选 FixedStepDiscrete,固定步长设为 1,与 ZOH 的采样周期一致。用变步长连续求解器去跑一个纯离散模型,会出现步长自动细分、离散模块在非采样点上被反复调用的问题,仿真变慢,波形也可能出现莫名其妙的抖动。
4. 参数怎么定:采样时间、Buffer 长度与帧格式的耦合
三个参数不是独立的:采样时间决定数据产生的节拍,量化间隔决定数值的粒度,Buffer 长度决定并串转换的切分点。改一个,另外两个的合理性都要重新评估。
4.1 采样时间与量化间隔是两回事
Sample time 管的是时间轴上的离散化,Quantization interval 管的是幅度轴上的离散化,两者都叫「离散」但作用维度完全不同。整条链路除 Zero-Order Hold 之外,其余离散模块的 Sample time 都建议填 -1,让 Simulink 自动继承上游速率,模型里只有 ZOH 一个速率源。
如果手贱把 Quantizer 的 Sample time 也写成固定值,比如 0.5,同时 ZOH 还是 1,Simulink 会插入 rate transition 模块做速率匹配,缓冲分组随之错位,Buffer 一次攒的 8 个比特可能跨越两个采样点。这类问题不会报错,只会让收端还原出的整数缓慢漂移,排查起来很费时间。
4.2 Buffer 的 size 与 overlap 怎么配
Buffer 模块的两个参数是一对:Output buffer size 决定每次输出多少个样本,Buffer overlap 决定相邻两帧之间重叠多少样本。overlap 必须小于 size,否则模型直接报错。串行化场景下,发送端用 size=1、overlap=0,接收端用 size=8、overlap=0。
| Output buffer size | Buffer overlap | 输出行为 | 适用位置 |
|---|---|---|---|
| 1 | 0 | 每个采样时刻推 1 个样本,等效串行 | 发送端,帧转串行 |
| 8 | 0 | 每 8 个样本凑成一帧整体输出 | 接收端,串行转帧 |
| 8 | 4 | 相邻帧重叠 4 个样本,输出量翻倍 | 滑窗类处理,本设计不用 |
| 8 | 8 | 非法,overlap 不得大于等于 size | 会直接报错 |
overlap 不为 0 时,同一批比特会被重复送出,收端还原出来的整数序列会出现额外样本,误以为是量化器出了问题。课程设计追求的是清晰的对应关系,overlap 一律取 0。
4.3 帧格式与 Reshape:维度对不上的典型报错
Buffer1 输出的是帧格式,Bit to Integer Converter 期待数组格式,中间的 Reshape 就是用来消解这个差异的。Reshape 的 Output dimensionality 选 1-D array,维度不写死时用 -1 让 Simulink 自动推断,写死成 [8] 也可以,前提是上游帧宽确实是 8。
想知道每一级端口上真正的维度,不要让仿真跑完再猜,先更新模型再读端口属性:
set_param(model, 'SimulationCommand', 'update'); % 只做编辑期更新,不跑仿真 blk = [model '/Buffer1']; ph = get_param(blk, 'PortHandles'); dims = get_param(ph.Outport(1), 'CompiledPortDimensions'); disp(dims); % 形如 [2 8],第一位是维度数,第二位是帧宽返回值的第一个元素是维度个数,第二个才是每一维的长度。看到 [2 8] 说明这一级输出的是长度 8 的向量,和 Bit to Integer Converter 的 nbits=8 匹配;看到 [2 1] 说明前面某个 Buffer 的 size 被写成了 1,比特没有攒成组,收端自然还原不出整数。
4.4 一条可复现的验证路径
每次改完参数,按 17.5 → 18 → 00010010 → 18 这条链手工核对一遍,比盯着示波器看波形高效:
x = 17.5; q = 1; nbits = 8; y = q * round(x / q); % 量化:17.5 -> 18 b = dec2bin(y, nbits); % 编码:00010010 bits = double(b - '0'); % 转成数值向量,模拟 Integer to Bit Converter 输出 bits = flip(bits); % 故意反转位序,模拟收端选错 bit order z = polyval(bits, 2); % 按二进制权值求和 -> 72 fprintf('%g -> %d -> %s -> 误还原 %d\n', x, y, b, z);polyval(bits, 2)这一行把比特向量当成二进制系数求和,等价于 bin2dec,但不需要再转字符串,方便在脚本里批量处理。跑出来是 72 而不是 18,就说明位序错位的代价有多大。把flip那一行去掉再跑一次,得到 18,两端参数就对齐了。
5. 导出仿真数据做误差统计,以及波形异常的排查顺序
看示波器能判断波形形状对不对,但判断不了量化误差的分布、有效位数这些量化指标。把数据捞到工作空间里算,才算是真的把仿真用起来了。
5.1 To Workspace 与量化误差后处理
在收端信号后面接一个 To Workspace 模块,Save format 选 Structure with time,Variable name 填 dout,Limit data points to last 取消勾选。仿真结束后在工作空间里直接做统计:
t = dout.time; % 时间向量 y = squeeze(dout.signals.values); % 还原序列,去掉多余维度 q = 1; % 与 Quantizer 的量化间隔保持一致 xq = q * round(y / q); % 反量化参考值 err = y - xq; % 量化误差 fprintf('最大误差 %.3f,均方根误差 %.4f\n', max(abs(err)), rms(err)); fprintf('实际量化台阶数 %d\n', numel(unique(xq)));squeeze用来消掉 To Workspace 保存时多出来的单维度,不写这一句,后面做减法会因为维度不一致报错。理论上最大误差不会超过 q/2 = 0.5,均方根误差应在 q/√12 ≈ 0.289 附近。如果实测的最大误差明显超过 0.5,说明链路上还有别的模块在动数值,比如收端的 Reshape 丢样本,或者 Buffer overlap 不为 0 导致重复计数。
有了均方根误差,还能反推有效位数:ENOB = (SINAD − 1.76) / 6.02。满量程正弦输入下,8 位理想的 ENOB 接近 8;实测掉到 6 以下,就要回头查量化间隔是不是被改过、编码位宽是不是和量化级数不匹配。
5.2 波形异常的排查顺序
| 症状 | 可能原因 | 定位手段 |
|---|---|---|
| 波形始终是直线 | 信号源是常数,采样保持看不出变化 | 换成 0.2Hz 正弦或锯齿波 |
| 台阶宽度远大于 1s | 求解器步长与 ZOH 采样周期不一致 | 求解器改 FixedStepDiscrete,步长设 1 |
| 收端整数缓慢漂移 | 链路上出现多速率,rate transition 打乱分组 | 除 ZOH 外,Sample time 一律填 -1 |
| 还原值是原值的位反转结果 | 两端 bit order 不一致 | 统一选 MSB first |
| 报维度不匹配 | Buffer overlap 大于等于 size,或 Reshape 维度写错 | 用 CompiledPortDimensions 逐级打印帧宽 |
| 负数或超大整数 | 编码端误选 Signed | Treat input values as 改回 Unsigned |
最后补一个容易被忽略的细节:仿真时间不要一上来就设几百秒。整条链路是纯离散的,固定步长 1s,跑到 20s 只有 20 个采样点,足够看清 17.5 到 18 的量化结果和 8 位比特流的并串转换过程。想观察量化误差随输入变化的规律,用 MATLAB 脚本扫一遍 0 到 255 的整数输入,比在 Simulink 里改一百次 Constant 快得多——毕竟量化器做的事只有一件:把输入舍入到最近的量化格点上。
本文还有配套的精品资源,点击获取