Szybkie i niezawodne parsowanie JSON – przykład z UART #1

W trzecim artykule z serii dotyczącej przetwarzania JSON na mikrokontrolerach, skupimy się na efektywnym parsowaniu danych. Wykorzystamy bibliotekę lwjson, aby szczegółowo omówić proces analizy JSON. Przyjrzymy się praktycznym przykładom wykorzystując interfejs UART oraz płytkę Nucleo-L476RG. Dowiedz się, jak sprawnie zarządzać danymi JSON i zyskaj wiedzę, która przyda się w Twoich projektach.

Wprowadzenie

Witaj w trzeciej części naszej serii na temat przetwarzania formatu JSON na mikrokontrolerach! Jeśli już odwiedzałeś mój blog, to na pewno pamiętasz wcześniejsze artykuły. W pierwszej części, zatytułowanej „Czym jest JSON – JavaScript Object Notation w teorii i praktyce„, przeszliśmy przez podstawy formatu JSON i dowiedzieliśmy się, dlaczego jest on tak powszechnie stosowany w dzisiejszym programowaniu. W drugiej części, „Wysyłanie danych w formacie JSON – przykład z UART„, dowiedzieliśmy się, jak przesyłać dane w formacie JSON przy użyciu interfejsu UART.

Teraz przyszedł czas na trzecią odsłonę, w której skupimy się na procesie parsowania danych JSON na mikrokontrolerze. To kluczowy krok w obszarze komunikacji, który umożliwia nam przenoszenie, przetwarzanie i analizowanie danych w sposób elastyczny i zrozumiały. Dzięki temu procesowi, nasze mikrokontrolery stają się jeszcze bardziej wszechstronne i potrafią pracować w różnych zastosowaniach.

W tym artykule skoncentrujemy się na wykorzystaniu biblioteki lwjson, która pozwoli nam na efektywne parsowanie danych JSON na mikrokontrolerze. Pokażę ci, jak korzystać z tej biblioteki, aby czerpać maksymalne korzyści z formatu JSON w swoich projektach. Będziemy również wykorzystywać interfejs UART oraz płytkę Nucleo-L476RG, co pozwoli nam na praktyczne przykłady i zrozumienie procesu na konkretnym sprzęcie.

Jeśli chcesz pogłębić swoją wiedzę na temat zastosowania JSON na mikrokontrolerach – ten artykuł jest dla Ciebie!

Parsowanie – istotny element w komunikacji JSON

Parsowanie JSON to istotny element komunikacji w programowaniu mikrokontrolerów. Ten proces umożliwia przenoszenie, przetwarzanie i analizowanie danych w sposób elastyczny, co przekłada się na efektywność pracy. Format JSON jest szeroko stosowany i powszechnie obsługiwany na różnych platformach, co sprawia, że dane mogą być łatwo przesyłane między różnymi urządzeniami. To szczególnie istotne w kontekście Internetu Rzeczy (IoT), gdzie wiele urządzeń musi komunikować się ze sobą.

Parsowanie JSON nie wymaga zbyt dużych zasobów, dlatego może być stosowane nawet na mikrokontrolerach z ograniczoną mocą obliczeniową. To czyni je bardziej wszechstronnymi w różnych aplikacjach. Jedną z głównych zalet parsowania JSON jest interoperacyjność. Dzięki temu formatowi dane mogą być łatwo dzielone i przetwarzane przez różne urządzenia i aplikacje, co znacząco ułatwia pracę w rozproszonych systemach. W rezultacie parsowanie JSON stanowi kluczowy krok w zapewnieniu efektywnej i elastycznej komunikacji.

Biblioteka do parsowania JSON – lwjson

W artykule będziemy korzystać z biblioteki lwjson. Jest lekką i wydajną biblioteką dedykowaną do parsowania poleceń JSON na mikrokontrolerach. Została stworzona przez Tilena Majerle – pracownika STMicroelectronics oraz autora bloga STM32F4-Discovery.net i wielu ciekawych bibliotek dedykowanych pod mikrokontrolery. Biblioteka ta jest stworzona z myślą o systemach o ograniczonych zasobach, takich jak mikrokontrolery. Jest jedną z bibliotek polecanych na oficjalnej stronie dotyczącej formatu JSON [lista na dole strony].

