line
ケントエンジンの自作ECU化
line

ケントエンジンのECU(インジェクション化)を紹介していきます。
その5


2009年
1月6日
前回の実験によりOSタイマの割込みにより点火タイミングのカウンタ設定のようなシビアなタイミングがソフトウェアで制御できないことが分かりました。ソフトウェアで実現できない場合にはハードウェアで実現することになります(笑)。そこで下図のようにD−FF(74HC74)を使い点火タイミングが正確に制御できるようにしてみます。D−FFのQ出力を8253/54のGATE入力に接続し、カウンタの開始タイミングをハード的に制御します。難点はGPOが4本必要なことです。


下図のように、上死点前ー67.5度D−FFのクリア信号をGPOで解除します。この信号はOSタイマにより撹乱されていますが、D−FFのQ出力はカム角32パルスで同期化されるので、正確に上死点前ー45度で8253/54を開始できます。点火後の上死点では再度クリア信号を出力してD−FFをクリアしておきます。ケントの点火時期はアイドリング時10〜15度、2000rpmくらいから進角が始まり、3000rpmで35〜40度くらいでしょうか?セルモータによるエンジン始動時は0度固定など、別に制御する必要があるかもしれません。


D−FFを使って、早速実験してみました上がカム角32パルスで下が点火タイミングです。乱れていません。


大分前ですが、ここでドエル角についてOR回路で別のGPO信号を使って制御すると書きましたが8253/54をモード0で使い、GATE信号でカウンタの開始を制御する場合には、その必要はありません。8253/54にカウンタ値を設定する場合には下位、上位の順でカウンタ値を設定します。下位バイトを書き込んだ時点でOUT端子がLOW になります。このことは回転数によって適当なタイミングでカウンタ値を書き込めば自由にドエル角確保できることを意味しています。そういえば、とっくに8253/54は廃版ですが、NECのuPD71054も廃版になっていました。唯一、新品で購入できるのはサンハヤトの互換チップです。51、55も揃ってます(笑)。



1月14日
MTUによる回転数とADコンバータによるスロットル開度の入力の実験を行いました。回転数の入力はカム角の32ステートの4つのステートでカム角パルス間隔をMTU#4のキャプチャモードでカウントして4つの平均を計算します。スロットル開度はボリュームの電圧(0〜5V)をSH2に内蔵されているADコンバータで入力します。ADコンバータの変換速度は1chあたり約10uSecです。
エンジンが最高回転の8000rpmで回っていても1ステートの間に4ch連続モードで変換可能です

sub_task(気筒に関係ないエンジンの全体的な処理を行うタスク)のステート0、8、16,24でMTUから回転数を入力します。ステート12ではスロットル用のADコンバータの変換を開始、ステート17で変換結果を得ています。周期割込ハンドラでシリアルコンソールに回転数とスロットル開度を出力します。エンジンエミュレータの擬似パルスの周波数とスロットル用のボリュームを変化させて動作をテストします。

以下、sub_taskの抜粋です。

   ・
   ・

// state 0
// 回転数入力
rev0 = sil_reh_mem (TGR4A);

   ・
   ・

// state 8
// 回転数入力
rev8 = sil_reh_mem (TGR4A);

   ・
   ・

// state 12
// AD0 A:スロットル変換開始
sil_wrb_mem(ADCSR0,0x39);

   ・
   ・

// state 16
// 回転数入力
rev16 = sil_reh_mem (TGR4A);

   ・
   ・

// state 17
// スロットル開度の加速度計算のため前の値を覚えておく
thr_old = thr_new;

//thr_new = sil_reh_mem(ADDRA0);
// ADは右に6BITシフトする

thr_new = ((sil_reh_mem(ADDRA0) >> 6) & 0x03ff);

   ・
   ・

// state 24
// 回転数入力
rev24 = sil_reh_mem (TGR4A);

// rev[0,8,16,24]はクランク軸1回に換算で16分割のうちの
// 4つのサンプルである。
// MTU4のクロック周期はT=28.64MHz/16=558.6nSEC
// rpm=1/(rev*16*T*4)*60
// これを回転数に変換するのは26852846/revの4つの加算
rev = 26852846/(rev0 + rev8 + rev16 + rev24);

