MATLAB QAM调制函数qammod详解:从原理到通信系统仿真实践
1. 从“符号”到“波形”:QAM调制到底在做什么?
如果你刚开始接触数字通信或者软件无线电,看到“QAM调制”这个词可能会觉得有点抽象。简单来说,它就是把一串0和1的数字信息,变成可以在空气中传播、在电缆里传输的模拟信号波形。你可以把它想象成一种“翻译”工作,把计算机能懂的语言(比特),翻译成物理世界能传输的语言(连续变化的电磁波)。而qammod这个函数,就是MATLAB里一个非常强大的“翻译官”。
为什么需要这个翻译官?因为我们的数字设备(手机、电脑)处理的是离散的比特,但无线信道、光纤、同轴电缆传输的是连续的模拟信号。直接发射一串0和1的电压跳变是不现实的,效率极低且抗干扰能力差。QAM(Quadrature Amplitude Modulation,正交幅度调制)就是一种高效的“翻译”方案。它巧妙地将信息同时加载到载波信号的幅度和相位上,或者说,加载在两个相互正交(相位差90度)的载波分量(I路和Q路)的幅度上。这样一来,一个符号(Symbol)就能携带多个比特的信息,极大地提升了频谱利用率。我们常说的16QAM、64QAM、256QAM,后面的数字就代表一个符号能携带的比特数(log2(M),M为调制阶数)。
在MATLAB中,qammod函数就是实现这一核心过程的工具。它接收你输入的数字比特序列,按照你指定的调制方式(比如16QAM),输出对应的复数符号。这个复数符号的实部(I)和虚部(Q),就对应了调制后信号在两个正交维度上的幅度值。后续只需要将这个复数序列乘以一个复载波(exp(1j2pifct)),就能得到最终的模拟信号(取实部)。对于通信系统仿真、算法验证、甚至一些软件无线电项目的基带处理部分,qammod都是你绕不开的一个核心函数。
2.qammod函数的核心参数与基础调用模式
要熟练使用一个工具,首先得了解它的“操作面板”。qammod函数的基本语法并不复杂,但其参数的选择直接决定了调制行为的细节。我们先从最基础的调用开始。
最基本的调用形式是:y = qammod(x, M)。这里,x是输入的整数或二进制数据,M是调制阶数,必须是2的幂次方(如4, 16, 64, 256)。y是输出的复数调制符号。
2.1 输入数据x的两种形态:整数映射与二进制输入
这是第一个容易混淆的点。x可以有两种形式:
整数输入(默认):此时
x中的每个元素应该是范围在[0, M-1]之间的整数。函数会直接将这个整数映射到对应的星座点上。例如,对于16QAM(M=16),输入x = [0, 3, 15, 10],函数会分别将这些整数映射到16个星座点中的第0、3、15、10个点。M = 16; dataInt = randi([0 M-1], 100, 1); % 生成100个0到15的随机整数 modSym_int = qammod(dataInt, M); scatterplot(modSym_int); % 绘制星座图 title('基于整数输入的16QAM星座图');这种模式非常直观,适合当你已经将比特信息编码成了符号索引的场景。
二进制输入:需要通过设置
'InputType'参数为'bit'来启用。此时,x应该是一个二进制序列(0和1)。函数会按log2(M)位一组进行分组,并将每组二进制数转换为对应的整数,再进行映射。这里有个关键细节:分组的顺序。MATLAB默认采用最高有效位优先(Most Significant Bit First)的方式。也就是说,对于一组二进制位[b1, b2, b3, b4](对应16QAM),b1是最高位(MSB),b4是最低位(LSB)。转换的整数是b1*2^3 + b2*2^2 + b3*2^1 + b4*2^0。M = 16; numBitsPerSym = log2(M); % 4 dataBits = randi([0 1], 400, 1); % 生成400个随机比特,足够100个符号 modSym_bit = qammod(dataBits, M, 'InputType', 'bit'); scatterplot(modSym_bit); title('基于二进制输入的16QAM星座图');这种模式更贴近实际通信系统,因为信源产生的通常是比特流。务必注意比特分组顺序,如果接收端解调时顺序不一致,会导致完全错误的解码。
2.2 调制阶数M与星座图
M定义了星座点的数量,也决定了频谱效率和抗噪能力的权衡。M越大,一个符号能携带的比特数越多(频谱效率高),但星座点之间的距离越近,在噪声干扰下更容易发生判决错误(抗噪能力差)。qammod支持方型QAM(如4, 16, 64, 256)和十字型QAM(如32, 128)。
- 方型QAM:星座点呈规则网格状分布,能量分布均匀,是最常用的形式。
qammod默认采用格雷码(Gray Coding)映射。格雷码的特点是相邻星座点对应的二进制序列只有一位不同。这样,在噪声导致符号判决跳到相邻点时,只会产生1个比特的错误,从而降低误比特率(BER)。这是qammod函数一个非常重要的内置优化。 - 十字型QAM:当
M不是2的偶次幂时(如32,128),函数会自动采用十字型星座图。这种星座图的平均能量比方型QAM稍低,但映射关系更复杂。
你可以通过scatterplot函数直观地查看调制后的星座图,这是调试和验证调制是否正确的最快方法。
2.3 平均符号能量归一化:'UnitAveragePower'参数
这是另一个至关重要的参数,直接影响仿真结果的真实性。调制符号的功率(能量)需要被规范化,以便进行公平的信噪比(SNR)比较和性能评估。
默认情况下,qammod输出的星座点坐标是整数集(例如16QAM的I/Q坐标可能为 ±1, ±3)。这样,星座图的平均功率(所有星座点能量除以M)不是1。在计算信噪比时,我们需要明确信号功率是多少。
'UnitAveragePower'参数就是用来控制这个的:
'UnitAveragePower', false(默认):不进行单位平均功率归一化。星座点坐标为整数集。此时星座图的平均功率P_avg = mean(abs(constellation).^2)是一个具体值(对于方型M-QAM,P_avg = 2*(M-1)/3)。'UnitAveragePower', true:将星座图进行缩放,使其平均功率为1(即E[|symbol|^2] = 1)。这是强烈推荐的设置,尤其是在进行BER vs. SNR性能仿真时。它保证了信号功率为1,那么加入的噪声功率N0就直接对应了信噪比SNR = 1/N0(在复数基带等效模型中),计算非常清晰。
M = 16; data = randi([0 M-1], 1000, 1); % 默认情况,功率不为1 y_default = qammod(data, M); avg_power_default = mean(abs(y_default).^2); fprintf('默认设置下的平均功率: %.4f (理论值: %.4f)\n', avg_power_default, 2*(M-1)/3); % 启用单位平均功率归一化 y_normalized = qammod(data, M, 'UnitAveragePower', true); avg_power_normalized = mean(abs(y_normalized).^2); fprintf('归一化后的平均功率: %.4f\n', avg_power_normalized);在绝大多数系统仿真中,你都应该设置'UnitAveragePower', true。
3. 深入星座映射:自定义与格雷码验证
虽然qammod默认的格雷码映射已经足够优秀且通用,但有时你可能需要验证其映射关系,或者在特殊研究场景下使用自定义的星座图。qammod函数也提供了相应的接口。
3.1 查看默认的星座图与映射表
在进行任何复杂操作前,先搞清楚默认情况是什么。我们可以通过调制一小段有序数据来“窥探”内部的映射关系。
M = 16; % 生成所有可能的符号索引 allSymbolIndices = (0:M-1)'; % 用整数模式调制 constellation = qammod(allSymbolIndices, M); % 绘制星座图 figure; scatterplot(constellation); title('16QAM默认星座图'); grid on; % 为了查看比特映射,我们需要用二进制模式,并输入所有可能的二进制组合 numBits = log2(M); % 4 % 生成所有4位二进制组合(MSB first) allBitCombinations = de2bi(0:M-1, numBits, 'left-msb'); % 'left-msb' 表示最左列为MSB % 转换为列向量,并按行展开成二进制流(因为qammod的bit输入要求列向量) bitStream = reshape(allBitCombinations.', [], 1); % 调制 constellation_bits = qammod(bitStream, M, 'InputType', 'bit'); % 由于我们是一个接一个调制了所有符号,得到的星座点顺序应该与allSymbolIndices一致 % 验证映射 disp('符号索引 | 二进制(MSB->LSB) | 星座点坐标(I+jQ)'); for i = 1:M idx = i-1; bits = allBitCombinations(i, :); point = constellation(i); fprintf('%4d | [%d%d%d%d] | %+.3f %+.3fj\n', ... idx, bits(1), bits(2), bits(3), bits(4), real(point), imag(point)); end运行这段代码,你可以清晰地看到从符号索引(或比特组合)到复平面坐标的完整映射表。观察相邻星座点(比如坐标(1,1)和(1,3))对应的二进制序列,你会发现它们通常只相差1个比特,这就是格雷码的优势。
3.2 使用自定义星座图:'CustomSymbolMapping'参数
在某些学术研究或非标准通信系统中,你可能需要使用非标准的星座形状或映射规则。qammod允许你通过'CustomSymbolMapping'参数完全自定义映射。
这个参数需要一个长度为M的向量,向量中的每个元素是你自定义的星座点对应的符号索引(0到M-1)。函数会根据你提供的这个映射向量,将输入数据x(整数模式)映射到你定义的星座点上。注意:自定义映射通常需要配合自定义的解调函数(如qamdemod的'CustomSymbolMapping'参数)一起使用。
假设我们想定义一个奇怪的8点星座(M=8),并且自己指定映射:
M = 8; % 第一步:定义8个星座点的坐标(复数) my_constellation = [1+1j, 1-1j, -1+1j, -1-1j, 3+0j, 0+3j, -3+0j, 0-3j]; % 第二步:定义符号索引到这些点的映射。 % 假设我们想让输入整数 0->第一个点(1+1j), 1->第二个点(1-1j), ... 7->第八个点(0-3j) % 那么映射向量就是 [0, 1, 2, 3, 4, 5, 6, 7] (看起来是自然的,但这里我们故意打乱一下) my_mapping = [0, 2, 4, 6, 1, 3, 5, 7]; % 一个打乱的映射 % 生成测试数据 data = (0:M-1)'; % 使用自定义映射进行调制 y_custom = qammod(data, M, 'CustomSymbolMapping', my_mapping); % 注意:qammod此时输出的复数点,其坐标是由内部计算决定的,我们需要根据映射向量和自定义星座坐标来手动赋值。 % 实际上,qammod的‘CustomSymbolMapping’参数并不直接接受星座坐标,而是接受符号索引的排列顺序。 % 更准确地说,它定义了输入整数k应该对应到“标准星座图”中的第my_mapping(k+1)个点。 % 因此,要完全使用自定义星座,流程更复杂,通常需要: % 1. 生成一个查找表(LUT):LUT = my_constellation(my_mapping+1); % 2. 直接使用查表法进行调制:y = LUT(data+1); % 所以,对于高度自定义的需求,直接使用查表法可能比强行套用qammod更清晰。 % 更实用的例子:使用qammod生成标准星座,但改变比特到符号的映射(非格雷码)。 % 例如,使用自然二进制映射。 natural_binary_mapping = 0:M-1; % 对于M=8,这就是[0,1,2,3,4,5,6,7] % 但qammod默认可能已经是某种优化映射。要强制使用自然映射,可以: % 先获取默认星座点 default_const = qammod(0:M-1, M); % 如果我们希望输入整数k直接映射到按自然顺序排列的星座点, % 而默认星座点坐标是固定的,所以‘CustomSymbolMapping’实际上是在重排这些固定坐标。 % 设置自定义映射为自然顺序 y_natural_map = qammod(data, M, 'CustomSymbolMapping', natural_binary_mapping); % 绘制对比 figure; subplot(1,2,1); scatterplot(qammod(data, M)); % 默认格雷映射 title('默认格雷映射星座图'); subplot(1,2,2); scatterplot(y_natural_map); title('自定义自然映射星座图'); % 你会发现星座点坐标没变,但点的颜色(对应输入整数)顺序变了。这会影响BER性能。这个例子说明了'CustomSymbolMapping'主要用于重排标准星座点与输入索引之间的对应关系,而不是定义全新的星座坐标。对于全新的星座形状,建议自行实现调制函数或使用更底层的工具。
4. 完整通信链路仿真:从比特到BER曲线
理论学习终究要服务于实践。最能体现qammod价值的场景,莫过于构建一个完整的基带通信系统仿真链路,并绘制出核心的性能指标——误比特率(BER)随信噪比(SNR)变化的曲线。这个过程会让你深刻理解每个参数的意义。
4.1 仿真链路搭建
我们以经典的加性高斯白噪声(AWGN)信道为例,搭建一个包含随机比特生成、QAM调制、上采样(可选)、过信道(加噪声)、匹配滤波(下采样)、解调、比特判决和BER计算的完整流程。
%% 参数设置 clear; close all; clc; M = 16; % 调制阶数 k = log2(M); % 每个符号的比特数 numSymbols = 10000; % 要传输的符号数(大一些,统计才准) numBits = numSymbols * k; % 总比特数 snr_dB = 0:2:20; % 信噪比范围,单位dB ber_sim = zeros(size(snr_dB)); % 存储仿真得到的BER %% 主仿真循环:遍历每个SNR值 for idx = 1:length(snr_dB) % 1. 生成随机二进制数据流 txBits = randi([0 1], numBits, 1); % 2. QAM调制(使用单位平均功率归一化!) txSym = qammod(txBits, M, 'InputType', 'bit', 'UnitAveragePower', true); % 3. 通过AWGN信道 % 注意:awgn函数默认假设输入信号功率为1(这正是我们设置'UnitAveragePower', true的原因) rxSym = awgn(txSym, snr_dB(idx), 'measured'); % 4. QAM解调 rxBits = qamdemod(rxSym, M, 'OutputType', 'bit', 'UnitAveragePower', true); % 5. 计算误比特数(BER) [numErrors, ber_sim(idx)] = biterr(txBits, rxBits); end %% 绘制BER曲线 figure; semilogy(snr_dB, ber_sim, 'bo-', 'LineWidth', 1.5, 'MarkerFaceColor', 'b'); grid on; xlabel('信噪比 (Eb/N0, dB)'); ylabel('误比特率 (BER)'); title(['AWGN信道下 ', num2str(M), 'QAM 性能仿真']); hold on; %% 绘制理论BER曲线作为对比(可选) % 对于AWGN信道下的矩形QAM,有近似理论公式。这里使用通信工具箱的函数。 if license('test', 'Communication_Toolbox') ber_theory = berawgn(snr_dB, 'qam', M); semilogy(snr_dB, ber_theory, 'r--', 'LineWidth', 1.5); legend('仿真结果', '理论值', 'Location', 'best'); else legend('仿真结果', 'Location', 'best'); end这段代码是一个标准的模板。有几点需要特别强调:
'UnitAveragePower', true:在调制和解调时必须同时设置,且与awgn函数的‘measured’模式配合,才能保证SNR定义的准确性。'InputType'/'OutputType':调制用'bit',解调也用'bit',保持一致性。- SNR的定义:在数字通信用
Eb/N0(每比特能量与噪声功率谱密度之比)更常见。但awgn函数默认的snr参数是信号功率与噪声功率之比(SNR)。对于功率归一化的信号,SNR与Eb/N0的换算关系是:Eb/N0 (dB) = SNR (dB) - 10*log10(k),其中k=log2(M)。上面的代码中snr_dB实际上是Eb/N0。如果你想直接使用awgn的SNR模式,需要先计算信号功率。
4.2 包含脉冲成型与匹配滤波的仿真
真实的通信系统不会直接发射符号脉冲,那样会产生巨大的带外频谱泄漏。我们需要对符号序列进行脉冲成型(通常使用根升余弦滤波器),在接收端进行匹配滤波。这个过程会引入码间串扰(ISI)和噪声的相关性,是更贴近实际的仿真。
%% 参数设置(新增滤波器参数) M = 16; k = log2(M); numSymbols = 5000; sps = 8; % 每个符号的采样点数(过采样率) rolloff = 0.5; % 根升余弦滤波器的滚降因子 span = 10; % 滤波器的符号跨度 snr_dB = 0:2:16; % 设计根升余弦发射滤波器 rctFilt = comm.RaisedCosineTransmitFilter(... 'Shape', 'Square root', ... 'RolloffFactor', rolloff, ... 'FilterSpanInSymbols', span, ... 'OutputSamplesPerSymbol', sps); % 设计对应的根升余弦接收滤波器(匹配滤波器) rcrFilt = comm.RaisedCosineReceiveFilter(... 'Shape', 'Square root', ... 'RolloffFactor', rolloff, ... 'FilterSpanInSymbols', span, ... 'InputSamplesPerSymbol', sps, ... 'DecimationFactor', sps); % 关键:接收滤波器完成匹配滤波并下采样回符号速率 ber_sim = zeros(size(snr_dB)); for idx = 1:length(snr_dB) % 1. 生成比特并调制 txBits = randi([0 1], numSymbols*k, 1); txSym = qammod(txBits, M, 'InputType', 'bit', 'UnitAveragePower', true); % 2. 脉冲成型(上采样+滤波) txWaveform = rctFilt(txSym); % 3. 过AWGN信道。注意噪声功率是针对波形采样点的。 rxWaveform = awgn(txWaveform, snr_dB(idx), 'measured'); % 4. 匹配滤波(滤波+下采样回符号速率) rxFiltSym = rcrFilt(rxWaveform); % 匹配滤波器会引入群延迟,需要丢弃头尾的瞬态样本 delay = span; % 通常延迟约为滤波器跨度 rxSym = rxFiltSym(delay+1:end-delay); % 5. 解调 rxBits = qamdemod(rxSym, M, 'OutputType', 'bit', 'UnitAveragePower', true); % 6. 计算BER,注意比特数对齐(丢弃延迟对应的比特) bitsToCompare = numSymbols * k; [~, ber_sim(idx)] = biterr(txBits(1:bitsToCompare), rxBits(1:bitsToCompare)); end % 绘图...这个仿真更复杂,但更真实。关键点在于:
- 过采样:
sps参数将每个符号用多个采样点表示,以便进行模拟波形的滤波和处理。 - 滤波器延迟:脉冲成型和匹配滤波会引入固定的延迟,必须在数据处理时将其剔除,否则发送和接收序列会对不齐,导致全部误码。
- 噪声添加时机:噪声是在连续时间波形(
txWaveform)上添加的,这模拟了信号在模拟信道中受到干扰的过程。
5. 常见“坑点”与性能优化实践
在实际使用qammod和进行通信仿真时,有一些细节问题如果不注意,很容易导致仿真结果错误或性能异常。下面是我在项目中多次踩坑后总结出的几点关键经验。
5.1 归一化功率不匹配:BER曲线“平移”的元凶
这是新手最容易出错的地方,也是导致BER曲线与理论值对不上的最常见原因。症状是:仿真得到的BER曲线形状看起来是对的,但整体在SNR轴上向左或向右平移了几个dB。
根因分析:这几乎总是因为发射端(调制)和接收端(解调)的功率归一化假设不一致,或者与信道噪声添加时的功率假设不匹配。
排查与解决:
- 黄金法则:在整个仿真链路中,坚持使用
'UnitAveragePower', true。确保qammod和qamdemod都设置此参数。 - 检查
awgn函数:awgn函数默认假设输入信号功率为1。如果你使用了'UnitAveragePower', true,那么信号功率就是1,直接使用awgn(x, snr)即可。如果你没有归一化,信号功率为P,那么你应该使用awgn(x, snr, 'measured'),让MATLAB先测量信号功率P,再添加噪声功率N = P / (10^(snr/10))。 - Eb/N0与SNR的换算:在学术论文和标准中,BER曲线通常横坐标是
Eb/N0。awgn函数的snr参数是信号功率与噪声功率之比SNR。对于M-QAM,换算关系为:Eb/N0 (dB) = SNR (dB) - 10*log10(log2(M))如果你的仿真横坐标想用Eb/N0,那么调用awgn时传入的snr值应该是:snr_for_awgn = EbN0_dB + 10*log10(log2(M))很多仿真代码错误地将Eb/N0值直接赋给awgn,导致曲线严重左移。
5.2 比特顺序混淆:解调出一堆乱码
当你使用'InputType', 'bit'时,一切正常。但当你尝试用qamdemod解调,并将结果与原始比特比较时,发现BER高达0.5左右(相当于随机猜),这很可能是比特顺序问题。
根因分析:qammod在比特模式下,默认按**最高有效位优先(MSB-first)**对输入比特流进行分组和映射。而你在生成测试比特流或处理外部数据时,可能采用了不同的顺序(如LSB-first)。此外,qamdemod的'OutputType', 'bit'也采用相同的MSB-first约定输出比特。如果收发两端约定不一致,就会完全错乱。
解决方案:
- 内部仿真:保持一致性。全程使用MATLAB的默认MSB-first约定。使用
de2bi和bi2de函数进行数模转换时,注意'left-msb'参数。 - 与外部设备交互:如果数据来自其他硬件(如FPGA、USRP),务必查明该设备使用的比特顺序。如果它是LSB-first,你需要在调制前或解调后使用
flip函数对每组比特进行翻转。
最稳妥的方式是在系统设计文档中明确约定比特顺序,并在代码中添加清晰的注释。% 假设外部设备是LSB-first,而MATLAB用MSB-first M = 64; bitsPerSym = log2(M); txBits_external = ... % 来自外部设备的LSB-first比特流 % 方法1:调制前翻转每组比特 txBits_reshaped = reshape(txBits_external, bitsPerSym, [])'; % 每行一个符号的比特 txBits_for_matlab = reshape(fliplr(txBits_reshaped)', [], 1); % 翻转每行后重排回列向量 txSym = qammod(txBits_for_matlab, M, 'InputType', 'bit'); % 方法2:解调后翻转(如果接收外部设备的信号) rxSym = ... % 接收到的符号 rxBits_matlab = qamdemod(rxSym, M, 'OutputType', 'bit'); rxBits_reshaped = reshape(rxBits_matlab, bitsPerSym, [])'; rxBits_for_external = reshape(fliplr(rxBits_reshaped)', [], 1); % 翻转后给外部设备
5.3 复数基带与通带仿真的区别
qammod输出的是复数符号a + bj,这代表的是复基带信号。a是同相分量(I),b是正交分量(Q)。在仿真中,我们通常直接在这个复基带上加噪声、滤波,因为这样计算效率高。
但在某些需要模拟真实射频过程的仿真中(比如想观察频谱搬移),你需要将基带信号上变频到通带(载波频率fc)。这通过将复基带信号乘以一个复载波实现:
fs = 100e3; % 采样率 fc = 20e3; % 载波频率 t = (0:length(txSym)-1)' / fs; txPassband = real(txSym .* exp(1j*2*pi*fc*t)); % 取实部得到实通带信号关键点:此时加入的噪声应该是实噪声,并且噪声带宽和功率需要根据通带信号重新计算。通带仿真计算量远大于基带仿真。对于大多数系统性能分析(如BER),基带等效模型完全足够且是标准做法。除非你特别关心射频非线性、镜像抑制等问题,否则无需进行通带仿真。
5.4 高阶QAM(如1024QAM)的精度与实现问题
MATLAB的通信工具箱理论上支持很高的调制阶数。但当你使用非常高阶的QAM(如1024QAM、4096QAM)时,可能会遇到两个问题:
- 星座点坐标的数值精度:高阶QAM的星座点坐标值(如±1, ±3, ±5, ... ±31 for 1024QAM)范围很大。虽然
qammod会处理,但在进行归一化('UnitAveragePower', true)后,这些值会变得非常密集。在加噪声后,解调时的量化判决需要很高的数值精度。虽然双精度浮点数通常够用,但在定点DSP或FPGA实现时,必须仔细设计定点位数,防止动态范围不足或量化噪声过大。 - 性能仿真所需的符号数:高阶QAM在低误码率下(如BER<1e-5)需要仿真非常多的符号才能得到统计上可靠的结果。例如,要验证10^-6的BER,理论上需要至少10^7个符号,对于1024QAM就是近千万个符号,仿真时间会很长。此时通常需要结合理论公式和外推算法,或者使用更高效的蒙特卡洛方法。
一个实用的建议是,在仿真高阶QAM时,可以先从高信噪比开始跑,因为高信噪比下误码少,达到统计稳定所需的符号数相对少一些。或者,直接使用berawgn等理论函数计算理论值作为参考,仿真主要用于验证趋势和关键点。
qammod函数是连接数字世界与模拟世界的桥梁,理解它的每一个参数,意味着你理解了QAM调制的核心。从简单的参数设置到完整的系统仿真,再到避坑指南,我希望这些从实际项目中沉淀下来的经验,能让你在使用这个强大工具时更加得心应手。记住,通信仿真的一半功力在于对模型细节的把握,而qammod正是这些细节的起点。当你下次再看到星座图上那些散落的点时,你看到的将不再仅仅是复平面上的位置,而是承载着信息、与噪声抗争、在信道中穿梭的每一个数据灵魂。
