アルドウィーノを使った
オープンソース、オープンハードの
ローコストECU
テージのECUが壊れているみたいなので
Speeduino(STM32)に装換しました。
その20:
不均等なプライマリパルスが入力された時の
Speeduinoの動作と考察
2025年
2月24日
アナログディスカバリのロジアナから生成したパターンジェネレータの不均等プライマリパルスでSpeeduinoを動かしてみます。点火パルスが重複して異常です。点火時期は0度に設定しています。手動で入力した不均等パルスでは、この現象は出なかったです。

上の波形を拡大してみます。赤ラインが基準です。基準は36歯ギアのプライマリとセカンダリパルス位置関係で決まります。現在は基準から130度前(トリガアングル130度)が上死点です。プライマリパルス1波が20度なので130度は6.5パルス分です。2発出ている点火パルスの前が間違い、後が6.5パルスで正解です。正しい位置より前に出るとケッチン状態になって始動性が悪くなります。

確認のため、基準=上死点(トリガアングル0度)に設定すると重複は無くなり、正常になります。トリガアングルを調整すると、正しく動作するのは30〜−30度くらいの狭い範囲です。また、不均等でない整列しているプライマリパルスなら、どこにトリガアングルを設定しても問題なく動作します。重複して点火パルスが出るのは電気的ノイズでは無く、Speeduinoの仕様との不適合の可能性が高いです。

2月27日
点火パルスが重複して出る不具合の件で、Speeduinoの点火のソースコードを解析しています。VSCはdefineやifdefが反映された形でソースコードが見られるのでプログラムを追うのに便利です。Speeduinoのプロジェクトを読み込んだところ、includeファイルが無いいうエラーが出ました。別のuCNCのプロジェクトをビルドした時にframework−arduinostm32が更新されて、インクルードパスが変わってしまったようです。赤枠のインクルードの行に波下線が付いて、下にエラー表示されます。

エラー表示のXにカーソルを合わせると、ヒントマークに変わります。

プルダウンメニューにインクルードパスの追加が出てきます。

念のため、上で表示されたディレクトリを確認すると見つからなかったclock.hがありました。

クリックして追加すると、インクルードパスに1行追加されます。この動作を3回繰り返すと、エラーが無くなりました。

framework−arduinostm32のあるディレクトリを確認すると、2/7に作られていてました。丁度、uCNCをやり始めた時です。元のディレクトリはリネームされてました。

ソースコードの解析で現在までに分かった事をまとめておきます。メイン関数のloopは経過時間別に必要な処理するように記述されています。時間は1mSec、5mSec、33.33mSec、66.66mSec、100mSec、1Secです。これらの必要な時間にセンサの読み取り、マップの読み取りなどを行います。これ以外の最速で実行される部分に通信処理と点火、噴射のスケジュール登録があります。点火と噴射のスケジュールはハードウェアタイマ(ワンショットモード)のオーバーフロー割り込みを使ってパルス発生させる機能です。

点火スケジュール登録部です。開始角度、上死点からの角度 、現在のクランク角度から、calculateIgnitionTimeout関数で開始時間を計算して、スケジュール構造体、開始時間、ドエル時間を引数にsetIgnitionSchedule関数でスケジュール登録しています。

開始時間の計算はangleToTimeMicroSecPerDegree関数によって角度から時間に変換されます。

setIgnitionSchedule関数はRUN中(スケジュール中)か、そうで無い場合で振り分けられます。

下はRUN中で無い時の_setIgnitionScheduleRunning関数です。割り込みを禁止し、タイマレジスタに設定して、割り込みを許可しています。scheduler.hに下のような記述があります。
・このスケジューラは、燃料システムと点火システムで使用する2つのスケジュールを維持するように設計されています。
・使用中の各タイマーからのオーバーフローベクトルがオーバーフローするのを待機することで機能し、これにより割り込みがトリガーされます。
・これは他のスケジューラーとは異なり、呼び出しは非定期的です。
(つまり、ある時刻にイベントをスケジューリングし、それが発生すると、明示的に要求/再登録しない限り、そのイベントは再発生しません)。
・各タイマーは常に1つのコールバックしか持つことができません。setCallback関数を2回呼び出すと、元のスケジュールは上書きされ、発生しなくなります。

