Skocz do zawartości

Czy serial.print działa jak delay?


Pomocna odpowiedź

Witam, pytanie może brzmi dziwnie ale jednak podczas pracy zauważyłem pewną zależność

mam taką oto funkcję która ma mi wyświetlać pewne wartości kontrolne na monitorze:

void serial_monitor(){ // wydruk pozycji na monitorze
Serial.print("Wartosc x1: ");
Serial.print(x1);
Serial.print("\t\t");
Serial.print("Wartosc y1: ");
Serial.print(y1);
Serial.print("\t\t");
Serial.print("Wartosc x2: ");
Serial.print(x2);
Serial.print("\t\t");
Serial.print("Wartosc y2: ");
Serial.print(y2);
Serial.print("\t\t");
Serial.print("Potencjometr 1: ");
Serial.print(s3);
Serial.print("\t");
Serial.print("Potencjometr 2: ");
Serial.print(s4);
Serial.print("\t\t");
Serial.print("S5: ");
Serial.print(s5);
Serial.print("\n");
}

i zauważyłem że funkcja ta mocno spowalnia działanie programu. W zasadzie nawet bardzo mocno.

Czy każde użycie serial.print działa jak malutki delay? a suma ich potrafi wpłynąć na poprawność działania programu?

ps. nie używam w całym programie ani jednego delay.

Link to post
Share on other sites

Serial.print() działa tak, że wysyła te znaki, które mu przekażesz, przez port szeregowy. Jeśli zastanowisz się przez chwilę nad tym jak działa port szeregowy, a do tego przypomnisz sobie, że nawet jeśli jest tam jakiś bufor, to nie jest nieograniczony, to szybko sam odpowiesz sobie na to pytanie. Jeśli do tego policzysz ile znaków wysyłasz i sprawdzisz jaką prędkość komunikacji ustawiłeś, to z łatwością także policzysz o ile taki print opóźnia. Polecam takie ćwiczenie, tak w ramach zadania domowego. Napisz nam ile ci wyszło.

Link to post
Share on other sites

Odpowiedź nie jest oczywista, bo port szeregowy w Arduino jest buforowany. Oznacza to, że produkujący znaki serial.print() umieszcza je w pewnym obszarze pamięci - buforze, gdzie oczekują na wysłanie. Sama funkcja print musi oczywiście dokonać konwersji tego co dostała na łańcuch znaków. Najszybciej jest wtedy gdy sama dostanie łańcuch, np:

Serial.print("Hello!")

wtedy przepisuje po prostu kolejne znaki do bufora nadajnika. Gorzej gdy dostanie zmienną typu int a najgorzej gdy coś typu float lub double. Wszystko to jednak zamyka się w czasach od kilkunastu (int) do kilkuset (float) mikrosekund. Znaki są sukcesywnie pobierane z bufora przez nadajnik UART i wysyłane z jakąś prędkością. Nawet duże szybkości transmisji typu 115200 są jednak bardzo wolne w porównaniu z możliwościami "produkowania" znaków przez program. Jeżeli więc rzadko wołasz Serial.print() to panuje jakaś równowaga albo bufor jest zwykle pusty. Natomiast wystarczy, że zrobisz właśnie to co pokazałeś a bufor się zapełnia. Wtedy nie ma już miejsca na nowe znaki i każdy kolejny będzie wrzucony dopiero gdy jakiś się wyśle. W tej sytuacji wysłanie słowa "Hello" (5 znaków) przy prędkości 9600 może trwać ponad 5ms.

EDIT: No i nie zdążyłem 🙂

Link to post
Share on other sites

Dzięki za wyjaśnienie 🙂

O ile się nie pomyliłem w zliczaniu to policzyłem ile wysyłam:

- 108 znaków (Char) po 8 bit każdy. (licząc każdą spację itd jako znak, np: /t liczę jako 2 znaki)

- 28 wartości (INT) po 36 bit każda.

Char dają 864 bit + int 896 w sumie wysyłam 1760bit

