ESP32使って手作りアイドリングストップキャンセラーを作ったので紹介する。
アマゾンやらメルカリやらで売られているものとは違うものを作った。
アイドリングストップキャンセラーなど当初は不要と思っていた
大した機能じゃなくね?
市販品は殆どの場合ECONスイッチの裏側のカプラーに割り込ませて制御するっぽい。なかには電源も別途ヒューズボックスから取り出さなければならないものもあるようだが▼これが一番ポン付けで簡単そう。
もともとアイドリングストップという機能は信号停車中なんかにエンジンをアイドリングさせておくとガソリンがもったいし経済的じゃないしという理由からついたものである。
一時期はすべての車に装備されたんじゃないかと思うくらいの勢いがあったと思う。(想像)
市販品高すぎww
Amazonやらメルカリで売られているアイドリングストップキャンセラーなる商品は3000円台から5000円ぐらいで4000円~5000円ぐらいがボリュームゾーンの価格帯のようだ。
なんだか高い。まあ材料費だけじゃなくアイデア料も含んでいるとはいえ元を取るまでに相当乗らないと無理なんじゃね?って思ってしまうほど。
マイコンボード使えば余裕でできんじゃね?とも思う。
ないよりあったほうがいいと言うのは正しいか
そんなアイドリングストップ機能は近年見直されつつあり、新車に実装されなくなってきているようなんだ。
ユーザーとしてはバッテリーを保護したいとか、スターターを保護したいなどアイドリングストップをしない理由はさまざまなれど最大の理由は財布を保護したいという理由なはず。
アイドリングストップするほうが財布にダメージが大きい傾向にあるという統計結果があるかどうかは知らんが、世の中のアイドリングストップ装着車に乗ってる多くの人がアイドリングストップキャンセル機能を後付しようという(現に商品は市場に溢れ売れ続けている)のはバッテリー保護、スターター保護は二の次で何より財布を保護したいためであろう。
ないよりあったほうがいいと言われていたアイドリングストップ機能は近年ではなくてもいい機能に成り下がった。
市販のアイドリングストップキャンセラーの問題点
そんなわけで自分も遅ればせながらアイドリングストップキャンセルの機能を自車(N-BOX)に盛り込もうと思いついたわけであるが市販品には一つの懸念があった。
それはECONボタンのキャンセルであって、厳密にはアイドリングストップのみのキャンセルではないということだ。
つまりECONボタンの裏側に装着するキャンセラーはエコ走行も同時にキャンセルしてしまい、ガソリン消費の激しい走りになってしまうわけだ。そういうのが好きな輩ならいいけど自分はそうではない。
本来売られている大半の商品はアイドリングストップキャンセラーというよりエコモードキャンセラーというのが正確なのだ。
それを回避したい。アイドリングストップだけキャンセルし、普段はエコモードで走りたい。
それを解決するのがボンネット配線の一時的な断線処理だ。どうやらエンジン始動時にボンネットが空いているとなぜかアイドリングストップにならないらしい。N-BOXの場合。その他の車種は知らん。その特性を利用した今回の改造だ。
もちろん実際にボンネットを開けるわけではないし、ずっと開けっ放し状態にしておくと常に警告されるのでウザく、エンジン始動後の僅かな時間だけ一時的にボンネット開放状態にするのだ。
作ってみた
ソフトウェア
ロジックは大したことなかった。
フローチャートは以下。
①エンジン始動とともに電源が入る。自動車は12Vだが、USB電源は5Vであり5VのUSB電源からマイコンボードへ電力供給することでいける
②アイドリングストップキャンセル可否の判断は特定のGPIOポートをショートさせておくか否かで決める。例えば27番ポートをGNDをロッカスイッチを挟んで設置しておく。ロッカースイッチがオンならアイドリングストップキャンセル機能を発動させるというロジック
③ボンネット配線の間にノーマルクローズリレーを挟んでおく。普段は常にクローズなのでなにも手を加えていないのと同じ機能になり特定の条件下でのみ断線をするというわけ
④リレーを断線状態にしたのちに8秒間待機する。delay処理を1行書くだけ
⑤ノーマルクローズリレーへの信号を止めクローズ状態に戻す。これによりボンネットが閉じた状態に戻る
ハードウェア
回路図
手書きで恐縮だけどこんな簡単な回路だ。SIGNというのが信号線でESP32から好きな出力ポートを選んで設定しよう。
必要な部品
ここに挙げているのは自分のシステムと同じものではなく一例なので好みのものを使ってほしい。
- ESP32マイクロコントローラー
ESP32を使うのはアイドリングストップキャンセルの機能だけだとオーバースペックだけど使う。いずれこの同じマイコンにブラインドスポットモニターとしても機能させるつもりだから。 - リレー(5V駆動)
フォトカプラーでもいけるかも知れんけど使ったことないしリレーみたいに簡単かどうかわからんから使わない - ブレッドボードとジャンプワイヤー適量
- その他熱収縮チューブなど
- コネクターオス・メスセット
必要な工具
- 自分の場合半田ごて
配線を切って自前のリード線をはんだ付けした - 電工ペンチ
- テスター
プログラム
今回のプログラムはアイドリングストップキャンセルの機能だけでなく、ブラインドスポットモニター機能も盛り込んであるのでかなり余分な要素がまざっている。
アイドリングストップキャンセラーの機能だけならsetup()の中の最初の数行だけで足りるのだ。
だからあまりこのプログラムは参考にならないかもしれないが許してほしい。
あとWi-Fi機能なんてのも入れてあってBlynkレガシーとつながるようにもしてある。特に意味はないんだけどやっぱりWi-Fi使えるESP32ならWi-Fi繋がないと面白くないじゃない?という理由。
不要なら削ってほしい。
//#define BONNET_TOGGLE_DUMMY #define NONE_TEMP_SENSOR //温度計取り付けたらコメントにする //#define INTERVAL_DEBUG //タイマーインターバル デバッグ中は長く取る #define FUNC_START LOW //ボンネットリレースイッチ操作時に使う #define FUNC_END HIGH const char* FirmwareVersion = "0.1.16"; #include <SPIFFS.h> #include <EEPROM.h> #include <DHT.h> #include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <WiFiMulti.h> #include <ArduinoOTA.h> #include "private.h" #include <BlynkSimpleEsp32.h> // 左センサー 右センサーGPIO #define Trig_Pin_L 17 #define Trig_Pin_R 25 //orange #define Echo_Pin_L 16 #define Echo_Pin_R 12 //yellow #define LEFT_LED_PIN 4 #define RIGH_LED_PIN 18 #define TRIG_L 1 #define TRIG_R 0 #define DHTPIN 15 //DHTセンサーアウトプットピンの指定 #define DHTTYPE DHT11 // DHT型の指定 #define BONNET_TOGGLE 26 #define BONNET_RELAY 27 #define EcoModeLedPin 2 #define LED_BUILTIN 16 #if defined(INTERVAL_DEBUG) #define TIMER_INTERVAL 2000L #define REPOINTERVAL 5000L #define TEMPINTERVAL 10000L // ミリ秒 #else #define TIMER_INTERVAL 100 // ミリ秒 #define REPOINTERVAL 5000L // 5秒 #define TEMPINTERVAL 60000L // 60秒 #endif #define Maximum_measurable_distance 399 //測定可能最大距離400cm #define VPIN_ALIV V1 #define VPIN_TEMP V2 #define VPIN_HUMI V3 #define VPIN_RIGH V4 #define VPIN_LEFT V5 #define VPIN_RTEST V6 #define VPIN_LTEST V7 #define WIFIMULTI //Wi-Fiマルチつかうのでこれは残す //const int MAX_WAIT = 20000; #define ECOMODECANCELWAIT 2000L //int V = 340;//音速 TaskHandle_t thp[2]; // マルチスレッドのタスクハンドル格納用 BlynkTimer timer1; //hw_timer_t * timer = NULL; String programName = __FILE__; DHT dht(DHTPIN, DHTTYPE); // DHTセンサーライブラリーを使うための設定 float temperature; portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; volatile int AliveRepoCounter = 0; volatile int DistantCheckCounter = 0; volatile int TempCheckCounter = 0; volatile bool AliveFlug = false; double distanceR ,distanceL; WiFiMulti wifiMulti; #define JST 3600*9 // 日本標準時間の定義 IPAddress ServerIPAddr; // スイッチの状態を保持するEEPROMのアドレス #define SWITCH_ADDR 0 // スイッチの状態 #define ECOMODE_ON 1 byte EcoModeState = ECOMODE_ON; bool InitialMove = true; //byte ButtonState2(); volatile int idlingStopCancelCounter = 0; //void left_led_control(void *); //void right_led_control(void *); //void aliveReport(int); static bool notWiFi; void led_blink(int OnSec, int OffSec, int n, int PORT) { for (int i = 0; i < n; i++) { digitalWrite(PORT, HIGH); // turn the LED on (HIGH is the voltage level) delay(OnSec); // wait for a second digitalWrite(PORT, LOW); // turn the LED off by making the voltage LOW delay(OffSec); // wait for a second } } void IRAM_ATTR onTimer(){ portENTER_CRITICAL_ISR(&timerMux); //ここに変数変更を書き込む DistantCheckCounter ++; // 距離計測タイマー AliveRepoCounter ++; // 生存報告 TempCheckCounter ++; // 温度計測 idlingStopCancelCounter ++; // 初期アイドリングストップキャンセル portEXIT_CRITICAL_ISR(&timerMux); } BLYNK_WRITE(VPIN_RTEST) { int a = param.asInt(); if(a){ Serial.println("Right test"); digitalWrite(RIGH_LED_PIN, HIGH); delay(1000); digitalWrite(RIGH_LED_PIN, LOW); } } BLYNK_WRITE(VPIN_LTEST) { int a = param.asInt(); if(a){ Serial.println("Left test"); digitalWrite(LEFT_LED_PIN, HIGH); delay(1000); digitalWrite(LEFT_LED_PIN, LOW); } } void trim(char* str) { // 先頭の空白を取り除く while (isspace(*str)) { str++; } if (*str == 0) { // 全部空白だった場合 return; } // 文字列の最後まで進み、末尾の空白を取り除く char* end = str + strlen(str) - 1; while (end > str && isspace(*end)) { end--; } // 終端文字を設定し、末尾の空白を削除する *(end + 1) = 0; } BLYNK_CONNECTED() { Blynk.setProperty(VPIN_LTEST, "offLabel","左LEDテスト"); Blynk.setProperty(VPIN_RTEST, "offLabel","右LEDテスト"); } void ArduinoOTAProcess() { Serial.print("connected to Wi-Fi "); // Blynk AUTH TOKENと接続サーバー定義 String ssid = WiFi.SSID(); ssid = WiFi.SSID(); if(ssid == ssid_name[0]) { ServerIPAddr = DomesticServer; Serial.printf("SSID = %s\n",ssid.c_str()); }else{ ServerIPAddr = GlobalServer; Serial.printf("SSID = %s\n",ssid.c_str()); } /////////////////// Arduino OTA 初期設定 ////////////// Serial.println("Start ArduinoOTA initialize."); // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] ArduinoOTA.setHostname("CarDevice"); // No authentication by default // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); ArduinoOTA .onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; else // U_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount // SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); Serial.println("ArduinoOTA setup done."); ////////////////////////////////////////////////////////////////// Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); // 時刻をJSTに補正する呪文 configTime(JST, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp"); //////////////////////////////////////////////////////////////////////////// Serial.printf("BLYNKAUTH = %s\n",BLYNKAUTH); int j = 0; while(!Blynk.connect()){ j++; if(j>2){ Serial.println("Blynk not connected"); break; } Blynk.config(BLYNKAUTH, ServerIPAddr, 8080); delay(500); Serial.print("."); } } void setup() { Serial.begin(115200); Serial.println(); // ファイル名 バージョンナンバー表示 int lastIndex = programName.lastIndexOf('\\'); String result = programName.substring(lastIndex + 1); Serial.printf("Program name : %s\n",result.c_str()); Serial.printf("firmware version : %s\n",FirmwareVersion); WiFi.mode(WIFI_STA); // ポート初期化 pinMode(EcoModeLedPin, OUTPUT); // on-board LED pinMode(LED_BUILTIN, OUTPUT); pinMode(BONNET_TOGGLE, INPUT_PULLUP); pinMode(BONNET_RELAY, OUTPUT); delay(10); // ボンネット機能Onならボンネット配線オフ #if defined(BONNET_TOGGLE_DUMMY) if(1) #else if(!digitalRead(BONNET_TOGGLE)) #endif { Serial.println("Idling stop cancel process start"); digitalWrite(EcoModeLedPin, HIGH); digitalWrite(BONNET_RELAY, FUNC_START); delay(8000); digitalWrite(BONNET_RELAY, FUNC_END); Serial.println("Idling stop cancel process end"); } // Connect to WiFi digitalWrite(LED_BUILTIN, HIGH); Serial.println("Wi-Fi Multi entry..."); WiFi.disconnect(true);//Wi-Fiマルチエントリーの前にこれらを入れておくと delay(100); //繋がりやすくなるらしい for (int i = 0; i < ROUTERS; i++) { wifiMulti.addAP(ssid_name[i], passwords[i]); // add Wi-Fi networks you want to connect to } Serial.println("Connecting to Wi-Fi"); int j=0; while(WiFi.status() != WL_CONNECTED){ if(++j>5){ Serial.println("Wi-Fi not connected"); break; } wifiMulti.run(); Serial.print("."); delay(500); } // Wi-Fiつながったときだけ以下の処理入る if(WiFi.status()==WL_CONNECTED){ notWiFi = false; ArduinoOTAProcess(); }else{ notWiFi = true; } pinMode(LEFT_LED_PIN, OUTPUT); pinMode(Trig_Pin_L, OUTPUT); pinMode(Echo_Pin_L, INPUT); digitalWrite(Trig_Pin_L, LOW); delay(1); pinMode(RIGH_LED_PIN, OUTPUT); pinMode(Trig_Pin_R, OUTPUT); pinMode(Echo_Pin_R, INPUT); digitalWrite(Trig_Pin_R, LOW); delay(1); dht.begin(); // ここの下数行はタイマー割り込みルーチンを設定するお決まりの命令 timer1.setInterval(TIMER_INTERVAL, onTimer); // check if Blynk server is connected every 3 seconds xTaskCreatePinnedToCore(left_led_control, "left_led_control", 4096, NULL, 2, &thp[0], 0); xTaskCreatePinnedToCore(right_led_control, "right_led_control", 4096, NULL, 3, &thp[1],0); led_blink(50, 20, 20, RIGH_LED_PIN); led_blink(50, 20, 20, LEFT_LED_PIN); Serial.println("\nSetup done"); }//setup //Send Trigger pulse double read_distance(int LR) { double time = 0; if(LR == TRIG_R){ digitalWrite(Trig_Pin_R,LOW); delayMicroseconds(2); digitalWrite(Trig_Pin_R,HIGH); delayMicroseconds(11); digitalWrite(Trig_Pin_R, LOW); time = pulseIn(Echo_Pin_R, HIGH); }else{ digitalWrite(Trig_Pin_L, LOW); delayMicroseconds(2); digitalWrite(Trig_Pin_L,HIGH); delayMicroseconds(11); digitalWrite(Trig_Pin_L, LOW); time = pulseIn(Echo_Pin_L, HIGH); } // 音速 331.5 + (0.6 * 摂氏温度) double sonic = 331.5 + ((int)temperature * 0.6); double dist = (time * sonic * 100) / 1000000 / 2; return dist; }//read_distance void loop() { timer1.run(); // Initiates SimpleTimer int WLCONNECTED; if(!notWiFi){ WLCONNECTED = WiFi.status(); if (WLCONNECTED == WL_CONNECTED) { // Wi-Fiに接続できた場合の処理 ArduinoOTA.handle(); // OTAの命令を入れておく if(Blynk.connected()){ Blynk.run(); }else{ Blynk.connect(); } } else { // Wi-Fiに接続できなかった場合の処理 Serial.printf("Wi-Fi try to connect %lu\n",millis()); // WiFi.reconnect();//一瞬で終わるがWi-Fiマルチだと再接続しないみたい wifiMulti.run(); Serial.printf("Wi-Fi end try %lu\n",millis()); } // 5秒カウント判定 if(AliveRepoCounter >= REPOINTERVAL / TIMER_INTERVAL){ portENTER_CRITICAL(&timerMux); AliveRepoCounter = 0; portEXIT_CRITICAL(&timerMux); // 5秒経過 aliveReport(WLCONNECTED); } } if(DistantCheckCounter){ portENTER_CRITICAL(&timerMux); DistantCheckCounter = 0; portEXIT_CRITICAL(&timerMux); // 0.1秒経過 char str[10]; distanceL = read_distance(TRIG_L); sprintf(str, "%.1lf", distanceL); Blynk.setProperty(VPIN_LEFT, "offLabel", str); distanceR = read_distance(TRIG_R);//戻り値はXXcm sprintf(str, "%.1lf", distanceR); Blynk.setProperty(VPIN_RIGH, "offLabel", str); } // EcoModeToggle(); }//loop void left_led_control(void *args) { int led_status = LOW; while(1){ // 0または測定可能距離を超えていたら消灯 if((distanceL == 0) || (distanceL >= Maximum_measurable_distance)){ if(led_status == HIGH){ led_status = LOW; // LEFT LED OFF digitalWrite(LEFT_LED_PIN, LOW); } }else{ if(led_status == LOW){ led_status = HIGH; // LEFT LED 点灯 digitalWrite(LEFT_LED_PIN, HIGH); } } delay(1); } }//left_led_control void right_led_control(void *args) { int led_status = LOW; while(1){ if((distanceR == 0) || (distanceR >= Maximum_measurable_distance)){ if(led_status == HIGH){ led_status = LOW; // RIGHT LED OFF digitalWrite(RIGH_LED_PIN, LOW); } }else{ if(led_status == LOW){ led_status = HIGH; // RIGHT LED 点灯 digitalWrite(RIGH_LED_PIN, HIGH); } } delay(1); } }//right_led_control void aliveReport(int WiFiCONNECTED) { static byte ecomode; if(TempCheckCounter > TEMPINTERVAL / TIMER_INTERVAL){ portENTER_CRITICAL(&timerMux); TempCheckCounter = 0; portEXIT_CRITICAL(&timerMux); #if defined(NONE_TEMP_SENSOR) temperature = 20; #else float temperature = dht.readTemperature(); // 温度読み取り float humidity = dht.readHumidity(); // 湿度読み取り // 読み取りに失敗しているかチェック // 失敗している場合はもう一度、温湿度の読み取りをする if (isnan(temperature) || isnan(humidity)) { Serial.println("Failed to read from DHT sensor!"); return; } Blynk.setProperty(VPIN_TEMP, "offLabel",temperature); Blynk.setProperty(VPIN_HUMI, "offLabel", humidity); #endif } if(WiFiCONNECTED){ char str[13]; //char str2[20]; int int_h,int_m,int_s; DisplayTime(str,&int_h,&int_m,&int_s); // 現在時刻取得 Blynk.setProperty(VPIN_ALIV, "offLabel", str); // Serial.println("Blynk alive time reports"); } if(ecomode != EcoModeState){ ecomode = EcoModeState; Serial.printf("EcoModeState:%d\n",EcoModeState); } }
アイドリングストップ機能をキャンセルするだけでこんなに長いプログラムが要るんか?っていったら要らない。余分な機能を満載しているから。それについては別の機会で説明できればと思う。
つけてみた
結線改造する場所
参考にさせていただいたサイトはこちら▼
Nボックスカスタムのアイドリングストップのみキャンセル・タイマー調整・中華製リレーに関するカスタム事例|車のカスタム情報はCARTUNE
写真のカプラーを外して(外さなくてもいいけど外したほうが楽)、若草色のリード線がボンネット開閉センサーだ
ただし若草色のリード線は2本あるので間違えないよう注意すること。
位置は車体前方に向かって左から3番目下から2段目の線。上から1段目左から4番目にも若草色の線があるが違うので絶対早とちりしないこと。俺は早とちりして切ってしまって結線しなおすのに1時間かかった。時間よりも無理な体勢の辛さが身に沁みた。
なにしろ究極的にリード線がギリギリで自動車が組み立てられているから余分な設備をつけようとするとマジで苦労する。
エレクトロタップでつけてもいいんだけどリード線が細すぎて接触不良を起こしそうだから俺ははんだ付けでつけた。
▼まだカプラーがついた状態から・・・
▼下部の爪をマイナスドライバーなどで持ち上げながらぐりぐり揺すってなんとか外す。
▼拡大した画。最初はここから必要な線の金具だけ外してバイパス回路をつけようと思ったけどぜんぜん外れなくて諦めて切断にいたった。
▼どれがボンネットセンサーの線なのか確認しているところ。自前のリード線にメス端子をつけたやつを奥のオス端子に噛み込ませているのがわかるだろうか。さらに手前の端子の同じ位置にはスマホのSIMピンを差し込んでそれにテスターを当てる。
我ながらSIMピンを入れるのはグッドアイデアだと思った。そうじゃないとテスターの端子が入る余地などないのだ。
▼そして分かったのがこの若草色のリード線だけどマジで気をつけてほしい。上の段じゃなくて下から2段目左から3列目だ。
▼切断したら被覆をカッターで丁寧に剥いた。短すぎるのと細すぎるのとで普通の電工ペンチの被覆部では太刀打ち出来ない。
そして上記2箇所の被覆を剥いたところと自前のリード線をはんだ付けし▼こういうボックスにいれたESP32とつなげた。
このボックスにはリレーも入っている。
動作確認
問題なし。
エンジン始動→ESP32電源ON→リレー作動→ボンネット警告がインフォメーションパネルに表示される→8秒後リレー解除→インフォメーションパネル戻る
走行→信号待ち→アイドリングストップにならない
動画も撮って近日中に公開するから一応そっちも見てほしい。これといった山場はないだろうけど。
【追記】夜中に自動車が警告音を出すwww
取り付けて1週間経過しましてその間、夜中に2度ほど自動車が警告音をけたたましく鳴らして近所迷惑レベルになり焦りました。
警報が鳴るのは自動車の鍵がかかっているときにボンネットが開けられると反応するんだと思います。この装置を装着するまでは一度もそういう誤作動はありませんでしたからこれが原因であることは間違いないでしょう。
装置そのもののどこに不具合があるのか理由はわかりません。
わかりませんが考えられる原因はいくつかあります。
- マイクロコントローラーの電源をカーナビから取っており夜中に瞬間的にUSB出力に通電する?
→対策1:電源を別の場所(カーナビの別のUSBポートまたは自動車のUSB電源)
→対策2:装置の主電源を切っておく(ただし毎回エンジン始動のたびに主電源を入れるのはダルい) - リレーが安い中国製なのでNC(ノーマルクローズ)を保持していられずあるとき瞬間的に接点が離れる
→対策1:リレーを変える
→対策2:リレーを2個用意し並列につなげてみる
今のところ考えられる要因は電源が瞬間的に入ってしまうかリレーがNCをずっと保持していられないということぐらいなので様子を見ながら改善していこうと思います。
なぜか昼間には誤作動しません。