以下、シリアルコンソールの出力結果です。回転数は200〜8500rpmくらい、スロットル開度は1〜1023までアナログ値として変換できています。

REV=8568 THR=1
REV=8612 THR=134
REV=8617 THR=320
REV=8620 THR=518
REV=8620 THR=733
REV=8617 THR=910
REV=8617 THR=1023
REV=8651 THR=1023
REV=8329 THR=1023
REV=3974 THR=1023
REV=1820 THR=1023
REV=1309 THR=1023
REV=920 THR=1023
REV=807 THR=1023
REV=669 THR=1023
REV=496 THR=1023


1月21日
マップの4点補完について考察します。
燃料の噴射量と進角値はマップから基本値を得ますが通常は回転数、スロットル開度ともマップの格子点から外れています。この時は近傍の4点から補完をする必要があります。下図でをマップの格子点とし、例としてスロットル開度が7%回転数1700回転の時のe点の値を補完して計算してみます

簡単にするため回転数は1000、1500、2000、2500のみ、スロットル開度は0、3、5、10%のみの噴射量の4x4のマップを作ります。

// 噴射量マップ
//            1000, 1500, 2000, 2500 回転数
// index         0     1     2     3
inj_val[][]={{  10,   20,   30,   40}, // 0%  0
                20,   30,   40,   50},  // 3%  1
                35,   45,   55,   65},  // 5%  2
                40,   50,   60,   70}}; // 10% 3


補完は以下の処理で計算できます。

1.現在の回転数の1700から
低い側、高い側のインデックスを得ます。
    rev_low_index = 1
    rev_high_index = 2

2.
在のスロットル開度7%から低い側、高い側のインデックスを得ます。
    thr_low_index = 2
    thr_high_index = 3

3.1、2から得られた4つの低高インデックスよりマップから各格子点の噴射量を得ます。
    a=inj_val
[thr_high_index][rev_low_index]
     =inj_val[3][1] = 50

    b=inj_val
[thr_high_index][rev_high_index]
     =inj_val[3][2] = 60

    c=inj_val
[thr_low_index][rev_low_index]
     =inj_val[2][1] = 45

    d=inj_val
[thr_low_index][rev_high_index]
     =inj_val[2][2] = 55

4.回転数に関して
    ba間の回転数の差分
    rev_diff=2000-1500 = 500

    入力回転数とaの差分
    rev_low_diff=1700-1500 = 200

    bと入力数回転数の差分
    rev_high_diff=2000-1700 = 300をそれぞれ計算します。

    上記と入力回転数からab間の補完値fを得ます。
   f=(rev_high_diff*a + rev_low_diff*b)/rev_diff
     =(300*50 + 200*60)/500
     =(15000 + 12000)/500
     = 54

    dc間の回転数の差分
    rev_diff=2000-1500 = 500

    入力回転数とcの差分
    rev_low_diff=1700-1500 = 200

    dと入力回転数の差分
    rev_high_diff=2000-1700 = 300をそれぞれ計算します。

    上記と
入力回転数からcd間の補完値gを得ます。
   g=(rev_high_diff*c + rev_low_diff*d)/rev_diff
     =(300*45 + 200*55)/500
     =(13500 + 11000)/500
     = 49

5.スロットル開度に関して
    fg間のスロットル開度の差分
  thr_diff=10-5 = 5

    fと入力スロットル開度の差分
  thr_high_diff=10-7 = 3

    入力スロットル開度とgの差分
  thr_low_diff=7-5 = 2をそれぞれ計算します

    上記と入力スロットル開度、4で得られたf=54g=49から補完値eを計算します。

  e=(thr_low_diff*f + thr_high_diff*g)/thr_diff
     =(2*54 + 3*49)/5
     = 51


以上のような計算で4点補完ができます。進角値についても同じ手法で2次元マップから4点補完を行います。


1月22日
早速、4点補完のテストプログラムを作ってみました。以下テストプログラムです。

#include <stdio.h>

