• Witaj na Forum Arduino Polska! Zapraszamy do rejestracji!
  • Znajdziesz tutaj wiele informacji na temat hardware / software.
Witaj! Logowanie Rejestracja


Ocena wątku:
  • 0 głosów - średnia: 0
  • 1
  • 2
  • 3
  • 4
  • 5
"Nakładanie" się funkcji - komunikacja przez BT
#1
Witajcie,

przygotowuję sobie urządzenie do odczytu informacji z gniazda OBDII, które gromadzi dane a w późniejszym czasie służyć będzie do analizy "stylu" jazdy kierowcy i eliminowaniu złych nawyków. Aktualnie potrzebuję danych odnośnie aktualnych obrotów silnika i aktualnej prędkości samochodu.

Do gniazda OBD wpięty jest ELM327 w wersji bluetooth. Natomiast Arduino współgra z modułem HC-05 poprzez Serial(38400). Wszystko jest zgodnie z oczekiwaniami, gdy aktywna jest tylko jedna funkcja odczytu. Natomiast, gdy uruchamiam dwie funkcje jednocześnie następuje chaos. Po kolei przedstawię jakie próby wykonałem, aby wyeliminować problem.

Wartości są wywoływane wysłaniem odpowiedniej komendy do modułu ELM327 (010C lub 010D). W odpowiedzi otrzymuję ciąg znaków (010C1410CXXXX lub 010D1410DXX). W przypadku, gdy obie funkcje są wykonywane ciągi znaków zaczynają na siebie nachodzić. Jednak czasami zdarza się, że działa to prawidłowo i odczyty są poprawne, jednak jest to mniej niż 1% odpowiedzi na zapytanie.

Próbowałem zmniejszyć częstotliwość wywoływania funkcji (kolejne zapytanie co sekundę) - bez efektów.
Utworzyłem z dwóch jedną funkcję - bez rezultatów.
W obrębie jednej funkcji wprowadziłem zmienną indeksującą, która zmieniała stan po ustaleniu wartości np. obrotów na 1, a warunek if (index==1) zezwalał na wywołanie funkcji sprawdzającej prędkość samochodu - również bez zmian.
Wywołanie przez timer funkcji pośredniej, w której szeregowo wywoływane były funkcje obliczające z użyciem delay w formie
void obd_check(){
rpm_calc();
delay(10);
velo_calc();
}
Również bez zmian.
Czy możecie naprowadzić mnie co tutaj jest nie tak i dlaczego te funkcje działą, ale w mniej niż jednym przypadku na 100 wykonań?
Czy może jest coś takiego jak wysłanie "pustego" zapytania do ELM327?

