# UNOR4_Chibi-T_EFI
Arduino UNO R4(RA4M1)/ 互換環境上で動作するエコラン車両用ECUプログラム
- メイン周期処理:
Routine- (AGTimerライブラリ
AGTimer.initによる24usec毎処理)
- (AGTimerライブラリ
- 割り込み:
- 車速入力:
WH_PULSE_ISR - クランク角 A: Arduino attachInterrupt +
ReadNe_ISR(パルスでクランク角更新) - カム角:
G_PULSE_ISR
- 車速入力:
- 高速 GPIO:
fastestdigitalWrite/fastestdigitalRead - フリータスク: FreeRTOS
statusTask: Serial1 出力 100ms 周期 (10Hz) / SerialUSB 出力 500ms 周期 (2Hz)- 5ms 周期
mapConsoleTask: MAP 書き換えコンソール
- src/main.cpp : コアロジック
- src/map_store.cpp : MAP のダブルバンク保持と EEPROM 永続化
- src/map_console.cpp : USB シリアル経由の MAP 書き換えコンソール
- src/fastestdigitalRW.hpp : ボード別最速 GPIO
- tools/send_map.py : PC 側の MAP 転送スクリプト
- lib/AGTimer_R4_Library : 周期タイマ
- platformio.ini : ビルド環境定義
- microSD/ : MAP 用 CSV(
tools/send_map.pyでそのまま転送できる) - log/ : 記録例
- document/arduino-workflow.puml : PlantUML ワークフロー図(詳細版)
PlatformIO 環境: platformio.ini
| Env | ボード | 備考 |
|---|---|---|
uno_r4_minima |
Arduino UNO R4 Minima | デフォルト |
rmc_ra4m1_20 |
カスタム RA4M1 (-D rmc_ra4m1_20) |
SD動作分岐あり |
ビルド例:
pio run -e uno_r4_minima
pio run -t upload
pio device monitor -b 115200| 項目 | 定義 | 説明 |
|---|---|---|
| ROUTINE_CYCLE_US | 24 | メイン周期 (µs) |
| PERIMETER_MM | 1548 | タイヤ周長(mm) |
| TACHO_RPM_MAX | 6000 | レブリミット (回転数上限保護) |
| Dwell_Time_US | 5000 | ドゥエル時間(us) IGコイルへの充電時間 |
| start_RPM | 1500 | スタータ自動停止の判定回転数(RPM) MAP切替用の専用定数ではない |
始動時・通常時で切り替わる専用定数(start_INJ_time 等)は無い。
燃料噴射量・点火進角・燃料噴射終了タイミングは全て単一の MAP テーブルから rpm に応じて読み出され、ビルドし直さずに USB シリアルから調整できる。
(MAP 節参照)
- WH_IN / G_IN / STR_IN / ENGOFF_IN は
74HC14によるシュミットトリガ回路でチャタリング防止・反転入力 - INJ_OUT / IGN_OUT / STR_OUT / DISRESET_OUT は
Nch MOSFETによる LOW アクティブ。 - 詳細は src/main.cpp 参照。
| 信号 | 物理ピン | 説明 |
|---|---|---|
| NE_A_IN | 2 | クランク角 A (1deg/パルス) |
| NE_B_IN | 8 | クランク角 B (位相判定) |
| NE_Z_IN | 9 | クランク角0deg基準 |
| WH_IN | 3 | 車軸パルス入力 1回転で1パルスIN |
| G_IN | 5 | カムパルス入力 クランク角720°毎に1パルス入力 |
| STR_IN | 6 | エンジンスタートスイッチ (LOW=ON) |
| ENGOFF_IN | 7 | キルスイッチ (LOW=ON=運転状態, HIGH=OFF=停止状態) |
| INJ_OUT | A0 | 燃料噴射 (LOW=ON) |
| IGN_OUT | A1 | 点火 (LOW=ON) |
| STR_OUT | A2 | スタータリレー (LOW=ON) |
| DISRESET_OUT | A3 | リセットランプ (LOW=点灯, HIGH=消灯) |
| MA735_CS | 10 | MA735 SPI CS |
LOW アクティブ出力注意 (INJ/IGN/STR/DISRESET)。
-
割り込みで角度/速度更新
-
ReadNe_ISR: NE_A パルスでNe_deg±1 更新(NE_Bの位相参照) -
G_PULSE_ISR: カムパルス同期フラグ設定 -
角度モデルと usecperdig
usecperdigは非エンコーダ・非MA735モード時にのみ補間に使用。
Ne_deg += ROUTINE_CYCLE_US / usecperdig(エンコーダ無効かつ MA735無効時)。- 通常は
EncoderEnabled = trueのため使用されない(初期値1.0、無信号タイムアウト時にリセット)。
-
-
周期関数
Routine:-
スタート/キル状態評価
-
スタータステートマシン (
StarterState): キルスイッチON時にスタートボタン押下(エッジ)でクランキング開始。
start_RPM以上を 0.1秒 維持で始動成功(スタータ自動停止)、2秒タイムアウトで始動失敗(再押しで再試行可)。
低rpm帯のMAP行(400/800/1200rpm、inj_end_ca=20)が始動用パラメータとして自動的に採用される
(始動専用のMAP切替フラグは無い。MAP 節参照)。STR_IDLE → [ボタン押下(エッジ)+キルスイッチON] → STR_CRANKING STR_CRANKING → [start_RPM以上を0.1秒維持] → STR_STARTED(スタータOFF) STR_CRANKING → [2秒タイムアウト] → STR_FAILED(スタータOFF・ENG_ON OFF) STR_FAILED → [ボタン再押し(エッジ)] → STR_CRANKING キルスイッチOFF → 常に STR_IDLE へ強制リセット(`Launch` はリセットしない)
-
カム同期タイムアウト→
cycleReset -
マップ更新:
updateEngineMap -
噴射開始条件 (角度 >=
INJ_STR_CA、MAP行のinj_end_caと噴射時間から逆算、0CA跨ぎ対応) -
360CA通過時に噴射継続中なら強制OFF(安全リセット)
-
噴射時間経過で OFF & 燃料量積算
-
点火進角計算 & 保持時間後 OFF
-
-
タスク
statusTask:- 高速パス (10Hz/100ms): Serial1 CSV出力 (
snprintf+ 一括write) - 低速パス (2Hz/500ms): 状態計算 (燃費, 稼働時間, 速度減衰), SerialUSB 出力
- 高速パス (10Hz/100ms): Serial1 CSV出力 (
噴射終了角度: calculatedINJ_END_CA(MAPの該当行の inj_end_ca 列をそのまま採用。
始動時・通常時で切り替える専用ロジックは無く、rpmに応じたMAP行選択(MAP 節)に一本化されている)
噴射開始角度: INJ_STR_CA(毎サイクルリセット時に逆算)
INJ_STR_CA = calculatedINJ_END_CA - (calculatedINJ_time * 100[µs] * 360[deg]) / tachoWidth[µs]
// INJ_STR_CA < 0 の場合(0CA跨ぎ): INJ_STR_CA += 720
0CA跨ぎ対応: 噴射開始が720CA付近・終了が次サイクル序盤(例: 716CA→20CA)の場合、
INJ_STR_CA が負値になるため +720 で正規化(0〜720CAの範囲に収める)。
cycleReset() 呼び出し時に噴射継続中(INJ_Status == 2)であれば INJ状態をリセットせず、
タイマーで正常終了させる。
360CA安全リセット: 1サイクル内で360CAを通過した時点でも噴射中の場合は強制OFFし、
意図しない噴射継続を防止(inj360Reset フラグで1サイクルに1回限り実行)。
噴射時間: calculatedINJ_time (0.1ms単位) → 実際 µs: injDuration = calculatedINJ_time * 100
燃料量近似:
gasml += ( (Δt * 0.0000007) + 0.0015 ) / 1.5073
係数は実測燃費から逆算したインジェクタ流量補正。
- 進角:
calculatedIGN_CA - ドゥエル時間(µs):
Dwell_Time_US- ドゥエル時間をクランク角に変換:
Dwell_Time_CA = Dwell_Time_US * 360 / tachoWidth
- ドゥエル時間をクランク角に変換:
- 条件:
Ne_deg >= (360 - calculatedIGN_CA - Dwell_Time_CA)で点火 LOW - 保持: クランク角が
360 - calculatedIGN_CAに達する orDwell_Time_US経過で HIGH 戻し - 点火: HIGH 戻しの瞬間にスパークプラグから放電
MAP は RAM 上のダブルバンク(src/map_store.cpp)で保持し、ビルドし直さずに USB シリアル経由で書き換えられる。
-
参照ロジック: RPM 昇順テーブルの、最初に
tachoRpm < rpmとなるエントリを採用(階段状)。 -
行数は可変(最大 24 行)。
-
列順(CSV / SD 読込も同一。4列必須):
rpm,inj_time(x0.1msec),ign_ca(deg),inj_end_ca(deg)inj_end_ca(噴射終了タイミング角度、0〜720CA)は、スタートボタン押下直後を含む全rpm帯で共通のMAP行から読み出される。
始動時専用のハードコード定数は無い。- 3列(旧書式)のCSVはエラーになる。
tools/send_map.py --legacy-inj-end-ca <値>で噴射終了角度を明示的に補って送ること(無断補完はしない)。
-
内蔵
defaultMap(src/map_store.cpp)は、400/800/1200/1600rpm をブレークポイントに以下のように構成されている:RPM未満 inj_time ign_ca inj_end_ca 意味 400 0 0 0 400rpm以下はアイドリング不能なのでエンジン停止相当 800 80 0 20 800rpm以下は始動状態 1200 80 0 20 1200rpm以下は始動状態 1600〜 可変 可変 680 通常走行域 -
起動時の優先順位: EEPROM に有効な MAP があればそれを採用、無ければ内蔵
defaultMapへフォールバック。
起動時にMAP SOURCE: EEPROM (15 rows)のようにどちらを使ったか出力する。 -
書き換えは AGTimer 割り込みから参照されるバンクを非アクティブ側で組み立ててから 1 バイトのストアで切り替えるため、エンジン稼働中でも安全に反映できる。
回転数を見ながら GUI で MAP を詰めるためのWebアプリ(webgui/)。
Windows / macOS / Ubuntu 共通。
接続方法は 2 通りあり、どちらも同じフロントエンドが動く。
| 方法 | 必要なもの | 使えるブラウザ |
|---|---|---|
| GitHub Pages(Web Serial) | なし(ブラウザだけ) | Chrome / Edge |
| ローカルサーバ | Python 3 + pyserial |
すべて |
# ローカルサーバ(追加の依存なし。標準ライブラリ + pyserial だけ)
python3 tools/map_gui.py # ポート自動検出、ブラウザを開く
python3 tools/map_gui.py --port /dev/cu.usbmodem1101
python3 tools/map_gui.py --fake # 実機なしでGUIを試す(モックECU)GitHub Pages 版は https://todateman.github.io/UNOR4_Chibi-T_EFI/ で、
Chrome / Edge の「Web Serial で接続」から直接 USB に繋がる。
外部 CDN を一切使っておらず Service Worker を積んであるので、一度開けばピットでオフラインでも動く。
Pages 版にはローカルサーバが居ないため、「ローカルサーバ経由」「モック ECU」のボタンは
自動的に無効になる(起動時に /api/status の応答を確かめている)。
公開するには初回だけ次の 2 つが必要。
- リポジトリの Settings → Pages → Source を
GitHub Actionsにする webgui/を含む変更をmainへマージする
(.github/workflows/pages.yml はmainへの push でのみ動く。
手動実行したい場合は Actions タブのDeploy Web GUI to Pages→Run workflow)
デプロイ前にテスト(node --test と test_map_protocol.py)が走り、失敗すると公開されない。
できること:
- CSV の読み込み / 書き出し(
microSD/RPM_*.CSVと同じ書式) - 噴射時間・点火進角・噴射終了角の 3 列とも、グラフの点をドラッグして編集。
選択範囲に ±5% / ±1 段 / 線形補間 / 平滑化。グラフは横に並ぶので、調整しながら右の MAP テーブルが常に見える
(噴射終了角のグラフは既定で非表示。噴射終了角のグラフにチェックで 3 枚目が出る) - 噴射終了角は 720CA で周回する角度として扱う。
線形補間・平滑化・差分表示・ライブ適用の変化量判定はすべて近い方の回りで測る
(680CA と 20CA はクランク角では 60CA しか離れていない。素の引き算だと既定 MAP の20 → 680の境界で補間が逆回りしてしまう) - Undo / Redo(Ctrl+Z / Ctrl+Shift+Z)と、実機 MAP との差分表示
- ライブトレース: 現在の回転数と、ファームが実際に採用している行をハイライト。
行ごとの滞在時間をヒートマップで表示 - ライブ適用: RPM ブレークポイントが実機と一致していれば、変更行だけを
MAP SETで送る。
セッションを使わないのでテレメトリが途切れず、エンジンを止める必要もない - テレメトリの CSV 記録と再生
- 転送後は CRC で検証する(全行読み戻すより速く、実機の内部表現と直接比較できる)
安全のために GUI 側で次を強制している。
MAP SAVEはeng=OFFかつrpm=0のときだけ押せる。
ベンチではノイズでERR ENGINE_RUNNINGが返ることがあるので最大 5 回リトライする- 稼働中は RPM 列を編集できない。
MAP SETは該当 RPM が無いと行を挿入する仕様なので、走行中に意図せずテーブル構造が変わるのを防ぐ - ライブ適用は明示的に有効化したときだけ動き、60 秒無操作で自動解除。
1 操作の変化量が噴射 ±1.0ms / 進角 ±5CA / 噴射終了 ±30CA を超える場合は送らない - 送信前にファームと同じ規則で検証し、違反セルを赤表示して転送を止める
- MAP 最終行の RPM がレブリミット(
TACHO_RPM_MAX= 6000)より手前だと警告する。
最終行を超えると噴射・点火が止まる(mapOutOfRange)ため - 噴射区間が 360CA を跨ぐ行を警告する。
ファームはNe_deg >= 360で噴射中なら強制 OFF する(360CA 安全リセット)ので、その行は MAP の指示より噴射が短くなる。
噴射区間はファームと同じ式inj_time × 100[us] × 360 / tachoWidthで角度に直し、その行が受け持つ最大 rpm で最悪値を見る。
噴射終了角はinjとrpmを合わせて初めて実害が見えるので、列単体の範囲検査では拾えない - 噴射終了角が前行から 90CA(周回距離)以上跳ぶと警告する
テストは実機なしで走る。
node --test "webgui/test/*.test.mjs" # 検証規則・CRC・編集操作・行分類
python3 tools/test_map_protocol.py # プロトコル往復(モックECU)microSD/RPM_*.CSV と同じ書式の CSV をそのまま送れる。pyserial が必要。
プロトコルの実装は tools/map_protocol.py にあり、Web GUI と共有している。
pip install pyserial
# 転送のみ(RAM へ反映。電源を切ると元に戻る)
python tools/send_map.py microSD/RPM_2026SUZUKA.CSV
# 転送 + EEPROM へ保存(エンジン停止時のみ)
python tools/send_map.py microSD/RPM_2026SUZUKA.CSV --save
# 現在の MAP を吸い出す
python tools/send_map.py --dump > current_map.csv
# 状態確認 / ポート明示
python tools/send_map.py --info
python tools/send_map.py map.csv --port /dev/cu.usbmodem1101
# 実機なしで動作確認(モックECU)
python tools/send_map.py microSD/RPM_2026SUZUKA.CSV --fake
# 3列(旧書式)CSVを送る場合は噴射終了角度を明示的に補う
python tools/send_map.py microSD/RPM_2024MOTEGI.CSV --legacy-inj-end-ca 680送信前にファームと同じ規則で検証し、通らない CSV は実機に触れる前に弾く。
転送後は MAP INFO の CRC と自前の計算値を突き合わせて検証し、不一致なら非ゼロ終了する。
シリアルモニタから手打ちでも操作できる(src/map_console.cpp)。
応答は必ず 1 行の OK ... / ERR ...。
| コマンド | 動作 | 稼働中 |
|---|---|---|
MAP? |
現在の MAP を CSV で出力(4列) | 可 |
MAP INFO |
出所 / 行数 / CRC / EEPROM 状態 / RPM / ENG を表示 | 可 |
MAP BEGIN |
転送セッション開始 (USB テレメトリを一時停止) |
可 |
<rpm>,<inj>,<ign>,<inj_end_ca> |
セッション中の 1 行 ( RPM で始まるヘッダ行は自動スキップ。4列必須) |
可 |
MAP END |
検証して原子的に反映。失敗時は破棄され現在の MAP は無傷 | 可 |
MAP ABORT |
セッション破棄 | 可 |
MAP SET <rpm> <inj> <ign> <inj_end_ca> |
1 行だけライブ変更 (該当 RPM が無ければ昇順を保って挿入) |
可 |
MAP SAVE |
EEPROM へ保存 (既存内容と同一なら書き込まず OK UNCHANGED) |
不可 |
MAP LOAD |
EEPROM から読み直して RAM へ反映 | 可 |
MAP DEFAULT |
内蔵 defaultMap へ戻す(EEPROM は変更しない) |
可 |
TELEM ON [ms] |
機械可読テレメトリを開始 (既定 OFF、100〜2000ms、100ms 単位) |
可 |
TELEM OFF |
停止して従来の 2Hz 人間向け出力へ戻す | 可 |
TELEM? |
on= / ms= / drop=(取りこぼし数)を表示 |
可 |
VER |
OK VER <fw> proto=<n>(現行 1.3.0 / proto=5。v3 で MAP を 4 列化、v4 でテレメトリに inj_end を追加、v5 で inj_end を ign の直後へ移動) |
可 |
PING |
OK PONG(副作用のない疎通確認・レイテンシ計測) |
可 |
HELP |
コマンド一覧 | 可 |
セッションを開いたままホストが消えた場合に備え、最終受信から 5 秒で ERR SESSION_TIMEOUT を返してセッションを破棄する。
(テレメトリがミュートされたまま復帰しなくなるのを防ぐ)
GUI のライブトレース用に、statusTask の高速パス(100ms 周期)から出力する。
既定は OFF なので、tools/send_map.py やシリアルモニタの見え方は従来どおり変わらない。
TELEM ON の間は 2Hz の人間向けタブ行を止め、2 つの書式が混ざらないようにする。
T\t<seq>\t<ms>\t<rpm>\t<inj01>\t<ign>\t<inj_end>\t<spd01>\t<ne>\t<row>\t<flags>
| 欄 | 内容 | 単位 |
|---|---|---|
seq |
連番(uint16 wrap)。取りこぼし検出に使う | — |
ms |
millis() |
ms |
rpm |
tachoRpm |
RPM |
inj01 |
calculatedINJ_time |
×0.1ms |
ign |
calculatedIGN_CA |
CA |
inj_end |
calculatedINJ_END_CA(MAP 範囲外では前回値が残る) |
CA |
spd01 |
speed |
×0.1km/h |
ne |
Ne_deg |
CA |
row |
採用中の MAP 行 index(255 = MAP 未使用。始動時・範囲外) | — |
flags |
bit0 ENG_ON / bit1 Launch / bit2 クランキング / bit3 mapOutOfRange |
— |
- タブ区切りを保っているのは、
send_map.pyが「タブを含む行=テレメトリ」として 読み飛ばす実装だから。
この書式なら CLI を 1 行も変えずに共存できる。 - MAP 由来の 3 値(
inj/ign/inj_end)を隣り合わせに並べた(proto=5 でinj_endを末尾から移動)。
列数は proto=4 と同じ 11 なので 旧ツールとは互換がない(spd以降が黙って 1 列ずれる)。
Web GUI /map_gui.pyはVERのprotoを見て、5 未満のファームにはTELEM ONを送らず、2Hz の旧形式表示にフォールバックする。
旧ツール × 新ファームは表示が壊れるので、ツールも同時に更新することignの隣に差し込むとrowとflagsが黙ってずれるので、順序は変えない。 calculatedINJ_END_CAは MAP 範囲外でもゼロクリアされない。
(updateEngineMap()はinj/ignだけ 0 にする)
噴射自体はmapOutOfRangeで止まるので実害は無いが、値としては前回値が残るため GUI はrow=255のとき表示しない。- 整数のみ。newlib-nano の
snprintfが%.1f非対応なのに合わせてある。
単位換算は PC 側の責務。 rowをファームから出しているのは、GUI が「実機が実際に採用した行」を推測せずに 表示できるようにするため。
クランキング中(startState == LOW)も rpm に応じた同じ MAP テーブルを参照するが、始動直後は rpm の変動が大きく PC 側の推測は容易にズレるため、ファーム自身が報告するrowを信用する設計にしてある。- USB CDC の
write()はホストが接続したまま読まないと FIFO が空くまで無限にスピンする。
usbLockを握ったままそうなるとコンソールが応答しなくなるため、Serial.availableForWrite()で空きを確認してから書き、足りなければそのサンプルを捨てる。
(落ちた分はseqの飛びで GUI が検出できる)
MAP END / MAP SAVE 時に以下を検査し、1 つでも違反したら全体を破棄する(部分適用しない)。
- 行数 1〜24
- RPM が厳密昇順、かつ 1〜20000
inj_time0〜255(x0.1ms → 最大 25.5ms)ign_ca0〜90(CA)inj_end_ca0〜720(CA。クランク角のフルサイクル、0CA跨ぎ対応と整合)
- UNO R4 Minima の RA4M1 データフラッシュ 8KB(消去単位 1KB)を Arduino
EEPROMライブラリ経由で使用。 - 先頭 1 ブロック内に magic / version / 行数 / CRC-16 / エントリを格納(約 152 バイト)。
CRC 不一致・magic 不一致なら内蔵defaultMapへ自動フォールバックする。 MAP SAVEは書き込み前に EEPROM 上の内容と行数 / CRC / エントリ本体を突き合わせ、同一なら書き込みを行わずOK UNCHANGEDを返す(データフラッシュの摩耗対策)。
なお構造体一括の書き込みにはEEPROM.putを使うこと。
EEPROM.updateはバイト単位 API で、差分 1 バイトごとに 1KB ページの消去+書き込みが走るため摩耗対策として逆効果になる。MAP SAVEはエンジン停止時(ENG_ON == falseかつtachoRpm == 0)のみ受理し、それ以外はERR ENGINE_RUNNINGを返す。
データフラッシュの消去・書き込みは数 ms ブロックするため、 24µs 周期の点火・噴射制御中に実行してはならない。
- SD カード MAP: 現状 SD は未使用。
SDMapEnabled/parseCSV()のスタブは残してあり、実装時はmapParseCsvLine()/mapStagingAppend()/mapStagingApply()をそのまま再利用できる。 - AFR 補正: A/Fセンサによる燃料噴射量補正のため、
Increase_Fuel分岐位置あり(未実装)
CSV パース・検証ルール・バンク切替・send_map.py のプロトコルはホスト側のテストで確認済み。
以下は実機でしか確認できない項目なので、車両に載せる前に順に潰すこと。
1 → 8 の順(無負荷 → クランキング → 実走)で進めると手戻りが少ない。
検証結果(Ardu-Stim + オシロによる代替検証、発見した不具合と修正内容を含む):
pio run -t upload
pio device monitor -b 115200- 起動直後に
MAP SOURCE: DEFAULT (15 rows)が出る
※ EEPROM 未初期化のボードのみ。既にMAP SAVE済みならMAP SOURCE: EEPROM (n rows)が正しい - 起動メッセージを取り逃した場合は
MAP INFOでsrc=/eeprom=を確認できる
(USB CDC が未接続の間の出力は捨てられるため、電源投入がモニタ起動より先だと起動行は流れない) -
MAP?の出力がsrc/map_store.cppのdefaultMap[]と一致する -
HELPでコマンド一覧が出る
python tools/send_map.py microSD/RPM.CSV-
RAMへ反映しました(読み戻し検証OK)が出て終了コード 0 -
MAP?がRPM.CSVの内容(INJ=110 系)になっている - 転送中に USB テレメトリ(タブ区切り行)が止まり、転送後に再開する
- 転送中も
Serial1(メーター・ロガー)側の CSV 出力は途切れない - 24 行の CSV でも RX FIFO 溢れなく通る(1 行ごとの ACK 同期が効いているか)
python tools/send_map.py microSD/RPM_2026SUZUKA.CSV --save-
EEPROMへ保存しましたが出る - USB を抜き差しして再起動 →
MAP SOURCE: EEPROM (15 rows) -
MAP?が 2026SUZUKA の内容 -
MAP DEFAULT→MAP?が既定値 →MAP LOAD→MAP?が EEPROM の内容に戻る
Ardu-Stim で回転信号を与えるか、実際にクランキングした状態で:
-
MAP SAVE→ERR ENGINE_RUNNINGが返る - 拒否後に電源を落として再起動しても、EEPROM の内容が変わっていない
-
MAP SET 3200 45 26 680→OK SETが返り、MAP?に即反映される -
MAP BEGIN〜MAP ENDの一括転送が稼働中でも通り、反映される -
MAP SET/MAP ENDの実行前後でエンジンが失火・ストールしない ← 最重要
printf 'RPM,INJ,IGN\n3000,40,20\n1000,40,20\n' > /tmp/bad_order.csv
python tools/send_map.py /tmp/bad_order.csv # ERR RPM_NOT_ASCENDING で非ゼロ終了- RPM 非昇順 / 25 行以上 /
ign_ca91 以上 のいずれでもMAP ENDがERRを返す - そのいずれの後でも
MAP?の内容が転送前から変化していない - 転送の途中で USB ケーブルを抜いても、次回起動時の MAP が壊れていない
「デバッグ」節の Ardu-Stim + DHO804 の構成で、変更前のファームと比較する:
- 転送・保存を行わない通常運転で、
INJ_OUT(A0) /IGN_OUT(A1) のタイミングが変更前と一致 - USB シリアルのロギングが従来どおり途切れない
-
MAP SET実行の瞬間に噴射パルスが1サイクル内で分断・二重化しない
(バンク切替が原子的に効いているかの実測確認) -
MAP SAVE(停止時)中にNe_degのカウント抜けが無い(未検証、理由は検証結果ドキュメント参照)
- 使用する MAP を
--saveで EEPROM に焼き、再起動してMAP INFOのcrc=を記録 -
python tools/send_map.py --dumpの出力が意図した CSV と一致 - レブリミット(
TACHO_RPM_MAX= 6000)付近で MAP 最終行を超えたとき、calculatedINJ_time/calculatedIGN_CAが 0 になり燃料噴射・点火が止まる - 実走行(車速センサ
WH_IN・実負荷・実燃料噴射量)は Ardu-Stim では代替不可のため未検証。実車で確認すること。
Ardu-Stim で回転信号を与えつつ、STR_IN/ENGOFF_IN 相当を操作する:
- rpm を 0→
start_RPM(1500) 以上へスイープしながら STR_IN を ON → STR_OUT が LOW になり、start_RPM到達から約100ms 後に STR_OUT が HIGH に戻る(STR_STARTED) - rpm を
start_RPM未満に維持したまま STR_IN を ON → STR_OUT が LOW になってから 2000ms 後に STR_OUT が HIGH に戻る(STR_FAILED) -
STR_FAILED状態で STR_IN を離さず維持 → 再クランキングしない(エッジ検出のみで再始動する設計の確認)
※Ardu-Stimベンチ環境では検証不可: STR_IN が実車配線の74HC14シュミットトリガを経由せず MCUへ直結されるため、ボタン押下中に生ピンが数千回/秒オーダーでチャタリングし、 OFF→ONエッジが繰り返し検出されてSTR_FAILEDからSTR_CRANKINGへ誤って再突入し続ける (実測: 1秒保持で生値が約1万回反転)。シュミットトリガ回路を通す実車配線での再検証が必要。 -
STR_FAILED後に STR_IN を一度離してから再度 ON →STR_CRANKINGへ再遷移し STR_OUT が再度 LOW になる - クランキング中に ENGOFF_IN を OFF → 即座に STR_OUT が HIGH に戻り
STR_IDLEに戻る - 上記のいずれの遷移でも、走行距離・燃費・稼働時間(
Launch連動の積算)が途切れない (キルスイッチOFFでLaunchはリセットされない仕様の確認)
EEPROM を完全な未書き込み状態へ戻すコマンドは用意していない。
既定値へ戻したい場合はMAP DEFAULT→MAP SAVE(出所はEEPROMのままになる)。
- AVR:
sbi/cbi直接制御 - UNO R4 (RA4M1): レジスタ
R_PORTx->PODR_b - その他: フォールバック
digitalWrite/digitalRead
クリティカル区間 (割り込み内) の遅延最小化に寄与。
AGTimer: AGTimer.init(period_us, callback)
本プロジェクトでは 24µs 周期で Routine 呼び出し。
周波数変更は ROUTINE_CYCLE_US を調整。
- 監視タスク:
statusTask(Serial1: 100ms(10Hz) / SerialUSB: 500ms(2Hz), 優先度 2, スタック 256 words) - MAP コンソール:
mapConsoleTask(5ms, 優先度 1, スタック 256 words) - USB(
Serial) 出力は両タスクで共有するため、mapConsoleUsbLock()/mapConsoleUsbUnlock()で排他する。
MAP 転送セッション中はstatusTaskの USB テレメトリを停止する(Serial1側は常時出力)。 statusTaskのスタック 256 words はsnprintf浮動小数点フォーマット分を含む。追加タスクはxTaskCreateで拡張可能。スタック追加時は増量推奨。
| ポート | 形式 | 周期 | フィールド | 出力先 |
|---|---|---|---|---|
Serial1 (HW UART) |
CSV + XORチェックサム | 10Hz (100ms) | RPM, INJ(ms), IGN_CA, INJ_END(CA), speed, distance, fuel(ml), km/L, worktime | ロガー (ESP32) https://github.com/todateman/Chibi-T_Furoshiki_Logger |
Serial (USB CDC) |
タブ区切り | 2Hz (500ms) | 上記 + Ne_deg | PC (USB) |
Serial1 の行末は *XX(XX = 先頭から * 直前までの全バイトのXOR、16進2桁)。
ロガーは不一致の行を捨てる。INJ_END(CA) は MAP 範囲外では前回値が残る。
INJ_END を IGN_CA の直後に挿入したため、EFI とロガーは必ず同時に更新する(旧形式とは非互換)。
speed は 0.1km/h 分解能。停止時は最終パルス経過時間で減衰し、約8秒後に 0.0 へ。
- 図ファイル:
document/Arduino_ECU_Workflow.puml - VS Code でのプレビュー: PlantUML 拡張(例:
jebbs.plantuml)を使用して開く。 - コマンド例 (Windows PowerShell):
java -jar "$env:USERPROFILE\.vscode\extensions\jebbs.plantuml-2.18.1\plantuml.jar" -tsvg document/Arduino_ECU_Workflow.puml- 図に含まれる主な数値注記:
- ROUTINE_CYCLE_US = 24µs
- PERIMETER_MM = 1548mm
- 速度上限 99.9km/h (内部999)
- カム同期タイムアウト ≈ 50ms
- RPMゼロ化 ≈ 1.2s無信号
- 速度強制ゼロ ≈ 8s無信号
- 点火保持 5ms
- MAP内パラメータ選択・燃料噴射・点火処理高速化
(現状では処理遅れに起因すると思われる過大な進角角度を設定している) - ビルド不要の MAP 書き換え (USB シリアル CSV 転送 + EEPROM 永続化)
- スタータ制御の自動化(Issue #11)
(start_RPM×0.1秒維持で自動停止、2秒タイムアウトで失敗検出・再試行可。始動専用MAPフラグは廃止しMAP方式に一本化) - SD から MAP 読込実装 (
parseCSV) ※ SD が使えるようになったらmapParseCsvLine()を再利用 - AFR センサ補正ロジック (
updateAFR) - クランク角推定のドリフト補正(非エンコーダ時)
- 例外検出 (センサ断線・異常 RPM)
- フラッシュ書き込みによる学習補正保存(MAP と同じ EEPROM 領域を拡張)
- 単位/係数の物理モデル化(燃料密度, 噴射流量)
| フラグ | 影響 |
|---|---|
uno_r4_minima |
自動 (PlatformIO env) |
rmc_ra4m1_20 |
SD 初期化ブロック有効 |
Ardu-Stimのdevelop/furoshikiブランチにあるfuroshiki_2025のパターンと、visa-mcpによるオシロスコープの制御を組み合わせて、ECUプログラムのデバッグを行う。
- VISAリソース(RIGOL DHO804):
USB0::6833::1101::DHO8A253701207::0::INSTR - CH1: D9
- CH2: D2
- CH3: A0
- CH4: A1
- 回転数は外部から可変変化させるので、GPIOピン出力を確認して現在のMAPに対して乖離がないか確認する
(現在のMAPはMAP?で吸い出せる。EEPROMに保存済みならdefaultMapではなくそちらが使われている点に注意) - 一度スタータボタンONの信号を入力しなければ
INJ_OUTとIGN_OUTの信号出力を開始しないので、手動で実行する。 - MAP書き換え機能の実機確認は 実機確認チェックリスト を参照。
評価結果:
- 本体: リポジトリ LICENSE (MIT)
- AGTimer ライブラリ: 同梱 MIT (作者表記参照)
実車/燃焼系制御へ適用する際は下記を検討:
- ウォッチドッグ / フェールセーフ / 過回転保護など追加必須
- 電源ノイズ対策 (車載 12V → 5V/3.3V 安定化)
- I/O レベルと駆動回路(インジェクタ / イグナイタ)の絶縁