/* 噴射量マップ                                           */
/*                1000, 1500, 2000, 2500 回転数          */
/* index              0     1     2     3         index  */
int inj_val[4][4]={{ 10,   20,   30,   40}, /* 0%  0     */
                   { 20,   30,   40,   50}, /* 3%  1     */
                   { 35,   45,   55,   65}, /* 5%  2     */
                   { 40,   50,   60,   70}};/* 10% 3     */

/********************************************/
/* 回転数、スロットル開度から4点補完をする  */
/*                                          */
/* get_ave:                                 */
/********************************************/
int get_ave(int rev, int thr)
{
int rev_index;
int rev_diff, rev_l_diff, rev_h_diff;
int thr_index;
int thr_diff, thr_l_diff, thr_h_diff;
int a, b, c, d;
int e, f, g;

    /* 入力回転数からインデックスと差分を計算します */
    if ((rev >= 1000) && (rev < 1500)) {
        rev_index = 0;
        rev_diff = 1500 - 1000;
        rev_l_diff = rev - 1000;
        rev_h_diff= 1500 - rev;
    }
    else if ((rev >= 1500) && (rev < 2000)) {
        rev_index = 1;
        rev_diff = 2000 - 1500;
        rev_l_diff = rev - 1500;
        rev_h_diff = 2000 - rev;
    }
    else if ((rev >= 2000) && (rev <= 2500)) {
        rev_index = 2;
        rev_diff = 2500 - 2000;
        rev_l_diff = rev - 2000;
        rev_h_diff = 2500 - rev;
    }

    /* 入力スロットル開度からインデックスと差分を計算します */
    if ((thr >= 0) && (thr < 3)) {
        thr_index = 0;
        thr_diff = 3 - 0;
        thr_l_diff = thr - 0;
        thr_h_diff = 3 - thr;
    }
    else if ((thr >= 3) && (thr < 5)) {
        thr_index = 1;
        thr_diff = 5 - 3;
        thr_l_diff = thr - 3;
        thr_h_diff = 5 - thr;
    }
    else if ((thr >= 5) && (thr <= 10)) {
        thr_index = 2;
        thr_diff = 10 - 5;
        thr_l_diff = thr - 5;
        thr_h_diff = 10 - thr;
    }

    /* 得られた低高インデックスより4点の格子点の噴射量を得ます */
    a = inj_val[thr_index + 1][rev_index    ];
    b = inj_val[thr_index + 1][rev_index + 1];
    c = inj_val[thr_index    ][rev_index    ];
    d = inj_val[thr_index    ][rev_index + 1];

    /* ab間の回転数の補完値fを得ます */
    f = (rev_h_diff*a + rev_l_diff*b)/rev_diff;

    /* cd間の回転数の補完値gを得ます */
    g = (rev_h_diff*c + rev_l_diff*d)/rev_diff;

    /* 上記のfとgからスロットル開度の補完値eを計算します */
    e = (thr_l_diff*f + thr_h_diff*g)/thr_diff;

    return e;
}


void main(void )
{
int rev, thr;

    rev = 1700;
    thr = 7;
    printf("ave = %d\n", get_ave(rev, thr));

    rev = 1000;
    thr = 5;
    printf("ave = %d\n", get_ave(rev, thr));

    rev = 1000;
    thr = 0;
    printf("ave = %d\n", get_ave(rev, thr));

    rev = 2500;
    thr = 10;
    printf("ave = %d\n", get_ave(rev, thr));

}

以下、実行結果です。正しく動作しています。

# cc -o get_ave get_ave.c
# ./get_ave
ave = 51
ave = 35
ave = 10
ave = 70


1月26日
switch文の考察をします。気筒用とサブタスクとも以下のように現在のステート値(カム角の32の位置)によって処理を振り分ける必要があります。そこで気になるのが
switch文がif〜then〜elseのように逐次的に展開されるのかということです。もしそうであれば一番最後は31個の条件判断をした後に実行されることになり、ずいぶんと遅れるのではという心配です。