こちらはRUN中の次のスケジュール登録をする_setIgnitionScheduleNext関数です。次の準備のために変数に設定して終了しています。この後はスケジュール登録とハードウェアタイマの関係を調べる必要があります。

3月2日
ソースコードの解析での続きです。点火用のタイマはTIM2を使います。下のboard_stm32_offictal.hのコメントは間違えています。TIM2は16ビットのアップカウンタと4個のコンペアレジスタから構成されます。コンペアレジスタとアップカウンタが一致した時に割り込みを発生して、ハンドラを呼びます。4つのコンペアレジスタはそれぞれIGN1〜IGN4に対応しています。テージではIGN1(水平気筒)とIGN2(垂直気筒)の2つみです。クロックは250KHzで約0.26秒でオバーフローします。1つの点火に使えるリソースは共通アップカウンタと1つのコンペアレジスタなので、必要な時間を待つには、アップカウンタの値を読んで、必要な時間を加算してコンペアレジスタに設定する必要があります。点火パルスを出力するには、開始(今から何uSec後にONする(通電開始))と終了(今から何uSec後にOFFする(点火))で、この動作をハンドラ(IGN信号のポート出力のON/OFF)を切替てセットで行う必要があります。

メインループの点火動作の解析を進めます。下はIGN1のみに単純化したメインループです。点火タイミングのスケジュールを登録するために、getCrankAngle関数で現在のクランク角度を得ています。

getCrankAngle関数の大まかな動作は初めに、処理に使う一時使用変数を用意します。

続いて、割り込み禁止にして、計算に必要な広域変数を一時使用変数にコピーして、割り込み許可します。

クランク角の計算は現在のプライマリ歯カウント数にプライマリ歯1つ分の角度を掛けて、基準になるプライマリ歯の1番目の上死点からの位置を加算して粗い角度を計算します。その後、経過時間分を補正しています。

3月3日
点火のソースコードの解析での続きです。getCrankAngle関数に続いて、calculateIgnitionTimeout関数で点火の開始時間を計算しています。

calculateIgnitionTimout関数の大まかな動きは1番シリンダの上死点が基準と一致しているか、していないかで処理が分かれています。これはchannel1IgnDegrees変数で切り分けています。ソースにも次のコメントがあります”これは1番シリンダが上死点からに達するまでのクランク角度(これは、ほぼすべてのエンジンで明らかに0ですが、異なるものもあります)”。普通のエンジンでは1番シリンダの上死点がクランク0度=基準に設定されていますが、今回デュアルホイールに改造したテージでは36歯ギアの取り付け鉄ボルトがセカンダリパルスなので、たまたま上死点前約130度になっています。今回の場合は一致していないので角度の差分を修正した後で緑枠の_calculateIgnitionTimout関数が実行されます。

_calculateIgnitionTimout関数は開始角から現在クランク角を引いて角度の差分を計算し、その後、angleToTimeMicroPerDegree関数で角度を時間に変換しています。

angleToTimeMicroPerDegree関数は引数の角度に1度あたりの時間を掛けて、時間に変換し、その後、丸めて返しています。

3月5日
点火のソースコード解析の続きです。setIgnitionSchedule関数を解析します。実際に点火動作を行う肝心の部分で、今から何マイクロSec後に点火するかをスケジュール登録します。

現在のステータス変数でRUN中か、そうで無いかで登録関数が切り分けられます。下は切り分けのためのコードです。

RUN中で無いときは、名前が紛らわしいですが、_setIgnitionSchedulesRunning関数が使われます。RUN中は点火パルスが通電している時間で、幅は狭いので多くの場合はこちらを通ることになります。引数のtimeoutをコンペアレジスタ用に変換してtimeout_timer_compare変数に入れています。タイマのレジスタにアクセスする前に割り込みを禁止にし、その後、startCompare変数にcounter(TIM2のタイマのフリーランしているハードウェアのカウンタレジスタ)とtimeout_timer_compareを加算して入れています。