Poniżej umieszczam kod funkcji dla obrotów (kod dla prędkości jest bliźniaczy). Kod bazuje na dostępnym w internecie wzorze:
Kod:
void rpm_calc(){
 boolean prompt_rpm,valid_rpm;  
 char recvChar_rpm;
 char bufin_rpm[15];
 int i_rpm;
 //
 if (!(obd_error_flag)){                                   //if no OBD connection error
 //
   valid_rpm=false;
   prompt_rpm=false;
   blueToothSerial.print("010C1");                        //send to OBD PID command 010C is for RPM, the last 1 is for ELM to wait just for 1 respond (see ELM datasheet)
   blueToothSerial.print("\r");                           //send to OBD cariage return char
   while (blueToothSerial.available() <= 0);              //wait while no data from ELM
   i_rpm=0;
   while ((blueToothSerial.available()>0) && (!prompt_rpm)){  //if there is data from ELM and prompt is false
     recvChar_rpm = blueToothSerial.read();                   //read from ELM
     if ((i_rpm<15)&&(!(recvChar_rpm==32))) {                     //the normal respond to previus command is 010C1/r41 0C ?? ??>, so count 15 chars and ignore char 32 which is space
       bufin_rpm[i_rpm]=recvChar_rpm;                                 //put received char in bufin array
       i_rpm=i_rpm+1;                                             //increase i
     }  
   if (recvChar_rpm==62) prompt_rpm=true;                       //if received char is 62 which is '>' then prompt is true, which means that ELM response is finished
 }
   if ((bufin_rpm[6]=='4') && (bufin_rpm[7]=='1') && (bufin_rpm[8]=='0') && (bufin_rpm[9]=='C')){ //if first four chars after our command is 410C
   valid_rpm=true;                                                                  //then we have a correct RPM response
   } else {
   valid_rpm=false;                                                                 //else we dont
   }
   if (valid_rpm){                                                                    //in case of correct RPM response
     rpm_retries=0;                                                               //reset to 0 retries
     rpm_error_flag=false;                                                        //set rpm error flag to false
           //start calculation of real RPM value
           //RPM is coming from OBD in two 8bit(bytes) hex numbers for example A=0B and B=6C
           //the equation is ((A * 256) + B) / 4, so 0B=11 and 6C=108
           //so rpm=((11 * 256) + 108) / 4 = 731 a normal idle car engine rpm
     rpm=0;
     Serial.print("Bufin_RPM: ");
     Serial.println(bufin_rpm);                                                                                            
     for (i_rpm=10;i_rpm<14;i_rpm++){                              //in that 4 chars of bufin array which is the RPM value
         if ((bufin_rpm[i_rpm]>='A') && (bufin_rpm[i_rpm]<='F')){        //if char is between 'A' and 'F'
           bufin_rpm[i_rpm]-=55;                                 //'A' is int 65 minus 55 gives 10 which is int value for hex A
         }
         if ((bufin_rpm[i_rpm]>='0') && (bufin_rpm[i_rpm]<='9')){        //if char is between '0' and '9'
           bufin_rpm[i_rpm]-=48;                                 //'0' is int 48 minus 48 gives 0 same as hex
         }
         rpm=(rpm << 4) | (bufin_rpm[i_rpm] & 0xf);              //shift left rpm 4 bits and add the 4 bits of new char
     }
     rpm=rpm >> 2;                                     //finaly shift right rpm 2 bits, rpm=rpm/4
   }
   Serial.print("Obroty: ");
   Serial.println(rpm);
   //
   if (!valid_rpm){                                              //in case of incorrect RPM response
     rpm_error_flag=true;                                    //set rpm error flag to true
     rpm_retries+=1;                                         //add 1 retry
     rpm=0;                                                  //set rpm to 0
     Serial.println("RPM_ERROR");
     if (rpm_retries>=RPM_CMD_RETRIES) obd_error_flag=true;  //if retries reached RPM_CMD_RETRIES limit then set obd error flag to true
   }
 }
}
 
Odpowiedź
#2
W Arduino nie da się uruchomić dwóch funkcji na raz. Przynajmniej w takim znaczeniu, że jak na komputerze odpalamy muzykę to w drugim okienku piszemy na forum. Dodawanie delay miałoby sens..., no prawie nigdy nie ma sensu. Procesor na tę chwilę działania delay nie robi nic poza obsługą przerwań (jest ślepy i głuchy), nie daje to urządzeniu żadnego dodatkowego czasu na obsługę zwykłych funkcji. Jeśli wywołujesz "co sekundę" dzięki delay(1000) to zamień sobie zgodnie z przykładami arduino wg szkicu 02.Digital BlinkWithoutDelay. Coś więcej można powiedzieć jak wkleisz jakiś kawałek kodu, który można skompilować. Spróbowałbym też podłączyć HC-05 pod zwykły UART, bo jeśli jest pod softserial i ustawiony na więcej niż 9600 to możesz gubić literki. Z kolei HC-05 też jest ciekawy, ma duży bufor na dane, i nie jest tak, że Ty tych danych sobie nie odczytasz, o nie nie. On poczeka i Ci je wrzuci w pierwszej kolejności do następnej transmisji. Więc jeśli dane po np. 15 znaku chcesz ignorować to je odbierasz, tworzysz zmienną i do niej wrzucasz, potem ją zerujesz/zapominasz o niej. Może na tym polega mieszanie?
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#3
Poczytaj sobie o wątkach, choćby tutaj. W przypadku Arduino są trochę sztuczne, ale coś tam da się zrobić.
 
Odpowiedź
#4
Dziękuję za odpowiedzi, mam pewne przypuszczenia, które sprawdzę wieczorem.

Cytat:W Arduino nie da się uruchomić dwóch funkcji na raz. Przynajmniej w takim znaczeniu, że jak na komputerze odpalamy muzykę to w drugim okienku piszemy na forum.
Rozumiem, dlatego kombinowałem z uruchamianiem szeregowym.