Biblioteka lwjson umożliwia parsowanie danych w formacie JSON, co oznacza, że może odczytywać i analizować JSON i przekształcać go na zrozumiane dane dla mikrokontrolera. Obsługuje różne typy danych JSON, takie jak liczby, ciągi znaków, tablice i obiekty, co pozwala na elastyczne przetwarzanie danych. lwjson została zaprojektowana z myślą o efektywnym wykorzystaniu pamięci i nie wykorzystuje dynamicznego alokowania pamięci, co jest istotne na mikrokontrolerach o ograniczonych zasobach.

Dzięki lwjson, parsowanie danych w formacie JSON staje się niezwykle efektywne i dostępne nawet na urządzeniach o ograniczonych zasobach. Oto kilka kluczowych cech tej biblioteki oraz jej zalety:

  1. Obsługa różnych typów danych: lwjson jest niezwykle wszechstronna, umożliwiając analizę różnych typów danych JSON, takich jak liczby, ciągi znaków, tablice i obiekty. Dzięki temu możesz elastycznie przetwarzać dane, dostosowując je do konkretnych potrzeb twojego projektu.
  2. Skalowalność i wydajność: Biblioteka lwjson została zaprojektowana z myślą o mikrokontrolerach, co oznacza, że jest niesamowicie wydajna i nie obciąża urządzenia nadmiernym narzutem na pamięć. To idealne rozwiązanie dla projektów, w których wydajność jest kluczowa, a zasoby są ograniczone.
  3. Brak dynamicznego alokowania pamięci: Jednym z najważniejszych aspektów lwjson jest to, że nie wykorzystuje dynamicznego alokowania pamięci. To oznacza, że nie musisz martwić się o zarządzanie pamięcią i ryzyko wycieków pamięci. Twój mikrokontroler będzie działał stabilnie i niezawodnie.
  4. Łatwa integracja: Biblioteka lwjson jest łatwa w integracji z różnymi platformami i mikrokontrolerami. Jej prosty i zrozumiały interfejs programistyczny sprawia, że korzystanie z niej jest przyjemnością, nawet dla początkujących programistów.

Warto wspomnieć, że biblioteka jest udostępniana na licencji MIT, co daje użytkownikom nieograniczone prawo do używania, kopiowania, modyfikowania i rozpowszechniania (w tym sprzedaży) oryginalnego lub zmodyfikowanego programu w postaci binarnej lub źródłowej. Jedynym wymaganiem jest, by we wszystkich wersjach zachowano warunki licencyjne i informacje o autorze.

Przygotowanie projektu w STM32CubeIDE

Korzystając z lwjson, zyskujesz nie tylko możliwość parsowania danych JSON na mikrokontrolerach, ale także dostęp do narzędzia, które sprawia, że Twoje projekty stają się bardziej wydajne, elastyczne i skalowalne. W kolejnych częściach artykułu pokażę Ci, jak w pełni wykorzystać potencjał tej biblioteki i w jaki sposób może ona usprawnić Twoje projekty programistyczne.

Przygotowanie przykładu wykorzystującego komunikację szeregową przez interfejs UART to doskonały sposób na praktyczne zrozumienie działania biblioteki. Projekt, który stworzymy, będzie oparty na bibliotekach HAL-a i CubeMX, dostarczając solidną podstawę do naszego eksperymentu. Oto kroki, które przewidziano na potrzeby projektu:

  1. Tworzenie Nowego Projektu: Rozpocznijmy od utworzenia nowego projektu w programie CubeMX. Wybieramy opcję „File -> New -> STM32 Project” i nadajemy projektowi odpowiednią nazwę. Warto zaznaczyć, że zalecane jest pozostawienie domyślnej inicjalizacji układów peryferyjnych.
  2. Konfiguracja UART: W naszym projekcie wykorzystamy interfejs USART w trybie asynchronicznym z prędkością 115200 bps. To kluczowy element, który pozwoli na komunikację szeregową i przesyłanie danych między naszym mikrokontrolerem a innymi urządzeniami.
  3. Konfiguracja diod LED: Do płytki Nucleo-L476RG podłączymy trzy diody, aby zobrazować obsługę i działanie biblioteki lwjson.

Przejdźmy zatem do konfiguracji projektu. Interfejs USART2 mamy już włączony dzięki domyślnej inicjalizacji peryferiów. Aby w wygodny sposób odbierać dane, włączymy jeszcze kontroler DMA1 na kanale 6 oraz przerwania. Docelowo chciałbym wykorzystać w projekcie przerwanie od zakończenie transmisji (IDLE), dzięki czemu otrzymamy przerwanie po przesłaniu całego polecenia JSON.

