メインコンテンツ

固定小数点データを使用した空燃比制御システム

この例では、Simulink® と Stateflow® を使用して設計された固定小数点空燃比制御システムのコードを生成および最適化する方法を示します。モデルの詳細な説明については、フォールトトレラント燃料制御システムのモデル化を参照してください。

この例では、Embedded Coder® システム ターゲット ファイル (ert.tlc) を使用します。

モデルの関連部分

図 1 〜 4 は、プラント サブシステムとコントローラー サブシステムを含む閉ループ システムである sldemo_fuelsys モデルの関連部分を示しています。プラントにより、エンジニアは設計サイクルの早い段階でシミュレーションを介してコントローラーを検証できます。この例では、関連するコントローラー サブシステム fuel_rate_control のコードを生成します。図 1 は、最上位のシミュレーション モデルを示しています。

% Open |sldemo_fuelsys|, set the parameters and update the model diagram
% to view the signal data types.
close_system('sldemo_fuelsys',0)
load_system('sldemo_fuelsys');
coder.example.configure('sldemo_fuelsys','ERT','fixed');
sldemo_fuelsys_data('sldemo_fuelsys','switch_data_type','fixed');
sldemo_fuelsys_data('sldemo_fuelsys','top_level_logging','on');
set_param('sldemo_fuelsys','ShowPortDataTypes','on');
set_param('sldemo_fuelsys','SampleTimeColors','on');
set_param('sldemo_fuelsys','Dirty','off');
sldemo_fuelsys([],[],[],'compile');
sldemo_fuelsys([],[],[],'term');

図 1: プラントとコントローラーの最上位モデル

空燃比制御システムは Simulink および Stateflow ブロックで構成されます。これはコードを生成するモデルの部分です。

open_system('sldemo_fuelsys/fuel_rate_control');

図 2: 空燃比コントローラー サブシステム

Intake Airflow Estimation and Closed Loop Correction システムには、Pumping Constant と Ramp Rate Ki の 2 つのルックアップ テーブルが含まれています。

open_system('sldemo_fuelsys/fuel_rate_control/airflow_calc');

図 3: airflow_calc サブシステム

制御ロジックは、さまざまな動作モードを指定する Stateflow チャートです。

open_system('sldemo_fuelsys/fuel_rate_control/control_logic');

図 4: 燃料比コントローラーのロジック

煩雑になったウィンドウをきれいにします。

close_system('sldemo_fuelsys/fuel_rate_control/airflow_calc');
close_system('sldemo_fuelsys/fuel_rate_control/fuel_calc');
close_system('sldemo_fuelsys/fuel_rate_control/control_logic');
hDemo.rt=sfroot;hDemo.m=hDemo.rt.find('-isa','Simulink.BlockDiagram');
hDemo.c=hDemo.m.find('-isa','Stateflow.Chart','-and','Name','control_logic');
hDemo.c.visible=false;
close_system('sldemo_fuelsys/fuel_rate_control');

空燃比制御システムのみをビルドします。コード生成プロセスが完了すると、生成されたコードの詳細が記載された HTML レポートが表示されます。コードの本体は fuel_rate_control.c にあります。

slbuild('sldemo_fuelsys/fuel_rate_control');
### Searching for referenced models in model 'fuel_rate_control'.
### Total of 1 models to build.
### Starting build procedure for: fuel_rate_control
### Successful completion of build procedure for: fuel_rate_control

Build Summary

Top model targets:

Model              Build Reason                                         Status                        Build Duration
====================================================================================================================
fuel_rate_control  Information cache folder or artifacts were missing.  Code generated and compiled.  0h 0m 17.81s  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 17.989s

図 5 に、ルックアップ テーブル Pumping Constant に対して生成されたコード スニペットを示します。

Pumping Constant のコードを確認するには、ブロックを右クリックし、[C/C++ コード]、[C/C++ コードに移動] を選択します。

rtwtrace('sldemo_fuelsys/fuel_rate_control/airflow_calc/Pumping Constant');

Pumping Constant のコードには、2 つのブレークポイント検索と 2 次元内挿が含まれています。SpeedVect ブレークポイントは不等間隔ですが、PressVect ブレークポイントは等間隔です。どちらも 2 のべき乗の間隔になっていません。現在の間隔では、除算を含む余分なコード (ROM) が発生し、すべてのブレークポイントをメモリ (RAM) に配置する必要があります。

図 5: Pumping Constant ルックアップ用に生成されたコード (不等間隔のブレークポイントを含む)

2 のべき乗の等間隔のブレークポイントによるコードの最適化

2 のべき乗の等間隔のブレークポイントを使用することで、生成されたコードのパフォーマンスを最適化できます。この例では、既存の測定データに基づいて、空燃比制御システムのルックアップ テーブル データを再マッピングします。

モデルを読み込んだときに、モデル PostLoadFcn によってモデル ワークスペースにルックアップ テーブル データが作成されました。sldemo_fuelsys_data を介して元のテーブル データを取得し、2 のべき乗の等間隔になるように変更して、モデル ワークスペースに再度割り当てます。