switch (state) {

    case CAM_0:
    // CAM_0の処理
           ・
            ・
    break;

    case CAM_1:
    // CAM_1の処理
           ・
           ・
    break;

    case CAM_2:
    // CAM_2の処理
            ・
            ・
    break;

       ・
       ・

    case CAM_31:
    // CAM_31の処理
           ・
           ・

    break;

    default:
            ・
            ・
    break;
}


gccはswitch文をテーブル形式に変換して実行速度の低下を防いでいるということなのでサンプルプログラムをコンパイルしてアセンブラのリストを出力してみます。ちなみにsh2のコンパイラのバージョンはgccの2.95.3です。以下サンプルプログラムです。

int sw(int s)
{
volatile int i;

    switch(s) {

      case 0:
        i = 0;
      break;

      case 1:
        i = 10;
      break;


      case 2:
        i = 20;
      break;


      case 3:
        i = 30;
      break;

    }

    return i;
}

main(void)
{
int i;
volatile int r;


    for (i=0; i<3; i++)
        r = sw(i);

}

以下、アセンブラリストのsw関数の先頭のみ抜粋しました。if〜then〜elseでは無く、テーブルになって直接ジャンプしているのが分かります。ということで長いswitch〜case文を書いても問題無いです。
_sw:
mov.l r14,@-r15
add #-8,r15
mov r15,r14
mov.l r4,@r14
mov.l @r14,r1
mov #0,r2
mov #3,r3
sub r2,r1
cmp/hi r3,r1
bt .L3
mova .L8,r0
add r1,r1
mov.w @(r0,r1),r1
add r0,r1
jmp @r1 <-レジスタの間接ジャンプ命令になって、直接処理へ飛んでいる
nop
.align 2
.align 2
.L8:
.word .L4-.L8
.word .L5-.L8
.word .L6-.L8
.word .L7-.L8
.align 2
.L4:
mov #0,r1 <-case 0の部分
mov.l r1,@(4,r14)
bra .L3
nop
.align 2
.L5:
mov #10,r1 <-case 1の部分
mov.l r1,@(4,r14)
bra .L3
 nop
  ・
  ・

2月3日
モノタロウで購入したセヴン関係のタカチのアルミケースです。ECU用の4x20桁LCD表示器や別のステッピングモータドライブタコメータ点火ユニット用も合わせて購入しました。セミ・オートマのSH2モータドライブ基板用は別送になりました。


会社のお昼休み工作です。エンジンエミュレータをケースに入れました。こちらは100円ショップのCD入れです。ボリュームやLEDを付けただけでケーブルは配線していません。これを見るとタッパウェア工作(ラジオの製作、初歩のラジオ)を思い出します(笑)。



2月8日
コンソール用のアルミケースをガレージに持って帰りスイッチLCDの穴を加工します。qcadで図面を書いてCNCで加工しました。エンドミルの直径は1.5mmです。


下がqcadで書いたスイッチ部の図面です。4角形が6個あるだけの単純な図面です。


加工の終ったアルミケースにLCDスイッチを仮止めしました。下側のコネクタの穴はフライス盤に固定しにくいので手加工し
ました。ケース表面の固定ネジはステンレスのサラネジに変更予定です。



2月20日
会社のお昼休みにコンソールのLCD制御プログラムのテストを行いました。LCDパネルは結構のろまなので表示部はタスク形式にする必要があります。


エンジンエミュレータも少しづつ作っています。入力系のケーブルとコネクタを配線しました。



3月14日
秋月の通販で購入した部品ですインジェクタ用のドライバ用FETダイオード、20桁x4行のLCDパネル(バックライト付き)予備用です。



3月16日
セミオートマでも同じSH2ボードを使ってますが、CPUの温度がけっこう高くなります。そこでCPUにヒートシンクを取り付けます。通販で購入した熱伝導エポキシでCPUにヒートシンクに直接取り付けました。


ガレージのフライス盤で穴開け加工したECU用の筐体カバーを持ってきました。



3月18日
ECUボードに5Vのレギュレータを追加しました。12Vから5Vに降圧します。手持ちのLM2596が3.3Vの固定電圧のため、外付け抵抗を追加して5Vに調整して使います。低い電圧を高くするのは抵抗を付けるだけで良いです。



line