SET_COMPAREはdefineで、タイマのハードウェアのコンペアレジスタにstartCompare変数の値を書いています。その後、ステータスをペンディングにして割り込みを許可して終了です。

RUN中のときは_setIgnitionScheduleNext関数が使われます。ここでは次のスケジュールのために設定をして、終了します。

ここまでの解析で、いつ点火するのかを角度から時間に変換しているため、上死点近くで圧縮が高くなると、回転が遅くなって登録時の点火タイミングでは、まだ本来の角度まで来ていないというのが、点火パルスが重複する原因と思われます。上にも書きましたが、メイン関数のloopで経過時間別に必要な処理をします。5mSec、66.66mSec、100mSecなどで、変化の少ないセンサなどは間引いて処理されます。点火と噴射のスケジュール登録はこれらとは別に最速で実行されます。クランキングのログから、どのくらいの速さでループしているか計算できます。ログの中に回転数(rpm)と1秒あたりのメインループのループ数(loop/S)があります。rpmを60で割ってからloop/Sを割れば1回転中に回ったループ数が分かります。クランキング時の331回転で3527になりました。因みに、9500回転では約123になります。10以下になると、点火、噴射の処理が不正確になります。

下は約280回転でクランキング中の重複の無い正常な点火タイミングの波形です。下の黄色の基準に対してIGN1はプライマリパルスで6.5個手前にあります。1パルスは20度なので20*6.5=130度で設定しているクランクアングルと一致します。IGN2はプライマリパルスで約7個後にあります。(6.5+7)*20=270度でLツインの点火間隔と一致します。緑の数字は広域変数のtoothCurrentCountでプライマリパルスの立ち上がりで起動される割り込みルーチンでインクリメントされます。36歯なので1〜36の間でカウントされ、これを参照すれば今どこのプライマリパルスを通過したかが分かります。上の表計算ではクランキング中は3500回ほどメインループを回っているので、ループ内でtoothCurrentCountを参照すれば、かなり正確に今どこにいるか分かります。例えばIGN1の場合は下の波形からtoothCurrentCountが30の時に、待ち時間6.25mSecでIGN1の点火スケジュールを1回のみ登録すれば、少なくとも複数パルスが出ることは無くなると思います。ただし、プライマリパルスの位置で点火するので、クランキング中はクランクアングル設定は無効になります。待ち時間の6.25mSecはクランキング回転数によって変化しますが、これを回避するはクランキング時の点火タイミングをプライマリパルスの立ち上がりに合わせれば良いです。この場合はIGN1/IGN2とも、待ち時間無で点火登録できます。因みに、プライマリパルスが36歯の場合、270が20で割り切れないので両方のタイミングを待ち時間を0にはできません。40歯なら可能です。

3月6日
プログラムを修正するにあたり、修正部分をTunerStudioから動的に制御できないか調べます。修正したプログラムをイネーブルにするスイッチとIGN1のプライマリパルスからの待ち時間が可変できると便利です。とりあえず、IGN2は固定しておきます。TunerStudioのメニューで流用できそうなパラメータを探すと、アクセサリの一番上に電動ファンの制御がありました。テージは空冷なので、この機能は使っていません。

ファンのモードにOFF、ON/OFFとPWMがあり、ON/OFFは修正コードのイネーブルに使えそうです。また、切り替え温度でIGN1の待ち時間が設定できそうです。


上のパラメータがソースコードでは何という変数名なのか調べる必要があります。TunerStudioの機能名とSpeeduinoの変数名は一致していないので、初めにDoxygenのWEBページでfanで検索して当たりを付けます。fanが付く変数や関数が多数ヒットしました。fanEnableは分かりやすく、モード値が入っていることが分かります。


切り替え温度は名前からは、それらしいのが見つからないのでSpeeduinoのソースコードで上のキーワードを順次検索します。fanSPが切り替え温度のようです。

