Skocz do zawartości

STM32 UART wysyłanie danych typu uint16_t


radek04

Pomocna odpowiedź

Cześć,

ponownie wrócił u mnie temat wysyłania z uC danych z czujników MPU9250 do PC. Poprzednio robiłem to z użyciem funkcji printf i terminala CoolTerm i w przypadku 6 osi (akcelerometr + żyroskop) i szybkości próbkowania 20 Hz nie było problemu.
Obecnie potrzebuję przesłać dane z 24 osi (4 akcelerometry i 4 żyroskopy na 4 IMU) z szybkością 500 Hz. No i tutaj printf nie wyrabia. Próbowałem też użyć sprintf oraz funkcję HAL_UART_Transmit, HAL_UART_Transmit_IT, HAL_UART_Transmit_DMA. (Z tą ostatnią mam problem, ponoć jest jakiś kłopot z CubeIDE i serią F7. Pierwsze testy pokazały mi jednak, że IT działa tak samo szybko jak DMA. Jeśli to prawda, to na razie ten problem pomijam. Jeśli jednak będzie potrzebne DMA, to tutaj też będę prosił o pomoc. W STM32F410RB Nucleo działa mi bez zarzutu.)
Tutaj jednak przy przesyłaniu 24 wartości typu float czas przesłania danych jest zbyt duży i rzeczywista szybkość próbkowania mi spada.
Wpadłem na pomysł, by zamiast danych typu float i funkcji (s)printf przesłać po prostu surowe wartości uint8_t z 2 rejestrów (lub jeszcze lepiej połączone od razu w uint16_t) dla każdej osi. Ale tutaj napotykam na problemy. Po pierwsze - jak wysłać te dane? Kolejno po sobie wartości z każdej osi? Czy jakoś je spakować "do kupy" i zrobić jedną transmisję w każdym przerwaniu zegarowym 500 Hz? Pierwszy pomysł wyglądałby mniej więcej tak (od razu proszę o korektę):

int16_t MPU9250_aX_A, MPU9250_aY_A ... MPU9250_gZ_D);

HAL_UART_Transmit_IT(&huart3, MPU9250_aX_A, sizeof(MPU9250_aX_A));
HAL_UART_Transmit_IT(&huart3, MPU9250_aY_A, sizeof(MPU9250_aY_A));
HAL_UART_Transmit_IT(&huart3, MPU9250_aZ_A, sizeof(MPU9250_aZ_A));
.
.
.
HAL_UART_Transmit_IT(&huart3, MPU9250_gX_D, sizeof(MPU9250_gX_D));
HAL_UART_Transmit_IT(&huart3, MPU9250_gY_D, sizeof(MPU9250_gY_D));
HAL_UART_Transmit_IT(&huart3, MPU9250_gZ_D, sizeof(MPU9250_gZ_D));

Kolejny problem to odbiór tych danych i ich konwersja do postaci zrozumiałej dla człowieka. Na razie otrzymuję w terminalu coś w stylu:

BĽR–ˇÍ.h)B*]+
,Ş.A,Ş)B+
)BŐń."únôA-0Ŕ,."-0~,ŞˇÍÉČÚą.hŐńçÖ~´)Uź˝..áR–)BBĽŐńĺ.Ŕ,çÖ~..ú‰‡.˙,Ş`“+
á.}.ÉČ­mnô[`¦“.űëm.ś
'—..˝.††`J.ĹV-0|<’ ĺ.D.›@úí.÷hú..^[.˝J„–÷h‰‡A+
.Š.‰‡~UźçÖ`“Ü÷˛h0`“JahUź©;BĽëmÉČÚą.˙á.ŠUź8\‰‡‰‡éjĺ.Aöš)B+

Korzystam z STM32F746ZG Nucleo, STM32CubeIDE 1.10.1, MCU Package 1.17.0.
Na razie dane przesyłam z Nucleo poprzez USB, docelowo będzie to nowy układ z rdzeniem STM32F756VGT6 i komunikacja poprzez BT lub WiFi.
4 czujniki mam podłączone do 2 magistrali I2C.

Proszę o wszelkie porady i pomysły, jak szybko przesłać wszystkie potrzebne dane i je poprawnie odczytać w PC.

Edytowano przez radek04
Link do komentarza
Share on other sites

Konwersja to pryszczyk... ale sprawdź czy to w ogóle jest realne.