Konfiguracja DMA 1 Channel 6
Włączenie przerwań od USART2

Jak już wspomniałem, dodamy jeszcze trzy diody LED , które pozwolą nam w łatwy sposób kontrolować działanie aplikacji. Diody podłączyłem do pinów PC8, PC6 oraz PC5. Skonfigurowałem je jako wyjścia (GPIO_Output) i oznaczyłem etykietami LED1, LED2 i LED3.

Konfiguracja wyprowadzeń mikrokontrolera

Na płytce Nucleo diody podłączamy do złącza CN10. Pamiętaj, żeby dodać rezystory ograniczające prąd diody – minimum kilkaset Ohm w zależności od tego, jakiej diody używasz.

Podłączenie diod LED

Projekt w CubeMX mamy przygotowany. Możemy wygenerować kod klikając na „Project->Generate Code” lub za pomocą skrótu klawiszowego „ALT+K”.

Teraz czas na dodanie biblioteki lwjson do projektu. Jeżeli jeszcze jej nie pobrałeś z repozytorium GitHub, najwyższy czas pobrać pliki. Do naszego projektu kopiujemy cały folder lwjson i umieszczamy go w folderze „Core” projektu.

Dodanie biblioteki lwjson

W zakładce „Project->Properties->C/C++ General->Path and Symbols->Includes” dodajemy ścieżkę do folderu z biblioteką.

Dodanie ścieżki do biblioteki

W ten sposób wstępnie przygotowaliśmy projekt z biblioteką lwjson. W naszym przykładzie skorzystamy z domyślnej konfiguracji biblioteki. Plik konfiguracyjny lwjson_opt.h został przygotowany w ten sposób, że jego nie powinniśmy modyfikować. Zawiera on domyślną konfigurację, która powinna pozostać taka, jaką przygotował autor biblioteki. Jeżeli chcemy dokonać zmian w którejś z definicji, robimy to w pliku lwjson_opts.h. Jeżeli zajrzysz do folderu „lwjson->src->include->lwjson”, znajdziesz tam plik o nazwie lwjson_opts_template.h. Wystarczy że usuniesz z nazwy słowo „template”. Plik ten jest pusty i można w nim umieszczać własne definicje poszczególnych ustawień konfiguracyjnych. Plik lwjson_opt.h automatycznie załączy naszą konfigurację. My pozostawimy plik lwjson_opts.h bez zmian, ale musi on znaleźć się w folderze, ponieważ bez niego nie zbudujemy poprawnie projektu.

Przykład – parsowanie instrukcji dla diod LED

Mamy przygotowany projekt. Jeżeli wszystko skonfigurowałeś poprawnie, powinien się prawidłowo kompilować. Możemy teraz napisać program, który będzie odbierał instrukcje przez interfejs USART2 i zaświecał lub gasił podłączone diody. Program będzie niewielki, dlatego kod umieszczę w funkcji main().

Odbieranie danych przez USART2

W pierwszej kolejności obsłużymy odbieranie danych przez interfejs szeregowy USART2. Przed funkcją main() dodajemy tablicę, w której będziemy przechowywali odebrane polecenia JSON oraz zmienną, która poinformuje o odebraniu danych.

uint8_t rx_buffer[256];
bool data_received = false;

Jak już wspominałem, do odbierania pełnych poleceń wykorzystamy przerwanie od wolnej linii (IDLE). Zostanie wywołane w momencie, gdy na linii RX interfejsu USART2 zakończy się transmisja. W funkcji main(), ale przed pętlą while(1) wywołujemy odbieranie danych.

HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer, 256);

Obsługę przerwania umieściłem w funkcji HAL_UARTEx_RxEventCallback(). Przy jej wywołaniu przez bibliotekę HAL-a na końcu bufora z danymi dodajemy znak '\0′. Biblioteka lwjson pracuje na stringach, dlatego nasze polecenie musi kończyć się tym znakiem. Poza tym ustawiamy flagę data_received oraz ponownie wywołujemy odbierania danych.

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if (huart->Instance == USART2)
	{
		rx_buffer[Size] = '\0';
		data_received = true;
		HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer, 256);
	}
}

Parsowanie danych z lwjson