Cytat:Dodawanie delay miałoby sens..., no prawie nigdy nie ma sensu. Procesor na tę chwilę działania delay nie robi nic poza obsługą przerwań (jest ślepy i głuchy), nie daje to urządzeniu żadnego dodatkowego czasu na obsługę zwykłych funkcji. Jeśli wywołujesz "co sekundę" dzięki delay(1000) to zamień sobie zgodnie z przykładami arduino wg szkicu 02.Digital BlinkWithoutDelay. Coś więcej można powiedzieć jak wkleisz jakiś kawałek kodu, który można skompilować.
Działanie funckji delay() również rozumiem. Zastosowałem to, bo myślałem o opóźnieniu uruchomienia kolejnej funkcji.
Cytat:Spróbowałbym też podłączyć HC-05 pod zwykły UART, bo jeśli jest pod softserial i ustawiony na więcej niż 9600 to możesz gubić literki.
To będzie kolejny trop.
Cytat:Z kolei HC-05 też jest ciekawy, ma duży bufor na dane, i nie jest tak, że Ty tych danych sobie nie odczytasz, o nie nie. On poczeka i Ci je wrzuci w pierwszej kolejności do następnej transmisji. Więc jeśli dane po np. 15 znaku chcesz ignorować to je odbierasz, tworzysz zmienną i do niej wrzucasz, potem ją zerujesz/zapominasz o niej. Może na tym polega mieszanie?
Cenna uwaga, również do sprawdzenia. Z tym, że zmienne mają odpowiedni rozmiar, odbierane są jakieś znaki końcowe. Jednak jeżeli zmienna "nie mieściłaby" całej odpowiedzi to wnioskuję, że również przy wywoływaniu tej samej funkcji dawałaby podobne objawy, a takich nie ma.

Cytat:Poczytaj sobie o wątkach, choćby tutaj. W przypadku Arduino są trochę sztuczne, ale coś tam da się zrobić.
Nie wiedziałem o takiej funkcji jak timer.attach(nr wątku, ...). Spróbuję to wykorzystać, bo wydaje mi się, że muszę "poustawiać" funkcje w osobnych wątkach.
Do tej pory miałem to zdefiniowane jako:
Kod:
Timer t;
void setup(){
...
t.every(500,sprawdzObroty);
t.every(500,sprawdzPredkosc);
...
}
void loop(){
t.update();
}
Spróbuję to zmienić na:
Kod:
Timer <2> t;
void setup(){
...
t.attach(0,500,sprawdzObroty);
t.attach(1,500,sprawdzPredkosc);
...
}
void loop(){
t.process();
}
Jednak w tym temacie, który podlinkowałeś jest coś co mnie zastanawia. Cytat:
Cytat: napisał(a):Niestety nic się nie dzieje...
Zawsze coś się dzieje ...

Kod:
Kod:
akcja.attach (0, 500, dioda1);
akcja.attach (1, 500, dioda2);

Kod:
Kod:
Dwie funkcje wywoływane są w tym samym czasie jedno 500 zmień np na 1000
Tutaj ktoś się walnął z dobrą radą, prawda? Huh
 