Chcesz przesłać dane z 24 osi po 16 bitów, czyli w paczce 384 bity. Na razie pomińmy narzut na bity startu i stopu. W ciągu sekundy chcesz przesłać 500 paczek, czyli 384 * 500 = 192000 bitów. Najbliższa prędkość transmisji szeregowej to 230400... ale teraz weźmy pod uwagę narzut na dodatkowe bity, jakieś tam przerwy między paczkami, jakąś synchronizację - czyli prędkość transmisji wychodzi 460800.

Jesteś w stanie zapewnić stabilne połączenie z tą prędkością? UART jeszcze to pewnie wytrzyma, ale przy WiFi będziesz miał problemy jeśli chciałbyś odbierać dane w czasie rzeczywistym.

  • Lubię! 2
Link do komentarza
Share on other sites

Tak prawdę mówiąc, to jest to kolejny problem. Jest to plan maksimum, będę go aktualizował. Mogę zmniejszyć do 12 osi, a od biedy do 8 osi jednocześnie. Szybkość akwizycji być może też zmniejszę do 400, a może nawet do 300 Hz. Ale tym zajmę się, gdy juz poprawnie uruchomię transmisję i zrobię konwersję.

Możemy przyjąć, że na razie chcę przesłać dane z 2 osi. Dane nie muszą być przesyłane w czasie rzeczywistym, może być opóźnienie.

Link do komentarza
Share on other sites

Najprostsze wydaje mi się zakodowanie całej tablicy n danych w base64, dodanie '\n' na końcu i wysłanie tego przez UART. Masz wtedy co prawda 65 bajtów zamiast 48, ale ładnie zakodowane w linijce tekstu, nie musisz się bawić w jakieś synchronizacje, a konwersja w jedną czy drugą stronę jest szybka, prosta i na pytanie "Base64 C" Google odpowiadają mnóstwem stron z algorytmami i bibliotekami.

Spróbuj i sprawdź ile dasz rady w ten sposób przesłać.

  • Lubię! 1
Link do komentarza
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

@radek04 Na początek spróbuj zamiast funkcji HAL_UART_Transmit_IT() użyć HAL_UART_Transmit(). To nie będzie zbyt szybkie rozwiązanie, ale dane powinny wyglądać o wiele lepiej. Docelowo faktycznie najlepiej użyć DMA i tutaj F7 nie jest jakoś szczególnie problematyczne, ale trzeba pamiętać o pamięci podręcznej. Proponuj jednak zacząć od użycia HAL_UART_Transmit i przetestowania, czy wtedy dane wyglądają poprawnie. Później można pomyśleć jak zoptymalizować działanie programu.

Edytowano przez Elvis
  • Lubię! 2
Link do komentarza
Share on other sites

2 minuty temu, matsobdev napisał:

sizeof() ma pewnie stałą wartość, to można nie liczyć jej za każdym razem.

Zaraz znów się obrazisz i będziesz mi minusy dawać, ale przyjmij do wiadomości że wyrażenie sizeof(cośtam) jest stałą obliczaną w czasie kompilacji...

  • Lubię! 2
Link do komentarza
Share on other sites

Oczywiście najpierw zacząłem od HAL_UART_Transmit, ale w połączerniu z printf, później HAL_UART_Transmit_IT z printf oraz sprintf. Te wszystkie dane były wyświetlane poprawnie.

Porobiłem testy szybkości (na F4, ale chodzi o porównanie). W while zapuściłem inkrementację licznika i sprawdzałem wypisywanie wartości w terminalu. Przybliżone wyniki ostatniej wartości po upływie 10 sekund:

printf HAL_UART_Transmit - 19 300
sprintf HAL_UART_Transmit - 20 800
sprintf HAL_UART_Transmit_IT - 918 000
sprintf HAL_UART_Transmit_DMA - 918 000 

Ale zdaje się, że to właśnie konwersja dokonywana przez printf oraz sprintf zajmuje najwięcej czasu. Dlatego chciałbym użyć sposobu bez konwersji danych na char czy string (przepraszam, jeśli jestem nieprecyzyjny, nie jestem w tym ekspertem, ale chyba takie coś jest robione w ww. funkcjach?).

Link do komentarza
Share on other sites

Zarówno HAL_UART_Transmit_IT, jak i HAL_UART_Transmit_DMA nie czeka na zakończenie poprzedniej transmisji tylko zwraca błąd. Więc jeśli miałeś program jak sam napisałeś:

2 godziny temu, radek04 napisał:
HAL_UART_Transmit_IT(&huart3, MPU9250_aX_A, sizeof(MPU9250_aX_A));
HAL_UART_Transmit_IT(&huart3, MPU9250_aY_A, sizeof(MPU9250_aY_A));
HAL_UART_Transmit_IT(&huart3, MPU9250_aZ_A, sizeof(MPU9250_aZ_A));

To coś takiego wysyła jedynie pierwszą wartość - działa szybko, bo pozostałe wywołania kończą się niepowodzeniem. Ale to nic mądrego nie wnosi - zacznij od poprawnego przesłania danych, a dopiero później zajmij się optymalizacją. Inaczej nic z tego nie wyjdzie.

  • Lubię! 1
Link do komentarza
Share on other sites

No właśnie. Czyli pewnie powinienem obsłużyć jakieś flagi, tak? W zasadzie do tej pory korzystałem jedynie z kursów na Forbocie i wystarczało. Teraz pora sięgnąć głębiej w temat 🙂 Nakierujecie mnie trochę?

Link do komentarza
Share on other sites

Materiał z kursu w zupełności wystarczy. Tylko nie wywołuj 24 razy HAL_UART_Transmit_IT - każde użycie tej funkcji zajmuje sporo czasu. Zamiast tego wszystkie wyniki zbierz do tablicy, a później całość wyślij jednym wywołaniem HAL_UART_Transmit(). Po stronie PC dostaniesz wyniki w postaci binarnej, ale to całkiem dobre rozwiązanie, wystarczy kilka linijek np. w Pythonie i będziesz miał wyniki czytelne dla użytkownika.

Co do transmisji to 24 wyniki * 2 bajty * 500 Hz daje jakieś 24000, przy 10 bitach na bajt 240000 bps, więc UART ustawiony na 460800 powinien to przesłać. Ale czy moduły BT/WiFi sobie poradzą to już inny temat.

W każdym razie proponuję zacząć od przesłania chociażby testowych danych oraz ich "rozszyfrowania" po stronie PC. A dopiero później optymalizacji prędkości transmisji. Bo inaczej może się okazać, że program działa szybko, ale przesyła tylko śmieci.

  • Lubię! 2
Link do komentarza
Share on other sites

No dobrze, ale rozumiem, że program po stronie PC musi odczytywać dane z UART i je dekodować, tak? Żadnym terminalem tego nie odbiorę poprawnie? Czy są jakieś aplikacje, które choć trochę pomogą mi w tym zadaniu?

Link do komentarza
Share on other sites

CoolTerm ma podobno przycisk "Hex View" - wystarczy, żeby wyświetlić dane binarne. Ale jeśli oczekujesz wyników w postaci ASCII to jak napisał @ethanak na początku tej dyskusji UART jest po prostu zbyt powolny.

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

OK, faktycznie jest. Grzebalem w opcjach, a nie widziałem przycisku. Otrzymuje takie coś. Zgodne z kodami ASCII. I faktycznie mogę w tej formie zapisać. Tylko jak to poprawnie przekonwertować na wartości przyspieszeń w PC? W uC zapisywałem np. jako (float)MPU9250_aX_A (później jeszcze konwersja na wartości przyspieszenia ziemskiego i normalizacja do przedziału od -1 do +1). 
Muszę dodatkowo kodować przed wysłaniem, czy takie surowe dane mogę przesłać i "przerobić" na wartości liczbowe już po stronie PC?

  

CoolTerm.jpg

Edytowano przez radek04
Link do komentarza
Share on other sites

Mam też kłopot z interpretacją tych wartości. Zrobiłem testową zmienną o wartości 0x01.

uint16_t testowa = 0b0000000000000001;
HAL_UART_Transmit_IT(&huart3, testowa, sizeof(testowa));

A na wyjściu otrzymuję:

F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28
F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28
F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28
F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28
F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28 F7 28

Dlaczego?
Liczyłem raczej na taki wynik:

00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01
00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01
00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01
00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01
00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01

Wartość uint16_t wyświetlana jest w 4 znakach na moim terminalu, prawda? Np. F7258 to cała wartość pojedynczego odczytu jednej osi (2 rejestry 8-bitowe)?

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • 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.