W pliku „main.c” załączamy bibliotekę lwjson oraz biblioteki do obsługi zmiennych bool i stringów, ponieważ będziemy z nich korzystać w projekcie.

#include <stdbool.h>
#include <string.h>


#include "lwjson/lwjson.h"

W funkcji main() utworzyłem dwie zmienne pomocnicze.

  char *str;
  size_t len = 0;

Teraz możemy przystąpić do inicjalizacji biblioteki lwjson. Potrzebne nam będą dwie zmienne. Jedna to tablica typu lwjson_token_t. Jest to tablica przechowująca obiekty (tokeny) sparsowane przez bibliotekę. Druga to struktura typu lwjson_t, gdzie przechowywane są zmienne pomocnicze niezbędne w procesie parsowania.

  lwjson_token_t tokens[128];
  lwjson_t lwjson;

Bibliotekę inicjalizujemy, wywołując funkcję lwjson_init(). Jako argumenty podajemy obiekty typu lwjson_t, tablicę z tokenami oraz rozmiar tej tablicy.

lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens));

Obsługę poleceń obsłużymy w pętli while(1). W momencie, gdy mikrokontroler odbierze dane przez USART2 (flaga data_received przyjmie wartość true), wywołujemy funkcję lwjson_parse(), która przeanalizuje odebrane dane. Jeżeli odebraliśmy poprawne polecenie w formacie JSON, zwróci nam wartość lwjsonOK.

if (lwjson_parse(&lwjson, (char*)rx_buffer) == lwjsonOK)

Mając poprawne dane, możemy sprawdzić, co w nich otrzymaliśmy. Będziemy chcieli przesyłać komunikaty o stanie diod LED. Będą one miały format obiektów z nazwą diody oraz jej stanem w postaci stringów „ON” lub „OFF”.

{
    "LED1":"ON",
    "LED2":"ON",
    "LED3":"ON"
}

Tworzymy sobie zatem pomocniczy token i szukamy pierwszego obiektu. Jeżeli zwróci nam wartość inną niż NULL, oznacza to, że taki obiekt został znaleziony w przesłanym JSON-ie.

if ((t = lwjson_find(&lwjson, "LED1")) != NULL)

Teraz możemy pobrać wartość obiektu i w zależności od przesłanej informacji zaświecić lub zgasić diodę.

str = (char *)lwjson_get_val_string(t, &len);

if (strncmp(str, "ON", len) == 0)
{
    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
}
else if (strncmp(str, "OFF", len) == 0)
{
    HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
}

Analogicznie obsługujemy pozostałe diody. Ogromną zaletą formatu JSON jest to, że w poleceniu możemy wysłać zarówno wszystkie trzy obiekty dla LED1, LED2, i LED3, jak i polecenie z tylko jednym obiektem. Jeżeli format będzie zachowany, to powinien on zostać poprawnie zinterpretowany. W ten sposób możemy włączać i wyłączać diody pojedynczo lub w dowolnej konfiguracji. A jeżeli chcemy, aby stan danej diody się nie zmienił, wystarczy pominąć ją w poleceniu JSON.

{
    "LED1":"OFF"
}

Podsumowanie

Parsowanie JSON, czyli proces analizy danych w elastycznym i zrozumiałym formacie, odgrywa kluczową rolę w efektywnej pracy mikrokontrolerów w różnych zastosowaniach. W artykule przedstawiłem bibliotekę lwjson, która stanowi doskonałe narzędzie do parsowania danych JSON na mikrokontrolerach. To wszechstronne oprogramowanie oferuje szeroki zakres funkcji i charakteryzuje się prostotą obsługi.

Zobaczyliśmy także praktyczny przykład z wykorzystaniem biblioteki lwjson, koncentrujący się na sterowaniu diodami LED. Ten realny scenariusz obrazuje, jak parsowanie JSON może być wykorzystane do bieżącej kontroli urządzeń. Dzięki dostępnym narzędziom i odpowiedniej konfiguracji, mikrokontrolery stają się bardziej elastyczne i przyjazne użytkownikom.

Na dzisiaj to wszystko. W kolejnym wpisie będę chciał pokazać, w jaki sposób analizować bardziej rozbudowane JSON-y. Jeżeli podobał Ci się wpis lub masz jakieś własne przemyślenia, zostaw komentarz. Pamiętaj też, aby polubić mój profil na Facebook-u oraz zasubskrybować kanał na YouTube.

Repozytorium GitHub

Materiały dodatkowe

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *