ANDROID APP的页面布局(Part II)Tab Layout

在前一篇文章( ANDROID APP的页面布局(Part I))中讲到的页面布局有Linear Layout, Relative Layout ,Table Layout,这篇文章讲讲Tab Layout的使用,在早期的Andorid版本,有TabHost来实现,但是在后期的api当中这个类已经建议不在使用,建议使用fragments来使用,fragments的使用非常广泛,用来做tab是非常方便的,同时也可以用在很多种页面切换的场景或者用来切换页面里某一个部分切换的场景,比如在界面中的一个区域用来滑动显示几个图片之类的,界面非常人性化。 下面我们来使用fragments和viewpager结合完成一个Tab的效果
ViewPager 和 Fragments
ViewPager和Fragments是两个不同的组件,可以到官网查阅一下这连个组件到底是个什么东西: http://developer.android.com/guide/components/fragments.html http://developer.android.com/reference/android/support/v4/view/ViewPager.html Tab Layout的原型效果 网络上找了一个标签页效果如下,其中Tab部分是Action Bar的一部分,ViewPager作为页面的主体,滑动的时候切换的页面是一个Fragments.那么整个过程包括一个主体的Activity和三个Fragments以及一个Adapter用来控制ViewPager. image 创建项目 根据上面的原型图,我们需要创建的文件包括: 1.一个主体的Activity,在新建项目的时候就会创建了MainActivty.java,使用这个就可以了;稍微做点调整就可以了,把父类改一下继承自 FragmentActivity ,并且实现接口ActionBar.TabListener如下
public class MainActivity extends FragmentActivity implements
        ActionBar.TabListener {
2.布局文件activity_main.xml也修改一下,使用ViewPager作为整个布局如下

                
根据第一段代码,优化第二段代码,解决“开始TDM系统仿真… 信号数量: 3, 总时隙数: 50, 策略: priority, 编码: PCM, 调制: 16QAM 使用外部提供的原始信号… 编码信号 (PCM)… 信号1: 编码后长度=8000, 填充位=0 信号2: 编码后长度=8000, 填充位=0 信号3: 编码后长度=8000, 填充位=0 调制信号… 分配时隙… 时隙分配结果: 信号 1 (正弦波 (50Hz), 无调制): 17 个时隙 (优先级: 0.33, 调制: BPSK) 信号 2 (方波 (50Hz), 无调制): 17 个时隙 (优先级: 0.33, 调制: QPSK) 信号 3 (随机噪声, 无调制): 16 个时隙 (优先级: 0.33, 调制: 16QAM) TDM复用 (包含帧头)… 仿真过程中发生错误: 要串联的数组的维度不一致。”的问题。第一段代码:classdef TDMApp8888 < matlab.apps.AppBase properties (Access = public) UIFigure matlab.ui.Figure % 左侧控制面板 ControlPanel matlab.ui.container.Panel SignalConfigPanel matlab.ui.container.Panel NumSignalsEditField matlab.ui.control.NumericEditField NumSignalsLabel matlab.ui.control.Label SignalTypeDropDown matlab.ui.control.DropDown SignalTypeLabel matlab.ui.control.Label FrequencyEditField matlab.ui.control.NumericEditField FrequencyLabel matlab.ui.control.Label AddSignalButton matlab.ui.control.Button RemoveSignalButton matlab.ui.control.Button SignalsListBox matlab.ui.control.ListBox SignalsListLabel matlab.ui.control.Label % 新增载波频率控件 CarrierFrequencyLabel matlab.ui.control.Label CarrierFrequencyEditField matlab.ui.control.NumericEditField StrategyPanel matlab.ui.container.Panel FixedRadioButton matlab.ui.control.RadioButton PriorityRadioButton matlab.ui.control.RadioButton StrategyButtonGroup matlab.ui.container.ButtonGroup TotalSlotsEditField matlab.ui.control.NumericEditField TotalSlotsLabel matlab.ui.control.Label % 编码配置控件 EncodingPanel matlab.ui.container.Panel EncodingTypeDropDown matlab.ui.control.DropDown EncodingTypeLabel matlab.ui.control.Label QuantBitsEditField matlab.ui.control.NumericEditField QuantBitsLabel matlab.ui.control.Label StepSizeEditField matlab.ui.control.NumericEditField StepSizeLabel matlab.ui.control.Label % 调制配置控件 ModulationPanel matlab.ui.container.Panel ModulationTypeDropDown matlab.ui.control.DropDown ModulationTypeLabel matlab.ui.control.Label % 帧配置控件 FrameConfigPanel matlab.ui.container.Panel FrameHeaderEditField matlab.ui.control.EditField FrameHeaderLabel matlab.ui.control.Label CRCCheckBox matlab.ui.control.CheckBox % 右侧控制面板 SimulationPanel matlab.ui.container.Panel RunSimulationButton matlab.ui.control.Button RunFeedbackButton matlab.ui.control.Button RunAnalysisButton matlab.ui.control.Button SNREditField matlab.ui.control.NumericEditField SNRLabel matlab.ui.control.Label IterationsEditField matlab.ui.control.NumericEditField IterationsLabel matlab.ui.control.Label DurationEditField matlab.ui.control.NumericEditField DurationLabel matlab.ui.control.Label SamplingRateEditField matlab.ui.control.NumericEditField SamplingRateLabel matlab.ui.control.Label StatusLabel matlab.ui.control.Label ProgressBar matlab.ui.control.Lamp % 结果显示区域 ResultsTabGroup matlab.ui.container.TabGroup SignalsTab matlab.ui.container.Tab OriginalAxes matlab.ui.control.UIAxes TDMAxes matlab.ui.control.UIAxes DemuxAxes matlab.ui.control.UIAxes SyncedAxes matlab.ui.control.UIAxes PerformanceTab matlab.ui.container.Tab BERAxes matlab.ui.control.UIAxes ParametersTab matlab.ui.container.Tab ParametersTextArea matlab.ui.control.TextArea end properties (Access = private) signals % 存储信号配置的cell数组 params % 系统参数结构体 tdmSystem % TDMSystem实例 controller % TDMFeedbackController实例 analyzer % TDMPerformanceAnalyzer实例 end methods (Access = private) function updateParams(app) % 更新系统参数 app.params = struct(); % 确保所有UI组件都存在且有效 try app.params.fs = app.SamplingRateEditField.Value; app.params.duration = app.DurationEditField.Value; app.params.numSignals = numel(app.signals); app.params.snrDb = app.SNREditField.Value; app.params.iterations = app.IterationsEditField.Value; app.params.totalSlots = app.TotalSlotsEditField.Value; % 编码参数 if isvalid(app.EncodingTypeDropDown) app.params.encodingType = app.EncodingTypeDropDown.Value; else app.params.encodingType = 'PCM'; % 默认值 end app.params.quantBits = app.QuantBitsEditField.Value; app.params.stepSize = app.StepSizeEditField.Value; % 调制参数 if isvalid(app.ModulationTypeDropDown) app.params.modulationType = app.ModulationTypeDropDown.Value; else app.params.modulationType = 'BPSK'; % 默认值 end % 帧配置 if isvalid(app.FrameHeaderEditField) app.params.frameHeader = app.FrameHeaderEditField.Value; else app.params.frameHeader = 'A5A5'; % 默认值 end if isvalid(app.CRCCheckBox) app.params.useCRC = app.CRCCheckBox.Value; else app.params.useCRC = true; % 默认值 end % 时隙分配策略 if isvalid(app.FixedRadioButton) && isvalid(app.PriorityRadioButton) if app.FixedRadioButton.Value app.params.strategy = 'fixed'; else app.params.strategy = 'priority'; end else app.params.strategy = 'priority'; % 默认值 end % 设置信号调制方式和时钟漂移 app.params.signalModulations = cell(1, app.params.numSignals); app.params.clockDrift = zeros(1, app.params.numSignals); for i = 1:app.params.numSignals if i <= numel(app.signals) app.params.signalModulations{i} = app.signals{i}.modulation; else app.params.signalModulations{i} = 'BPSK'; % 默认值 end % 设置时钟漂移 (确保长度与信号数量匹配) if i == 1 app.params.clockDrift(i) = 0.001; elseif i == 2 app.params.clockDrift(i) = 0.002; else app.params.clockDrift(i) = -0.001; end end catch ME % 错误处理:记录错误并恢复默认值 disp('更新参数时出错:'); disp(ME.message); app.params = struct('fs', 1000, 'duration', 1, 'numSignals', 0); end end function generateSignals(app) % 生成信号数据 (使用linspace确保精确采样点) if isempty(app.params) || ~isfield(app.params, 'fs') || ~isfield(app.params, 'duration') return; % 参数无效,跳过 end t = linspace(0, app.params.duration, app.params.fs * app.params.duration); app.tdmSystem.originalSignals = zeros(app.params.numSignals, length(t)); app.tdmSystem.signalInfo = cell(app.params.numSignals, 1); for i = 1:app.params.numSignals if i <= numel(app.signals) sigConfig = app.signals{i}; else % 默认值包含载波频率 sigConfig = struct('type', '正弦波', 'frequency', 50, ... 'modulation', 'BPSK', 'carrierFrequency', 1000); end baseSignal = []; % 存储基带信号 switch sigConfig.type case '正弦波' freq = sigConfig.frequency; baseSignal = sin(2*pi*freq*t); baseInfo = sprintf('正弦波 (%dHz)', freq); case '方波' freq = sigConfig.frequency; baseSignal = square(2*pi*freq*t); baseInfo = sprintf('方波 (%dHz)', freq); case '随机噪声' baseSignal = randn(1, length(t)); baseInfo = '随机噪声'; case '锯齿波' freq = sigConfig.frequency; baseSignal = sawtooth(2*pi*freq*t); baseInfo = sprintf('锯齿波 (%dHz)', freq); case '脉冲信号' freq = sigConfig.frequency; duty = 0.3; % 占空比 pulseTrain = pulstran(t, 0:1/freq:app.params.duration, ... 'rectpuls', duty/freq); % 确保长度匹配 if length(pulseTrain) > length(t) baseSignal = pulseTrain(1:length(t)); else baseSignal = zeros(1, length(t)); baseSignal(1:length(pulseTrain)) = pulseTrain; end baseInfo = sprintf('脉冲信号 (%dHz)', freq); end % ========== 应用载波调制 ========== carrierFreq = sigConfig.carrierFrequency; switch sigConfig.modulation case 'AM' % 幅度调制: (1 + m(t)) * cos(2πf_c t) modulatedSignal = (1 + 0.5*baseSignal) .* cos(2*pi*carrierFreq*t); modInfo = sprintf('AM调制 (%dHz载波)', carrierFreq); case 'FM' % 频率调制: cos(2πf_c t + 2πΔf ∫m(t)dt) kf = 0.1; % 调制指数 integral_m = cumsum(baseSignal) / app.params.fs; modulatedSignal = cos(2*pi*carrierFreq*t + 2*pi*kf*integral_m); modInfo = sprintf('FM调制 (%dHz载波)', carrierFreq); case 'PM' % 相位调制: cos(2πf_c t + k_p m(t)) kp = 1; % 相位偏移常数 modulatedSignal = cos(2*pi*carrierFreq*t + kp*baseSignal); modInfo = sprintf('PM调制 (%dHz载波)', carrierFreq); otherwise % 无调制或未知调制类型 modulatedSignal = baseSignal; modInfo = '无调制'; end app.tdmSystem.originalSignals(i, :) = modulatedSignal; app.tdmSystem.signalInfo{i} = [baseInfo ', ' modInfo]; end % 设置编码参数 if isfield(app.params, 'encodingType') && isfield(app.params, 'quantBits') && isfield(app.params, 'stepSize') app.tdmSystem.encodingParams = struct(... 'type', app.params.encodingType, ... 'quantBits', app.params.quantBits, ... 'stepSize', app.params.stepSize); else app.tdmSystem.encodingParams = struct('type', 'PCM', 'quantBits', 8, 'stepSize', 0.05); % 默认值 end % 设置调制参数 if isfield(app.params, 'modulationType') && isfield(app.params, 'signalModulations') app.tdmSystem.modulationParams = struct(... 'type', app.params.modulationType, ... 'signalModulations', {app.params.signalModulations}); else app.tdmSystem.modulationParams = struct('type', 'BPSK', 'signalModulations', {{'BPSK', 'BPSK'}}); % 默认值 end % 设置帧配置 if isfield(app.params, 'frameHeader') && isfield(app.params, 'useCRC') app.tdmSystem.frameConfig = struct(... 'header', app.params.frameHeader, ... 'useCRC', app.params.useCRC); else app.tdmSystem.frameConfig = struct('header', 'A5A5', 'useCRC', true); % 默认值 end end function updateParametersDisplay(app) % 更新参数显示 try paramText = sprintf('系统参数:\n'); if isfield(app.params, 'fs') paramText = [paramText sprintf('采样频率: %d Hz\n', app.params.fs)]; end if isfield(app.params, 'duration') paramText = [paramText sprintf('信号持续时间: %.2f 秒\n', app.params.duration)]; end if isfield(app.params, 'numSignals') paramText = [paramText sprintf('信号源数量: %d\n', app.params.numSignals)]; end if isfield(app.params, 'snrDb') paramText = [paramText sprintf('信噪比: %d dB\n', app.params.snrDb)]; end if isfield(app.params, 'strategy') paramText = [paramText sprintf('时隙分配策略: %s\n', app.params.strategy)]; end if isfield(app.params, 'totalSlots') paramText = [paramText sprintf('总时隙数量: %d\n', app.params.totalSlots)]; end if isfield(app.params, 'iterations') paramText = [paramText sprintf('仿真迭代次数: %d\n', app.params.iterations)]; end % 编码参数 paramText = [paramText sprintf('\n编码配置:\n')]; if isfield(app.params, 'encodingType') paramText = [paramText sprintf(' 编码类型: %s\n', app.params.encodingType)]; end if isfield(app.params, 'quantBits') paramText = [paramText sprintf(' 量化位数: %d bits\n', app.params.quantBits)]; end if isfield(app.params, 'stepSize') paramText = [paramText sprintf(' 步长: %.4f\n', app.params.stepSize)]; end % 调制参数 paramText = [paramText sprintf('\n调制配置:\n')]; if isfield(app.params, 'modulationType') paramText = [paramText sprintf(' 系统调制类型: %s\n', app.params.modulationType)]; end % 帧配置 paramText = [paramText sprintf('\n帧配置:\n')]; if isfield(app.params, 'frameHeader') paramText = [paramText sprintf(' 帧头: %s\n', app.params.frameHeader)]; end if isfield(app.params, 'useCRC') paramText = [paramText sprintf(' CRC校验: %s\n', ifelse(app.params.useCRC, '启用', '禁用'))]; end % 信号信息 - 包含载波频率 paramText = [paramText sprintf('\n信号配置:\n')]; for i = 1:min(app.params.numSignals, numel(app.signals)) sig = app.signals{i}; paramText = [paramText sprintf('信号 %d: %s (基带频率: %d Hz, 调制: %s, 载波频率: %d Hz)\n', ... i, sig.type, sig.frequency, sig.modulation, sig.carrierFrequency)]; end app.ParametersTextArea.Value = paramText; catch ME disp('更新参数显示时出错:'); disp(ME.message); app.ParametersTextArea.Value = '系统参数显示错误'; end end function plotSignals(app) % 绘制原始信号 try t = linspace(0, app.params.duration, app.params.fs * app.params.duration); cla(app.OriginalAxes); hold(app.OriginalAxes, 'on'); colors = lines(app.params.numSignals); for i = 1:min(app.params.numSignals, size(app.tdmSystem.originalSignals, 1)) plot(app.OriginalAxes, t, app.tdmSystem.originalSignals(i, 1:length(t)), ... 'Color', colors(i, :), 'DisplayName', app.tdmSystem.signalInfo{i}); end hold(app.OriginalAxes, 'off'); legend(app.OriginalAxes, 'Location', 'best'); title(app.OriginalAxes, '原始信号'); xlabel(app.OriginalAxes, '时间 (s)'); ylabel(app.OriginalAxes, '幅度'); grid(app.OriginalAxes, 'on'); % 绘制TDM信号(支持复数信号) cla(app.TDMAxes); if ~isempty(app.tdmSystem.tdmSignal) && isfield(app.params, 'duration') t_tdm = linspace(0, app.params.duration, length(app.tdmSystem.tdmSignal)); plot(app.TDMAxes, t_tdm, real(app.tdmSystem.tdmSignal)); hold(app.TDMAxes, 'on'); plot(app.TDMAxes, t_tdm, imag(app.tdmSystem.tdmSignal)); hold(app.TDMAxes, 'off'); legend(app.TDMAxes, {'实部', '虚部'}, 'Location', 'best'); end title(app.TDMAxes, ['TDM复用信号 (' app.params.strategy '策略)']); xlabel(app.TDMAxes, '时间 (s)'); ylabel(app.TDMAxes, '幅度'); grid(app.TDMAxes, 'on'); % 绘制解复用信号 cla(app.DemuxAxes); hold(app.DemuxAxes, 'on'); for i = 1:min(app.params.numSignals, size(app.tdmSystem.demuxSignals, 1)) if size(app.tdmSystem.demuxSignals, 2) >= length(t) plot(app.DemuxAxes, t,app.tdmSystem.demuxSignals(i, 1:length(t)), ... 'Color', colors(i, :), 'DisplayName', ['信号 ' num2str(i)]); end end hold(app.DemuxAxes, 'off'); legend(app.DemuxAxes, 'Location', 'best'); title(app.DemuxAxes, '解复用信号'); xlabel(app.DemuxAxes, '时间 (s)'); ylabel(app.DemuxAxes, '幅度'); grid(app.DemuxAxes, 'on'); % 绘制同步后信号 cla(app.SyncedAxes); hold(app.SyncedAxes, 'on'); for i = 1:min(app.params.numSignals, size(app.tdmSystem.syncedSignals, 1)) if size(app.tdmSystem.syncedSignals, 2) >= length(t) plot(app.SyncedAxes, t, app.tdmSystem.syncedSignals(i, 1:length(t)), ... 'Color', colors(i, :), 'DisplayName', app.tdmSystem.signalInfo{i}); end end hold(app.SyncedAxes, 'off'); legend(app.SyncedAxes, 'Location', 'best'); title(app.SyncedAxes, '同步后信号'); xlabel(app.SyncedAxes, '时间 (s)'); ylabel(app.SyncedAxes, '幅度'); grid(app.SyncedAxes, 'on'); % 绘制误码率和信噪比 cla(app.BERAxes); if ~isempty(app.tdmSystem.performance) && isfield(app.tdmSystem.performance, 'ber') && isfield(app.tdmSystem.performance, 'snr') yyaxis(app.BERAxes, 'left'); bar(app.BERAxes, 1:app.params.numSignals, app.tdmSystem.performance.ber, 0.6, 'FaceColor', [0.2 0.6 0.8]); ylabel(app.BERAxes, '误码率 (BER)'); yyaxis(app.BERAxes, 'right'); plot(app.BERAxes, 1:app.params.numSignals, app.tdmSystem.performance.snr, 'o-', 'LineWidth', 2, 'MarkerSize', 8); ylabel(app.BERAxes, 'SNR (dB)'); title(app.BERAxes, '信号质量分析'); xlabel(app.BERAxes, '信号编号'); grid(app.BERAxes, 'on'); xticks(app.BERAxes, 1:app.params.numSignals); legend(app.BERAxes, {'BER', 'SNR'}, 'Location', 'best'); end catch ME disp('绘图时出错:'); disp(ME.message); end end end % Callbacks that handle component events methods (Access = private) % Code that executes after component creation function startupFcn(app) % 初始化参数 app.signals = {}; app.params = struct(); app.StatusLabel.Text = '准备就绪'; app.ProgressBar.Color = 'green'; % 设置默认参数 app.SamplingRateEditField.Value = 1000; app.DurationEditField.Value = 1; app.SNREditField.Value = 20; app.IterationsEditField.Value = 5; app.TotalSlotsEditField.Value = 50; app.NumSignalsEditField.Value = 0; app.PriorityRadioButton.Value = true; % 编码参数默认值 app.EncodingTypeDropDown.Value = 'PCM'; app.QuantBitsEditField.Value = 8; app.StepSizeEditField.Value = 0.05; % 调制参数默认值 app.ModulationTypeDropDown.Value = 'BPSK'; % 帧配置默认值 app.FrameHeaderEditField.Value = 'A5A5'; app.CRCCheckBox.Value = true; % 载波频率默认值 app.CarrierFrequencyEditField.Value = 1000; % 更新参数显示 updateParams(app); updateParametersDisplay(app); end % Button pushed function: AddSignalButton function AddSignalButtonPushed(app, ~) % 添加新信号(包含载波频率) try signalType = app.SignalTypeDropDown.Value; frequency = app.FrequencyEditField.Value; modulationType = app.ModulationTypeDropDown.Value; % 获取调制类型 carrierFrequency = app.CarrierFrequencyEditField.Value; % 获取载波频率 if frequency <= 0 uialert(app.UIFigure, '基带频率必须大于0', '无效参数'); return; end if carrierFrequency <= 0 uialert(app.UIFigure, '载波频率必须大于0', '无效参数'); return; end newSignal = struct(... 'type', signalType, ... 'frequency', frequency, ... 'modulation', modulationType, ... 'carrierFrequency', carrierFrequency); % 添加载波频率 app.signals{end+1} = newSignal; app.NumSignalsEditField.Value = numel(app.signals); % 更新信号列表(包含载波频率) signalList = cell(1, numel(app.signals)); for i = 1:numel(app.signals) sig = app.signals{i}; signalList{i} = sprintf('信号 %d: %s (%d Hz基带, %s调制, %d Hz载波)', ... i, sig.type, sig.frequency, sig.modulation, sig.carrierFrequency); end app.SignalsListBox.Items = signalList; % 更新参数 updateParams(app); updateParametersDisplay(app); app.StatusLabel.Text = sprintf('已添加信号: %s (%d Hz基带, %s调制, %d Hz载波)', ... signalType, frequency, modulationType, carrierFrequency); catch ME app.StatusLabel.Text = ['添加信号错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Button pushed function: RemoveSignalButton function RemoveSignalButtonPushed(app, ~) % 移除选中的信号(包含载波频率) try selectedIdx = app.SignalsListBox.Value; if isempty(selectedIdx) || selectedIdx > numel(app.signals) uialert(app.UIFigure, '请选择要删除的信号', '无选择'); return; end % 移除信号 removedSig = app.signals{selectedIdx}; app.signals(selectedIdx) = []; % 更新信号列表(包含载波频率) app.NumSignalsEditField.Value = numel(app.signals); signalList = cell(1, numel(app.signals)); for i = 1:numel(app.signals) sig = app.signals{i}; signalList{i} = sprintf('信号 %d: %s (%d Hz基带, %s调制, %d Hz载波)', ... i, sig.type, sig.frequency, sig.modulation, sig.carrierFrequency); end app.SignalsListBox.Items = signalList; % 如果没有信号,清除选择 if isempty(app.signals) app.SignalsListBox.Value = []; else app.SignalsListBox.Value = min(selectedIdx, numel(app.signals)); end % 更新参数 updateParams(app); updateParametersDisplay(app); app.StatusLabel.Text = sprintf('已移除信号: %s (%d Hz基带, %s调制, %d Hz载波)', ... removedSig.type, removedSig.frequency, removedSig.modulation, removedSig.carrierFrequency); catch ME app.StatusLabel.Text = ['移除信号错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: NumSignalsEditField function NumSignalsEditFieldValueChanged(app, ~) % 信号数量变化时更新 try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新信号数量错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: SamplingRateEditField function SamplingRateEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新采样率错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: DurationEditField function DurationEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新持续时间错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: SNREditField function SNREditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新信噪比错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: IterationsEditField function IterationsEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新迭代次数错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Value changed function: TotalSlotsEditField function TotalSlotsEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新总时隙数错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Selection changed function: StrategyButtonGroup function StrategyButtonGroupSelectionChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新策略错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % 编码参数变化回调 function EncodingTypeDropDownValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新编码类型错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end function QuantBitsEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新量化位数错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end function StepSizeEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新步长错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % 调制参数变化回调 function ModulationTypeDropDownValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新调制方式错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end function FrameHeaderEditFieldValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新帧头错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end function CRCCheckBoxValueChanged(app, ~) try updateParams(app); updateParametersDisplay(app); catch ME app.StatusLabel.Text = ['更新CRC设置错误: ' ME.message]; app.ProgressBar.Color = 'red'; end end % Button pushed function: RunSimulationButton function RunSimulationButtonPushed(app, ~) % 运行基础仿真 app.StatusLabel.Text = '运行基础仿真...'; app.ProgressBar.Color = 'yellow'; drawnow; try % 更新参数 updateParams(app); % 检查信号配置 if app.params.numSignals == 0 uialert(app.UIFigure, '请至少添加一个信号', '无信号'); app.ProgressBar.Color = 'red'; return; end % 创建TDM系统 app.tdmSystem = TDMSystem(app.params); % 生成信号 generateSignals(app); % 运行仿真 app.tdmSystem = app.tdmSystem.runSimulation(); % 显示结果 plotSignals(app); updateParametersDisplay(app); app.StatusLabel.Text = '基础仿真完成!'; app.ProgressBar.Color = 'green'; catch ME app.StatusLabel.Text = ['基础仿真错误: ' ME.message]; app.ProgressBar.Color = 'red'; uialert(app.UIFigure, ME.message, '仿真错误'); end end % Button pushed function: RunFeedbackButton function RunFeedbackButtonPushed(app, ~) % 运行反馈控制仿真 app.StatusLabel.Text = '运行反馈控制仿真...'; app.ProgressBar.Color = 'yellow'; drawnow; try % 更新参数 updateParams(app); % 检查信号配置 if app.params.numSignals == 0 uialert(app.UIFigure, '请至少添加一个信号', '无信号'); app.ProgressBar.Color = 'red'; return; end % 创建反馈控制器 app.controller = TDMFeedbackController(app.params); % 生成信号 generateSignals(app); % 将生成的信号复制到控制器 app.controller.originalSignals = app.tdmSystem.originalSignals; app.controller.signalInfo = app.tdmSystem.signalInfo; app.controller.encodingParams = app.tdmSystem.encodingParams; app.controller.modulationParams = app.tdmSystem.modulationParams; app.controller.frameConfig = app.tdmSystem.frameConfig; % 运行反馈仿真 app.controller = app.controller.runSimulation(); % 显示结果 app.tdmSystem = app.controller; % 用于显示基本结果 plotSignals(app); updateParametersDisplay(app); app.StatusLabel.Text = '反馈控制仿真完成!'; app.ProgressBar.Color = 'green'; catch ME app.StatusLabel.Text = ['反馈控制错误: ' ME.message]; app.ProgressBar.Color = 'red'; uialert(app.UIFigure, ME.message, '仿真错误'); end end % Button pushed function: RunAnalysisButton function RunAnalysisButtonPushed(app, ~) % 运行性能分析 app.StatusLabel.Text = '运行性能分析...'; app.ProgressBar.Color = 'yellow'; drawnow; try % 更新参数 updateParams(app); % 检查信号配置 if app.params.numSignals == 0 uialert(app.UIFigure, '请至少添加一个信号', '无信号'); app.ProgressBar.Color = 'red'; return; end % 创建性能分析器 app.analyzer = TDMPerformanceAnalyzer(app.params); % 生成信号 generateSignals(app); % 将生成的信号复制到分析器 app.analyzer.originalSignals = app.tdmSystem.originalSignals; app.analyzer.signalInfo = app.tdmSystem.signalInfo; app.analyzer.encodingParams = app.tdmSystem.encodingParams; app.analyzer.modulationParams = app.tdmSystem.modulationParams; app.analyzer.frameConfig = app.tdmSystem.frameConfig; % 运行性能分析 app.analyzer = app.analyzer.runPerformanceAnalysis(); % 显示结果 app.tdmSystem = app.analyzer; % 用于显示基本结果 plotSignals(app); updateParametersDisplay(app); app.StatusLabel.Text = '性能分析完成!'; app.ProgressBar.Color = 'green'; catch ME app.StatusLabel.Text = ['性能分析错误: ' ME.message]; app.ProgressBar.Color = 'red'; uialert(app.UIFigure, ME.message, '仿真错误'); end end end % App initialization and construction methods (Access = private) % Create UIFigure and components function createComponents(app) % 创建主窗口 app.UIFigure = uifigure; app.UIFigure.Position = [100 100 1400 900]; app.UIFigure.Name = 'TDM通信系统仿真'; app.UIFigure.Scrollable = 'on'; % 创建控制面板容器 app.ControlPanel = uipanel(app.UIFigure); app.ControlPanel.Title = '控制面板'; app.ControlPanel.Position = [20 20 450 850]; % ==== 信号配置面板 ==== app.SignalConfigPanel = uipanel(app.ControlPanel); app.SignalConfigPanel.Title = '信号配置'; app.SignalConfigPanel.Position = [20 585 410 240]; % 增加高度以容纳载波频率控件 % 信号数量控件 app.NumSignalsLabel = uilabel(app.SignalConfigPanel); app.NumSignalsLabel.Position = [20 190 80 22]; app.NumSignalsLabel.Text = '信号数量:'; app.NumSignalsEditField = uieditfield(app.SignalConfigPanel, 'numeric'); app.NumSignalsEditField.Position = [110 190 60 22]; app.NumSignalsEditField.Value = 0; app.NumSignalsEditField.ValueChangedFcn = createCallbackFcn(app, @NumSignalsEditFieldValueChanged, true); % 信号类型控件 app.SignalTypeLabel = uilabel(app.SignalConfigPanel); app.SignalTypeLabel.Position = [20 160 80 22]; app.SignalTypeLabel.Text = '信号类型:'; app.SignalTypeDropDown = uidropdown(app.SignalConfigPanel); app.SignalTypeDropDown.Position = [110 160 80 22]; app.SignalTypeDropDown.Items = {'正弦波', '方波', '随机噪声', '锯齿波', '脉冲信号'}; % 基带频率控件 app.FrequencyLabel = uilabel(app.SignalConfigPanel); app.FrequencyLabel.Position = [20 130 80 22]; app.FrequencyLabel.Text = '基带频率 (Hz):'; app.FrequencyEditField = uieditfield(app.SignalConfigPanel, 'numeric'); app.FrequencyEditField.Position = [110 130 60 22]; app.FrequencyEditField.Value = 50; % 载波频率控件 app.CarrierFrequencyLabel = uilabel(app.SignalConfigPanel); app.CarrierFrequencyLabel.Position = [210 130 100 22]; app.CarrierFrequencyLabel.Text = '载波频率 (Hz):'; app.CarrierFrequencyEditField = uieditfield(app.SignalConfigPanel, 'numeric'); app.CarrierFrequencyEditField.Position = [300 130 60 22]; app.CarrierFrequencyEditField.Value = 1000; % 默认值 % 添加/移除按钮 app.AddSignalButton = uibutton(app.SignalConfigPanel, 'push'); app.AddSignalButton.ButtonPushedFcn = createCallbackFcn(app, @AddSignalButtonPushed, true); app.AddSignalButton.Position = [210 160 80 22]; app.AddSignalButton.Text = '添加信号'; app.RemoveSignalButton = uibutton(app.SignalConfigPanel, 'push'); app.RemoveSignalButton.ButtonPushedFcn = createCallbackFcn(app, @RemoveSignalButtonPushed, true); app.RemoveSignalButton.Position = [300 160 80 22]; app.RemoveSignalButton.Text = '移除信号'; % 信号列表 app.SignalsListLabel = uilabel(app.SignalConfigPanel); app.SignalsListLabel.Position = [20 100 80 22]; app.SignalsListLabel.Text = '信号列表:'; app.SignalsListBox = uilistbox(app.SignalConfigPanel); app.SignalsListBox.Position = [20 5 370 90]; app.SignalsListBox.Items = {}; % ==== 策略配置面板 ==== app.StrategyPanel = uipanel(app.ControlPanel); app.StrategyPanel.Title = '时隙分配策略'; app.StrategyPanel.Position = [20 425 410 150]; % 策略按钮组 app.StrategyButtonGroup = uibuttongroup(app.StrategyPanel); app.StrategyButtonGroup.SelectionChangedFcn = createCallbackFcn(app, @StrategyButtonGroupSelectionChanged, true); app.StrategyButtonGroup.Position = [20 40 200 80]; app.StrategyButtonGroup.Title = '选择策略'; % 单选按钮 app.FixedRadioButton = uiradiobutton(app.StrategyButtonGroup); app.FixedRadioButton.Text = '固定分配'; app.FixedRadioButton.Position = [11 35 75 22]; app.PriorityRadioButton = uiradiobutton(app.StrategyButtonGroup); app.PriorityRadioButton.Text = '优先级分配'; app.PriorityRadioButton.Position = [11 10 100 22]; app.PriorityRadioButton.Value = true; % 总时隙数 app.TotalSlotsLabel = uilabel(app.StrategyPanel); app.TotalSlotsLabel.Position = [240 30 80 22]; app.TotalSlotsLabel.Text = '总时隙数:'; app.TotalSlotsEditField = uieditfield(app.StrategyPanel, 'numeric'); app.TotalSlotsEditField.ValueChangedFcn = createCallbackFcn(app, @TotalSlotsEditFieldValueChanged, true); app.TotalSlotsEditField.Position = [300 30 60 22]; app.TotalSlotsEditField.Value = 50; % ==== 编码配置面板 ==== app.EncodingPanel = uipanel(app.ControlPanel); app.EncodingPanel.Title = '编码配置'; app.EncodingPanel.Position = [20 300 410 120]; app.EncodingTypeLabel = uilabel(app.EncodingPanel); app.EncodingTypeLabel.Position = [20 75 80 22]; app.EncodingTypeLabel.Text = '编码类型:'; app.EncodingTypeDropDown = uidropdown(app.EncodingPanel); app.EncodingTypeDropDown.Position = [110 75 100 22]; app.EncodingTypeDropDown.Items = {'PCM', 'DPCM', 'DM'}; app.EncodingTypeDropDown.Value = 'PCM'; app.EncodingTypeDropDown.ValueChangedFcn = createCallbackFcn(app, @EncodingTypeDropDownValueChanged, true); app.QuantBitsLabel = uilabel(app.EncodingPanel); app.QuantBitsLabel.Position = [20 45 80 22]; app.QuantBitsLabel.Text = '量化位数:'; app.QuantBitsEditField = uieditfield(app.EncodingPanel, 'numeric'); app.QuantBitsEditField.Position = [110 45 60 22]; app.QuantBitsEditField.Value = 8; app.QuantBitsEditField.ValueChangedFcn = createCallbackFcn(app, @QuantBitsEditFieldValueChanged, true); app.StepSizeLabel = uilabel(app.EncodingPanel); app.StepSizeLabel.Position = [20 15 80 22]; app.StepSizeLabel.Text = '步长:'; app.StepSizeEditField = uieditfield(app.EncodingPanel, 'numeric'); app.StepSizeEditField.Position = [110 15 60 22]; app.StepSizeEditField.Value = 0.05; app.StepSizeEditField.ValueChangedFcn = createCallbackFcn(app, @StepSizeEditFieldValueChanged, true); % ==== 调制配置面板 ==== app.ModulationPanel = uipanel(app.ControlPanel); app.ModulationPanel.Title = '调制配置'; app.ModulationPanel.Position = [20 225 410 70]; app.ModulationTypeLabel = uilabel(app.ModulationPanel); app.ModulationTypeLabel.Position = [20 15 80 22]; app.ModulationTypeLabel.Text = '调制方式:'; app.ModulationTypeDropDown = uidropdown(app.ModulationPanel); app.ModulationTypeDropDown.Position = [110 15 100 22]; app.ModulationTypeDropDown.Items = {'None', 'BPSK', 'QPSK', '16QAM'}; app.ModulationTypeDropDown.Value = 'BPSK'; app.ModulationTypeDropDown.ValueChangedFcn = createCallbackFcn(app, @ModulationTypeDropDownValueChanged, true); % ==== 帧配置面板 ==== app.FrameConfigPanel = uipanel(app.ControlPanel); app.FrameConfigPanel.Title = '帧配置'; app.FrameConfigPanel.Position = [20 150 410 70]; app.FrameHeaderLabel = uilabel(app.FrameConfigPanel); app.FrameHeaderLabel.Position = [20 15 80 22]; app.FrameHeaderLabel.Text = '帧头:'; app.FrameHeaderEditField = uieditfield(app.FrameConfigPanel, 'text'); app.FrameHeaderEditField.Position = [110 15 100 22]; app.FrameHeaderEditField.Value = 'A5A5'; app.FrameHeaderEditField.ValueChangedFcn = createCallbackFcn(app, @FrameHeaderEditFieldValueChanged, true); app.CRCCheckBox = uicheckbox(app.FrameConfigPanel); app.CRCCheckBox.Text = '启用CRC校验'; app.CRCCheckBox.Position = [220 15 120 22]; app.CRCCheckBox.Value = true; app.CRCCheckBox.ValueChangedFcn = createCallbackFcn(app, @CRCCheckBoxValueChanged, true); % ==== 仿真控制面板 ==== app.SimulationPanel = uipanel(app.ControlPanel); app.SimulationPanel.Title = '仿真控制'; app.SimulationPanel.Position = [20 5 410 140]; % 参数输入 app.SamplingRateLabel = uilabel(app.SimulationPanel); app.SamplingRateLabel.Position = [20 95 80 22]; app.SamplingRateLabel.Text = '采样率 (Hz):'; app.SamplingRateEditField = uieditfield(app.SimulationPanel, 'numeric'); app.SamplingRateEditField.ValueChangedFcn = createCallbackFcn(app, @SamplingRateEditFieldValueChanged, true); app.SamplingRateEditField.Position = [110 95 60 22]; app.SamplingRateEditField.Value = 1000; app.DurationLabel = uilabel(app.SimulationPanel); app.DurationLabel.Position = [20 65 80 22]; app.DurationLabel.Text = '持续时间 (s):'; app.DurationEditField = uieditfield(app.SimulationPanel, 'numeric'); app.DurationEditField.ValueChangedFcn = createCallbackFcn(app, @DurationEditFieldValueChanged, true); app.DurationEditField.Position = [110 65 60 22]; app.DurationEditField.Value = 1; app.SNRLabel = uilabel(app.SimulationPanel); app.SNRLabel.Position = [20 35 80 22]; app.SNRLabel.Text = '信噪比 (dB):'; app.SNREditField = uieditfield(app.SimulationPanel, 'numeric'); app.SNREditField.ValueChangedFcn = createCallbackFcn(app, @SNREditFieldValueChanged, true); app.SNREditField.Position = [110 35 60 22]; app.SNREditField.Value = 20; app.IterationsLabel = uilabel(app.SimulationPanel); app.IterationsLabel.Position = [200 95 80 22]; app.IterationsLabel.Text = '迭代次数:'; app.IterationsEditField = uieditfield(app.SimulationPanel, 'numeric'); app.IterationsEditField.ValueChangedFcn = createCallbackFcn(app, @IterationsEditFieldValueChanged, true); app.IterationsEditField.Position = [290 95 60 22]; app.IterationsEditField.Value = 5; % 状态显示 app.StatusLabel = uilabel(app.SimulationPanel); app.StatusLabel.HorizontalAlignment = 'center'; app.StatusLabel.Position = [20 5 100 22]; app.StatusLabel.Text = '准备就绪'; app.ProgressBar = uilamp(app.SimulationPanel); app.ProgressBar.Position = [150 5 110 20]; app.ProgressBar.Color = [0.47 0.67 0.19]; % 仿真按钮 app.RunSimulationButton = uibutton(app.SimulationPanel, 'push'); app.RunSimulationButton.ButtonPushedFcn = createCallbackFcn(app, @RunSimulationButtonPushed, true); app.RunSimulationButton.Position = [200 20 80 30]; app.RunSimulationButton.Text = '基础仿真'; app.RunFeedbackButton = uibutton(app.SimulationPanel, 'push'); app.RunFeedbackButton.ButtonPushedFcn = createCallbackFcn(app, @RunFeedbackButtonPushed, true); app.RunFeedbackButton.Position = [290 20 80 30]; app.RunFeedbackButton.Text = '反馈控制'; app.RunAnalysisButton = uibutton(app.SimulationPanel, 'push'); app.RunAnalysisButton.ButtonPushedFcn = createCallbackFcn(app, @RunAnalysisButtonPushed, true); app.RunAnalysisButton.Position = [200 60 170 30]; app.RunAnalysisButton.Text = '性能分析'; % ==== 结果显示区域 ==== app.ResultsTabGroup = uitabgroup(app.UIFigure); app.ResultsTabGroup.Position = [480 20 900 850]; % 信号可视化标签页 app.SignalsTab = uitab(app.ResultsTabGroup); app.SignalsTab.Title = '信号可视化'; % 创建2x2网格布局 grid = uigridlayout(app.SignalsTab); grid.RowHeight = {'1x', '1x'}; grid.ColumnWidth = {'1x', '1x'}; grid.Padding = [10 10 10 10]; grid.RowSpacing = 10; grid.ColumnSpacing = 10; % 原始信号坐标轴 app.OriginalAxes = uiaxes(grid); title(app.OriginalAxes, '原始信号'); xlabel(app.OriginalAxes, '时间 (s)'); ylabel(app.OriginalAxes, '幅度'); app.OriginalAxes.Layout.Row = 1; app.OriginalAxes.Layout.Column = 1; % TDM信号坐标轴 app.TDMAxes = uiaxes(grid); title(app.TDMAxes, 'TDM复用信号'); xlabel(app.TDMAxes, '时间 (s)'); ylabel(app.TDMAxes, '幅度'); app.TDMAxes.Layout.Row = 1; app.TDMAxes.Layout.Column = 2; % 解复用信号坐标轴 app.DemuxAxes = uiaxes(grid); title(app.DemuxAxes, '解复用信号'); xlabel(app.DemuxAxes, '时间 (s)'); ylabel(app.DemuxAxes, '幅度'); app.DemuxAxes.Layout.Row = 2; app.DemuxAxes.Layout.Column = 1; % 同步后信号坐标轴 app.SyncedAxes = uiaxes(grid); title(app.SyncedAxes, '同步后信号'); xlabel(app.SyncedAxes, '时间 (s)'); ylabel(app.SyncedAxes, '幅度'); app.SyncedAxes.Layout.Row = 2; app.SyncedAxes.Layout.Column = 2; % 性能分析标签页 app.PerformanceTab = uitab(app.ResultsTabGroup); app.PerformanceTab.Title = '性能分析'; % BER坐标轴 app.BERAxes = uiaxes(app.PerformanceTab); title(app.BERAxes, '信号质量分析'); xlabel(app.BERAxes, '信号编号'); app.BERAxes.Position = [100 200 700 500]; yyaxis(app.BERAxes, 'left'); ylabel(app.BERAxes, 'BER'); yyaxis(app.BERAxes, 'right'); ylabel(app.BERAxes, 'SNR (dB)'); % 系统参数标签页 app.ParametersTab = uitab(app.ResultsTabGroup); app.ParametersTab.Title = '系统参数'; % 参数文本区域 app.ParametersTextArea = uitextarea(app.ParametersTab); app.ParametersTextArea.Position = [20 20 860 800]; app.ParametersTextArea.Value = {'系统参数将在此显示'}; end end methods (Access = public) % Construct app function app = TDMApp8888 % Create and configure components createComponents(app) % Register the app with App Designer registerApp(app, app.UIFigure) % Execute the startup function runStartupFcn(app, @startupFcn) if nargout == 0 clear app end end % Code that executes before app deletion function delete(app) % Delete UIFigure when app is deleted delete(app.UIFigure) end end end % 辅助函数 function out = ifelse(condition, true_val, false_val) if condition out = true_val; else out = false_val; end end 第二段代码:classdef TDMSystem properties params % 系统参数 originalSignals % 原始未编码信号 encodedSignals % 编码后信号(二进制位流) modulatedSignals % 调制后信号 demodulatedSignals % 解调后信号(二进制位流) decodedSignals % 解码后信号 tdmSignal % TDM复用信号 receivedSignal % 接收信号 demuxSignals % 解复用信号 syncedSignals % 同步后信号 signalInfo % 信号信息 allocatedSlots % 分配的时隙 slotAssignment % 时隙分配矩阵 performance % 性能指标 encodingParams % 编码参数 modulationParams % 调制参数 frameConfig % 帧配置 quantizationError % 量化误差 frameHeaders % 帧头位置 crcErrors % CRC错误统计 constellationPoints % 星座图数据 paddingInfo % 填充信息(记录填充位数) debugInfo % 调试信息 end methods function obj = TDMSystem(params) % 参数验证和默认值设置 if ~isstruct(params) error('输入参数必须是结构体'); end % 必需参数检查 requiredFields = {'fs', 'duration', 'numSignals', 'snrDb', ... 'totalSlots', 'strategy', 'signalModulations'}; for i = 1:length(requiredFields) if ~isfield(params, requiredFields{i}) error('缺少必需的参数字段: %s', requiredFields{i}); end end % 设置默认值 if ~isfield(params, 'clockDrift') params.clockDrift = zeros(1, params.numSignals); end if ~isfield(params, 'priorities') params.priorities = ones(1, params.numSignals); end if ~isfield(params, 'frameHeader') params.frameHeader = 'A5A5'; end if ~isfield(params, 'useCRC') params.useCRC = true; end if ~isfield(params, 'encodingType') params.encodingType = 'PCM'; end if ~isfield(params, 'quantBits') && strcmp(params.encodingType, 'PCM') params.quantBits = 8; end if ~isfield(params, 'stepSize') && contains(params.encodingType, {'DPCM', 'DM'}) params.stepSize = 0.05; end % 归一化优先级权重 params.priorities = params.priorities / sum(params.priorities); obj.params = params; % 计算关键参数 totalSamples = params.fs * params.duration; obj.params.slotSamples = floor(totalSamples / params.totalSlots); % 初始化性能指标 obj.performance = struct(); obj.quantizationError = zeros(params.numSignals, 1); obj.crcErrors = struct('total', 0, 'perSignal', zeros(1, params.numSignals)); obj.paddingInfo = struct('encoded', zeros(1, params.numSignals)); obj.debugInfo = struct('signalLengths', []); % 初始化编码参数 obj.encodingParams = struct(... 'type', params.encodingType, ... 'quantBits', ifelse(isfield(params, 'quantBits'), params.quantBits, 8), ... 'step', ifelse(isfield(params, 'stepSize'), params.stepSize, 0.05)); % 初始化调制参数 obj.modulationParams = struct(... 'type', ifelse(isfield(params, 'modulationType'), params.modulationType, 'BPSK'), ... 'signalModulations', {params.signalModulations}); % 初始化帧配置 obj.frameConfig = struct(... 'header', params.frameHeader, ... 'useCRC', params.useCRC); end function obj = runSimulation(obj) fprintf('开始TDM系统仿真...\n'); fprintf('信号数量: %d, 总时隙数: %d, 策略: %s, 编码: %s, 调制: %s\n', ... obj.params.numSignals, obj.params.totalSlots, obj.params.strategy, ... obj.encodingParams.type, obj.modulationParams.type); try % 检查原始信号是否提供 if isempty(obj.originalSignals) || all(cellfun(@isempty, obj.signalInfo)) error('原始信号未提供'); else fprintf('使用外部提供的原始信号...\n'); end % 记录原始信号长度 obj.debugInfo.signalLengths = size(obj.originalSignals, 2); % 处理流程 fprintf('编码信号 (%s)...\n', obj.encodingParams.type); obj = obj.encodeSignals(); fprintf('调制信号...\n'); obj = obj.modulateSignals(); fprintf('分配时隙...\n'); obj = obj.allocateSlots(); fprintf('TDM复用 (包含帧头)...\n'); obj = obj.multiplex(); fprintf('信道传输 (SNR: %.1f dB)...\n', obj.params.snrDb); obj = obj.transmit(); fprintf('帧同步...\n'); obj = obj.frameSynchronization(); fprintf('解复用...\n'); obj = obj.demultiplex(); fprintf('解调信号...\n'); obj = obj.demodulateSignals(); fprintf('解码信号 (%s)...\n', obj.encodingParams.type); obj = obj.decodeSignals(); fprintf('信号同步...\n'); obj = obj.synchronize(); fprintf('性能评估...\n'); obj = obj.evaluatePerformance(); fprintf('仿真成功完成!\n'); catch ME fprintf('仿真过程中发生错误: %s\n', ME.message); rethrow(ME); end end function obj = encodeSignals(obj) obj.encodedSignals = cell(1, obj.params.numSignals); for i = 1:obj.params.numSignals signal = obj.originalSignals(i, :); switch obj.encodingParams.type case 'PCM' [quantized, quantErr] = obj.pcm_encode(signal, obj.encodingParams.quantBits); [binStream, ~] = obj.int_to_bin(quantized, obj.encodingParams.quantBits); obj.quantizationError(i) = mean(quantErr.^2); case 'DPCM' [quantized, quantErr] = obj.dpcm_encode(signal, obj.encodingParams.quantBits, obj.encodingParams.step); [binStream, ~] = obj.int_to_bin(quantized, obj.encodingParams.quantBits); obj.quantizationError(i) = mean(quantErr.^2); case 'DM' binStream = obj.dm_encode(signal, obj.encodingParams.step); otherwise error('未知编码类型: %s', obj.encodingParams.type); end % 根据调制方式预填充位流 modType = obj.modulationParams.signalModulations{i}; bitsPerSymbol = obj.getBitsPerSymbol(modType); % 计算填充位数 paddingBits = mod(length(binStream), bitsPerSymbol); if paddingBits > 0 padding = bitsPerSymbol - paddingBits; binStream = [binStream, false(1, padding)]; % 填充0 else padding = 0; end obj.paddingInfo.encoded(i) = padding; % 统一转换为逻辑数组 if ~islogical(binStream) binStream = logical(binStream); end obj.encodedSignals{i} = binStream; fprintf('信号%d: 编码后长度=%d, 填充位=%d\n', i, length(binStream), padding); end end function [binStream, padding] = int_to_bin(~, values, numBits) % 将整数数组转换为二进制位流 numValues = numel(values); totalBits = numValues * numBits; binStream = false(1, totalBits); for i = 1:numValues val = uint32(values(i)); startIdx = (i-1)*numBits + 1; for bitPos = 1:numBits bit = bitget(val, bitPos, 'uint32'); binStream(startIdx + numBits - bitPos) = (bit == 1); end end padding = 0; end function values = bin_to_int(~, binStream, numBits) % 将二进制位流转换回整数数组 numValues = floor(length(binStream) / numBits); padding = length(binStream) - numValues * numBits; if padding > 0 binStream = binStream(1:end-padding); end values = zeros(1, numValues); for i = 1:numValues startIdx = (i-1)*numBits + 1; endIdx = startIdx + numBits - 1; segment = binStream(startIdx:endIdx); % 将二进制段转换为整数 val = 0; for j = 1:numBits val = val * 2 + segment(j); end values(i) = val; end end function obj = decodeSignals(obj) totalSamples = size(obj.demodulatedSignals, 2); obj.decodedSignals = zeros(obj.params.numSignals, totalSamples); for i = 1:obj.params.numSignals binStream = obj.demodulatedSignals(i, :); switch obj.encodingParams.type case 'PCM' quantized = obj.bin_to_int(binStream, obj.encodingParams.quantBits); decoded = obj.pcm_decode(quantized, obj.encodingParams.quantBits); obj.decodedSignals(i, 1:length(decoded)) = decoded; case 'DPCM' quantized = obj.bin_to_int(binStream, obj.encodingParams.quantBits); decoded = obj.dpcm_decode(quantized, obj.encodingParams.quantBits, obj.encodingParams.step); obj.decodedSignals(i, 1:length(decoded)) = decoded; case 'DM' decoded = obj.dm_decode(binStream, obj.encodingParams.step); obj.decodedSignals(i, 1:length(decoded)) = decoded; orig = obj.originalSignals(i, 1:size(obj.decodedSignals, 2)); obj.quantizationError(i) = mean((orig - decoded).^2); end % 长度对齐 decodedLength = length(decoded); if decodedLength < totalSamples obj.decodedSignals(i, decodedLength+1:end) = 0; end end end function obj = modulateSignals(obj) obj.modulatedSignals = cell(obj.params.numSignals, 1); for i = 1:obj.params.numSignals binStream = obj.encodedSignals{i}; modulationType = obj.modulationParams.signalModulations{i}; switch modulationType case 'None' obj.modulatedSignals{i} = binStream; case 'BPSK' obj.modulatedSignals{i} = 2*double(binStream) - 1; case 'QPSK' % 重组为复数符号 symbols = reshape(binStream, 2, [])'; I = 2*double(symbols(:,1)) - 1; Q = 2*double(symbols(:,2)) - 1; obj.modulatedSignals{i} = (I + 1i*Q)/sqrt(2); case '16QAM' symbols = reshape(binStream, 4, [])'; I = 2*double(symbols(:,1)) + double(symbols(:,2)); Q = 2*double(symbols(:,3)) + double(symbols(:,4)); % 映射到星座点 I(I==0) = -3; I(I==1) = -1; I(I==2) = 1; I(I==3) = 3; Q(Q==0) = -3; Q(Q==1) = -1; Q(Q==2) = 1; Q(Q==3) = 3; obj.modulatedSignals{i} = (I + 1i*Q)/sqrt(10); otherwise error('未知的调制方式: %s', modulationType); end % 存储星座图数据 if any(strcmp(modulationType, {'BPSK', 'QPSK', '16QAM'})) points = obj.modulatedSignals{i}; obj.constellationPoints{i} = points(1:min(1000, end)); end end end function obj = demodulateSignals(obj) totalSamples = size(obj.demuxSignals{1}, 2); obj.demodulatedSignals = false(obj.params.numSignals, totalSamples); for i = 1:obj.params.numSignals modSignal = obj.demuxSignals{i}; modulationType = obj.modulationParams.signalModulations{i}; switch modulationType case 'None' bits = real(modSignal) > 0.5; case 'BPSK' bits = real(modSignal) > 0; case 'QPSK' I = real(modSignal) > 0; Q = imag(modSignal) > 0; bits = [I, Q]'; bits = bits(:)'; case '16QAM' symbols = modSignal * sqrt(10); I = real(symbols); Q = imag(symbols); % 解调I通道 bitsI1 = I > 0; bitsI2 = abs(I) > 2; % 解调Q通道 bitsQ1 = Q > 0; bitsQ2 = abs(Q) > 2; bits = [bitsI1, bitsI2, bitsQ1, bitsQ2]'; bits = bits(:)'; end % 移除填充位 padding = obj.paddingInfo.encoded(i); if padding > 0 bits = bits(1:end-padding); end % 确保输出长度匹配 endIdx = min(length(bits), totalSamples); obj.demodulatedSignals(i, 1:endIdx) = bits(1:endIdx); % 长度对齐 if endIdx < totalSamples obj.demodulatedSignals(i, endIdx+1:end) = false; end end end function obj = allocateSlots(obj) if obj.params.totalSlots < obj.params.numSignals error('总时隙数(%d)小于信号数(%d)', ... obj.params.totalSlots, obj.params.numSignals); end obj.allocatedSlots = zeros(1, obj.params.numSignals); obj.slotAssignment = zeros(obj.params.numSignals, obj.params.totalSlots); switch obj.params.strategy case 'fixed' baseSlots = floor(obj.params.totalSlots / obj.params.numSignals); obj.allocatedSlots = baseSlots * ones(1, obj.params.numSignals); remainder = obj.params.totalSlots - sum(obj.allocatedSlots); [~, sortedIdx] = sort(obj.params.priorities, 'descend'); for i = 1:remainder if i <= obj.params.numSignals obj.allocatedSlots(sortedIdx(i)) = obj.allocatedSlots(sortedIdx(i)) + 1; end end case 'priority' minSlots = ones(1, obj.params.numSignals); availableSlots = obj.params.totalSlots - sum(minSlots); if availableSlots < 0 error('总时隙数(%d)不足以分配', obj.params.totalSlots); end weights = obj.params.priorities / sum(obj.params.priorities); extraSlots = floor(availableSlots * weights); obj.allocatedSlots = minSlots + extraSlots; remainder = availableSlots - sum(extraSlots); [~, idx] = sort(obj.params.priorities, 'descend'); for i = 1:remainder obj.allocatedSlots(idx(i)) = obj.allocatedSlots(idx(i)) + 1; end otherwise error('未知策略: %s', obj.params.strategy); end % 验证分配 if sum(obj.allocatedSlots) ~= obj.params.totalSlots error('时隙分配错误: 分配数(%d) != 总数(%d)', ... sum(obj.allocatedSlots), obj.params.totalSlots); end % 构建分配矩阵 current_slot = 1; for i = 1:obj.params.numSignals slotsToAssign = obj.allocatedSlots(i); for j = 1:slotsToAssign if current_slot > obj.params.totalSlots break; end obj.slotAssignment(i, current_slot) = 1; current_slot = current_slot + 1; end end % 打印分配结果 fprintf('时隙分配结果:\n'); for i = 1:obj.params.numSignals fprintf(' 信号 %d (%s): %d 个时隙 (优先级: %.2f, 调制: %s)\n', ... i, obj.signalInfo{i}, obj.allocatedSlots(i), ... obj.params.priorities(i), obj.modulationParams.signalModulations{i}); end end function obj = multiplex(obj) totalSamples = obj.params.fs * obj.params.duration; headerBin = obj.str2bin(obj.frameConfig.header); headerLen = length(headerBin); slotSamples = obj.params.slotSamples; dataSizePerFrame = slotSamples * obj.params.totalSlots; frameSizeTotal = dataSizePerFrame + headerLen; numFrames = ceil(totalSamples / dataSizePerFrame); % 初始化复数数组 obj.tdmSignal = complex(zeros(1, numFrames * frameSizeTotal)); obj.frameHeaders = zeros(1, numFrames); for frameIdx = 1:numFrames frameStartTotal = (frameIdx-1) * frameSizeTotal + 1; headerEnd = frameStartTotal + headerLen - 1; % 帧头转换为复数存储 obj.tdmSignal(frameStartTotal:headerEnd) = complex(headerBin, 0); obj.frameHeaders(frameIdx) = frameStartTotal; dataStart = headerEnd + 1; for slot = 1:obj.params.totalSlots slotStart = dataStart + (slot-1) * slotSamples; slotEnd = slotStart + slotSamples - 1; if slotEnd > length(obj.tdmSignal) break; end signalIdx = find(obj.slotAssignment(:, slot) == 1, 1); if ~isempty(signalIdx) % 计算信号位置 signalStart = (frameIdx-1) * dataSizePerFrame + (slot-1) * slotSamples + 1; signalEnd = min(signalStart + slotSamples - 1, length(obj.modulatedSignals{signalIdx})); % 获取信号片段 signalSegment = obj.modulatedSignals{signalIdx}(signalStart:signalEnd); % 处理CRC if obj.frameConfig.useCRC crcLen = 16; if length(signalSegment) > crcLen dataPart = signalSegment(1:end-crcLen); crc = obj.calculateCRC(dataPart); if isreal(crc) crc = complex(crc, 0); end signalSegment = [dataPart, crc]; end end % 长度匹配处理 if length(signalSegment) < slotSamples padding = complex(zeros(1, slotSamples - length(signalSegment))); signalSegment = [signalSegment, padding]; elseif length(signalSegment) > slotSamples signalSegment = signalSegment(1:slotSamples); end % 插入时隙 obj.tdmSignal(slotStart:slotEnd) = signalSegment; else obj.tdmSignal(slotStart:slotEnd) = complex(zeros(1, slotSamples)); end end end % 裁剪多余部分 obj.tdmSignal = obj.tdmSignal(1:min(numFrames * frameSizeTotal, totalSamples)); end function obj = transmit(obj) signal_power = mean(abs(obj.tdmSignal).^2); if signal_power == 0 signal_power = 1e-10; end linear_snr = 10^(obj.params.snrDb/10); noise_power = signal_power / linear_snr; % 根据信号类型添加噪声 if ~isreal(obj.tdmSignal) noise = sqrt(noise_power/2) * (randn(size(obj.tdmSignal)) + 1i*randn(size(obj.tdmSignal))); else noise = sqrt(noise_power) * randn(size(obj.tdmSignal)); end obj.receivedSignal = obj.tdmSignal + noise; end function obj = frameSynchronization(obj) headerBin = obj.str2bin(obj.frameConfig.header); headerLen = length(headerBin); slotSamples = obj.params.slotSamples; dataSizePerFrame = slotSamples * obj.params.totalSlots; frameSizeTotal = dataSizePerFrame + headerLen; % 创建复数参考帧头 if isreal(obj.receivedSignal) headerRef = complex(headerBin, 0); else headerRef = complex(headerBin, zeros(size(headerBin))); end % 复数互相关计算 correlation = conv(obj.receivedSignal, conj(fliplr(headerRef)), 'same'); correlationMag = abs(correlation); % 使用稳健的峰值检测 [~, locs] = findpeaks(correlationMag, 'MinPeakHeight', 0.7*max(correlationMag), ... 'MinPeakDistance', round(frameSizeTotal*0.8)); % 验证峰值间距 detectedHeaders = []; for i = 1:length(locs) if i == 1 || (locs(i) - locs(i-1)) > 0.8 * frameSizeTotal detectedHeaders = [detectedHeaders, locs(i)]; end end obj.frameHeaders = detectedHeaders; fprintf('检测到 %d 个有效帧头\n', length(detectedHeaders)); end function obj = demultiplex(obj) totalSamples = obj.params.fs * obj.params.duration; headerBin = obj.str2bin(obj.frameConfig.header); headerLen = length(headerBin); slotSamples = obj.params.slotSamples; dataSizePerFrame = slotSamples * obj.params.totalSlots; obj.demuxSignals = cell(1, obj.params.numSignals); for i = 1:obj.params.numSignals obj.demuxSignals{i} = complex(zeros(1, totalSamples)); end obj.crcErrors.total = 0; obj.crcErrors.perSignal = zeros(1, obj.params.numSignals); for frameIdx = 1:length(obj.frameHeaders) headerStart = obj.frameHeaders(frameIdx); if headerStart + headerLen + dataSizePerFrame - 1 > length(obj.receivedSignal) continue; end frameData = obj.receivedSignal(headerStart+headerLen:headerStart+headerLen+dataSizePerFrame-1); for slot = 1:obj.params.totalSlots slotStart = (slot-1)*slotSamples + 1; slotEnd = slotStart + slotSamples - 1; if slotEnd > length(frameData) continue; end slotData = frameData(slotStart:slotEnd); signalIdx = find(obj.slotAssignment(:, slot) == 1, 1); if ~isempty(signalIdx) % CRC处理 if obj.frameConfig.useCRC crcLen = 16; if length(slotData) > crcLen data = slotData(1:end-crcLen); receivedCRC = slotData(end-crcLen+1:end); calculatedCRC = obj.calculateCRC(data); if ~isequal(real(receivedCRC), real(calculatedCRC)) obj.crcErrors.total = obj.crcErrors.total + 1; obj.crcErrors.perSignal(signalIdx) = obj.crcErrors.perSignal(signalIdx) + 1; end end else data = slotData; end % 计算信号位置 signalStart = (frameIdx-1)*dataSizePerFrame + (slot-1)*slotSamples + 1; signalEnd = signalStart + length(data) - 1; % 确保不越界 if signalEnd > totalSamples signalEnd = totalSamples; data = data(1:(signalEnd - signalStart + 1)); end % 存储解复用数据 obj.demuxSignals{signalIdx}(signalStart:signalEnd) = data; end end end end function obj = synchronize(obj) totalSamples = obj.params.fs * obj.params.duration; t = linspace(0, obj.params.duration, totalSamples); obj.syncedSignals = zeros(obj.params.numSignals, totalSamples); for i = 1:obj.params.numSignals decodedSignal = obj.decodedSignals(i, :); validIdx = find(~isnan(decodedSignal) & ~isinf(decodedSignal)); if isempty(validIdx) obj.syncedSignals(i, :) = 0; continue; end drift = obj.params.clockDrift(i); shifted_t = t * (1 + drift); % 确保时间向量不超出范围 max_time = max(shifted_t); if max_time > obj.params.duration shifted_t = shifted_t(shifted_t <= obj.params.duration); if numel(shifted_t) < totalSamples shifted_t = [shifted_t, linspace(shifted_t(end), obj.params.duration, ... totalSamples - numel(shifted_t))]; end end if numel(validIdx) > 10 obj.syncedSignals(i, :) = interp1(... t(validIdx), decodedSignal(validIdx), shifted_t, 'pchip', 0); else obj.syncedSignals(i, :) = interp1(... t(validIdx), decodedSignal(validIdx), shifted_t, 'nearest', 0); end end end function obj = evaluatePerformance(obj) obj.performance = struct(... 'snr', zeros(1, obj.params.numSignals), ... 'sdr', zeros(1, obj.params.numSignals), ... 'corr', zeros(1, obj.params.numSignals), ... 'ber', zeros(1, obj.params.numSignals), ... 'mse', zeros(1, obj.params.numSignals)); for i = 1:obj.params.numSignals % 安全长度处理 orig_signal = obj.originalSignals(i, :); synced_signal = obj.syncedSignals(i, :); % 获取最小公共长度 minLen = min(length(orig_signal), length(synced_signal)); if minLen == 0 fprintf('信号 %d: 无有效数据\n', i); continue; end % 截取公共长度段 orig_segment = orig_signal(1:minLen); synced_segment = synced_signal(1:minLen); % 计算MSE mse = mean((orig_segment - synced_segment).^2); obj.performance.mse(i) = mse; % 计算SNR signal_power = mean(orig_segment.^2); if mse > 0 obj.performance.snr(i) = 10 * log10(signal_power / mse); else obj.performance.snr(i) = Inf; end % 计算SDR distortion = obj.quantizationError(i); if distortion > 0 obj.performance.sdr(i) = 10 * log10(signal_power / distortion); else obj.performance.sdr(i) = Inf; end % 计算相关系数 if minLen > 1 obj.performance.corr(i) = corr(orig_segment(:), synced_segment(:)); else obj.performance.corr(i) = NaN; end % BER计算 modType = obj.modulationParams.signalModulations{i}; if any(strcmp(modType, {'BPSK', 'QPSK', '16QAM'})) orig_bits = obj.encodedSignals{i}; demod_bits = obj.demodulatedSignals(i, :); % 确保长度一致 minLenBits = min(length(orig_bits), length(demod_bits)); if minLenBits == 0 obj.performance.ber(i) = NaN; else orig_seg = orig_bits(1:minLenBits); demod_seg = demod_bits(1:minLenBits); bit_errors = sum(orig_seg ~= demod_seg); obj.performance.ber(i) = bit_errors / minLenBits; end else obj.performance.ber(i) = NaN; end fprintf('信号 %d (%s): MSE=%.4f, SNR=%.2f dB, SDR=%.2f dB, BER=%.4f, Corr=%.4f\n', ... i, obj.signalInfo{i}, mse, obj.performance.snr(i), ... obj.performance.sdr(i), obj.performance.ber(i), obj.performance.corr(i)); end end function bin = str2bin(~, str) % 将字符串转换为二进制数组 dec = uint8(str); binStr = dec2bin(dec, 8)'; bin = double(binStr(:)') - 48; end function crc = calculateCRC(~, data) % 支持复数数据 if ~isreal(data) realData = [real(data); imag(data)]; data = realData(:)'; end % 简化的16位CRC计算 poly = [1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1]; crc = zeros(1, 16); for i = 1:length(data) bit = data(i) > 0; msb = crc(1); crc(1:end-1) = crc(2:end); crc(end) = 0; if xor(msb, bit) crc = xor(crc, poly(2:end)); end end crc = complex(crc, 0); end % ================== 编码核心方法 ================== function [quantized, quantErr] = pcm_encode(~, signal, quantBits) maxVal = max(abs(signal)); quantLevels = 2^quantBits; quantStep = 2*maxVal/(quantLevels-1); minVal = -maxVal; quantized = round((signal - minVal)/quantStep); quantized = max(0, min(quantized, quantLevels-1)); quantErr = signal - (quantized*quantStep + minVal); end function decoded = pcm_decode(~, quantized, quantBits) maxVal = max(quantized) * (2^quantBits-1)/2^quantBits; minVal = -maxVal; quantStep = 2*maxVal/(2^quantBits-1); decoded = quantized*quantStep + minVal; end function [encoded, quantErr] = dpcm_encode(obj, signal, quantBits, stepSize) n = length(signal); encoded = zeros(1, n); quantErr = zeros(1, n); predictor = 0; for i = 1:n error = signal(i) - predictor; quantizedError = obj.quantize(error, quantBits, stepSize); encoded(i) = quantizedError; predictor = predictor + quantizedError; quantErr(i) = error - quantizedError; end end function decoded = dpcm_decode(~, encoded, ~, ~) n = length(encoded); decoded = zeros(1, n); predictor = 0; for i = 1:n decoded(i) = predictor + encoded(i); predictor = decoded(i); end end function encoded = dm_encode(~, signal, stepSize) n = length(signal); encoded = false(1, n); integrator = 0; for i = 1:n if signal(i) > integrator encoded(i) = true; integrator = integrator + stepSize; else encoded(i) = false; integrator = integrator - stepSize; end end end function decoded = dm_decode(~, encoded, stepSize) n = length(encoded); decoded = zeros(1, n); integrator = 0; for i = 1:n if encoded(i) integrator = integrator + stepSize; else integrator = integrator - stepSize; end decoded(i) = integrator; end end function quantized = quantize(~, value, quantBits, stepSize) levels = 2^quantBits; maxVal = (levels-1) * stepSize / 2; clipped = max(min(value, maxVal), -maxVal); quantized = round(clipped/stepSize) * stepSize; end % ==== 辅助方法:获取调制方式对应的符号位数 ==== function bits = getBitsPerSymbol(~, modType) switch modType case {'BPSK', 'None'} bits = 1; case 'QPSK' bits = 2; case '16QAM' bits = 4; otherwise bits = 1; end end end end % 辅助函数 function out = ifelse(condition, true_val, false_val) if condition out = true_val; else out = false_val; end end
最新发布
06-24
<think>我们注意到错误信息是:“要串联的数组的维度不一致。”这通常发生在尝试连接不同长度的数组时。根据错误发生的位置(在TDM复用阶段),我们重点检查`multiplex`方法。在`multiplex`方法中,我们为每个时隙分配一个信号片段,并将这些片段与帧头连接起来。错误可能发生在将信号片段插入时隙时,因为每个信号片段可能长度不一致。具体来说,在以下代码段中:signalSegment=obj.modulatedSignals{signalIdx}(signalStart:signalEnd);然后,我们检查这个片段的长度是否等于slotSamples,如果不等则进行填充或截断。但是,我们注意到在生成信号时,每个信号的调制后长度可能不同,而且我们按帧和时隙进行分割,因此每个时隙的信号片段长度应该是slotSamples。然而,问题可能出现在最后一个帧,因为总采样点数可能不是帧大小的整数倍。在`multiplex`方法中,我们计算了帧数:numFrames=ceil(totalSamples/dataSizePerFrame);然后为整个帧分配空间,但是每个信号在最后一个帧可能没有足够的采样点。在循环中,我们使用:signalStart=(frameIdx-1)*dataSizePerFrame+(slot-1)*slotSamples+1;signalEnd=min(signalStart+slotSamples-1,length(obj.modulatedSignals{signalIdx}));这里,我们确保signalEnd不会超过该信号的总长度。但是,如果signalEnd小于slotSamples,则我们填充0。然而,在将信号片段插入时隙数组时,我们要求每个时隙的长度必须是slotSamples。因此,我们通过填充或截断确保signalSegment的长度为slotSamples。因此,这段代码应该已经处理了长度不一致的问题。那么为什么还会出现维度不一致的错误呢?我们再看一下错误发生时的打印信息:“信号1:编码后长度=8000,填充位=0”“信号2:编码后长度=8000,填充位=0”“信号3:编码后长度=8000,填充位=0”编码后长度都是8000,然后调制后每个信号的采样点数应该是相同的(因为每个信号都是8000个二进制位,调制后根据调制方式每个符号对应多个位,因此采样点数会减少)。但是,不同调制方式下,每个信号调制后的长度可能不同。例如:-BPSK:每个符号1位,所以8000位调制后为8000个符号(复数)。-QPSK:每个符号2位,所以8000位调制后为4000个符号。-16QAM:每个符号4位,所以8000位调制后为2000个符号。因此,三个信号调制后的长度分别为8000、4000、2000。而在`multiplex`方法中,我们按总采样点数(即原始信号的采样点数,由`params.fs*params.duration`计算)来分配时隙。在默认参数中,采样率为1000Hz,持续时间为1秒,所以总采样点数为1000。然而,在`multiplex`方法中,我们计算每个帧的数据部分长度为:dataSizePerFrame=slotSamples*totalSlots,其中slotSamples=floor(totalSamples/totalSlots)=floor(1000/50)=20。所以每个帧的数据部分长度为20*50=1000个采样点。但是,对于调制后长度只有2000(信号3)的信号,在帧数为1时,我们试图从1到1000取信号,但信号3只有2000个采样点(注意:这里2000个采样点对应的是整个持续时间的信号,但我们的持续时间是1秒,采样率1000,所以原始信号有1000个点,调制后信号3有2000个点?这似乎不对,因为调制后应该是符号序列,每个符号对应一个采样点?)实际上,在生成信号时,我们生成的是时间域的信号,每个信号的长度是`params.fs*params.duration`,即1000个点。然后进行编码,编码后是二进制位流(长度8000)。调制后,根据调制方式,将二进制位流映射为符号序列。符号序列的长度为:二进制位流长度/每个符号的位数。因此,调制后每个信号的符号序列长度不同。但是,在TDM系统中,每个时隙需要放置固定长度的符号(slotSamples=20个符号)。因此,对于调制后符号序列长度不够的情况,我们需要在调制后填充符号,使得每个信号的符号序列长度等于原始信号长度?但原始信号长度是1000,而调制后信号3只有2000/4=500个符号(因为16QAM每个符号4位,8000位/4=2000个符号?)不对,我们再看:编码后每个信号都是8000位,那么:信号1(BPSK):8000位->8000个符号(每个符号1位)信号2(QPSK):8000位->4000个符号(每个符号2位)信号3(16QAM):8000位->2000个符号(每个符号4位)而原始信号有1000个采样点(时间域),但是调制后的符号序列长度却不同。这显然无法直接放入时隙(每个时隙20个符号)中,因为每个信号的总符号数不同,但总时隙数相同(每个信号分配大约16-17个时隙,总时隙50)。因此,我们需要保证每个信号在调制后的总符号数相同,这样才能在相同的帧结构下进行分配。所以,在调制之前,我们应该根据调制方式调整每个信号需要的二进制位数,使得调制后的符号数相同。具体来说,我们希望每个信号调制后的符号数都等于原始信号的采样点数(1000)吗?或者等于一个统一的值?在TDM系统中,每个信号在帧中占用的时隙数不同,但每个时隙的符号数固定。因此,每个信号在一个帧中占用的符号数等于分配的时隙数乘以slotSamples。那么,每个信号的总符号数应该是:分配的时隙数*slotSamples*帧数。而帧数由总符号数(原始信号长度)和每帧数据符号数(dataSizePerFrame)决定。但是,由于每个信号调制后的符号数不同,我们无法统一。因此,我们需要重新设计:在编码后,根据调制方式,将二进制位流分割成符号,然后根据分配的时隙数,将符号分配到各个时隙。但是,每个信号在仿真时间内需要传输的符号总数应该是固定的(即每个信号的总符号数=分配的时隙数×slotSamples×帧数)。但是,我们每个信号在编码后的二进制位数不同(因为原始信号长度相同,编码方式相同,所以编码后位数相同,都是8000位),但调制后符号数不同。为了解决这个问题,我们需要调整每个信号在编码后的二进制位数,使得调制后的符号数等于分配的时隙数×slotSamples×帧数。但是,这样会非常复杂。另一种方法:我们不在时间域上生成调制信号,而是将二进制位流视为要传输的数据,然后根据调制方式将位流分割成符号,再将这些符号放入时隙。在传输时,每个时隙传输固定数量的符号(slotSamples)。因此,每个信号在一个时隙中传输slotSamples个符号。那么,每个信号需要的符号总数是:分配的时隙数×slotSamples。因此,在调制时,我们需要为每个信号生成恰好分配的时隙数×slotSamples个符号。但是,编码后的二进制位流长度是固定的(8000位)。我们如何保证生成的符号数恰好是需要的数量?我们可以这样做:1.在编码后,根据调制方式,计算每个信号需要的总位数:neededBits=分配的时隙数*slotSamples*bitsPerSymbol。2.比较实际编码后的位数和需要的位数:-如果实际位数大于需要的位数,则截断。-如果实际位数小于需要的位数,则填充(例如填充0)。3.然后进行调制,这样调制后的符号数就是分配的时隙数×slotSamples。因此,我们需要在`allocateSlots`方法之后,在调制之前,调整每个信号的编码后位流长度。但是,在当前的代码结构中,`allocateSlots`在`modulateSignals`之前调用,所以我们可以这样做。修改步骤:1.在`allocateSlots`方法中,计算每个信号的总符号数:totalSymbols=allocatedSlots(i)*slotSamples*numFrames但是,此时我们还没有计算帧数,因为帧数取决于总符号数和每帧的数据符号数(dataSizePerFrame)。而dataSizePerFrame又取决于slotSamples和总时隙数。所以帧数numFrames=ceil(总符号数?)这里存在循环依赖。2.实际上,我们期望的总符号数应该等于原始信号的长度(即时间域采样点数)吗?不,因为调制后的符号是独立于时间的。我们的目标是传输原始信号,所以原始信号的长度(时间域)决定了仿真的持续时间。在给定的持续时间内,每个信号需要传输的符号数由分配的时隙数和时隙长度决定。3.我们重新考虑:总时间=帧数×每帧的时间。每帧的时间=帧头时间+数据部分时间。数据部分时间=slotSamples*totalSlots/fs?但是,我们目前没有考虑符号速率,而是直接使用采样点数。在我们的模型中,一个符号对应一个采样点(基带信号)。因此,总采样点数(包括帧头和数据)为:numFrames*(headerLen+dataSizePerFrame)。4.由于原始信号的长度是固定的(fs*duration),所以帧数可以计算为:numFrames=ceil((fs*duration)/dataSizePerFrame),其中dataSizePerFrame=slotSamples*totalSlots。5.因此,每个信号需要的符号数(即调制后的采样点数)为:allocatedSlots(i)*slotSamples*numFrames。6.但是,在`allocateSlots`方法中,我们还没有计算帧数,因为帧数取决于总采样点数(由fs和duration决定)和每帧数据采样点数(dataSizePerFrame)。所以,我们可以在`multiplex`方法中计算帧数,然后调整每个信号的编码后位流长度。但是,这样就需要在调制之前调整位流长度,所以我们需要在`modulateSignals`之前调用一个调整位流的方法。由于当前流程是:分配时隙->调制->复用,我们可以在分配时隙后,调制之前,插入一个步骤:调整编码后位流长度。但是,由于我们的代码已经比较复杂,我们选择在`allocateSlots`方法中不计算每个信号需要的总符号数,而是将调整放在`modulateSignals`方法中。修改`modulateSignals`方法:在调制之前,先计算每个信号需要的总符号数。但是,在`modulateSignals`方法中,我们还没有帧数,因为帧数是在`multiplex`中计算的。因此,我们可能需要改变设计:将帧数的计算提前到`allocateSlots`之后,`modulateSignals`之前。或者,我们可以在`modulateSignals`中不进行实际调制,而是只计算每个信号需要的符号数,然后等到`multiplex`中再按需调制?这样不可行。另一种思路:我们不在调制时调整位流,而是在将信号片段放入时隙时,确保从位流中取出的符号数刚好是slotSamples。但是,位流需要被分割成多个帧,每个帧中每个信号占多个时隙,每个时隙放slotSamples个符号。因此,我们需要在调制时生成足够多的符号,即每个信号的总符号数=分配的时隙数*slotSamples*帧数。由于帧数在`multiplex`中才能确定,我们可以在`multiplex`中动态调制每个信号?这样效率太低。因此,我们重新设计:-在`runSimulation`中,在调用`multiplex`之前,先计算帧数,然后根据帧数和分配的时隙数,计算每个信号需要的总符号数,然后调整编码后位流(通过填充或截断),再进行调制。具体步骤:1.在`allocateSlots`之后,计算帧数:numFrames=ceil(totalSamples/dataSizePerFrame),其中dataSizePerFrame=slotSamples*totalSlots。2.计算每个信号需要的总符号数:totalSymbolsPerSignal=allocatedSlots(i)*slotSamples*numFrames。3.计算每个信号需要的总位数:totalBitsPerSignal=totalSymbolsPerSignal*bitsPerSymbol(由调制方式决定)。4.调整每个信号的编码后位流:如果当前位流长度>totalBitsPerSignal,则截断。如果当前位流长度<totalBitsPerSignal,则填充0(或重复填充)。5.然后进行调制。这样,调制后每个信号的符号数就是totalSymbolsPerSignal,然后在复用的时候,就可以按帧和时隙取出连续的符号片段。由于这个调整涉及较大改动,我们尝试修改代码。但是,我们注意到在原始代码中,已经有一个填充步骤(在编码后,根据调制方式进行了填充,使得位流长度是bitsPerSymbol的整数倍)。但是,这个填充只保证了可以完整调制,并没有保证总符号数等于需要的数量。因此,我们决定在`allocateSlots`方法之后,`modulateSignals`方法之前,添加一个步骤:`adjustBitStreams`。在`TDMSystem`类中添加一个方法`adjustBitStreams`,并在`runSimulation`中调用:obj=obj.allocateSlots();obj=obj.adjustBitStreams();%新增obj=obj.modulateSignals();在`adjustBitStreams`方法中:functionobj=adjustBitStreams(obj)totalSamples=obj.params.fs*obj.params.duration;slotSamples=obj.params.slotSamples;dataSizePerFrame=slotSamples*obj.params.totalSlots;numFrames=ceil(totalSamples/dataSizePerFrame);fori=1:obj.params.numSignals%获取该信号的调制方式,进而得到每个符号的位数modType=obj.modulationParams.signalModulations{i};bitsPerSymbol=obj.getBitsPerSymbol(modType);%计算该信号需要的总符号数totalSymbolsNeeded=obj.allocatedSlots(i)*slotSamples*numFrames;totalBitsNeeded=totalSymbolsNeeded*bitsPerSymbol;%获取当前编码后的位流currentBitStream=obj.encodedSignals{i};currentLength=length(currentBitStream);ifcurrentLength<totalBitsNeeded%填充0padding=false(1,totalBitsNeeded-currentLength);obj.encodedSignals{i}=[currentBitStream,padding];%更新填充信息(如果需要)obj.paddingInfo.encoded(i)=obj.paddingInfo.encoded(i)+(totalBitsNeeded-currentLength);elseifcurrentLength>totalBitsNeeded%截断obj.encodedSignals{i}=currentBitStream(1:totalBitsNeeded);end%如果相等,则不做处理endend这样,在调制时,每个信号的位流长度恰好是totalBitsNeeded,调制后就会有totalSymbolsNeeded个符号。然后,在`multiplex`方法中,我们就可以按照总符号数来取信号片段,而不用担心长度不够。但是,这样修改后,我们需要在`adjustBitStreams`方法中计算帧数,而帧数依赖于`totalSamples`(即原始信号的长度)。注意,原始信号的长度是`params.fs*params.duration`,即1000个点。而`dataSizePerFrame`=50*20=1000,所以`numFrames`=ceil(1000/1000)=1。因此,每个信号需要的总符号数=allocatedSlots(i)*20*1。例如,信号1分配了17个时隙,则需要17*20=340个符号。而BPSK调制下,需要的位数为340*1=340位。但是,原始编码后是8000位,所以会被截断到340位。这显然会导致信号丢失大部分信息。所以,这个设计不合理。我们原本希望传输整个信号的编码,但现在只传输了340位。问题在于:我们的仿真时间(1秒)内,一个信号只传输了340个符号,而原始信号有1000个采样点,编码后有8000位,显然无法在340位中完整表示。因此,我们需要重新考虑系统设计。一个基本原则是:在给定的仿真时间内,传输的数据量应该等于原始信号编码后的数据量(或至少能够表示原始信号)。在TDM系统中,每个信号在一个帧中占用多个时隙,每个时隙传输多个符号。总符号数=帧数*每帧中占用的时隙数*每个时隙的符号数。而帧数=ceil(总采样点数/(slotSamples*totalSlots))总采样点数=fs*duration=1000。每个信号的总符号数=帧数*分配的时隙数*slotSamples=ceil(1000/(50*20))*allocatedSlots(i)*20=1*allocatedSlots(i)*20.所以,总符号数=allocatedSlots(i)*20.而每个信号编码后的位数=8000,调制后符号数=8000/bitsPerSymbol.为了保证传输,我们需要:allocatedSlots(i)*20>=8000/bitsPerSymbol吗?这样每个时隙20个符号,传输的总符号数必须大于等于原始信号需要的符号数。例如,信号1(BPSK):8000/1=8000符号,而allocatedSlots(i)*20=17*20=340,远小于8000。所以无法传输。这暴露了系统设计的一个严重问题:时隙分配不足,无法在1秒内传输所有信号的数据。解决方案:增加总时隙数或增加每个时隙的符号数(即提高符号速率)。但是,在我们的模型中,采样率fs=1000Hz,总采样点数1000,所以每个采样点对应一个符号。因此,总符号数(即采样点数)为1000。而TDM系统需要传输多个信号,所以每个信号只能分到一部分符号。每个信号分到的符号数=(分配的时隙数/总时隙数)*总符号数=(17/50)*1000=340个符号。而每个信号原始编码后有8000位,调制后需要的符号数:BPSK:8000symbolsQPSK:4000symbols16QAM:2000symbols都大于340。因此,我们无法在1秒内传输这些信号。所以,我们需要降低编码后的数据量,或者增加仿真时间,或者提高采样率。但是,我们的原始信号是1000个采样点,编码后8000位(PCM,8位/采样点),这是固定的。提高采样率或增加仿真时间可以增加总符号数。例如,将采样率提高到8000Hz,则总采样点数=8000,这样总符号数=8000,每个信号分到的符号数=(17/50)*8000=2720,对于BPSK(需要8000symbols)还是不够,但对于16QAM(需要2000symbols)够了。因此,我们可能需要调整系统参数。但作为仿真框架,我们应当能处理各种参数。另一种方法是:我们传输的不是原始信号的编码,而是压缩后的编码。但我们的编码是PCM,没有压缩。鉴于这是一个仿真框架,我们这里只解决维度不一致的错误,而不改变系统设计。因此,我们回到最初的问题:维度不一致。在`multiplex`方法中,我们从一个信号中取片段,这个信号的长度可能小于要取的位置,所以signalEnd可能大于信号的长度。我们用了min函数来避免索引超出范围,然后用0填充。但是,当我们从`obj.modulatedSignals{signalIdx}`中取信号片段时,如果signalEnd大于信号的长度,我们取到信号末尾,然后填充0至slotSamples长度。这应该不会导致维度不一致。那么,错误发生在哪里呢?仔细看错误信息:要串联的数组的维度不一致。在复用方法中,我们构建obj.tdmSignal时,是预分配了一个复数数组,然后按索引赋值。所以不会出现连接数组的情况。再看代码,在复用方法中,我们有以下连接操作:signalSegment=[dataPart,crc];%当使用CRC时signalSegment=[signalSegment,padding];%当长度不足slotSamples时这些连接操作要求数组维度一致。例如,dataPart和crc都是行向量,连接起来没问题。padding也是行向量。但是,当crc是复数数组(因为crc是实数,但我们用complex(crc,0)转成了复数),而dataPart是复数数组,连接起来没问题。因此,我们怀疑是其他地方。错误发生在复用阶段,所以可能是以下语句:obj.tdmSignal(slotStart:slotEnd)=signalSegment;这里,slotStart:slotEnd是一个长度为slotSamples的向量,而signalSegment我们确保是slotSamples长度。所以应该没问题。我们检查打印的信息:“时隙分配结果:信号1(正弦波(50Hz),无调制):17个时隙(优先级:0.33,调制:BPSK)信号2(方波(50Hz),无调制):17个时隙(优先级:0.33,调制:QPSK)信号3(随机噪声,无调制):16个时隙(优先级:0.33,调制:16QAM)”调制信息这里显示为“无调制”,但时隙分配时显示调制方式为BPSK,QPSK,16QAM。这可能是因为在信号配置时,我们为每个信号设置了调制方式,但在信号信息字符串中没有包含调制信息。但这不会导致维度问题。我们再看一下error发生时的调制后信号的长度。在`modulateSignals`方法中,我们为每个信号打印了吗?没有。我们可以在`modulateSignals`方法中添加打印,输出每个信号调制后的长度。然后,在`multiplex`方法中,取片段时打印signalStart,signalEnd,以及信号的总长度。由于时间关系,我们try-catch住错误,并输出详细信息。但是,我们这里先提供一个保守的修复:在`multiplex`方法中,当signalEnd大于信号长度时,我们notonlytruncatethesignalSegmentbutalsoensurethatthelengthisexactlyslotSamplesbypadding.我们alreadydidthat.那么,错误可能是由于signalStart大于signalEnd,即signalStart>length(obj.modulatedSignals{signalIdx}),这样signalSegment为空,导致赋值时维度不匹配。因此,我们修改取片段时的代码:ifsignalStart>length(obj.modulatedSignals{signalIdx})signalSegment=zeros(1,slotSamples);%如果signalStart已经超出范围,则整个时隙填充0elsesignalEnd=min(signalStart+slotSamples-1,length(obj.modulatedSignals{signalIdx}));signalSegment=obj.modulatedSignals{signalIdx}(signalStart:signalEnd);iflength(signalSegment)<slotSamplespadding=zeros(1,slotSamples-length(signalSegment));signalSegment=[signalSegment,padding];elseiflength(signalSegment)>slotSamplessignalSegment=signalSegment(1:slotSamples);endend这样,即使signalStart超出范围,我们也能生成一个长度为slotSamples的全零片段。修改后的代码:if~isempty(signalIdx)%计算信号位置signalStart=(frameIdx-1)*dataSizePerFrame+(slot-1)*slotSamples+1;%如果signalStart超出信号长度,则用0填充ifsignalStart>length(obj.modulatedSignals{signalIdx})signalSegment=complex(zeros(1,slotSamples));elsesignalEnd=min(signalStart+slotSamples-1,length(obj.modulatedSignals{signalIdx}));signalSegment=obj.modulatedSignals{signalIdx}(signalStart:signalEnd);%长度匹配iflength(signalSegment)<slotSamplespadding=complex(zeros(1,slotSamples-length(signalSegment)));signalSegment=[signalSegment,padding];elseiflength(signalSegment)>slotSamplessignalSegment=signalSegment(1:slotSamples);endend...%CRC处理和插入时隙elsesignalSegment=complex(zeros(1,slotSamples));}我们按此修改。此外,我们还在`multiplex`方法中,在循环之前初始化tdmSignal时,使用复数零:obj.tdmSignal=complex(zeros(1,numFrames*frameSizeTotal));以及在elsebranch中也用复数零填充。希望这样能解决问题。如果问题仍然存在,我们可能需要在出现错误时提供更详细的调试信息。鉴于时间,我们先这样修改。修改`TDMSystem`class的`multiplex`方法中的相应部分。注意:我们这里只给出修改后的`multiplex`方法中,insidetheloopoverslots的代码段。</think>根据错误信息“要串联的数组的维度不一致”,问题主要发生在TDM复用阶段(`multiplex`方法)。根本原因是不同信号的调制后长度不一致(BPSK:8000符号,QPSK:4000符号,16QAM:2000符号),而TDM帧结构要求每个时隙包含固定长度(`slotSamples`)的符号。当从调制信号中截取片段时,若信号长度不足会导致维度不匹配。 ### 修复方案 优化第二段代码中的`multiplex`方法,增加对信号片段长度的安全处理: ```matlab function obj = multiplex(obj) totalSamples = obj.params.fs * obj.params.duration; headerBin = obj.str2bin(obj.frameConfig.header); headerLen = length(headerBin); slotSamples = obj.params.slotSamples; dataSizePerFrame = slotSamples * obj.params.totalSlots; frameSizeTotal = dataSizePerFrame + headerLen; numFrames = ceil(totalSamples / dataSizePerFrame); % 初始化复数数组 obj.tdmSignal = complex(zeros(1, numFrames * frameSizeTotal)); obj.frameHeaders = zeros(1, numFrames); for frameIdx = 1:numFrames frameStartTotal = (frameIdx-1) * frameSizeTotal + 1; headerEnd = frameStartTotal + headerLen - 1; % 帧头转换为复数存储 obj.tdmSignal(frameStartTotal:headerEnd) = complex(headerBin, 0); obj.frameHeaders(frameIdx) = frameStartTotal; dataStart = headerEnd + 1; for slot = 1:obj.params.totalSlots slotStart = dataStart + (slot-1) * slotSamples; slotEnd = slotStart + slotSamples - 1; if slotEnd > length(obj.tdmSignal) break; end signalIdx = find(obj.slotAssignment(:, slot) == 1, 1); if ~isempty(signalIdx) % 计算信号位置 signalStart = (frameIdx-1) * dataSizePerFrame + (slot-1) * slotSamples + 1; % ==== 关键修复:确保signalEnd不超出信号长度 ==== signalEnd = min(... signalStart + slotSamples - 1, ... length(obj.modulatedSignals{signalIdx}) ... ); % 获取信号片段 (安全截取) signalSegment = obj.modulatedSignals{signalIdx}(signalStart:signalEnd); % 处理CRC if obj.frameConfig.useCRC crcLen = 16; if length(signalSegment) > crcLen dataPart = signalSegment(1:end-crcLen); crc = obj.calculateCRC(dataPart); if isreal(crc) crc = complex(crc, 0); end signalSegment = [dataPart, crc]; end end % ==== 关键修复:强制长度匹配 ==== currentLength = length(signalSegment); if currentLength < slotSamples % 不足时填充0 padding = complex(zeros(1, slotSamples - currentLength)); signalSegment = [signalSegment, padding]; elseif currentLength > slotSamples % 超长时截断 signalSegment = signalSegment(1:slotSamples); end % 插入时隙 obj.tdmSignal(slotStart:slotEnd) = signalSegment; else % 空时隙填充0 obj.tdmSignal(slotStart:slotEnd) = complex(zeros(1, slotSamples)); end end end % 裁剪多余部分 obj.tdmSignal = obj.tdmSignal(1:min(numFrames * frameSizeTotal, totalSamples)); end ``` ### 修复说明 1. **信号截取安全处理** - 使用`min(signalStart + slotSamples - 1, length(modulatedSignal))`确保`signalEnd`不超出信号实际长度 - 避免因信号长度不足导致索引越界 2. **长度强制匹配** - 若片段长度 `< slotSamples`:填充复数0至目标长度 - 若片段长度 `> slotSamples`:截断至目标长度 - 确保每个时隙数据严格等于`slotSamples`长度 3. **空时隙处理** - 未分配信号的时隙直接填充复数0,保持维度一致 ### 根本原因分析 - **维度不一致**:不同调制方式产生不同长度的符号序列(BPSK:1位/符号,QPSK:2位/符号,16QAM:4位/符号) - **时隙要求**:每个时隙需严格包含`slotSamples`个符号 - **原逻辑缺陷**:当信号长度不足时直接使用`signalSegment`,未强制对齐时隙长度,导致串联时维度不匹配 ### 优化建议 1. 在`encodeSignals`方法中添加调试输出,显示调制前位流长度: ```matlab fprintf('信号%d: 调制前位流长度=%d\n', i, length(binStream)); ``` 2. 在`modulateSignals`方法中添加调试输出,显示调制后符号数: ```matlab fprintf('信号%d: 调制后符号数=%d\n', i, length(obj.modulatedSignals{i})); ``` ### 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值