本文旨在補充《超圖解ESP32應用實作》第18章的車上診斷系統(OBD)實驗,方便沒有汽車的讀者使用ESP32當作OBD模擬器,傳遞虛擬的行車速度和引擎轉速值給另一個ESP32,並且把數值顯示在連線的手機瀏覽器。
本文的麵包板組裝電路,沿用《超圖解ESP32應用實作》第17章的「動手做17-1:ESP32 CAN匯流排通訊實驗」,加上一個類比搖桿來模擬車速和引擎轉速值:
- 水平(X)搖桿的輸入值:改變車速值0~180 (km/h)。
- 垂直(Y)搖桿的輸入值:改變引擎轉速值1500~4000 (RPM)
連接類比搖桿的ESP32開發板A,扮演車上診斷系統(OBD),執行OBD模擬程式;開發板B則是OBD診斷裝置,執行第18章「動手做18-2:在手機瀏覽器呈現即時車速和引擎轉速」的程式。
ESP32車上診動系統(OBD)模擬程式
OBD模擬器修改自coniferconifer編寫的ESP32-ECU-emulator,筆者僅刪除一些不必要的變數和前置處理器指令,然後修改CAN收發器的接腳設定,並且加入讀取搖桿數值的程式碼,請編譯底下的程式,上傳到「開發板A」備用:
#include <CAN.h> #include <OBD2.h> #define CTX_PIN 21 // CAN收發器傳送腳 #define CRX_PIN 22 // CAN收發器接收腳 #define IN_X 32 // 可變電阻X(水平搖桿)的輸入腳 #define IN_Y 33 // 可變電阻Y(垂直搖桿)的輸入腳 #define ECU_ID 0x7e8 //ECU id + 8 #define MIL_ON 0x80 #define DTC_CNT 0x01 uint8_t DTC[] = { MIL_ON | DTC_CNT , 0x00, 0x00} ; //A,B,C,D uint16_t freezeDTC = 0x1234; uint16_t fuelSystemStatus = 0x0200; float engineEfficiency = 22.75; // % float shortTermFuelTrimBank1 = -5.47;// % float longTermFuelTrimBank1 = 7.2;// % float shortTermFuelTrimBank2 = -5.47;// % float longTermFuelTrimBank2 = 7.2;// % uint8_t fuelPressure = 765; uint8_t intakeManifoldAbsolutePressure = 255; uint16_t engineRPM = 3925; uint8_t vehicleSpeed = 120; uint8_t timingAdvance = 8; uint8_t airIntakeTemperature = 42 ; float mafAirFlowRate = 6.50 ; float throttlePosition = 5.5 ;// % uint8_t commandedSecondaryAirStatus = 1; uint8_t oxygenSensorsPresentIn2Banks = 3; float oxygenSensor1 = 1.23 ; //Volt float shortTermFuelTrim = 99.2; // % uint8_t obdStandardThisVehicleConformsTo = 0x0a; uint8_t oxygenSensorsPresentIn4Banks = 0x01; uint8_t auxiliaryInputStatus = 0x01; uint16_t distanceTraveledWithMilOn = 1000; // km DISTANCE_TRAVELED_WITH_MIL_ON float fuelRailPressure = 5177.265; //kPa float fuelRailGaugePressure = 655350.0; //kPa uint8_t engineCoolantTemperature = 82; uint8_t fuelLevel = 80; // % float oxygenSensorFuelAir = 1.0; //ratio float oxygenSensorFuelAirVoltage = 1.23; //volt max 8V float controlModuleVoltage = 14.24;//Volt float commandedEGR = 1.8; // % float EGRError = 0.0 ; // % float commandedEvaporativePurge = 10.0; //% COMMANDED_EVAPORATIVE_PURGE uint8_t warmUpsSinceCodesCleared = 255; // counts WARM_UPS_SINCE_CODES_CLEARED uint16_t distanceTraveledSinceCodesCleared = 30613; //km DISTANCE_TRAVELED_SINCE_CODES_CLEARED float evapSystemVaporPressure = 21.34 ; // EVAP_SYSTEM_VAPOR_PRESSURE uint8_t absoluteBarometricPressure = 215; //kPa ABSOLULTE_BAROMETRIC_PRESSURE float catalystTemperatureBank1Sensor1 = 48.0; //dgree CATALYST_TEMPERATURE_BANK_1_SENSOR_1 float absoluteLoadValue = 19.61; //% ABSOLUTE_LOAD_VALUE uint8_t ambientAirTemperature = 31; //degree uint8_t fuelType = 1; //FUEL_TYPE int i = 0; uint8_t pidList1[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList2[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList3[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList4[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList5[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList6[4] = {0xff , 0xff, 0xff, 0xff} ; uint8_t pidList7[4] = {0xff , 0xff, 0xff, 0xff} ; void setPidList1_20(uint8_t pid) { Serial.printf("PidList1_20 %d %02x %02x %02x %02x\r\n", pid, pidList1[0], pidList1[1], pidList1[2], pidList1[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList1[0]); CAN.write(pidList1[1]); CAN.write(pidList1[2]); CAN.write(pidList1[3]); CAN.endPacket(); } void setPidList21_40(uint8_t pid) { Serial.printf("PidList1_20 %d %02x %02x %02x %02x\r\n", pid, pidList2[0], pidList2[1], pidList2[2], pidList2[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList2[0]); CAN.write(pidList2[1]); CAN.write(pidList2[2]); CAN.write(pidList2[3]); CAN.endPacket(); } void setPidList41_60(uint8_t pid) { Serial.printf("PidList41-60 %d %02x %02x %02x %02x\r\n", pid, pidList3[0], pidList3[1], pidList3[2], pidList3[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList3[0]); CAN.write(pidList3[1]); CAN.write(pidList3[2]); CAN.write(pidList3[3]); CAN.endPacket(); } void setPidList61_80(uint8_t pid) { Serial.printf("PidList61-80 %d %02x %02x %02x %02x\r\n", pid, pidList4[0], pidList4[1], pidList4[2], pidList4[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList4[0]); CAN.write(pidList4[1]); CAN.write(pidList4[2]); CAN.write(pidList4[3]); CAN.endPacket(); } void setPidList81_a0(uint8_t pid) { Serial.printf("PidList81-a0 %d %02x %02x %02x %02x\r\n", pid, pidList5[0], pidList5[1], pidList5[2], pidList5[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList5[0]); CAN.write(pidList5[1]); CAN.write(pidList5[2]); CAN.write(pidList5[3]); CAN.endPacket(); } void setPidLista1_c0(uint8_t pid) { Serial.printf("PidListA1-C0 %d %02x %02x %02x %02x\r\n", pid, pidList6[0], pidList6[1], pidList6[2], pidList6[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList6[0]); CAN.write(pidList6[1]); CAN.write(pidList6[2]); CAN.write(pidList6[3]); CAN.endPacket(); } void setPidListc1_e0(uint8_t pid) { Serial.printf("PidListC1-E0 %d %02x %02x %02x %02x\r\n", pid, pidList7[0], pidList7[1], pidList7[2], pidList7[3]); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(pid); CAN.write(pidList7[0]); CAN.write(pidList7[1]); CAN.write(pidList7[2]); CAN.write(pidList7[3]); CAN.endPacket(); } /* Monitor status since DTCs cleared. (Includes malfunction indicator lamp (MIL) status and number of DTCs.) */ void setDTC(uint8_t *DTC) { uint8_t *dtc = DTC; Serial.println("DTC"); CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(MONITOR_STATUS_SINCE_DTCS_CLEARED); CAN.write(*dtc++); CAN.write(*dtc++); CAN.write(*dtc++); CAN.write(*dtc); CAN.endPacket(); } void set4bytes(uint32_t data, uint8_t PID) { CAN.beginPacket(ECU_ID); CAN.write(0x06); //DLC 6 CAN.write(0x41); CAN.write(PID); CAN.write((uint8_t)(data >> 24)); CAN.write((uint8_t)(data >> 16)); CAN.write((uint8_t)(data >> 8)); CAN.write((uint8_t)(data & 0x000000ff)); CAN.endPacket(); } void set2bytes(uint16_t data, uint8_t PID) { CAN.beginPacket(ECU_ID); CAN.write(4); //DLC 4 CAN.write(0x41); CAN.write(PID); CAN.write((uint8_t)(data >> 8)); CAN.write((uint8_t)(data & 0x00ff)); CAN.endPacket(); } void set1byte( uint8_t data, uint8_t PID) { CAN.beginPacket(ECU_ID); CAN.write(0x03); //DLC 3 CAN.write(0x41); CAN.write(PID); CAN.write(data); CAN.endPacket(); } void odb2responder(void * parameter) { int valX, valY; // 暫存類比搖桿輸入值 while (1) { int packetSize = CAN.parsePacket(); if (packetSize) { if (!CAN.packetRtr()) { if (!CAN.packetExtended()) { uint8_t len = CAN.read(); uint8_t service = CAN.read(); uint8_t pid = CAN.read(); Serial.printf("DLC %02x 服務 %02x PID %02x\r\n", len, service, pid); if (len != 2) break; switch (service) { case 0x01: switch (pid) { case PIDS_SUPPORT_01_20: Serial.println("PID list requested"); setPidList1_20(pid); break; case PIDS_SUPPORT_21_40: setPidList21_40(pid); break; case PIDS_SUPPORT_41_60: setPidList41_60(pid); break; case 0x60: setPidList61_80(pid); break; case 0x80: setPidList81_a0(pid); break; case 0xa0: setPidLista1_c0(pid); break; case 0xc0: setPidListc1_e0(pid); break; case MONITOR_STATUS_SINCE_DTCS_CLEARED: setDTC(DTC); break; case FREEZE_DTC: set2bytes(freezeDTC, pid); break; case FUEL_SYSTEM_STATUS: set2bytes(fuelSystemStatus, pid); break; case CALCULATED_ENGINE_LOAD: // engine efficiency if (engineEfficiency > 100.0 ) engineEfficiency = 100.0; if (engineEfficiency < 0 ) engineEfficiency = 0; set1byte( (uint8_t)((engineEfficiency * 255.0) / 100.0), pid); break; case ENGINE_COOLANT_TEMPERATURE: // engine coolant temperature if (engineCoolantTemperature < -40) engineCoolantTemperature = -40; if (engineCoolantTemperature > 215 ) engineCoolantTemperature = 215; set1byte(engineCoolantTemperature + 40, pid); break; case SHORT_TERM_FUEL_TRIM_BANK_1 : set1byte((uint8_t)(((shortTermFuelTrimBank1 + 100.0) * 128.0) / 100.0), pid); break; case LONG_TERM_FUEL_TRIM_BANK_1 : set1byte((uint8_t)(((longTermFuelTrimBank1 + 100.0) * 128.0) / 100.0), pid); break; case SHORT_TERM_FUEL_TRIM_BANK_2 : set1byte((uint8_t)(((shortTermFuelTrimBank2 + 100.0) * 128.0) / 100.0), pid); break; case LONG_TERM_FUEL_TRIM_BANK_2 : set1byte((uint8_t)(((longTermFuelTrimBank2 + 100.0) * 128.0) / 100.0), pid); break; case FUEL_PRESSURE : set1byte(fuelPressure / 3, pid); break; case INTAKE_MANIFOLD_ABSOLUTE_PRESSURE: set1byte(intakeManifoldAbsolutePressure, pid); break; case ENGINE_RPM: // rpm valY = analogRead(IN_Y); // 垂直(Y)搖桿的輸入值 engineRPM = (uint16_t)map(valY, 0, 1023, 1500, 4000); // 引擎轉速 set2bytes( engineRPM, pid); break; case VEHICLE_SPEED: // speed valX = analogRead(IN_X); // 水平(X)搖桿的輸入值 vehicleSpeed = (uint8_t)map(valX, 0, 1023, 0, 180); // 車速 set1byte(vehicleSpeed, pid ); break; case TIMING_ADVANCE : set1byte((timingAdvance + 64) * 2, pid); break; case AIR_INTAKE_TEMPERATURE: set1byte(airIntakeTemperature + 40, pid ); break; case MAF_AIR_FLOW_RATE: set2bytes((uint16_t)(mafAirFlowRate * 100.0), pid ); break; case THROTTLE_POSITION : set1byte((uint8_t)((throttlePosition * 255.0) / 100.0), pid); break; case COMMANDED_SECONDARY_AIR_STATUS : set1byte(commandedSecondaryAirStatus, pid ); break; case OXYGEN_SENSORS_PRESENT_IN_2_BANKS: set1byte( oxygenSensorsPresentIn2Banks, pid); break; case OXYGEN_SENSOR_1_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_2_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_3_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_4_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_5_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_6_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_7_SHORT_TERM_FUEL_TRIM: case OXYGEN_SENSOR_8_SHORT_TERM_FUEL_TRIM: if ( oxygenSensor1 > 1.275 ) oxygenSensor1 = 1.275; if ( oxygenSensor1 < 0 ) oxygenSensor1 = 0.0; if (shortTermFuelTrim < -100.0 ) shortTermFuelTrim = -100.0; if (shortTermFuelTrim > 99.2 ) shortTermFuelTrim = 99.2; set2bytes((uint16_t)((oxygenSensor1 * 200.0) * 256.0 + (shortTermFuelTrim + 100.0) * 128.0 / 100.0), pid); break; case OBD_STANDARDS_THIS_VEHICLE_CONFORMS_TO: set1byte(obdStandardThisVehicleConformsTo, pid ); break; case OXYGEN_SENSORS_PRESENT_IN_4_BANKS : set1byte(oxygenSensorsPresentIn4Banks, pid ); break; case AUXILIARY_INPUT_STATUS: set1byte(auxiliaryInputStatus, pid ); break; case RUN_TIME_SINCE_ENGINE_START: set2bytes( (uint16_t)(millis() / 1000) , pid); break; case DISTANCE_TRAVELED_WITH_MIL_ON : set2bytes(distanceTraveledWithMilOn , pid); break; case FUEL_RAIL_PRESSURE: if ( fuelRailPressure > 5177.265 ) fuelRailPressure = 5177.265; if ( fuelRailPressure < 0) fuelRailPressure = 0.0; set2bytes((uint16_t)(fuelRailPressure / 0.079), pid); break; case FUEL_RAIL_GAUGE_PRESSURE: if ( fuelRailGaugePressure > 655350 )fuelRailGaugePressure = 655350.0; if ( fuelRailGaugePressure < 0 ) fuelRailGaugePressure = 0.0; set2bytes((uint16_t)(fuelRailGaugePressure / 10), pid ); break; case OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_2_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_3_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_4_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_5_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_6_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_7_FUEL_AIR_EQUIVALENCE_RATIO : case OXYGEN_SENSOR_8_FUEL_AIR_EQUIVALENCE_RATIO : set4bytes((uint32_t) (oxygenSensorFuelAir * 65535.0 / 2.0) << 16 + (uint32_t)((oxygenSensorFuelAirVoltage) * 65535.0 / 8.0), pid); break; case COMMANDED_EGR: set1byte( (uint8_t)((commandedEGR * 255.0) / 100.0), pid ); break; case EGR_ERROR : set1byte( (uint8_t)((EGRError + 100) * 128.0 / 100.0), pid ); break; case COMMANDED_EVAPORATIVE_PURGE: set1byte((uint8_t)((commandedEvaporativePurge * 255.0) / 100.0), pid ); break; case FUEL_TANK_LEVEL_INPUT: // fuel level set1byte( (uint8_t)((fuelLevel * 255) / 100), pid ); break; case WARM_UPS_SINCE_CODES_CLEARED : set1byte((uint8_t)warmUpsSinceCodesCleared , pid ); break; case DISTANCE_TRAVELED_SINCE_CODES_CLEARED: break; set2bytes((uint16_t) distanceTraveledSinceCodesCleared, pid); break; case EVAP_SYSTEM_VAPOR_PRESSURE: if ( evapSystemVaporPressure > 8191.75 ) evapSystemVaporPressure = 8191.75; if (evapSystemVaporPressure < -8192 )evapSystemVaporPressure = -8, 192; set2bytes((int16_t)(evapSystemVaporPressure * 4.0), pid); break; case ABSOLULTE_BAROMETRIC_PRESSURE: set1byte(absoluteBarometricPressure, pid); break; case CATALYST_TEMPERATURE_BANK_1_SENSOR_1 : case CATALYST_TEMPERATURE_BANK_2_SENSOR_1 : case CATALYST_TEMPERATURE_BANK_1_SENSOR_2 : case CATALYST_TEMPERATURE_BANK_2_SENSOR_2 : if (catalystTemperatureBank1Sensor1 > 6513.5) catalystTemperatureBank1Sensor1 = 6513.5; if (catalystTemperatureBank1Sensor1 < -40.0) catalystTemperatureBank1Sensor1 = -40.0; set2bytes( (uint16_t)((catalystTemperatureBank1Sensor1 + 40.0) * 10.0), pid); break; case CONTROL_MODULE_VOLTAGE: set2bytes( (uint16_t)(controlModuleVoltage * 1000.0), pid); break; case ABSOLUTE_LOAD_VALUE: if (absoluteLoadValue > 25700.0 ) absoluteLoadValue = 25700.0; set2bytes( (uint16_t)((absoluteLoadValue * 255.0) / 100.0) , pid); break; case AMBIENT_AIR_TEMPERATURE : set1byte((uint8_t) ambientAirTemperature + 40, pid); break; case FUEL_TYPE : set1byte((uint8_t) fuelType, pid); break; default: break; } break; case 0x09: switch (pid) { case 0x02: // setVIN(); break; case 0x0a: // setECUname(); break; default: break; } break; default: break; } } while (CAN.available()) { CAN.read(); } delay(1); } } } } void setup() { Serial.begin(115200); Serial.println("ESP32 ECU emulator"); CAN.setPins(CRX_PIN, CTX_PIN); // 指定CAN收發器的接腳 if (!CAN.begin(500e3)) { // 嘗試用500Kbps連線 Serial.println ("CAN初始化失敗~"); while (1); // 若初始化失敗,程式將停在這裡。 } else { Serial.println ("CAN初始化完畢"); } analogSetAttenuation(ADC_11db); // 設定類比輸入電壓上限3.6V analogSetWidth(10); xTaskCreatePinnedToCore( odb2responder, "odb2responder", 8096, NULL, 1, NULL, 1); } void loop() { // 每隔一秒更新虛擬數據 fuelLevel += 1; shortTermFuelTrimBank1 = (float)random(1, 10) - 5.0; mafAirFlowRate = (float)random(1, 30) / 3.0; throttlePosition += 1; engineCoolantTemperature += 1; engineCoolantTemperature = engineCoolantTemperature % 128; engineEfficiency += 1.0; engineEfficiency = (float)((int)engineEfficiency % 100); delay(1000); }
這個程式一開始宣告了一些儲存OBD參數的變數,例如:fuelSystemStatus(燃料系統狀態)、engineEfficiency(引擎效率)、fuelPressure(燃油壓力)、engineRPM(引擎轉速)、vehicleSpeed(車輛速度)…,當然這些數值都是虛構的,其中一些值在loop()中,每隔1秒更新,例如,引擎轉速和車輛速度值原本透過底下的敘述更新,筆者將它們刪除:
vehicleSpeed += 1; // 速度值+1 engineRPM += random(1, 10); // 隨機設定1~9轉速值
在setup()中,程式把一個FreeRTOS任務“odb2responder”指定給核心1執行,odb2responder任務負責持續偵聽CAN匯流排的訊息並回應相關請求,假設偵聽到 (0x7DF, 0x02, 0x01, 0x0D),代表「即時行車速度」請求的訊息,這個程式片段將被執行:
case VEHICLE_SPEED: // speed valX = analogRead(IN_X); // 讀取水平(X)搖桿的輸入值 vehicleSpeed = (uint8_t)map(valX, 0, 1023, 0, 180); // 將資料對應成0~180車速 set1byte(vehicleSpeed, pid ); // 發出1位元組資料,pid值即是0x0D break;
讀取OBD訊息的程式
開發板B執行的是「動手做18-2:在手機瀏覽器呈現即時車速和引擎轉速」單元的程式,只是調整了CAN收發器的接腳設定:
#include <WiFi.h> #include <AsyncTCP.h> #include <ArduinoJson.h> #include <ESPAsyncWebServer.h> #include <WebSocketsServer.h> #include <SPIFFS.h> #include <esp32_can.h> #include <esp32_obd2.h> #define INTERVAL 1000 #define CTX_PIN 21 #define CRX_PIN 22 #define LED_PIN 5 const char* ssid = "ESP32-OBDII"; const char* password = "87654321"; AsyncWebServer server(80); // 建立HTTP伺服器物件 AsyncWebSocket ws("/ws"); // 建立WebSocket物件 void onSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Serial.printf("來自%s的用戶%u已連線\n", client->remoteIP().toString().c_str(), client->id()); break; case WS_EVT_DISCONNECT: Serial.printf("用戶%u已離線\n", client->id()); break; case WS_EVT_ERROR: Serial.printf("用戶%u出錯了:%s\n", client->id(), (char *)data); break; case WS_EVT_DATA: Serial.printf("用戶%u傳入資料:%s\n", client->id(), (char *)data); break; } } void notifyClients() { JsonDocument doc; doc["spd"] = OBD2.pidRead(VEHICLE_SPEED); doc["rpm"] = OBD2.pidRead(ENGINE_RPM); String output; serializeJson(doc, output); ws.textAll(output); // 向所有連線的用戶端傳遞JSON字串 } void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); if (!SPIFFS.begin(true)) { Serial.println("無法載入SPIFFS記憶體"); return; } WiFi.softAP(ssid, password); IPAddress IP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(IP); // 設置首頁 server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html"); server.serveStatic("/favicon.ico", SPIFFS, "/www/favicon.ico"); server.serveStatic("/fonts/SevenSegment.woff2", SPIFFS, "/www/fonts/SevenSegment.woff2"); // 查無此頁 server.onNotFound([](AsyncWebServerRequest * req) { req->send(404, "text/plain", "Not found"); }); ws.onEvent(onSocketEvent); // 附加事件處理程式 server.addHandler(&ws); server.begin(); // 啟動網站伺服器 Serial.println("HTTP伺服器開工了~"); // 處理OBD(CAN)通訊 CAN0.setCANPins((gpio_num_t)CRX_PIN, (gpio_num_t)CTX_PIN); Serial.println("嘗試連線到OBD2 CAN匯流排…"); while (1) { // 嘗試連線到OBD2 CAN匯流排… if (!OBD2.begin()) { Serial.println("無法連線!"); digitalWrite(LED_PIN, LOW); delay(500); digitalWrite(LED_PIN, HIGH); delay(1000); } else { //連線成功! Serial.println("連線成功!"); digitalWrite(LED_PIN, LOW); break; } } } void loop() { static uint32_t prevTime = 0; // 前次時間,宣告成「靜態」變數。 uint32_t now = millis(); // 目前時間 if (now - prevTime >= INTERVAL) { prevTime = now; notifyClients(); // 向網路用戶端傳遞感測資料 } ws.cleanupClients(); }
編譯執行此程式之前,請先上傳data資料夾裡的網頁檔。把開發板A和B都接上電腦,然後將手機或電腦的Wi-Fi連上開發板B,再開啟瀏覽器連到“192.168.4.1”,即可看到開發板A傳入的OBD模擬資料;撥動搖桿,引擎轉速和行車速度也會跟著改變。
讀取所有OBD模擬值的程式
充當OBD診斷器的開發板B,可以執行Sandeep Mistry先生編寫的OBD2_03_DataPrinter範例程式,請記得要修改CAN收發器的接腳設定。編譯上傳到開發板B,它將序列埠監視窗顯示開發板A傳入的各項虛擬參數值:
赵老师,您好, 真的非常感谢!感动了,您费心了<3
不客气!