アルドウィーノを使った
オープンソース、オープンハードの
ローコスト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の点火のソースコードを解析しています。VSCdefineifdefが反映された形でソースコードが見られるのでプログラムを追うのに便利です。Speeduinoのプロジェクトを読み込んだところ、includeファイル無いいうエラーが出ました。別のuCNCのプロジェクトをビルドした時に
framework−arduinostm32が更新されて、インクルードパス変わってしまったようです。赤枠のインクルードの行に波下線が付いて、下にエラー表示されます。


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


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


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


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



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


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


点火スケジュール登録部です。開始角度、上死点からの角度 、現在のクランク角度から、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変数counterTIM2のタイマのフリーランしているハードウェアのカウンタレジスタ)とtimeout_timer_compareを加算して入れています。


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


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


ここまでの解析で、いつ点火するのかを角度から時間に変換しているため、上死点近くで圧縮高くなると、回転が遅くなって登録時の点火タイミングでは、まだ本来の角度まで来ていないというのが、点火パルスが重複する原因と思われます。上にも書きましたが、メイン関数のloopで経過時間別に必要な処理をします。5mSec66.66mSec100mSecなどで、変化の少ないセンサなどは間引いて処理されます。点火噴射スケジュール登録はこれらとは別に最速で実行されます。クランキングのログから、どのくらいの速さでループしているか計算できます。ログの中に回転数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切り替え温度のようです。