I tu nie wiem czy dobrze robię 9600(bit/sek) / 1760bit = 5,45 ms (opóźnienie spowodowane buforem).

Dlaczego w zasadzie w kursie podają wartość 9600 skoro można używać wyższej 115200?

Czy ustawienie wyższej prędkości wiąże się z jakimiś problemami?

Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Prawie dobrze. To jako bonus, może spróbujemy to sprawdzić, używając funkcji millis() żeby zmierzyć czas przed i po?

Prędkość 9600 to swojego rodzaju standard z czasów modemowych. Jest to na tyle niska prędkość, że nawet z najgorszymi kablami i najgorszym sprzętem powinna w miarę działać. Oczywiście, jeśli wyższe prędkości ci działają dobrze, to jak najbardziej możesz ich używać.

Link to post
Share on other sites

Każdy znak opatrywany jest przez UART bitami startu i stopu więc musisz liczyć na każdą literę po 10 bitów. Wszystko więc licz w znakach, będzie łatwiej: prędkość 9600 to dokładnie 960 znaków/s czyli 1.04ms/znak.

Każdy int jest zamieniany na łańcuch znaków ale wypisywanych jest tylko tyle ile potrzeba w zależności od wartości. Liczba -1234 to 5 znaków, ale 2 to tylko jeden. Trudno powiedzieć ile int-y zajmują w transmisji.

Każdy Serial.println() dodaje jeszcze dwa znaki końca linii (CR/LF).

Bufor ma 64 znaki - dopóki go nie zapełnisz, Serial.print() jest bardzo szybkie. Potem zwalnia do prędkości rzeczywistej transmisji.

Link to post
Share on other sites

Ok wykorzystałem funkcję millis() do pomiaru czasu:

- 131 milliseconds (długość programu z funkcją serial.print)

- 12 milliseconds (długość programu bez funkcji serial.print)

- 119 milliseconds (to opóźnienie spowodowane przepełnionym buforem)

Nie ukrywam że jestem zaskoczony aż tak dużym wynikiem 🤯

Jak rozumiem takie samo opóźnienie uzyskał bym wstawiając w miejsce funkcji delay(119);

Wniosek jest taki, że chcąc otrzymywać informacje na monitorze lepiej każdy Serial.print wywoływać od razu np. po obliczeniu wartości a nie wszystkie w jednym momencie.

Pozwoli to na równomierne obciążenie bufora i niegenerowanie kolejki do wysłania.

Plusem wywołania tego w jednej funkcji była możliwość wyłączenia zaśmiecania ekranu wstawiając // przed funkcję gdy chciałem sprawdzić jakąś inną wartość, ale cóż 😋

Z pewnością wymyślę inne rozwiązanie 🙂

Link to post
Share on other sites

Tak, trzeba uważać żeby "jednym strzałem" nie przepełnić bufora, ale też żeby strumień znaków uśredniony za jakiś dłuższy czas nie przekraczał możliwości nadajnika UART bo w końcu kiedyś się zapcha.

Jeśli masz problem z jednorazowymi "doładowaniami" - np. rzadko wołana funkcja która wysyła dużo, a program nie korzysta na maksa z RAMu to możesz pogrzebać w bibliotekach i zwiększyć długość bufora. Uwaga: w mega328 nadajnik ma własne 64 bajty a odbiornik własne 64. Przez to zajętość pamięci RAM po użyciu obiektu Serial od razy rośnie o sumę tego, ale za to odbiornik także może pracować nie gubiąc znaków gdy wykonuje się np. delay() lub jakieś obliczenia.

Długości buforów UARTa zdefiniowane są zaraz na początku pliku:

Arduino\hardware\arduino\avr\cores\arduino\HardwareSerial.h

EDIT: Zawsze możesz skorzystać z preprocesora C. Na początku definiujesz sobie symbol np:

#define  SERIAL_DEBUG_MESSAGES

a potem każdy Serial.print() wypisujący chwilowo jakiś informacje pomocnicze zamykasz w nawias:

#ifdef  SERIAL_DEBUG_MESSAGES
Serial.print(cośtamcośtam);
#endif

Wtedy gdy zakomentujesz linię definicji, wycinasz z tekstu programu widzianego przez kompilator wszystkie wydruki pomocnicze.

Symboli możesz zrobić wiele różnych i w ten sposób łatwo sterować postacią kodu, który dostaje kompilator do analizy.

Link to post
Share on other sites

W tej chwili akurat jadę na Arduino Mega (ATmega2560) więc ramu mam aż nad to, ale też nie uważam żeby ta funkcja była mi na tyle niezbędna w tej formie żebym musiał zmieniać ustawienia bufora.

Ale w tej chwili naszło mnie jeszcze jedno pytanie.

4 z tych wartości dodatkowo są wyświetlane na wyświetlaczu LED (2x16) zamontowanym w kontroler czy tam też jest jakiś bufor na którym mogą pojawić się opóźnienia?

Jedyne co zauważyłem to że wyłączenie Serial.print'ów powodujące przyspieszenie programu prowadzi do migotania wartości na LCD co by świadczyło że wyświetlacz nadąża z dużym tempem wyświetlania a oko już nie.

Przyczyną jest oczywiście bardzo szybkie wyświetlanie i czyszczenie LCD. Ale zastanawia mnie czy tam też jest gdzieś ukryta taka "pułapka" jak przy pozornie nieszkodliwym serial.print która może w pewnym momencie wygenerować problemy trudne do zdiagnozowania.

Link to post
Share on other sites

Z LCD to zależy jak go podłączyłeś. Jeśli równolegle (6 przewodów) to wtedy wszelkie operacje robi procesor programowo. Niczego nie buforuje, ale to jest szybkie. Jeden zapis znaku to okolice 1us.

Natomiast jeśli szeregowo przez I2C to sprawa organizacji zależy od biblioteki. I2C jest porównywalnie szybkie z UART jeśli chodzi o transfer, ale tutaj do zapisu jednego znaku na LCD trzeba wysłać kilka bajtów z prędkością zwykle ok. 100kHz. To daje 100us na bajt plus jakieś narzuty na protokół I2C (liczyłbym min. dwukrotne, bo trzeba wysłać adres itd) i robi się blisko 0.5ms na jeden pokazany znak. Można by to kiedyś zmierzyć. Jeżeli biblioteka tego nie buforuje tylko zwisa w oczekiwaniu na wysłanie poprzedniego bajtu, no to masz milisekundy pracy procesora przy wypisywaniu jednego int-a.

Link to post
Share on other sites

Generalna zasada jest taka, że komunikacja z czymkolwiek zewnętrznym zajmuje czas, zależny od dokładnego sposobu, w jaki ta komunikacja przebiega i zazwyczaj te czasu są o rzędy wielkości większe, niż operacje na wewnętrznej pamięci czy rejestrach. Bufory mogą pomagać w przypadku, gdy komunikacja jest sporadyczna i jednokierunkowa, ale jeśli nadajesz ciągle, to i tak się zapełnią. Podobnie, jeśli chcesz odpowiedź, to i tak musisz czekać aż wszystko się wyśle w obie strony.

Jeszcze uwaga do twoich obliczeń -- nie chciałem tak od razu wytykać wszystkiego, ale jak Marek już zespoilował, to wypada poprawić. Znak "\t" to jest jeden bajt, podobnie jak "\n".

Link to post
Share on other sites

Co do LCD HD44780 jak mniemam bo z tekstu wnioskuję, że o ten chodzi, to proponuję przystosować go do współpracy z SPI za pomocą układu 74HC595 - prędkość transmisji zdecydowanie większa i zostaną jeszcze 2 piny rejestru np do sterowania podświetlaniem lub odczytu/zapisu wewnętrznej pamięci wyświetlacza. Praktycznie rzecz ujmując do LCD powinien być podpięty osobny mikrokontroler zajmujący się tylko i wyłącznie obsługą wyświetlacza np jakiś mały ATtiny dzięki czemu główny uC nie musi wykonywać funkcji obsługi LCD a tylko wysyłać dane do sterownika, który w czasie oczekiwania na nowe dane od master uC może np. wyświetlać jakieś animacje lub mrugać podświetlaniem służąc jednocześnie za bufor dla przychodzących danych - to bardzo wygodne rozwiązanie i wcale nie takie trudne.

