В моем скетче если что регулируйте задержки строка 62 и 66 . Одна из низ это между отправкой байт на эбу. У меня там стоит 1мс , в ваших скетчах 5мс. Но может и 1 будет работать.
Плюсы моего скетча это: отсутствие delay. Переподключение при обрыве связи, проверка КС , а значит если будет мусор , то на индикацию не пройдут некорректные данные. Также легко добавлять новые пиды.
в вашем скетче delay-и присутсвтуют и нет проверки корректности полученных от PCM данных, поэтому на экране возможно будут проскакивать нереальные значения и подтормаживания, а также плохое реагирования на кнопку. Так для прикола проверьте мой мой скетч.
Если говорить про Delay(1000), которая в в основном цикле, то она там совсем не нужна. Её нужно закомментировать. Я её использовал для отладки. А в остальных случаях я так думаю без задержек не получится. Да и максимум там задержка на 50-100мс.
100 это уже очень прилично я вам скажу. Нормальные программы так не пишут. Например , обороты двс скачками будут отображаться. Мой скетч тоже далеко не идеал. Но delay больших нет. Максимум 5 вроде гдето было. Подобные пару скетчей у меня отлично работают. Посмотрите принцип получения байт от PCM.
Добрый день MaksVV. Спасибо за скетч. У меня работает свой сейчас. Читаю параметры температуры, скорости и обороты двигателя. Но с напряжением по адресу 0x42h почему-то не получается. Возвращает 0x83 0xF1 0x10 0x7F 0x01 0x12 0x16.
MaksVV пишет:
вот исправленный скетч, в том есть косяки . Видимо такой PID 01 42 ЭБУ не поддерживает.
С программами не работал, опыта нет (если подскажете куда копать в этом плане, то тоже буду признателен). Сразу пишу скетч для Ардуино. Если к OBD2 разъёму подключить ELM 327, то данные можно считывать через тот же Car Scanner, например. Но я хочу вывести на панель значение температуры, от этого и пришлось этим заняться.
Могу выложить свои творения которые делал в 2019 году. НО по мимо программы, нужно еще чтоб аппаратная часть была рабочей.
Выложи, пожалуйста, буду очень признателен. По поводу аппаратной части - вот как раз я тоже выше вопрос про это задал. Хотел бы узнать, кто какие ещё варианты использует?
Видел такой вот ещё вариант и на lm393 , вот не понятно, какой в итоге использовать и может ли поменяться результат, если буду использовать эти схемы (пока не собирал, но детали все есть под рукой для сборки).
Mersovod оптимально через Advanced Serial Port Monitor заснифить обмен этой программы через ELM и потом уже нужный обмен повторить через ардуино.
Правильно ли я понимаю, что если не использовать сторонний софт, то надо будет параллельно к сканеру подключиться к K-линии моей же собранной аппаратной схемой, и через SoftwareSerial поставить порт на чтение и вывести все шестнадцатиричные значения через монитор Serial порта?
А если через Advanced Serial Port Monitor, то какая аппаратная часть нужна для этого? Что-то типа "BM9213M Универсальный автомобильный адаптер K-L-линии USB" ? И также надо будет параллельно сканеру подключаться?
* сканером я называю ELM или официальное диагностическое оборудование Mercedes (которое тоже у меня присутствует)
Advanced Serial Port Monitor перехватывает обмен на COM порту компьютера и по этому ничего дополнительно цеплять на K-line не надо !
Просто цепляем elm, запускаем Advanced Serial Port Monitor, запускаем ПО от elm и общаемся с ЭБУ. Потом смотрим в логе Advanced Serial Port Monitor - что посылалось-принималось через COM порт и уже пробуем посылать/принимать эти данные через ваш девайс.
мозги ==> аппаратная часть собранная мной (подсоединённая к K-линии) ==> арудина ==> USB (COM порт)
Ты говоришь что при использовании Advanced Serial Port Monitor ничего цеплять не нужно. Ок, я втыкаю ELM в мозги. ELM работает по блютузу, запускаю на андройде приложение для коннекта к ELM. А COM порт-то на компе как будет слушаться программой Advanced Serial Port Monitor? Если он никуда не подключён. Туплю пока )
Komandir, эти скетчи, по сути, вроде то же самое, что в начале темы были, но попробую.
Вопрос сейчас в другом - я попробовал использовать Advanced Serial Port Monitor, но если я запускаю одновременно мерсовский софт сканирования ЭБУ и открываю в Advanced Serial Port Monitor, то я тем самым занимаю порт и мерсовский софт не работает, т.к. порт занят. Может быть какие-то конкретные настройки нужно сделать в Advanced Serial Port Monitor?
Я точно не помню - может порядок запуска важен. Вообще Advanced Serial Port Monitor не должен занимать порт. Он как бы встраивается между ... Может софт видит что Advanced Serial Port Monitor "подгляывает" и не хочет раскрывать секреты ...
У меня сканер подключается с физическому COM порту №2, через переходник RS232 TO RS485 (RS485 выходом в COM порт). Должен ли я эту галочку "Режим интерфейса RS485" поставить?
А что означают вот эти две красные полоски? Просто они кликабельные и надпись при наведении ни о чем мне не говорит ) Не понятно, должны ли они быть нажаты во время мониторинга или нет.
В общем попробовал я промониторить, выдаёт очень много данных, там порт постоянно общается со сканером, даже до инициализации, поэтому очень сложно было разобраться. Пошёл по другому пути - попробовал разобраться в скетче, описанном в посте #55 от Aku, в итоге вроде как общение с ЭБУ началось, по крайне мере ошибок, которые там заложены в коде не вылезло и я дошёл за запросов температуры. Мозги стандарта ISO 9141, соответственно для запроса температуры ОЖ используется пакет
{0x68, 0x6A, 0xF1, 0x01, 0x05, 0xC9}
в ответ ЭБУ мне присылает такое:
48 6B 1 41 5 0 FA 0 0 0 0
По коду используется 5 байт, тут он = 0, но если смотреть через сканер то должно быть 27 градусов (ЭБУ такое значение отдаёт).
В ответе контрольная сумма верная. Как вариант это ответ от другого узла ЭБУ.
Вы бы все-таки выложили перехваченный массив данных в HEX формате. Можно нажать Clear и сразу после нажатия подключить разъем OBD к авто. Посмотрим что там видно ... Если монитор отображает температуру, то должно быть видно посылки-ответы типа 0x68, 0x6A, 0xF1, 0x01, 0x05, 0xC9
Выкладываю ссылку на лог обмена сканера с ЭБУ на том моменте, где я смотрел температуру ОЖ (но там на экране ещё было два или три других параметра, которые не убрать). На момент сканирования температура была 26 градусов. В логе сразу всё скопом - отправка и приём данных. Что-то мне подсказывает, что он ничем не поможет.
В итоге решил подключить собранную сборку и написанный, загруженный скетч в ардуинку к самой машине (до этого тестировал на купленнм с разборки ЭБУ дома без подсоединения датчиков) и ВОУЛЯ! Всё заработало на реальной машине!
А откуда узнать подобные запросы на параметры, которые описаны тут?
Например я хочу получить уровень топлива, вернуть мозги должны PID = 2F, но какой запрос я должен сделать и как самому мне понимать какие делать для соответствующих PID'ов?
Каждый бит указывает на поддержку определенного PID'а диапазона. Единица в самом правом бите указывает на наличие поддерживаемых PID'ов в следующем диапазоне. Standard PID's
Скажу честно, не пробовал. Авто на улице в снегу)). Бегаю с ноутом для отладки. Не очень удобно. Может быть в выходной попробую.
Заметил, что протокол очень чувствителен к временным интервалам.
В моем скетче если что регулируйте задержки строка 62 и 66 . Одна из низ это между отправкой байт на эбу. У меня там стоит 1мс , в ваших скетчах 5мс. Но может и 1 будет работать.
Плюсы моего скетча это: отсутствие delay. Переподключение при обрыве связи, проверка КС , а значит если будет мусор , то на индикацию не пройдут некорректные данные. Также легко добавлять новые пиды.
Предлагаю свой вариант бортового МК. Добавил немного функционал.
1. Выбор параметра кнопкой.
2. Если температура двигателя меньше 40 градусов, 3 параметра перебираются автоматически.
3. При срабатывании какого-нибудь порога, моргает индикатор и пикает зуммер.
#define F_CPU 16000000UL #include "din_ind.h" //#include "ds18b20.h" #define TX 1 #define SPEAKER 2 #define BUTTON 3 #define MODEDISPMAX 6 // количество режимов //#define TIMEDISP 300000UL // время отображения параметра 5 минут #define TIMEDISP 60000UL // время отображения параметра 1 минут //#define TIMEDISP 5000UL // время отображения параметра 5сек #define TIMEKLINE 200 // время между опросами Kline //100 #define BEEPON 70 // длительность включения BEEP #define BEEPOFF 50 // длительность выключения BEEP #define BEEPPAUSE 10000 // длительность между пачками BEEP #define TIME_DS18B20 1000 //время чтения и отображения DS18B20 (температуры АЦП) #define TIMEADC 500 //время чтения и отображения напряжения АЦП #define ADC_AVERAGE 10 // кол-во замеров ADC (max 255) #define ADC_REF 1022 // опорное напряжение ADC //1035 #define COUNTTOOGLELED 500 // кол-во прерываний для отключения индикации #define REFRESHMODE 5000 // время смены показаний char NotInit = false; // флаг инициализации unsigned int Iso = 0; // стандарт по ISO unsigned char ModeDisp = 0; // текущий режим unsigned long ForceBeepCount = 0; // команда на включение бипера на заданное количество Beep unsigned long PrevButtMillis = 0; unsigned char RxBuffer[11] = {0}; signed char Temperature = 0; unsigned int Cel = 0; unsigned char Speed = 0; unsigned short Rpm = 0; unsigned short Volt = 0; signed short mVoltTemp = 0; char ForceDisplayVoltage = false; // флаг принудительного отображения напряжения char ForceDisplayTemper = false; // флаг принудительного отображения температуры char ForceDisplayCel = false; // флаг принудительного отображения нагрузки char ForceDisplaySpeed = false; // флаг принудительного отображения скорости char ForceDisplayRPM = false; // флаг принудительного отображения оборотов char ForceToogleLed = false; // флаг принудительного моргания индикатора char ModeToogle = false; // флаг моргания при достижении 40 градусов void setup() { InitPorts(); InitTimer2(); InitDinInd(); InitKL_5baud(); ReadTrouble(); PrevButtMillis = millis(); // запоминаем первое нажатие кнопки } void loop() { InitKL_5baud(); ProcessKLine(); ProcessDisplay(); //Temperature++; //if (Temperature>=70) Temperature = 70; //Speed++; //if (Speed>=100) Speed = 100; delay(1000); //ForceToogleLed = 1; } /////////////////////////////// //Функция инициализации Портов /////////////////////////////// void InitPorts() { pinMode(TX,OUTPUT); digitalWrite(TX,HIGH); pinMode(BUTTON, INPUT_PULLUP); pinMode(SPEAKER, OUTPUT); digitalWrite(SPEAKER,LOW); ACSR |= (1 << ACD); // Analog Comparator: Off //pinMode(13,OUTPUT); //PORTB &= ~(1<<5); //PORTB ^= (1<<5); } //////////////////////// //функция опроса кнопки //////////////////////// void ProcessButton() { static char PrevButtonState = false; // предыдущее состояние кнопки static char ButtonState = false; // текущее состояние кнопки по отжатию ButtonState = (!digitalRead(BUTTON)); // читаем текущие состояние кнопки по нажатию // ButtonState = digitalRead(BUTTON); // читаем текущие состояние кнопки по отжатию if (!PrevButtonState && ButtonState && (millis() - PrevButtMillis > 200)) { // была не нажата, стала нажата и с прошлого нажатия прошло 200ms PrevButtMillis = millis(); // Запоминаем время первого срабатывания ButtonState = true; // Запоминаем состояние кнопки по нажатию // ButtonState = false; // Запоминаем состояние кнопки по отжатию if (++ModeDisp >= MODEDISPMAX) ModeDisp = 0; } else PrevButtonState = ButtonState; // не нажата } //////////////////////////// // Функция обработки данных //////////////////////////// void ProcessData() { if ((Temperature >= 110)&&(Temperature <= 115)) {ForceBeepCount = 5; ForceDisplayTemper = true; ForceToogleLed = true;} // моргаем инд. и переходим на 0 режим //if ((Temperature >= 39)&&(Temperature <= 40)) {ForceBeepCount = 4; ForceDisplayTemper = true; ForceToogleLed = true;} // моргаем инд. и переходим на 0 режим if (Temperature == 40) {ForceBeepCount = 4; ForceDisplayTemper = true; ForceToogleLed = true;} // моргаем инд. и переходим на 0 режим //if ((Temperature >= 38)&&(Temperature <= 39)) {ModeToogle = true;} // перестаем перебирать режим if (Temperature == 39) {ModeToogle = true;} // перестаем перебирать режим if ((Volt >= 55)&&(Volt <= 110)||(Volt >= 150)) {ForceBeepCount = 2; ForceToogleLed = true;} // если напряжение меньше 5,5В и 11В или больше 15В то не пикаем if ((Speed >= 75)&&(Speed <= 76)) {ForceBeepCount = 3; ForceDisplaySpeed = true; ForceToogleLed = true;} // моргаем инд. и переходим на 0 режим if ((Rpm >= 4000)&&(Rpm >= 4500)){ForceBeepCount = 1; ForceDisplayRPM = true; ForceToogleLed = true;} // моргаем инд. и переходим на 0 режим } ////////////////////////////////// // Функция отображения параметров ////////////////////////////////// void ProcessDisplay() { unsigned long CurrentStateMillis = millis(); static char StateModeStart = false; static unsigned long PrevModeDispMillis; if (!ModeToogle) { // если достигли 40 градусов то больше не перебираем ModeToogle = false; // обнуляем чтоб больше не заходить, если не выбрали if ((millis() - PrevModeDispMillis >= REFRESHMODE)) { // меняем показания через 5 секунд if (++ModeDisp >= 3) ModeDisp = 0; PrevButtMillis = millis(); // запоминаем время первого срабатывания чтоб не переходить на 0 режим через время PrevModeDispMillis = millis(); } } if (!(StateModeStart && (CurrentStateMillis - PrevButtMillis) < TIMEDISP))//если не 0 режим и после нажатия прошло время отображения параметра ModeDisp = 0; //0 //переходим на 0 режим, температура салона if (ForceDisplayVoltage) { //принудительно отображаем напряжение ForceDisplayVoltage = false; // обнуляем чтоб больше не заходить, если не выбрали ModeDisp = 1; PrevButtMillis = millis(); // запоминаем время первого срабатывания StateModeStart = false; // после моргания переходим на 0 режим } if (ForceDisplayTemper) { //принудительно отображаем температуру ForceDisplayTemper = false; // обнуляем чтоб больше не заходить, если не выбрали ModeDisp = 2; PrevButtMillis = millis(); // запоминаем время первого срабатывания StateModeStart = false; // после моргания переходим на 0 режим } if (ForceDisplayCel) { //принудительно отображаем нагрузку ForceDisplayCel = false; // обнуляем чтоб больше не заходить, если не выбрали ModeDisp = 3; PrevButtMillis = millis(); // запоминаем время первого срабатывания StateModeStart = false; // после моргания переходим на 0 режим } if (ForceDisplaySpeed) { //принудительно отображаем скорость ForceDisplaySpeed = false; // обнуляем чтоб больше не заходить, если не выбрали ModeDisp = 4; PrevButtMillis = millis(); // запоминаем время первого срабатывания StateModeStart = false; // после моргания переходим на 0 режим } if (ForceDisplayRPM) { //принудительно отображаем обороты ForceDisplayRPM = false; // обнуляем чтоб больше не заходить, если не выбрали ModeDisp = 5; PrevButtMillis = millis(); // запоминаем время первого срабатывания StateModeStart = false; // после моргания переходим на 0 режим } switch (ModeDisp) { //температура DS18B20 case 0: StateModeStart = true; // обнуляем чтоб больше не заходить, если не выбрали static unsigned long PrevReadDS18B20Millis; if ((millis() - PrevReadDS18B20Millis >= TIME_DS18B20)) { // читаем и выводим через некоторое время Digit[0] = 16; //ReadTemperDs18B20(); mVoltTemp = (Read_ADC_Temp()*ADC_REF/1024UL/10)-60; ADC_Temp_Ind(); PrevReadDS18B20Millis = millis(); } break; //напряжение борт. сети case 1: static unsigned long PrevADCMillis; if ((millis() - PrevADCMillis >= TIMEADC)) { // выводим через некоторое время //Volt = Read_ADC()*20*ADC_REF/1024UL/100; //99 чуть больше ADC_Volt_Ind(); PrevADCMillis = millis(); } break; //температура K-Line case 2: unsigned char temper_module; if (Temperature >=0) temper_module = Temperature; else temper_module = - Temperature; if (Temperature < 0) Digit[0] = 18; // "прочерк" else { if ((temper_module%1000/100) == 0) Digit[0] = 16; // если 0 то пусто else Digit[0] = temper_module%1000/100; // Сотни } Digit[1] = temper_module%100/10; // Десятки Digit[2] = temper_module%10; // Единицы Digit[3] = 19; // "Градус" Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 1; break; //нагрузка K-Line case 3: if ((Cel%1000/100) == 0) Digit[0] = 16; // если 0 то пусто else Digit[0] = Cel%1000/100; // Сотни Digit[1] = Cel%100/10; // Десятки Digit[2] = Cel%10; // Единицы Digit[3] = 24; // "L" Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; break; // скорость K-Line case 4: if (((Speed)%1000/100) == 0) Digit[0] = 16; // если 0 то пусто else Digit[0] = Speed%1000/100; // Сотни Digit[1] = Speed%100/10; // Десятки Digit[2] = Speed%10; // Единицы Digit[3] = 31; // "c" Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; break; //обороты K-Line case 5: if (Rpm % 10000/1000 == 0) Digit[0] = 16; // если 0 то пусто else Digit[0] = Rpm%10000/1000; // Тысячи Digit[1] = Rpm%1000/100; // Сотни Digit[2] = Rpm%100/10; // Десятки Digit[3] = Rpm%10/1; // Единицы Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; break; } } //////////////////////////////////// // Функция чтения параметров K-Line //////////////////////////////////// void ProcessKLine() { static unsigned char index; static unsigned long PrevKlineMillis; if ((millis() - PrevKlineMillis >= TIMEKLINE)) { switch (index) { //температура case 0: if (Iso == 9141) { static unsigned char EngCoolTemp[6] = {0x68, 0x6A, 0xF1, 0x01, 0x05, 0xC9}; TxBuf(EngCoolTemp,sizeof(EngCoolTemp)); } if (Iso == 14230) { static unsigned char EngCoolTemp[6] = {0xC2, 0x33, 0xF1, 0x01, 0x05, 0xEC}; TxBuf(EngCoolTemp,sizeof(EngCoolTemp)); } delay(50); RxBuf(); //записываем в RxBuffer if (RxBuffer[4]==0x05) { Temperature = RxBuffer[5]; Temperature = Temperature - 40; } else { NotInit = false; // инициализируем снова Serial.end(); digitalWrite(TX,HIGH); } break; //Нагрузка case 1: if (Iso == 9141) { static unsigned char CalcEngLoad[6] = {0x68, 0x6A, 0xF1, 0x01, 0x04, 0xC8}; TxBuf(CalcEngLoad,sizeof(CalcEngLoad)); } if (Iso == 14230) { static unsigned char CalcEngLoad[6] = {0xC2, 0x33, 0xF1, 0x01, 0x04, 0xEB}; TxBuf(CalcEngLoad,sizeof(CalcEngLoad)); } delay(50); RxBuf(); //записываем в RxBuffer if (RxBuffer[4]==0x04) { Cel = RxBuffer[5] * 100; Cel = Cel / 255; } else { NotInit = false; // инициализируем снова Serial.end(); digitalWrite(TX,HIGH); } break; //скорость case 2: if (Iso == 9141) { static unsigned char VehicleSpeed[6] = {0x68, 0x6A, 0xF1, 0x01, 0x0D, 0xD1}; TxBuf(VehicleSpeed,sizeof(VehicleSpeed)); } if (Iso == 14230) { static unsigned char VehicleSpeed[6] = {0xC2, 0x33, 0xF1, 0x01, 0x0D, 0xF4}; TxBuf(VehicleSpeed,sizeof(VehicleSpeed)); } delay(50); RxBuf(); //записываем в RxBuffer if (RxBuffer[4]==0x0D) { Speed = RxBuffer[5]; } else { NotInit = false; // инициализируем снова Serial.end(); digitalWrite(TX,HIGH); } break; //обороты case 3: if (Iso == 9141) { static unsigned char EngineRPM[6] = {0x68, 0x6A, 0xF1, 0x01, 0x0C, 0xD0}; TxBuf(EngineRPM,sizeof(EngineRPM)); } if (Iso == 14230) { static unsigned char EngineRPM[6] = {0xC2, 0x33, 0xF1, 0x01, 0x0C, 0xF3}; TxBuf(EngineRPM,sizeof(EngineRPM)); } delay(50); RxBuf(); //записываем в RxBuffer if (RxBuffer[4]==0x0C) { Rpm = (256*RxBuffer[5]) + RxBuffer[6]; Rpm = Rpm/4; } else { NotInit = false; // инициализируем снова Serial.end(); digitalWrite(TX,HIGH); } break; //Напряжение ADC case 4: Volt = Read_ADC()*20*ADC_REF/1024UL/100; //99 чуть больше break; //Напряжение термодатчика //case 5: //mVoltTemp = (Read_ADC_Temp()*ADC_REF/1024UL/10)-60; //break; } if (++index>=5) index = 0; //4 + ADC PrevKlineMillis = millis(); } } ////////////////////////// // Функция обработки Beep ////////////////////////// void ProcessBeeper() { static unsigned char index; static unsigned long BeepMillis; static unsigned long PrevBeepMillis; static char count; switch (index) { case 0: if (!((millis()-PrevBeepMillis) >= BeepMillis)) break; if (ForceBeepCount) { count = ForceBeepCount; //ForceBeepCount = 0; BeepMillis = 0; index = 1; PrevBeepMillis = millis(); } break; case 1: if (!((millis()-PrevBeepMillis) >= BeepMillis)) break; Beeper_ON(); BeepMillis = BEEPON; index = 2; PrevBeepMillis = millis(); break; case 2: if (!((millis()-PrevBeepMillis) >= BeepMillis)) break; Beeper_OFF(); if (--count>0) { BeepMillis = BEEPOFF; index = 1; PrevBeepMillis = millis(); } else { BeepMillis = BEEPPAUSE; index = 0; PrevBeepMillis = millis(); } break; } ForceBeepCount = 0; } /////////////////////////////////////////////// //Функция инициализации Таймера2 для Дин. Инд. /////////////////////////////////////////////// void InitTimer2() { cli();// Глобально запрещаю прерывания TCCR2A = 0; TCCR2B = 0; TIMSK2 = 0; TCCR2A |= (1<<COM2A0); // Toggle OC2A,No CTC TCCR2B |= (1<<CS21)|(1<<CS20); // Presc. = 32 //TCCR2B |= (1<<CS22); // Presc. = 64 TIMSK2 |= (1<<TOIE2); // enable timer overflow interrupt sei(); // Глобально разрешаю прерывания } ////////////////////////////////// // Обработчик прерывания дин. инд. ////////////////////////////////// ISR (TIMER2_OVF_vect) { static short i = 0; i++; if (ForceToogleLed) { ForceToogleLed = false; // обнуляем чтоб больше не заходить, если не выбрали if (i>=COUNTTOOGLELED) { i = 0; if (OnLed) OnLed = false; else OnLed = true; } } else { OnLed = true; i = 0; } /* static short z = 0; static char On = 0; z++; if (z>=700) { z = 0; if (On) { On = false; PORTB &= ~(1<<5); } else { On = true; PORTB |= (1<<5); } } */ RefreshDinInd(); ProcessButton(); ProcessData(); ProcessBeeper(); } /////////////////////////////////////////////// // Функция чтения байта с выходом по тайм ауту /////////////////////////////////////////////// char getSerial(unsigned char &retVal, unsigned long timeout) { unsigned long start = millis(); while (!Serial.available()) { // ждем байт if ((millis() - start) > timeout) { return true; // не дождались, вышли по тайм ауту } } retVal = Serial.read(); return false; // дождались байт } ///////////////////////////////// //Функция чтения данных по KLine ///////////////////////////////// void RxBuf() { char bByteTimeout = false; unsigned char byteNum = 0; while (!bByteTimeout) { bByteTimeout = getSerial(RxBuffer[byteNum],20); // сохранить ответ if (bByteTimeout) { RxBuffer[byteNum] = 0; // текущий байт в 0, если тайм-аут произошел до того, как он мог быть записан } byteNum++; } for (unsigned char i = byteNum; byteNum < 11; byteNum++) { // заполнение остатка текущего сообщения нулями RxBuffer[byteNum] = 0; } } /////////////////////////////////// //Функция отправки данных по KLine /////////////////////////////////// void TxBuf(unsigned char *array, unsigned char bufSizeTx) { for (unsigned char i = 0; i < bufSizeTx; i++) { Serial.write(array[i]); unsigned char Clear; getSerial(Clear,5); delay(5); // 5ms <= P4 <= 20ms } } /////////////////////////////// //Функция инициализации K-Line /////////////////////////////// void InitKL_5baud() { if (!NotInit) { // если не было инициализации, то инициализируем delay(2500); digitalWrite(TX, LOW ); delay(200); digitalWrite(TX, HIGH ); delay(400); digitalWrite(TX, LOW ); delay(400); digitalWrite(TX, HIGH ); delay(400); digitalWrite(TX, LOW ); delay(400); digitalWrite(TX, HIGH ); delay(200); Serial.begin(10400); unsigned char InitError = 0; // номер ошибки unsigned char Sync = 0; unsigned char Key1 = 0; unsigned char Key2 = 0; unsigned char InvKey2 = 0; unsigned char Clear = 0; unsigned char InvAddress = 0; if (!getSerial(Sync,300)) { if (Sync == 0x55) { if (!getSerial(Key1,20)) { // если успешно прочитали key1 if(!getSerial(Key2,20)) { // если успешно прочитали key2 delay(25); // 25ms <= W4 <= 50ms InvKey2 = ~Key2; Serial.write(InvKey2); getSerial(Clear,5); // читаем эхо invKey2 if (!getSerial(InvAddress,50)) { // если успешно прочитали инверсию байта if (InvAddress == 0xCC) { // если получили 0xCC if ((Key1 == 0x08) && (Key2 == 0x08)) Iso = 9141; if (Key2 == 0x8F) Iso = 14230; //MsbToLed(Key1); // отображаем key1, key2 //LsbToLed(Key2); NotInit = true; // инит успешно, больше не инициализируем } else { InitError = 6; // байт не равен 0xCC } } else { InitError = 5; // не получили инверсию байта 0x33 } } else { InitError = 4; // не получили key2 } } else { InitError = 3; // не получили key1 } } else { InitError = 2; // байт sync не равен 0x55 } } else { InitError = 1; // не получили sync } if (InitError) { // выводим ошибки, если есть Digit[0] = 14; // E Digit[1] = 22; // r Digit[2] = 30; // r. Digit[3] = InitError; Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; delay(5000); //5000 } delay(TIMEKLINE); // 55ms <= P3 (wait between messages) <= 5s //delay(2000); // если отображаем key1, key2 } // NotInit } ///////////////////////// //Функция включения BEEP ///////////////////////// void Beeper_ON() { digitalWrite(SPEAKER,HIGH); } ////////////////////////// //Функция выключения BEEP ////////////////////////// void Beeper_OFF() { digitalWrite(SPEAKER,LOW); } //////////////////////// //Функция чтения ошибок //////////////////////// void ReadTrouble() { if (Iso == 9141) { static unsigned char Trouble[5] = {0x68, 0x6A, 0xF1, 0x03, 0xC6}; TxBuf(Trouble,sizeof(Trouble)); } if (Iso == 14230) { static unsigned char Trouble[5] = {0xC2, 0x33, 0xF1, 0x03, 0xE9}; TxBuf(Trouble,sizeof(Trouble)); } delay(50); RxBuf(); //записываем в RxBuffer //RxBuffer[5] = 0xFF; //RxBuffer[6] = 0x12; unsigned char dtc1 = (RxBuffer[5] & 0xC0) | (dtc1 >> 6); unsigned char dtc2 = (RxBuffer[5] & 0x30) >> 4; unsigned char dtc3 = RxBuffer[5] & 0x0F; unsigned char dtc4 = (RxBuffer[6] & 0xF0) >> 4; unsigned char dtc5 = RxBuffer[6] & 0x0F; if (dtc1||dtc2||dtc3||dtc4||dtc5) { // если есть ошибки Digit[0] = 14; // E Digit[1] = 22; // r Digit[2] = 30; // r. switch (dtc1) { case 0x00 : Digit[3] = 26; break; case 0x40 : Digit[3] = 12; break; case 0x80 : Digit[3] = 11; break; case 0xC0 : Digit[3] = 28; break; } Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; delay(2000); Digit[0] = dtc2; Digit[1] = dtc3; Digit[2] = dtc4; Digit[3] = dtc5; delay(2000); } } ////////////////////////// //Функция удаления ошибок ////////////////////////// /* void CLRTrouble() { if (Iso == 9141) { static unsigned char ClearTrouble[5] = {0x68, 0x6A, 0xF1, 0x04, 0xC7}; TxBuf(ClearTrouble,sizeof(ClearTrouble)); } if (Iso == 14230) { static unsigned char ClearTrouble[5] = {0xC2, 0x33, 0xF1, 0x04, 0xEA}; TxBuf(ClearTrouble,sizeof(ClearTrouble)); } delay(50); } */ ////////////////////// // Функция чтения ADC ////////////////////// unsigned long Read_ADC() { unsigned long Result = 0; // Настраиваю АЦП DIDR0 |= (1 << 7); // отключаем цифровой вход ADC7 напряжение ADMUX |= (1 << REFS1)|(1 << REFS0) // Int. 1.1V Ref. with ext. cap. at AREF. |(1 << MUX2)|(1 << MUX1)|(1 << MUX0); // ADC7 volt //|(1 << MUX3)|(1 << MUX2)|(1 << MUX1); // 1.1V ADCSRA |= (1 << ADEN) // ADC enable |(1 << ADPS2)|(1 << ADPS1)|(1 << ADPS0); // CK/128 delay(1); for (unsigned char i=0;i<ADC_AVERAGE;i++) { do {ADCSRA |= (1 << ADSC);} // Старт преобразования while ((ADCSRA & (1 << ADIF)) == 0); // ждем флаг ADIF Result = Result + ADCW; // Result + (ADCL|ADCH << 8); ADCSRA |= (1 << ADIF); // сбрасываем флаг ADIF ADCSRA &= ~(1 << ADSC); // Стоп преобразования } Result = Result / ADC_AVERAGE; DIDR0 = 0; ADMUX = 0; ADCSRA = 0; return Result; } void ADC_Volt_Ind() { if ((Volt%1000/100)==0) Digit[0] = 16; else Digit[0] = Volt%1000/100; // Десятки вольт Digit[1] = Volt%100/10; // Единицы вольт Digit[2] = Volt%10; // 1/10 вольт Digit[3] = 28; // U Dp0 = 0; Dp1 = 1; Dp2 = 0; Dp3 = 0; } /////////////////////////// // Функция чтения ADC темп /////////////////////////// unsigned long Read_ADC_Temp() { unsigned long Result = 0; // Настраиваю АЦП DIDR0 |= (1 << 6); // отключаем цифровой вход ADC6 температура ADMUX |= (1 << REFS1)|(1 << REFS0) // Int. 1.1V Ref. with ext. cap. at AREF. |(1 << MUX2)|(1 << MUX1); // ADC6 temp //|(1 << MUX3)|(1 << MUX2)|(1 << MUX1); // 1.1V ADCSRA |= (1 << ADEN) // ADC enable |(1 << ADPS2)|(1 << ADPS1)|(1 << ADPS0); // CK/128 delay(1); for (unsigned char i=0;i<ADC_AVERAGE;i++) { do {ADCSRA |= (1 << ADSC);} // Старт преобразования while ((ADCSRA & (1 << ADIF)) == 0); // ждем флаг ADIF Result = Result + ADCW; // Result + (ADCL|ADCH << 8); ADCSRA |= (1 << ADIF); // сбрасываем флаг ADIF ADCSRA &= ~(1 << ADSC); // Стоп преобразования } Result = Result / ADC_AVERAGE; DIDR0 = 0; ADMUX = 0; ADCSRA = 0; return Result; } /////////////////////////// // Функция чтения ADC темп /////////////////////////// void ADC_Temp_Ind() { if (mVoltTemp < 0) { // Отрицательная температура mVoltTemp =-mVoltTemp; // Перевожу отрицательное число в положительное Digit[1] = 18; // Символ "-" if (mVoltTemp < 10) { // -9*...-1* Digit[2] = mVoltTemp % 10; // Единицы градусов Digit[3] = 19; // Символ градуса } else { // -55...-10 Digit[2] = mVoltTemp % 100 / 10; // Десятки градусов Digit[3] = mVoltTemp % 10; // Единицы градусов } } else { // Положительная температура if (mVoltTemp > 99) { // 100...125 Digit[1] = mVoltTemp % 1000 / 100; // Сотни градусов Digit[2] = mVoltTemp % 100 / 10; // Десятки градусов Digit[3] = mVoltTemp % 10; // Единицы градусов } else { if (mVoltTemp > 9) { // 10*...99* Digit[1] = mVoltTemp % 100 / 10; // Десятки градусов Digit[2] = mVoltTemp % 10; // Единицы градусов Digit[3] = 19; // Символ градуса } else { // 0*...9* Digit[1] = 16; // Пустой символ Digit[2] = mVoltTemp % 10; // Единицы градусов Digit[3] = 19; // Символ градуса } } } Dp0 = 0; Dp1 = 0; Dp2 = 0; Dp3 = 0; }а все таки #45 пробовали?
MaksVV,Спасибо за помощь. Нет. Не дошло до этого. Когда у меня заработало, смысл пропал. Но если очень нужно, то попробую.
в вашем скетче delay-и присутсвтуют и нет проверки корректности полученных от PCM данных, поэтому на экране возможно будут проскакивать нереальные значения и подтормаживания, а также плохое реагирования на кнопку. Так для прикола проверьте мой мой скетч.
Если говорить про Delay(1000), которая в в основном цикле, то она там совсем не нужна. Её нужно закомментировать. Я её использовал для отладки. А в остальных случаях я так думаю без задержек не получится. Да и максимум там задержка на 50-100мс.
100 это уже очень прилично я вам скажу. Нормальные программы так не пишут. Например , обороты двс скачками будут отображаться. Мой скетч тоже далеко не идеал. Но delay больших нет. Максимум 5 вроде гдето было. Подобные пару скетчей у меня отлично работают. Посмотрите принцип получения байт от PCM.
Привет, MaksVV. А почему в скетче №45 Вы считываете напряжение из одного байта SysVolt = buf[n+2]/10.0 ?
В документации на Pid-ы другая формула
(hex)
(Dec)
Добрый день MaksVV. Спасибо за скетч. У меня работает свой сейчас. Читаю параметры температуры, скорости и обороты двигателя. Но с напряжением по адресу 0x42h почему-то не получается. Возвращает 0x83 0xF1 0x10 0x7F 0x01 0x12 0x16.
вот исправленный скетч, в том есть косяки . Видимо такой PID 01 42 ЭБУ не поддерживает.
Добрый день! Пытаюсь повторить Ваше творение ! не могу найти библиотеку данную ! Если не сложно поделитесь . Спасибо! #include "din_ind.h"
Добрый! Библиотека моя личная. Могу скинуть. Моя почта "elektronshik.kudzinсобакаgmail.com"
Ребят, только учусь. Мне нужно тоже получить из машинки температуру. Машина Mercedes W168.
Перебрал почти все скетчи тут, в итоге, для наглядности, по скетчу в сообщении #36 (от MaksVV) выдаёт следующее:
Какие-нибудь программы видят авто через шнурок ?
С программами не работал, опыта нет (если подскажете куда копать в этом плане, то тоже буду признателен). Сразу пишу скетч для Ардуино. Если к OBD2 разъёму подключить ELM 327, то данные можно считывать через тот же Car Scanner, например. Но я хочу вывести на панель значение температуры, от этого и пришлось этим заняться.
Могу выложить свои творения которые делал в 2019 году. НО по мимо программы, нужно еще чтоб аппаратная часть была рабочей.
Mersovod оптимально через Advanced Serial Port Monitor заснифить обмен этой программы через ELM и потом уже нужный обмен повторить через ардуино.
Могу выложить свои творения которые делал в 2019 году. НО по мимо программы, нужно еще чтоб аппаратная часть была рабочей.
Выложи, пожалуйста, буду очень признателен. По поводу аппаратной части - вот как раз я тоже выше вопрос про это задал. Хотел бы узнать, кто какие ещё варианты использует?
Видел такой вот ещё вариант и на lm393 , вот не понятно, какой в итоге использовать и может ли поменяться результат, если буду использовать эти схемы (пока не собирал, но детали все есть под рукой для сборки).
Mersovod оптимально через Advanced Serial Port Monitor заснифить обмен этой программы через ELM и потом уже нужный обмен повторить через ардуино.
Правильно ли я понимаю, что если не использовать сторонний софт, то надо будет параллельно к сканеру подключиться к K-линии моей же собранной аппаратной схемой, и через SoftwareSerial поставить порт на чтение и вывести все шестнадцатиричные значения через монитор Serial порта?
А если через Advanced Serial Port Monitor, то какая аппаратная часть нужна для этого? Что-то типа "BM9213M Универсальный автомобильный адаптер K-L-линии USB" ? И также надо будет параллельно сканеру подключаться?
* сканером я называю ELM или официальное диагностическое оборудование Mercedes (которое тоже у меня присутствует)
Advanced Serial Port Monitor перехватывает обмен на COM порту компьютера и по этому ничего дополнительно цеплять на K-line не надо !
Просто цепляем elm, запускаем Advanced Serial Port Monitor, запускаем ПО от elm и общаемся с ЭБУ. Потом смотрим в логе Advanced Serial Port Monitor - что посылалось-принималось через COM порт и уже пробуем посылать/принимать эти данные через ваш девайс.
Не до конца понял.
Как сейчас у меня:
мозги ==> аппаратная часть собранная мной (подсоединённая к K-линии) ==> арудина ==> USB (COM порт)
Ты говоришь что при использовании Advanced Serial Port Monitor ничего цеплять не нужно. Ок, я втыкаю ELM в мозги. ELM работает по блютузу, запускаю на андройде приложение для коннекта к ELM. А COM порт-то на компе как будет слушаться программой Advanced Serial Port Monitor? Если он никуда не подключён. Туплю пока )
Я про elm в виде шнурка в USB/COM компьютера. А мерседесовская приблуда куда включается ?
Я про elm в виде шнурка в USB/COM компьютера. А мерседесовская приблуда куда включается ?
А, понял, хорошо, попробую через мерседесовский сканер, он с проводом. Спасибо!
#define BAUD_200 0 #define BAUD_5 1 bool Protocol = BAUD_5; // тут выбираем протокол 200 baud или 5 baud byte ADDRESS = 0x33; // тут выбираем адрес ЭБУ byte StartCommunication [] = {0xc1, ADDRESS, 0xf1, 0x81, 0x66}; byte delaybyte_TX = 1; //задержка между отправкой байт, мс int bit_time = 0; uint32_t prev = 0; #include <SoftwareSerial.h> #define RX 2 #define TX 3 SoftwareSerial K_line(RX, TX); bool InitGauge = 0; // флаг инита панели void setup() { Serial.begin (9600); //открываем соединение терминала для отладки pinMode (TX, OUTPUT); digitalWrite (TX, 1); // BUS idle delay (300); Serial.print("Delayu zapros na PCM. Adress: "); Serial.println(ADDRESS, HEX); Serial.println("Fast Init"); digitalWrite (TX, 0); delay (25); digitalWrite (TX, 1); delay (25); K_line.begin(10400); // } void loop() { byte Checksum = 0; for (byte i = 0; i < sizeof(StartCommunication); i++) { if (i != sizeof(StartCommunication) - 1) Checksum += StartCommunication[i]; else StartCommunication[i] = Checksum; K_line.write (StartCommunication[i]); delay (delaybyte_TX); } Serial.println ("Send StartCommunication"); while (K_line.available()) { Serial.print(K_line.read(), HEX); Serial.print (" "); } while (1); }Так ещё можно попробовать ...
Komandir, эти скетчи, по сути, вроде то же самое, что в начале темы были, но попробую.
Вопрос сейчас в другом - я попробовал использовать Advanced Serial Port Monitor, но если я запускаю одновременно мерсовский софт сканирования ЭБУ и открываю в Advanced Serial Port Monitor, то я тем самым занимаю порт и мерсовский софт не работает, т.к. порт занят. Может быть какие-то конкретные настройки нужно сделать в Advanced Serial Port Monitor?
Я точно не помню - может порядок запуска важен. Вообще Advanced Serial Port Monitor не должен занимать порт. Он как бы встраивается между ... Может софт видит что Advanced Serial Port Monitor "подгляывает" и не хочет раскрывать секреты ...
Что вот эти настройки обозначают?
У меня сканер подключается с физическому COM порту №2, через переходник RS232 TO RS485 (RS485 выходом в COM порт). Должен ли я эту галочку "Режим интерфейса RS485" поставить?
С такими переходниками не сталкивался.
Может не тот снифер ... я брал тут - https://advanced-serial-port-monitor.soft112.com/
У меня он сейчас не работает - бесплатно работает ограниченное время. Запускать надо в режиме Наблюдатель.
Вашу тему не знаю, но у себя применяю VSPE сплиттер, программа для 32 битной версии бесплатна
А что означают вот эти две красные полоски? Просто они кликабельные и надпись при наведении ни о чем мне не говорит ) Не понятно, должны ли они быть нажаты во время мониторинга или нет.
Я вроде не нажимал там ничего. Просто в HEX обмен смотрел и все.
Выложу тут ссылку на инициализацию - https://russianblogs.com/article/33311624928/
В общем попробовал я промониторить, выдаёт очень много данных, там порт постоянно общается со сканером, даже до инициализации, поэтому очень сложно было разобраться. Пошёл по другому пути - попробовал разобраться в скетче, описанном в посте #55 от Aku, в итоге вроде как общение с ЭБУ началось, по крайне мере ошибок, которые там заложены в коде не вылезло и я дошёл за запросов температуры. Мозги стандарта ISO 9141, соответственно для запроса температуры ОЖ используется пакет
{0x68, 0x6A, 0xF1, 0x01, 0x05, 0xC9}
в ответ ЭБУ мне присылает такое:
48 6B 1 41 5 0 FA 0 0 0 0
По коду используется 5 байт, тут он = 0, но если смотреть через сканер то должно быть 27 градусов (ЭБУ такое значение отдаёт).
В итоге я чёт не пойму, где тут температура?
В ответе контрольная сумма верная. Как вариант это ответ от другого узла ЭБУ.
Вы бы все-таки выложили перехваченный массив данных в HEX формате. Можно нажать Clear и сразу после нажатия подключить разъем OBD к авто. Посмотрим что там видно ... Если монитор отображает температуру, то должно быть видно посылки-ответы типа 0x68, 0x6A, 0xF1, 0x01, 0x05, 0xC9
Выкладываю ссылку на лог обмена сканера с ЭБУ на том моменте, где я смотрел температуру ОЖ (но там на экране ещё было два или три других параметра, которые не убрать). На момент сканирования температура была 26 градусов. В логе сразу всё скопом - отправка и приём данных. Что-то мне подсказывает, что он ничем не поможет.
Там есть кнопочка ASCII/HEX - можно в HEX ???
Вот в HEX
Насколько я вижу тут нет протокола OBD ... Это какой то свой протокол MB.
Получается, нужен всё-таки шнур с Элемкой? Там точно OBD будет ....
Скорее всего. MB не делится своими протоколами.
> в ответ ЭБУ мне присылает такое:
> 48 6B 1 41 5 0 FA 0 0 0 0
В итоге решил подключить собранную сборку и написанный, загруженный скетч в ардуинку к самой машине (до этого тестировал на купленнм с разборки ЭБУ дома без подсоединения датчиков) и ВОУЛЯ! Всё заработало на реальной машине!
Это дома:
А это в машине:
Ok
Кидаю. Это пример, на его основе соберете свой.
unsigned char EngCoolTemp[6] = {0xC2, 0x33, 0xF1, 0x01, 0x05, 0xEC};static unsigned char VehicleSpeed[6] = {0x68, 0x6A, 0xF1, 0x01, 0x0D, 0xD1};А откуда узнать подобные запросы на параметры, которые описаны тут?
Например я хочу получить уровень топлива, вернуть мозги должны PID = 2F, но какой запрос я должен сделать и как самому мне понимать какие делать для соответствующих PID'ов?
Нужно отправить PID 01 00. Будет ответ всех поддерживаемых.
А как узнать какой за что отвечает?
Запрос 0100 только для диапазона 0x01-0x20.
Ответ содержит 4 байта. Для запроса 0100, к примеру, приходит ответ:10111110 00011111 10101000 00010011
Каждый бит указывает на поддержку определенного PID'а диапазона. Единица в самом правом бите указывает на наличие поддерживаемых PID'ов в следующем диапазоне. Standard PID's
Это я вроде понял, не пойму откуда берётся последнее значение?
{0xC2, 0x33, 0xF1, 0x01, 0x05, 0xEC};
Т.е. 0хЕС
Это контрольная сумма - в данном протоколе младший байт суммы всех остальных !
0xC2+0x33+0xF1+0x01+0x05=0x01EC
В ответе от ЭБУ последний байт считается по той же схеме и если он не совпадает - произошла ошибка передачи !!!