Odpowiedź
#5
(08-02-2018, 11:21)krn78 napisał(a): Poczytaj sobie o wątkach, choćby tutaj. W przypadku Arduino są trochę sztuczne, ale coś tam da się zrobić.
Akurat biblioteka timers jest OK, o tyle że nie używa przerwań, co tu byłoby niewskazane, bo gdyby jakaś czynność odczytu była wywoływana cyklicznie co 1000ms, a jej czas trwania byłby znacząco długi to często dochodziłoby do przerywania odczytu na czas wywołania innej funkcji odczytu. A dodatkowo mocno nie zalecane jest używanie przerwań do funkcji długich, np. Serial.println(), a tym bardziej jak oczekujemy na odpowiedź. Przerwanie funkcji odczytu by zmienić np. stan portu dla led, zmienić wartość zmiennej -  byłoby OK.  
Dokładnie w tej bibliotece zostały użyte mechanizmy z przykładu blinkwithoutdelay - zliczanie ms, sprawdzanie czy zadany okres czasu upłynął i zamiana ostatniej wartości millis() na początkową. Bardzo ładnie poukładane w funkcje/obiekty.
Kod:
akcja.attach (0, 500, dioda1);
akcja.attach (1, 500, dioda2);
Tu nie ma czegoś takiego, że to zostanie uruchomione jednocześnie czy tam w wątkach, a tylko w takim interwale czasowym. Przykładowo jeśli akcja dla diody1 zostanie uruchomiana w 0,11 sekundzie działania programu i czas wykonania funkcji trwa 0,01s to akcja dioda2 zostanie wykonana w 0,12 sekundzie działania programu. Kolejne zostaną wywołane w 0,51 i 0,52 sekundzie. Jak coś zje czas pętli na 600ms to kolejne wywołają się w 1,11 i 1,12.
Dalej od programisty zależy jak tego użyje.
Tak na piechotę można zrobić lepsze zarządzanie obciążeniem procesora, gdzie jeden odczyt będzie wykonywany w każdej parzystej sekundzie, a drugi odczyt innej wartości w nieparzystej, czy w setkach ms parzystych i nieparzystych gdy chcemy to robić z częstotliwością 10Hz, a się da(nie będzie tak, że dwa odczyty obok siebie zablokują działanie urządzenia na 15s, jak np. może być gdy rozmawiamy z modemem GSM). A jak ktoś umie to i bibliotekę można z tego zrobić by pętla wyglądała bardziej elegancko (przykład zmodyfikowany blinkwithioutdelay):
Kod:
/*
 Blink without Delay

 Turns on and off a light emitting diode (LED) connected to a digital pin,
 without using the delay() function. This means that other code can run at the
 same time without being interrupted by the LED code.

 The circuit:
 - Use the onboard LED.
 - Note: Most Arduinos have an on-board LED you can control. On the UNO, MEGA
   and ZERO it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN
   is set to the correct LED pin independent of which board is used.
   If you want to know what pin the on-board LED is connected to on your
   Arduino model, check the Technical Specs of your board at:
   https://www.arduino.cc/en/Main/Products

 created 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 modified 11 Nov 2013
 by Scott Fitzgerald
 modified 9 Jan 2017
 by Arturo Guadalupi

 This example code is in the public domain.

 http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
*/

// constants won't change. Used here to set a pin number:
const int ledPin1 =  8;// the number of the LED pin
const int ledPin2 =  9;// the number of the LED pin
const int ledPin3 =  LED_BUILTIN;// the number of the LED pin

uint32_t licznik;

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;        // will store last time LED was updated

// constants won't change:
const long interval = 100;           // interval at which to blink (milliseconds)
uint8_t flaga100MS,licznikMS, flaga1S, licznikS,flaga1M,licznikM;// flagi pokazują czy upłynęła
//ta jednostka czasu, liczniki je zliczają

void setup() {
 Serial.begin(115200);
 // set the digital pin as output:
 pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
    pinMode(ledPin3, OUTPUT);
}