Link to post
Share on other sites

Pomysł z użyciem zewnętrznego kontrolera do LCD wydaje się być bardzo fajny, ale to zupełnie nie mój poziom jeszcze:/

Choć ideę i zasadę działania rozumiem, to nie mam kompletnie pojęcia jak miałbym się za to zabrać:/ Ani od strony Hardware ani Software, chyba na takie rzeczy muszę jeszcze trochę poczekać 🙂

Chyba, że byłbyś w stanie przybliżyć zagadnienie takiego rozwiązania 🙂

Link to post
Share on other sites

Mogę spróbować przybliżyć to zagadnienie ale bez opanowania z Twojej strony podstaw komunikacji za pomocą SPI i I2C będzie raczej trudno. Sprowadzają się do umiejętności przestawienia kilku bitów w odpowiednich rejestrach (wszystko w notach katalogowych avr).

Ogólny zarys wygląda tak: bierzesz jakiś uC w zależności od sposobu jakiego chcesz użyć i robisz z niego "dedykowany" sterownik wyświetlacza komunikujący się z głównym uC przez właśnie spi albo i2c (TWI). Możesz np. użyć atmege8 wtedy ekspander nie będzie potrzebny bo mega8 ma wystarczającą ilość pinów do obsługi wyświetlacza i łączysz ją po SPI z np. arduino. Ewentualnie jakiś mniejszy np ATtiny25 albo podobny i łączysz z ekspanderem np. 74HC595 (SPI) lub PCF8574 (I2C). Możesz też wykorzystać ekspander do zredukowania potrzebnej ilości pinów do obsługi wyświetlacza. Dane wysyłasz wtedy szeregowo bit po bicie a ekspander ustawia odpowiednie stany na swoim wyjściu równolegle.

Współpraca takiego sterownika np. z arduino polega jedynie na tym, że wysyłasz dane do sterownika a on już robi całą resztę czyli steruje wyświetlaczem. W tym czasie arduino może robić coś innego zupełnie nie zawracając sobie rdzenia obsługą wyświetlacza. Tymi danymi może być np komenda interpretowana przez sterownik wg. Twojej własnej inwencji np. wysyłasz 1 bajt z wartością np. 123 co program sterownika może zinterpretować aby wyświetlić całą zawartość pamięci czy wymrugać inwokację z "Pana Tadeusza" morsem za pomocą podświetlania wypisując jej tekst w czasie kiedy arduino pobiera próbki ADC z czujników i przelicza skomplikowane algorytmy interpretacji próbek.

Zasadnicza różnica pomiędzy typami komunikacji polega na ilości potrzebnych pinów i prędkości transmisji SPI - 3 piny jednostronna 4: 2 stronna, prędkość 1/4 F_CPU z innym kontrolerem avr, z ekspanderem: max taktowania uC.

I2C (czyli TWI) - 2piny, prędkość 100kHz do 400kHz korzystając z modułu TWI avr.

Jeśli Cię to interesuje pytaj o szczegóły postaram się pomóc, jeśli nie ja to ktoś inny. Pytania dotyczące arduino i bibliotek kieruj raczej do innych forumowiczów bo osobiście się na arduino nie znam i nie interesuje mnie ono zupełnie, nie używam arduino IDE.

Link to post
Share on other sites

Jest też bardzo dużo modułów wyświetlaczy, których sterowniki już potrafią się komunikować po I²C czy SPI i nie trzeba wymyślać do nich własnych wynalazków. Oczywiście to nie rozwiąże oryginalnego problemu, bo jakakolwiek komunikacja, czy to przez UART, czy przez I²C albo SPI (a także protokołami równoległymi) zajmuje czas.

Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.