td = sldemo_fuelsys_data('sldemo_fuelsys', 'get_table_data');

2 のべき乗の等間隔のブレークポイント用に新しいテーブル データを計算します。

ntd.SpeedVect   = 64 : 2^5 : 640;             % 32 rad/sec
ntd.PressVect   = 2*2^-5 : 2^-5 : 1-(2*2^-5); % 0.03 bar
ntd.ThrotVect   = 0:2^1:88;                   % 2 deg
ntd.RampRateKiX = 128:2^7:640;                % 128 rad/sec
ntd.RampRateKiY = 0:2^-2:1;                   % 0.25 bar

テーブル データを再マッピングします。

ntd.PumpCon  = interp2(td.PressVect,td.SpeedVect,td.PumpCon, ntd.PressVect',ntd.SpeedVect);
ntd.PressEst = interp2(td.ThrotVect,td.SpeedVect,td.PressEst,ntd.ThrotVect',ntd.SpeedVect);
ntd.SpeedEst = interp2(td.PressVect,td.ThrotVect,td.SpeedEst,ntd.PressVect',ntd.ThrotVect);
ntd.ThrotEst = interp2(td.PressVect,td.SpeedVect,td.ThrotEst,ntd.PressVect',ntd.SpeedVect);

Ramp Rate テーブル データを再計算します。

ntd.RampRateKiZ = (1:length(ntd.RampRateKiX))' * (1:length(ntd.RampRateKiY)) * td.Ki;

Pumping Constant の 2 のべき乗の間隔

figure('Tag','CloseMe');
mesh(td.PressVect,td.SpeedVect,td.PumpCon), hold on
mesh(ntd.PressVect,ntd.SpeedVect,ntd.PumpCon)
xlabel('PressVect'), ylabel('SpeedVect'), zlabel('PumpCon')
title(sprintf('Pumping Constant\nOriginal Spacing (%dx%d) vs. Power of Two Spacing (%dx%d)',...
    size(td.PumpCon,1),size(td.PumpCon,2),size(ntd.PumpCon,1),size(ntd.PumpCon,2)));

Pressure Estimate の 2 のべき乗の間隔

clf
mesh(td.ThrotVect,td.SpeedVect,td.PressEst), hold on
mesh(ntd.ThrotVect,ntd.SpeedVect,ntd.PressEst)
xlabel('ThrotVect'), ylabel('SpeedVect'), zlabel('PressEst')
title(sprintf('Pressure Estimate\nOriginal Spacing (%dx%d) vs. Power of Two Spacing (%dx%d)',...
    size(td.PressEst,1),size(td.PressEst,2),size(ntd.PressEst,1),size(ntd.PressEst,2)));

Speed Estimate の 2 のべき乗の間隔

clf
mesh(td.PressVect,td.ThrotVect,td.SpeedEst), hold on,
mesh(ntd.PressVect,ntd.ThrotVect,ntd.SpeedEst)
xlabel('PressVect'), ylabel('ThrotVect'), zlabel('SpeedEst')
title(sprintf('Speed Estimate\nOriginal Spacing (%dx%d) vs. Power of Two Spacing (%dx%d)',...
    size(td.SpeedEst,1),size(td.SpeedEst,2),size(ntd.SpeedEst,1),size(ntd.SpeedEst,2)));

Throttle Estimate の 2 のべき乗の間隔

clf
mesh(td.PressVect,td.SpeedVect,td.ThrotEst), hold on
mesh(ntd.PressVect,ntd.SpeedVect,ntd.ThrotEst)
xlabel('PressVect'), ylabel('SpeedVect'), zlabel('ThrotEst')
title(sprintf('Throttle Estimate\nOriginal Spacing (%dx%d) vs. Power of Two Spacing (%dx%d)',...
    size(td.ThrotEst,1),size(td.ThrotEst,2),size(ntd.ThrotEst,1),size(ntd.ThrotEst,2)));

Ramp Rate Ki の 2 のべき乗の間隔

clf
mesh(td.RampRateKiX,td.RampRateKiY,td.RampRateKiZ'), hold on
mesh(ntd.RampRateKiX,ntd.RampRateKiY,ntd.RampRateKiZ'), hidden off
xlabel('RampRateKiX'), ylabel('RampRateKiY'), zlabel('RampRateKiZ')
title(sprintf('Ramp Rate Ki\nOriginal Spacing (%dx%d) vs. Power of Two Spacing (%dx%d)',...
    size(td.RampRateKiZ,1),size(td.RampRateKiZ,2),size(ntd.RampRateKiZ,1),size(ntd.RampRateKiZ,2)));

既定の構成では、モデルは最上位の信号のシミュレーション データをログに記録します。これらのシミュレーション結果は、ワークスペース変数 sldemo_fuelsys_output に保存されます。新しいデータでモデル ワークスペースを更新する前に、シミュレーションの結果を hDemo.orig_data に保存し、後で 2 のべき乗の等間隔のテーブル シミュレーションと比較できるようにします。

set_param('sldemo_fuelsys','StopTime','8')
sim('sldemo_fuelsys')
hDemo.orig_data = sldemo_fuelsys_output;

モデル ワークスペースに新しいテーブル データを再割り当てします。

hDemo.hWS = get_param('sldemo_fuelsys', 'ModelWorkspace');
hDemo.hWS.assignin('PressEst',   ntd.PressEst);
hDemo.hWS.assignin('PressVect',  ntd.PressVect);
hDemo.hWS.assignin('PumpCon',    ntd.PumpCon);
hDemo.hWS.assignin('SpeedEst',   ntd.SpeedEst);
hDemo.hWS.assignin('SpeedVect',  ntd.SpeedVect);
hDemo.hWS.assignin('ThrotEst',   ntd.ThrotEst);
hDemo.hWS.assignin('ThrotVect',  ntd.ThrotVect);
hDemo.hWS.assignin('RampRateKiX',ntd.RampRateKiX);
hDemo.hWS.assignin('RampRateKiY',ntd.RampRateKiY);
hDemo.hWS.assignin('RampRateKiZ',ntd.RampRateKiZ);

等間隔のデータ用にルックアップ テーブルを再構成します。

hDemo.lookupTables = find_system(get_param('sldemo_fuelsys','Handle'),...
    'BlockType','Lookup_n-D');

for hDemo_blkIdx = 1 : length(hDemo.lookupTables)
    hDemo.blkH = hDemo.lookupTables(hDemo_blkIdx);
    set_param(hDemo.blkH,'IndexSearchMethod','Evenly spaced points')
    set_param(hDemo.blkH,'InterpMethod','None - Flat')
    set_param(hDemo.blkH,'ProcessOutOfRangeInput','None')
end

2 のべき乗の等間隔の実装のシミュレーションを再実行し、シミュレーションの結果を hDemo.pow2_data に保存します。

sim('sldemo_fuelsys')
hDemo.pow2_data = sldemo_fuelsys_output;

燃料流量および空燃比のシミュレーション結果を比較します。シミュレーションでは、Pumping Constant ルックアップ テーブルと Ramp Rate Ki ルックアップ テーブルを使用し、元のテーブル データに対して、2 のべき乗で等間隔に配置されたブレークポイントが非常によく一致していることが示されています。

figure('Tag','CloseMe');
subplot(2,1,1);
plot(hDemo.orig_data.get('fuel').Values.Time, ...
     hDemo.orig_data.get('fuel').Values.Data,'r-');
hold
plot(hDemo.pow2_data.get('fuel').Values.Time, ...
     hDemo.pow2_data.get('fuel').Values.Data,'b-');
ylabel('FuelFlowRate (g/sec)');
title('Fuel Control System: Table Data Comparison');
legend('original','even power of two');
axis([0 8 .75 2.25]);
subplot(2,1,2);
plot(hDemo.orig_data.get('air_fuel_ratio').Values.Time, ...
     hDemo.orig_data.get('air_fuel_ratio').Values.Data,'r-');
hold
plot(hDemo.pow2_data.get('air_fuel_ratio').Values.Time, ...
     hDemo.pow2_data.get('air_fuel_ratio').Values.Data,'b-');
ylabel('Air/Fuel Ratio');
xlabel('Time (sec)');
legend('original','even power of 2','Location','SouthEast');
axis([0 8 11 16]);
Current plot held
Current plot held

空燃比制御システムをリビルドし、生成されたルックアップ テーブル コードの違いを比較します。

slbuild('sldemo_fuelsys/fuel_rate_control');
### Searching for referenced models in model 'fuel_rate_control'.
### Total of 1 models to build.
### Starting build procedure for: fuel_rate_control
### Successful completion of build procedure for: fuel_rate_control

Build Summary

Top model targets:

Model              Build Reason                     Status                        Build Duration
================================================================================================
fuel_rate_control  Generated code was out of date.  Code generated and compiled.  0h 0m 11.379s 

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 11.645s

図 6 は、"Pumping Constant" ルックアップ テーブル用に生成された、同じ部分のコード スニペットを示しています。2 のべき乗の等間隔のブレークポイント用に生成されたコードは、不等間隔のブレークポイントの場合よりも大幅に効率化しています。このコードは、2 つの単純なブレークポイントの計算と 2 次元ルックアップ テーブル データへの直接のインデックスで構成されています。コストのかかる除算が回避され、ブレークポイント データをメモリ内に保持する必要がなくなっています。

rtwtrace('sldemo_fuelsys/fuel_rate_control/airflow_calc/Pumping Constant');

図 6: Pumping Constant ルックアップ用に生成されたコード (2 のべき乗の等間隔のブレークポイント)

例を閉じます。

close(findobj(0,'Tag','CloseMe'));
clear hDemo* td ntd
close_system('sldemo_fuelsys',0);

コード効率化のためのモデル アドバイザー

2 のべき乗の等間隔のブレークポイントを使用してコード効率を向上させる手法は、固定小数点コード生成におけるいくつかの重要な最適化の 1 つです。Simulink モデル アドバイザーは、Simulink および Stateflow モデルのコード効率を向上させる他の手法を特定するための優れたツールです。必ず Embedded Coder フォルダーまたは Simulink Coder™ でチェックを実行してください。

参考

トピック