void loop() {
//zabawy z licznikami na poczatku loop
 unsigned long currentMillis = millis();

 if (currentMillis - previousMillis >= interval) {
   // save the last time you blinked the LED
   previousMillis = currentMillis;
   licznikMS++;
   flaga100MS=1;
 }
if(licznikMS>=10)
   {
    licznikMS=0;
    licznikS++;
    flaga1S=1;
   }
if (licznikS>=60)
{
 licznikS=0;
 licznikM++;
 flaga1M=1; //minuty się wyzerują po przepełnieniu zmiennej
}


   
//rzeczy do zrobienia

if(flaga100MS) //gdy minie 100ms
{
 if(licznikMS%4==0) //to wykona sie co 400ms
 {
   digitalWrite(ledPin1,!(digitalRead(ledPin1)));//zmiana stanu wyjscia ledPin1
 }
}

if(flaga1S) //gdy minie sekunda
{
Serial.print(licznikM);
Serial.print(":");
Serial.println(licznikS);
  if(licznikS%3==0) //to wykona sie co 3s
 {
   digitalWrite(ledPin2,!(digitalRead(ledPin2)));//zmiana stanu wyjscia ledPin2  
 }
}

if(flaga1M) //gdy minie minuta
{
Serial.print("Minutka");
 if(licznikM%2==0) //to wykona się co 2 minuty
 {
   digitalWrite(ledPin3,!(digitalRead(ledPin3)));//zmiana stanu wyjscia ledPin3  
 }
}
licznik++;
if(licznik>10000000)
{
Serial.println(licznik);
licznik=0;
}


//zerujemy flagi  na koniec petli loop
 flaga100MS=0;flaga1S=0;flaga1M=0;
}
Biblioteka timers działałaby podobnie,  gdy początkowo uruchomić wątki z odpowiednim odstępem czasu. Natomiast po pewnym czasie może się rozjeżdżać o czas potrzebny na wykonanie całej pętli, bo raczej nie jest badany czas między wątkami. W przykładzie powyżej nawet jak program dostanie czkawki, to kolejne wykonania i tak będą w różnych setkachms, sekundach, minutach.
"Rozmowa" z innym urządzeniem powinna być tak realizowana, że wysyła się zapytanie, ustawia jakąś flagę oczekiwania na odpowiedź i wychodzi z funkcji. Może raczej osobna funkcja do wysyłania, a inna do zbierania danych i ich obróbki. Ustawienie flagi powoduje sprawdzanie w pętli głównej czy przyszła cała odpowiedź (przerwanie UART odbiera znaki i ładuje do bufora, na koniec ustawia flagę żeby to brać) lub upłynął timeout, nie odpali się ponownie komunikacja dopóki flaga nie pozwoli. Jeśli timeout to kasujemy flagę i pytamy ponownie, informujemy użytkownika o braku komunikacji, możemy zliczać nieudane próby, itp. Jeśli odpowiedź przyszła to możemy coś z nią zrobić, skasować flagę i wysłać kolejne pytanie. No chyba, że odpowiedź jest natychmiastowa - na odpowiedzi z czujników (DS18B20), modemów itp. lepiej nie czekać.
Niestety przerwanie UART, event z przykładów dla UART nie są dla softserial. Jeśli komunikujemy się z urządzeniem, a chcemy mieć jakiś podgląd co się dzieje w programie to lepiej jest softserialem wysyłać na jakiś terminal np. Putty (również przez BT HC-05), a sprzętowy UART wykorzystać do odczytu - większa prędkość, stabilność i więcej możliwości. 
Takie tam przemyślenia filozoficzne Big Grin.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#6
Zamieniłem bibliotekę Timer.h na Timers.h i wprowadziłem wątki. Niestety bez efektu. Edytowałem funkcje i sprawdziłem jakie dane otrzymuję po każdym zapytaniu. Może analizując odpowiedź dacie mi podpowiedź co jest winne za "mieszanie".
Odpowiedzi przy wywoływaniu tylko funkcji sprawdzającej obroty:
Kod:
RPM_BUFIN: 010C1410C110A 11 E7 >ING... 41 0C 11 6D >010C1 41 0C 11 0A >010C1 41 0CM?
RPM_BUFIN: 010C1410C11E7 11 E7 >010C1 41 0C 14 32 >>010C1 41 0C 11 0A >010C1 41 0C??
RPM_BUFIN: 010C1410C1432 11 E7 >010C1 41 0C 14 32 >010C1 41 0C 1A 67 >>010C1 41 0C@??
RPM_BUFIN: 010C1410C1A67 1E 45 >>010C1 41 0C 14 32 >010C1 41 0C 1A 67 >010C1 41 0C xV?
RPM_BUFIN: 010C1410C1E45 1E 45 >010C1 41 0C 24 E8 >>010C1 41 0C 1A 67 >010C1 41 0C 0&?
RPM_BUFIN: 010C1410C24E8 1E 45 >010C1 41 0C 24 E8 >010C1 41 0C 2A 97 >>010C1 41 0C h??
Odpowiedzi przy wywołaniu funkcji sprawdzającej obroty i prędkość:
Kod:
RPM_BUFIN: 010C1410C0CA1010C1010D1D141 0C 0C B2 >1407F 01 12 >010C141 0C 0C A1 >010C1010+??
VELOCITY_BUFIN: 010C1010D1D141 0C 0C B2 >1407F 01 12 >010C1 41 0C 0C A1 >010C1010.??
RPM_BUFIN: 410D00>C0CA1010C1010D1D141 0C 0C B2 >41 0D 00 >010C1 010D1 41 0C 0C C5 >0C1010??
VELOCITY_BUFIN: 010C1010D1D141 0C 0C B2 >41 0D 00 >010C1010D1 41 0C 0C C5 >0C1010??
RPM_BUFIN: 410D00>C0CA1010C1010D10 >010C1010D1 41 0C 0C F9 010C1010D1 41 0C 0C C5 >41 0D 0??
O czym świadczy taki "bałagan"?
 
Odpowiedź
#7
Muszę znaleźć swoją magiczną kulę... Wkleisz ten kod cały, wraz z linkami do bibliotek, tak by można go było skompilować?
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
  


Skocz do:


Przeglądający: 1 gości