Aby wykorzystać potencjał modułu LTE GPS HAT – NB-IoT/LTE/GPRS/GPS SIM7000E, który w pierwszej części tego cyklu omówiony został pod względem wysyłania wiadomości SMS, warto zwrócić uwagę na funkcję monitorowania lokalizacji GPS. Połączenie obu tych funkcjonalności daje świetną podstawę do stworzenia wielu bardziej rozbudowanych projektów z zakresu systemów wbudowanych.
Niezbędne informacje dotyczące obsługi GNSS
Postęp uzyskiwania pozycji GNSS można weryfikować za pomocą komendy AT+CGNSINF. Od razu pojawia się tutaj pojęcie, które być może nie dla wszystkich jest jasne (sama nie znałam go do momentu pierwszej styczności z odbiornikami GPS), a mianowicie GPS Fix.
Przed uzyskaniem owego „fiksa”, moduł lokalizacyjny znajduje się w fazie wyszukiwania sygnału. Odbiornik GNSS musi odebrać stabilne pakiety danych z minimum 4 satelitów (trzy satelity nie wystarczają do jednoznacznego rozwiązania problemu, ponieważ nieznany jest również błąd zegara odbiornika; czwarty satelita pozwala wyznaczyć zarówno pozycję, jak i poprawkę czasu).
Wyróżnia się dwa główne stany pracy modułu w drodze do uzyskania pozycji:
- Brak fiksa (No Fix): Moduł ma włączone zasilanie sekcji GPS, ale nie odnalazł jeszcze wystarczającej liczby satelitów lub sygnał jest zbyt słaby (np. wewnątrz budynku). W logach komendy
AT+CGNSINFstan ten objawia się zerem na drugiej pozycji oraz ciągiem pustych przecinków. Jeżeli ten stan utrzymuje się ponad 15 minut, a my znajdujemy się w budynku, warto wystawić antenę na otwarte niebo – ten zabieg może pomóc od razu uzyskać fix. - Uzyskanie fiksa (GPS Fix): Moment, w którym algorytmy pozycjonujące modułu SIM7000E obliczyły pozycję. W oknie terminala status zmienia się na
1, a puste dotychczas pola zostają zastąpione realnymi danymi o szerokości i długości geograficznej.
Przy każdym uruchomieniu modułu potrzebujemy czasu na uzyskanie pierwszego pozycjonowania. Czas ten określa się wskaźnikiem TTFF (ang. Time To First Fix), który zależy od informacji dostępnych odbiornikowi w chwili uruchomienia. Podczas zimnego startu odbiornik nie ma aktualnego czasu, przybliżonej pozycji ani ważnych danych orbitalnych satelitów. Przy tzw. gorącym starcie zachowane są część danych i przybliżona pozycja, ale odbiornik musi pobrać aktualne efemerydy. Gorący start jest zatem możliwy, gdy odbiornik ma aktualny czas, pozycję, almanach i efemerydy. Według dokumentacji SIM7000 TTFF w warunkach otwartego nieba wynosi poniżej 35 s dla zimnego startu i poniżej 1 s dla gorącego startu.Jeżeli uruchamiamy fabrycznie nowy moduł, wtedy nieraz trzeba poczekać nieco dłużej. Nie należy wtedy zbyt szybko rezygnować – taki start może zająć nawet 15 minut.
Testowanie modułu za pomocą komend AT
W tym artykule nadal będziemy korzystać z komunikacji za pośrednictwem interfejsu UART – szczegóły opisaliśmy już w pierwszej części artykułu (link znajdziesz tutaj). Podobnie jak w przypadku wysyłania wiadomości SMS oraz sprawdzania poprawności działania modułu, możemy pierwszy raz włączyć GPS za pomocą komend AT i terminala. Poniżej przedstawiono komendy dla tej procedury.
AT+CGNSPWR=1– komenda służąca do włączenia zasilania sekcji GNSS. Po jej wysłaniu następuje aktywacja wewnętrznego odbiornika geolokalizacyjnego. Prawidłowa odpowiedź toOK. Brak wywołania poleceniaAT+CGNSPWR=1skutkuje pozostaniem sekcji nawigacyjnej w stanie uśpienia.AT+CGNSINF– pytanie o status i dane nawigacyjne. W odpowiedzi otrzymujemy pełny ciąg informacji o pozycji. Przy braku fiksa modemu pojawi się ciąg przecinków bez użytecznych danych, natomiast po uzyskaniu fiksa zaobserwujemy m.in szerokość i długość geograficzną, aktualny czas i wysokość.AT+CGNSPWR=0– komenda pozwalająca na oszczędzanie energii – wyłącza ona zasilanie sekcji GNSS, zmniejszając pobór mocy, gdy nie ma potrzeby pozyskiwania danych o lokalizacji.
Parsowanie odpowiedzi na zapytanie o status i dane nawigacyjne
Po wysłaniu komendy AT+CGNSINF moduł zwróci odpowiedź w formacie przedstawionym poniżej:
+CGNSINF: 1,1,20260710101729.000,YY.YYYYYY,XX.XXXXXX,286.800,0.00,195.1,1,,3.0,3.2,1.0,,20,2,2,,35,,
W powyższej przykładowej odpowiedzi pojawiło się kilka pustych miejsc między przecinkami – są to parametry opcjonalne, których moduł w danym momencie nie zdołał wyliczyć.
Interpretacja odpowiedzi na polecenie AT+CGNSINF
Znaczenie poszczególnych pól ramki zwracanej przez moduł SIM7000E podano w tabeli poniżej.
| Pozycja | Przykładowe wartości | Co to oznacza? |
|---|---|---|
| 1 | 1 | 1 oznacza prawidłowo włączoną sekcję GPS. |
| 2 | 1 | 1 oznacza pełny GPS Fix modułu. |
| 3 | 20260710101729.000 | Data i godzina: 10.07.2026 godz. 10:17:29. Czas pobierany jest bezpośrednio z zegarów satelitów. |
| 4 | YY.YYYYYY | Szerokość geograficzna (Latitude). |
| 5 | XX.XXXXXX | Długość geograficzna (Longitude). |
| 6 | 286.800 | Wysokość (MSL Altitude) , podawana w metrach nad poziomem morza. |
| 7 | 0.00 | Prędkość poruszania się w km/h. |
| 8 | 195.1 | Kierunek ruchu w stopniach względem północy (przydatne tylko w ruchu). |
| 9 | 1 | 1 oznacza klasyczny, autonomiczny GPS. |
| 11 | 3.0 | HDOP opisuje wpływ geometrii użytych satelitów na niepewność pozycji poziomej. Im mniejsza wartość, tym korzystniejsza geometria. Wynik 3,0 oznacza geometrię wystarczającą do wielu zastosowań, ale sam nie określa błędu pozycji w metrach. |
| 15 | 20 | Moduł widzi 20 satelitów na niebie. |
| 16 | 2 | Liczba satelitów systemu GPS wykorzystanych do obliczenia konkretnej pozycji. |
| 17 | 2 | Moduł SIM7000E jest wielosystemowy (GNSS) i dołożył do obliczeń satelity rosyjskiego systemu GLONASS. |
| 19 | 35 | Maksymalna wartość C/N0 wśród odbieranych sygnałów satelitarnych, podawana w dB-Hz. Wynik 35 dB-Hz wskazuje na użyteczny poziom najsilniejszego sygnału, ale nie pozwala samodzielnie ocenić jakości całej pozycji. |
Obsługa odbiornika GNSS z pomocą STM32
Implementacja kodu na STM32 realizuje funkcję pozycjonowania satelitarnego (GNSS), opartą na strukturze reprezentującej moduł modemu oraz wskaźnikach przekazywanych do funkcji. Cała architektura została zmodularyzowana w następujący sposób:
- logika komunikacji z modułem trafiła do plików
Modem_Core.horazModem_Core.c, - za funkcjonalność GNSS odpowiadają pliki
Modem_GPS.hiModem_GPS.c.
W przypadku konfiguracji środowiska dla poniższej implementacji kodu rozszerzyliśmy ją o obsługę transmisji danych do modemu przez DMA. Poza standardowymi krokami podejmowanymi podczas konfiguracji, które dokładnie opisaliśmy w pierwszej części, konieczne jest dodanie kanału DMA dla kierunku nadawania.
Plik nagłówkowy Modem_Core.h definiuje strukturalny typ danych sim7000e_t, który stał się centralnym elementem architektury programu. Struktura przechowuje wskaźnik do interfejsu UART (UART_HandleTypeDef*), dane pinu PWRKEY (GPIO_TypeDef*, uint16_t), bufor odpowiedzi modemu wraz z indeksem, zmienną reprezentującą bieżący stan komunikacji oraz dane pozycji GNSS. Ostatnim elementem są deklaracje funkcji bazowych, wspólne dla wszystkich funkcjonalności modułu.
/*
* Modem_Core.h
* Podstawowa warstwa komunikacji z modulem SIM7000E dla STM32 (HAL):
* definicja struktury reprezentujacej modem, typ stanu (modem_state_t)
* oraz funkcje bazowe do wysylania komend AT i odbierania odpowiedzi.
* Transmisja danych do modemu odbywa sie przez HAL_UART_Transmit_DMA,
* dlatego bufor nadawczy (bufout) jest polem struktury, a nie lokalna
* zmienna w funkcji - DMA operuje na nim asynchronicznie, wiec musi
* pozostac wazny (w pamieci) tak dlugo, jak trwa transmisja.
*/
#ifndef INC_MODEM_CORE_H_
#define INC_MODEM_CORE_H_
/* Includes ------------------------------------------------------------------*/
#include "stm32l4xx_hal.h"
#include <string.h>
/* Private define --------------------------------------------------------------*/
#define MODEM_BUFFER_SIZE 256
/* Exported types ---------------------------------------------------------------*/
// Stan modemu - typ wyliczeniowy, zeby nie zgadywac co oznacza "0" czy "1"
typedef enum {
MODEM_STATE_OK,
MODEM_STATE_CMD,
MODEM_STATE_TIMEOUT,
MODEM_STATE_ERROR
} modem_state_t;
// Glowna struktura reprezentujaca caly modul SIM7000E
typedef struct {
UART_HandleTypeDef *huart; // interfejs UART podpiety do modemu (np. &huart1)
GPIO_TypeDef *pwr_port; // port GPIO linii PWRKEY
uint16_t pwr_pin; // numer pinu PWRKEY (np. GPIO_PIN_0)
char bufin[MODEM_BUFFER_SIZE]; // bufor odbiorczy - gromadzi surowa odpowiedz modemu
uint16_t bufin_index;
char bufout[MODEM_BUFFER_SIZE]; // bufor nadawczy - zrodlo danych dla HAL_UART_Transmit_DMA
modem_state_t status;
double lat; // szerokosc geograficzna - dostep przez Modem_GPS_Get_Lat()
double lon; // dlugosc geograficzna - dostep przez Modem_GPS_Get_Lon()
uint8_t fix; // 1 = wazny fix, 0 = brak/blad odczytu
} sim7000e_t;
/* Exported function prototypes --------------------------------------------------*/
void Debug_Print(const char *text); // wypisanie tekstu na kanal diagnostyczny (USART2/VCP)
void Modem_Send_String(sim7000e_t *sim, const char *str); // wyslanie surowego tekstu do modemu przez DMA
void Clear_Modem_Buffer(sim7000e_t *sim); // zerowanie bufora odbiorczego modemu
void Modem_Clear_UART_Errors(sim7000e_t *sim); // czyszczenie flag bledow UART (OR/NE/FE/PE)
uint8_t Modem_Wait_For_Response(sim7000e_t *sim, const char *expected, uint32_t timeout_ms); // oczekiwanie na okreslony fragment tekstu w odpowiedzi modemu
uint8_t Modem_Send_Command(sim7000e_t *sim, const char *cmd, const char *expected, uint32_t timeout_ms); // wyslanie komendy AT i oczekiwanie na odpowiedz
uint8_t Modem_Try_AT(sim7000e_t *sim, uint8_t attempts); // wielokrotna proba komunikacji AT bez ingerencji w PWRKEY
void Fatal_Error(sim7000e_t *sim, const char *msg); // zatrzymanie programu w przypadku bledu krytycznego
#include "Modem_GPS.h"
#endif /* INC_MODEM_CORE_H_ */
Plik Modem_Core.c zawiera implementacje funkcji zadeklarowanych w powyższym nagłówku. Podobnie jak w pierwszej części artykułu, zastosowano tutaj mechanizm oczekiwania na odpowiedź modułu. W przypadku poniższego kodu zmianą jest natomiast sposób wysyłania danych – Modem_Send_String() korzysta z HAL_UART_Transmit_DMA() zamiast blokującego polecenia HAL_UART_Transmit(), a bufor źródłowy transmisji (sim->bufout) jest polem struktury, a nie zmienną lokalną. DMA korzysta z pamięci wskazanej podczas wywołania funkcji, co powoduje konieczność pozostawania źródła danych pod wskazanym adresem przez cały czas transmisji.
/*
* Modem_Core.c
*
* Implementacja funkcji bazowych do komunikacji z modulem SIM7000E na
* STM32 (HAL): wysylanie komend AT przez DMA, odbior i parsowanie
* odpowiedzi, obsluga bledow i PWRKEY. Wspolna dla wszystkich
* funkcjonalnosci modemu (GPS, SMS, itd.), ktore korzystaja z tych
* funkcji przez wskaznik na sim7000e_t.
*/
/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"
/* Private user code -----------------------------------------------------------*/
// Wskaznik do UART-u diagnostycznego (VCP przez ST-Link) - ustawiany w main.c
extern UART_HandleTypeDef huart2;
#define DEBUG_UART huart2
// Wypisuje tekst diagnostyczny na USART2 (kanal do PC). Uzywane wylacznie
// do celow debugowania, nie wplywa na logike modemu.
void Debug_Print(const char *text)
{
HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)text, strlen(text), 100);
}
/*
* Wysyla surowy ciag znakow do modemu przez DMA.
* Tresc kopiowana jest najpierw do sim->bufout - pola struktury, a nie
* lokalnej zmiennej - poniewaz HAL_UART_Transmit_DMA jest funkcja
* nieblokujaca: zwraca sterowanie od razu, a rzeczywista wysylka
* odbywa sie w tle. Gdyby zrodlem danych byla zmienna lokalna funkcji,
* po jej wyjsciu trafilaby na stos i mogla zostac nadpisana, zanim
* DMA skonczylby jej faktycznie wysylac.
* Po zleceniu transmisji funkcja czeka na powrot UART-u do stanu
* READY, aby kolejne wywolanie (np. nastepnej komendy AT) nie
* nadpisalo sim->bufout w trakcie trwajacej jeszcze wysylki.
*/
void Modem_Send_String(sim7000e_t *sim, const char *str)
{
// Czekamy, aż ewentualna poprzednia transmisja DMA dobiegnie końca
while(sim->huart->gState != HAL_UART_STATE_READY) {
// Krótkie opóźnienie lub brak blokowania całej pętli głównej
}
strncpy(sim->bufout, str, MODEM_BUFFER_SIZE - 1);
sim->bufout[MODEM_BUFFER_SIZE - 1] = '\0';
HAL_UART_Transmit_DMA(sim->huart, (uint8_t*)sim->bufout, strlen(sim->bufout));
}
// Zeruje bufor odbiorczy wewnatrz struktury wskazywanej przez sim
void Clear_Modem_Buffer(sim7000e_t *sim)
{
memset(sim->bufin, 0, sizeof(sim->bufin));
sim->bufin_index = 0;
}
/*
* Czysci flagi bledow sprzetowych UART (Overrun, Noise, Framing, Parity).
* Moze to byc przydatne po intensywnej transmisji, gdy w wyniku bledu
* sprzetowego UART dalszy odbior zostalby zablokowany. Wyczyszczenie
* flag pozwala rozpoczac kolejny odbior bez pozostalosci po
* ewentualnych bledach peryferium UART.
*/
void Modem_Clear_UART_Errors(sim7000e_t *sim)
{
__HAL_UART_CLEAR_OREFLAG(sim->huart);
__HAL_UART_CLEAR_NEFLAG(sim->huart);
__HAL_UART_CLEAR_FEFLAG(sim->huart);
__HAL_UART_CLEAR_PEFLAG(sim->huart);
}
/*
* Zatrzymuje program w przypadku bledu krytycznego, z ktorego dalsza
* czesc procedury nie moze sensownie kontynuowac. Ustawia sim->status
* na MODEM_STATE_ERROR, wypisuje komunikat diagnostyczny na kanal
* debug i zatrzymuje program w petli nieskonczonej.
*/
void Fatal_Error(sim7000e_t *sim, const char *msg)
{
sim->status = MODEM_STATE_ERROR;
Debug_Print("\r\n### BLAD KRYTYCZNY: ");
Debug_Print(msg);
Debug_Print(" ###\r\n");
Debug_Print(sim->bufin);
while(1)
{
// Celowo pusta petla - program zatrzymuje sie tutaj.
}
}
/*
* Odbiera znaki z modemu bajt po bajcie i dopisuje je do sim->bufin,
* az do wykrycia oczekiwanego fragmentu tekstu ("expected") lub uplywu
* czasu "timeout_ms". Dodatkowo wykrywa typowe komunikaty bledu modemu
* (ERROR, +CME ERROR, +CMS ERROR) i przerywa oczekiwanie natychmiast.
*
* Na starcie czysci flagi bledow UART (patrz Modem_Clear_UART_Errors) -
* to zapobiega sytuacji, w ktorej nierozwiazany Overrun Error z
* poprzedniej intensywnej transmisji blokuje caly dalszy odbior
* w milczeniu.
*
* expected -> fragment tekstu, na ktory czekamy (np. "OK", "READY", ">")
* timeout_ms -> maksymalny czas oczekiwania w milisekundach
* return -> 1 jesli znaleziono oczekiwany tekst, 0 w przypadku bledu/timeoutu
*/
uint8_t Modem_Wait_For_Response(sim7000e_t *sim, const char *expected, uint32_t timeout_ms)
{
uint8_t rx;
uint32_t start = HAL_GetTick();
sim->status = MODEM_STATE_CMD;
Modem_Clear_UART_Errors(sim);
while((HAL_GetTick() - start) < timeout_ms)
{
if(HAL_UART_Receive(sim->huart, &rx, 1, 10) == HAL_OK)
{
// Filtr znakow: akceptujemy tylko znaki drukowalne ASCII oraz CR/LF.
if(!((rx >= 32 && rx <= 126) || rx == '\r' || rx == '\n'))
{
continue;
}
if(sim->bufin_index < MODEM_BUFFER_SIZE - 1)
{
sim->bufin[sim->bufin_index++] = rx;
sim->bufin[sim->bufin_index] = '\0';
}
else
{
sim->status = MODEM_STATE_ERROR;
return 0;
}
if(strstr(sim->bufin, "\r\nERROR\r\n") ||
strstr(sim->bufin, "+CME ERROR") ||
strstr(sim->bufin, "+CMS ERROR"))
{
sim->status = MODEM_STATE_ERROR;
return 0;
}
if(strstr(sim->bufin, expected))
{
if(strcmp(expected, ">") == 0)
{
sim->status = MODEM_STATE_OK;
return 1;
}
if(strstr(sim->bufin, "\r\nOK\r\n"))
{
sim->status = MODEM_STATE_OK;
return 1;
}
}
}
else
{
// Brak odczytu w tym cyklu - sprawdzamy, czy to nie Overrun Error.
// Jesli tak, czyscimy flage natychmiast, zamiast czekac do konca
// timeoutu z zablokowanym odbiorem.
if(__HAL_UART_GET_FLAG(sim->huart, UART_FLAG_ORE))
{
Modem_Clear_UART_Errors(sim);
}
}
}
sim->status = MODEM_STATE_TIMEOUT;
return 0;
}
/*
* Wysyla pojedyncza komende AT do modemu i czeka na okreslona odpowiedz.
* Przed wyslaniem czysci bufor odbiorczy, aby nie mieszac z poprzednia
* odpowiedzia. W przypadku bledu wypisuje diagnostyke.
* cmd -> tekst komendy AT (z zakonczeniem \r\n)
* expected -> oczekiwany fragment odpowiedzi
* timeout_ms -> maksymalny czas oczekiwania
* return -> 1 sukces, 0 blad/timeout
*/
uint8_t Modem_Send_Command(sim7000e_t *sim, const char *cmd, const char *expected, uint32_t timeout_ms)
{
Clear_Modem_Buffer(sim);
Debug_Print("\r\n>> ");
Debug_Print(cmd);
Modem_Send_String(sim, cmd);
if(!Modem_Wait_For_Response(sim, expected, timeout_ms))
{
Debug_Print("\r\nTIMEOUT/BLAD\r\n");
Debug_Print(sim->bufin);
Debug_Print("\r\n");
return 0;
}
Debug_Print("\r\n<< ");
Debug_Print(sim->bufin);
Debug_Print("\r\n");
return 1;
}
/*
* Wielokrotnie probuje podstawowej komendy "AT", nie ruszajac PWRKEY.
* attempts -> liczba prob
* return 1 -> jesli modem odpowiedzial w ktorejkolwiek probie, 0 jesli brak odpowiedzi
*/
uint8_t Modem_Try_AT(sim7000e_t *sim, uint8_t attempts)
{
while(attempts--)
{
if(Modem_Send_Command(sim, "AT\r\n", "OK", 1500))
{
return 1;
}
HAL_Delay(1000);
}
return 0;
}
Właściwa obsługa GNSS została wydzielona do dwóch osobnych plików Modem_GPS.h oraz Modem_GPS.c. Pierwszy z nich zawiera deklarację czterech funkcji: Modem_GPS_Activate() odpowiedzialną za aktywację silnika GNSS, Modem_GPS_Read() cyklicznie odpytującą odbiornik o pozycję z pomocą odpowiedniej komendy AT i parsującą odpowiedź z modemu oraz dwie funkcje dostępowe, które zwracają ostateczne współrzędne (Modem_GPS_Get_Lat() i Modem_GPS_Get_Lon()). Na kanał diagnostyczny wypisywana jest surowa odpowiedź modułu wraz z jej rozbiciem na poszczegóne pola (status fiksa, szerokość i długość geograficzna). Rozbicie logiki funkcjonalnej modemu na osobne moduły sprawia, że dodanie kolejnej funkcjonalności w przyszłości nie wymaga ingerencji w kod już przetestowanej warstwy bazowej.
/*
* Modem_GPS.h
*
* Funkcje odpowiedzialne za obsluge GNSS w module SIM7000E:
* aktywacja silnika GNSS oraz odpytywanie o biezaca pozycje.
* Korzysta z typu sim7000e_t i funkcji bazowych z Modem_Core.h.
*/
#ifndef INC_MODEM_GPS_H_
#define INC_MODEM_GPS_H_
/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"
/* Exported types ---------------------------------------------------------------*/
/* Exported function prototypes --------------------------------------------------*/
uint8_t Modem_GPS_Activate(sim7000e_t *sim); // AT+CGNSPWR=1, aktywacja silnika GNSS
uint8_t Modem_GPS_Read(sim7000e_t *sim); // odczyt lokalizacji (AT+CGNSINF) - zwraca 0 jezeli brak fixa lub 1 po zlapaniu fixa
double Modem_GPS_Get_Lat(sim7000e_t *sim); // odczyt szerokosci geograficznej
double Modem_GPS_Get_Lon(sim7000e_t *sim); // odczyt dlugosci geograficznej
#endif /* INC_MODEM_GPS_H_ */Definicje funkcji umieszczono w pliku źródłowym Modem_GPS.c.
/*
* Modem_GPS.c
*
* Implementacja funkcji GNSS - patrz Modem_GPS.h.
*/
/* Includes ------------------------------------------------------------------*/
#include "Modem_GPS.h"
#include <stdlib.h> // atoi, atof
#include <string.h>
/* Private user code -----------------------------------------------------------*/
/*
* Aktywuje silnik GNSS w module (AT+CGNSPWR=1). Bez tego AT+CGNSINF
* w Modem_GPS_Read bedzie zwracac puste dane (run=0).
*/
uint8_t Modem_GPS_Activate(sim7000e_t *sim)
{
return Modem_Send_Command(sim, "AT+CGNSPWR=1\r\n", "OK", 3000);
}
// Pomocniczy wskaźnik do N-tego pola za przecinkami
static const char* get_field_ptr(const char* str, uint8_t target_field) {
uint8_t current_field = 0;
if (target_field == 0) return str;
while (*str != '\0') {
if (*str == ',') {
current_field++;
if (current_field == target_field) {
return str + 1;
}
}
str++;
}
return NULL;
}
/*
* Odpytuje modul o biezaca pozycje komenda AT+CGNSINF i parsuje
* odpowiedz, wypelniajac pola sim->lat, sim->lon, sim->fix.
*
* Format odpowiedzi:
* +CGNSINF: <run>,<fix>,<data/czas UTC>,<lat>,<lon>,<wys>,...
*
* W kazdym przypadku bledu (brak odpowiedzi, brak fixa, blad parsowania)
* pola struktury sa jawnie nadpisywane wartosciami sygnalizujacymi
* brak danych (fix=0, lat=9999.9f, lon=9999.9f) - dzieki temu program
* glowny nigdy nie odczyta "starych", nieaktualnych wspolrzednych z
* poprzedniego, udanego odczytu.
*
* return -> 1 jesli uzyskano wazny fix, 0 w przeciwnym razie
*/
uint8_t Modem_GPS_Read(sim7000e_t *sim) {
// 1. Zapytanie o status NMEA
if (!Modem_Send_Command(sim, "AT+CGNSINF\r\n", "+CGNSINF:", 3000)) {
Debug_Print("Brak odpowiedzi na AT+CGNSINF\r\n");
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// 2. Wyszukanie ciągu początkowego
const char *p = strstr(sim->bufin, "+CGNSINF:");
if (p == NULL) {
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
p += strlen("+CGNSINF:");
while (*p == ' ') p++;
// 3. Sprawdzenie statusu FIX (Pole 1)
const char *fix_ptr = get_field_ptr(p, 1);
if (fix_ptr == NULL || *fix_ptr != '1') {
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// 4. Pobranie Latitude (Pole 3) i Longitude (Pole 4)
const char *lat_ptr = get_field_ptr(p, 3);
const char *lon_ptr = get_field_ptr(p, 4);
if (lat_ptr != NULL && lon_ptr != NULL && *lat_ptr != ',' && *lon_ptr != ',') {
sim->lat = strtod(lat_ptr, NULL); // Konwersja do double
sim->lon = strtod(lon_ptr, NULL);
sim->fix = 1;
return 1; // FIX JEST I SPARSOWANY!
}
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// Odczyt szerokosci geograficznej z ostatniego udanego pomiaru
double Modem_GPS_Get_Lat(sim7000e_t *sim)
{
return sim->lat;
}
// Odczyt dlugosci geograficznej z ostatniego udanego pomiaru
double Modem_GPS_Get_Lon(sim7000e_t *sim)
{
return sim->lon;
}
Implementacja kodu w pliku main.c została ograniczona do minimum. Zawiera ona standardową inicjalizację peryferiów, sekcję USER CODE, gdzie widnieją jedynie powiązania struktury modem z konkretnym uchwytem UART i pinem PWRKEY, sekwencję startową oraz pętlę główną, w której co 10 sekund odpytujemy odbiornik o lokalizację. Po uzyskaniu ważnego namiaru satelitarnego następuje wypisanie na kanał diagnostyczny sparsowanych współrzędnych geograficznych.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Modem_Core.h"
#include "Modem_GPS.h"
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart1_tx;
/* USER CODE BEGIN PV */
sim7000e_t modem; // jedyna globalna instancja - reprezentuje fizyczny modul
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// Powiazanie struktury modem z konkretnym UART-em i pinem PWRKEY
modem.huart = &huart1;
modem.pwr_port = GPIOB;
modem.pwr_pin = GPIO_PIN_0;
/*
* ETAP WSTEPNY: czyszczenie bufora sprzetowego UART z ewentualnego
* szumu/przypadkowych bajtow, ktore mogly pojawic sie na linii RX
* podczas resetu/programowania STM32, zanim wyslano jakakolwiek komende.
*/
uint8_t flush;
while(HAL_UART_Receive(modem.huart, &flush, 1, 10) == HAL_OK);
modem.status = MODEM_STATE_CMD;
Modem_Clear_UART_Errors(&modem);
Clear_Modem_Buffer(&modem);
Debug_Print("\r\n");
Debug_Print("=================================\r\n");
Debug_Print(" START PROCEDURY ODPYTYWANIA O LOKALIZACJE GPS\r\n");
Debug_Print("=================================\r\n");
/*
* STABILIZACJA ZASILANIA
* Czas potrzebny na ustabilizowanie napiecia zasilajacego
* modul modemu po wlaczeniu calego ukladu.
*/
HAL_Delay(3000);
/*
* PROBA AUTOBAUD
* Modul SIM7000E moze wystartowac samodzielnie wraz z
* podaniem zasilania (bez potrzeby impulsu PWRKEY).
*/
Debug_Print("Proba komunikacji AT...\r\n");
uint8_t modem_ready = Modem_Try_AT(&modem, 15);
/*
* JEZELI MODEM NIE ODPOWIADA - DOPIERO TERAZ PWRKEY
*/
if(!modem_ready)
{
Debug_Print("Brak odpowiedzi.\r\n");
Debug_Print("Uruchamiam PWRKEY...\r\n");
HAL_GPIO_WritePin(modem.pwr_port, modem.pwr_pin, GPIO_PIN_SET);
HAL_Delay(1500);
HAL_GPIO_WritePin(modem.pwr_port, modem.pwr_pin, GPIO_PIN_RESET);
HAL_Delay(12000);
modem_ready = Modem_Try_AT(&modem, 10);
if(!modem_ready)
{
Fatal_Error(&modem, "Modem nie odpowiada po impulsie PWRKEY");
}
}
Debug_Print("AT OK\r\n");
/*
* AKTYWACJA GNSS
* AT+CGNSPWR=1 wlacza silnik GNSS w module. Bez tego
* AT+CGNSINF w petli glownej bedzie zwracac puste dane (run=0).
*/
if(!Modem_GPS_Activate(&modem))
{
Fatal_Error(&modem, "Brak aktywacji GNSS.");
}
Debug_Print("GNSS OK\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if (Modem_GPS_Read(&modem))
{
char msg[128];
snprintf(msg, sizeof(msg), "Fix OK -> Lat: %.6f | Lon: %.6f\r\n",
Modem_GPS_Get_Lat(&modem), Modem_GPS_Get_Lon(&modem));
Debug_Print(msg);
}
else
{
Debug_Print("Brak fixu GPS.\r\n");
}
HAL_Delay(10000);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
Error_Handler();
}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = 64;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 10;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel4_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LD4_GPIO_Port, LD4_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(MODEM_PWR_GPIO_Port, MODEM_PWR_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : LD4_Pin */
GPIO_InitStruct.Pin = LD4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD4_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : MODEM_PWR_Pin */
GPIO_InitStruct.Pin = MODEM_PWR_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(MODEM_PWR_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
Obecna forma kodu pozwala na zweryfikowanie poprawności komunikacji z silnikiem GNSS oraz uzyskanie gotowych do dalszego wykorzystania współrzędnych geograficznych. Warto dodać, że w celu poprawnej obsługi funkcji snprintf() należy dodać opcję -u _printf_float w ustawieniach linkera STM32CubeIDE.

Prezentacja otrzymanych wyników w terminalu
Obsługa GPS z pomocą ESP32
Przygotowaliśmy również implementację analogicznego programu na platformę Arduino IDE. W poprzedniej części artykułu cały kod zamknęliśmy w jednym pliku .ino, tym razem, jak wyżej, zdecydowaliśmy się na modularną architekturę opartą na strukturach i wskaźnikach. Takie podejście jest bowiem bliższe temu, w jaki sposób organizuje się kod w projektach produkcyjnych i staje się szczególnie wygodne w momencie, gdy moduł ma obsługiwać więcej niż jedną funkcjonalność jednocześnie – np. wysyłkę SMS oraz odczyt lokalizacji GPS w ramach tego samego urządzenia. Cała logika komunikacji z modemem (obsługa komend AT, bufor odpowiedzi, stan modułu) została wydzielona do wspólnej struktury i osobnych plików, a każda nowa funkcjonalność – jak GNSS opisany w tym artykule – dokłada jedynie własny moduł, korzystając z tego samego „rdzenia”.
Plik Modem_Core.h jest niemal analogiczny do implementacji opisanej powyżej. Jedyna widoczna różnica polega na tym, że struktura nie korzysta z typów UART_HandleTypeDef* oraz GPIO_TypeDef*, natomiast środowisko Arduino ukrywa rejestry GPIO za funkcjami wysokopoziomowymi, dlatego wystarczającym polem jest tu zwykły numer GPIO (uint8_t) oraz wskaźnik na obiekt HardwareSerial.
/*
* Modem_Core.h
*
* Podstawowa warstwa komunikacji z modulem SIM7000E: definicja
* struktury reprezentujacej modem, typ stanu (modem_state_t) oraz
* funkcje bazowe do wysylania komend AT i odbierania odpowiedzi.
* Funkcje specyficzne dla GPS/SMS sa w osobnych plikach (Modem_GPS.*,
* Modem_SMS.*) i wlacznie z niniejszym plikiem naglowkowym.
*/
#ifndef MODEM_CORE_H
#define MODEM_CORE_H
/* Includes ------------------------------------------------------------------*/
#include <Arduino.h>
/* Private define --------------------------------------------------------------*/
#define MODEM_BUFFER_SIZE 256
/* Exported types ---------------------------------------------------------------*/
// Stan modemu - typ wyliczeniowy, zeby nie zgadywac co oznacza "0" czy "1"
typedef enum {
MODEM_STATE_OK,
MODEM_STATE_CMD,
MODEM_STATE_TIMEOUT,
MODEM_STATE_ERROR
} modem_state_t;
// Glowna struktura reprezentujaca caly modul SIM7000E
typedef struct {
HardwareSerial *serial;
uint8_t pwr_pin;
char buffer[MODEM_BUFFER_SIZE];
uint16_t buffer_index;
modem_state_t status;
double lat; // szerokosc geograficzna - dostep przez Modem_GPS_Get_Lat()
double lon; // dlugosc geograficzna - dostep przez Modem_GPS_Get_Lon()
uint8_t fix; // 1 = wazny fix, 0 = brak/blad odczytu
} sim7000e_t;
/* Exported function prototypes --------------------------------------------------*/
void Debug_Print(const char *text); // wypisanie tekstu na kanal diagnostyczny
void Modem_Send_String(sim7000e_t *modem, const char *str); // wyslanie surowego tekstu do modemu
void Clear_Modem_Buffer(sim7000e_t *modem); // zerowanie bufora odpowiedzi modemu
void Modem_Clear_UART_Errors(sim7000e_t *modem); // czyszczenie bufora UART
uint8_t Modem_Wait_For_Response(sim7000e_t *modem, const char *expected, uint32_t timeout_ms); // oczekiwanie na okreslony fragment tekstu w odpowiedzi modemu
uint8_t Modem_Send_Command(sim7000e_t *modem, const char *cmd, const char *expected, uint32_t timeout_ms); // wyslanie komendy AT i oczekiwanie na odpowiedz
uint8_t Modem_Try_AT(sim7000e_t *modem, uint8_t attempts); // wielokrotna proba komunikacji AT bez ingerencji w PWRKEY
void Fatal_Error(sim7000e_t *modem, const char *msg); // zatrzymanie programu w prszypadku bledu krytycznego
#include "Modem_GPS.h"
#endif /* MODEM_CORE_H */
Plik Modem_Core.cpp również realizuje implementację funkcji dla pliku Modem_Core.h, zasadniczo wykorzystaliśmy tutaj ten sam mechanizm, co w części 1, tylko tym razem operując na polach struktury przez wskaźniki (zamiast, jak poprzednio, przez zmienne globalne).
/*
* Modem_Core.cpp
* Implementacja funkcji bazowych do komunikacji z modulem SIM7000E:
* wysylanie komend AT, odbior i parsowanie odpowiedzi, obsluga
* bledow i PWRKEY. Wspolna dla wszystkich funkcjonalnosci modemu
* (GPS, SMS, itd.), ktore korzystaja z tych funkcji przez wskaznik
* na sim7000e_t.
*/
/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"
#include <string.h> // funkcje strstr/strcmp/memset do parsowania odpowiedzi modemu
/* Private user code -----------------------------------------------------------*/
// Wypisuje tekst diagnostyczny na Serial (kanal do PC). Uzywane wylacznie do celow debugowania, nie wplywa na logike modemu.
void Debug_Print(const char *text)
{
Serial.print(text);
}
// Wysyla surowy ciag znakow do modemu przez port zapisany w strukturze
void Modem_Send_String(sim7000e_t *modem, const char *str)
{
modem->serial->print(str);
}
// Zeruje bufor odpowiedzi wewnatrz struktury wskazywanej przez modem
void Clear_Modem_Buffer(sim7000e_t *modem)
{
memset(modem->buffer, 0, sizeof(modem->buffer));
modem->buffer_index = 0;
}
/*
*Czysci programowy bufor odbiorczy UART modemu
* W ESP32 niskopoziomowy sterownik automatycznie dba o sprzetowe bledy FIFO (jak ORE/FE).
* Wyczyszczenie programowego bufora (ring buffer) pozwala pozbyc sie smieci i
* rozpoczac kolejny odbior bez pozostalosci po ewentualnych bledach transmisji.
*/
void Modem_Clear_UART_Errors(sim7000e_t *modem)
{
while(modem->serial->available() > 0)
{
modem->serial->read();
}
}
/*
* Zatrzymuje program w przypadku bledu krytycznego, z ktorego dalsza
* czesc procedury nie moze sensownie kontynuowac (np. brak modemu,
* brak aktywacji wymaganej funkcji, brak potwierdzenia komendy AT).
*/
void Fatal_Error(sim7000e_t *modem, const char *msg)
{
modem->status = MODEM_STATE_ERROR;
Debug_Print("\r\n### BLAD KRYTYCZNY: ");
Debug_Print(msg);
Debug_Print(" ###\r\n");
Debug_Print(modem->buffer);
while(1)
{
delay(1);
}
}
/*
* Odbiera znaki z modemu bajt po bajcie i dopisuje je do modem->buffer,
* az do wykrycia oczekiwanego fragmentu tekstu ("expected") lub uplywu
* czasu "timeout_ms". Dodatkowo wykrywa typowe komunikaty bledu modemu
* (ERROR, +CME ERROR, +CMS ERROR) i przerywa oczekiwanie natychmiast,
* bez czekania na pelny timeout. Jesli bufor sie zapelni (odpowiedz
* modemu dluzsza niz MODEM_BUFFER_SIZE), traktujemy to jako blad -
* w przeciwnym razie kolejne bajty byłyby cicho odrzucane i program
* mogłby nigdy nie znalezc oczekiwanego tekstu.
*
* Na starcie czysci bufor UART (patrz Modem_Clear_UART_Errors) -
* to zapobiega sytuacji, w ktorej nierozwiazany problem z poprzedniej
* intensywnej transmisji blokuje caly dalszy odbior w milczeniu.
*
* expected->fragment tekstu, na ktory czekamy (np. "OK", "READY", ">")
* timeout_ms->maksymalny czas oczekiwania w milisekundach
* return->1 jesli znaleziono oczekiwany tekst, 0 w przypadku bledu/timeoutu
*/
uint8_t Modem_Wait_For_Response(sim7000e_t *modem, const char *expected, uint32_t timeout_ms)
{
uint8_t rx;
uint32_t start = millis();
modem->status = MODEM_STATE_CMD;
Modem_Clear_UART_Errors(modem);
while((millis() - start) < timeout_ms)
{
unsigned long read_start = millis();
bool byte_received = false;
while(millis() - read_start < 10)
{
if(modem->serial->available() > 0)
{
rx = modem->serial->read();
byte_received = true;
break;
}
}
if(byte_received)
{
if(!((rx >= 32 && rx <= 126) || rx == '\r' || rx == '\n'))
{
continue;
}
if(modem->buffer_index < MODEM_BUFFER_SIZE - 1)
{
modem->buffer[modem->buffer_index++] = rx;
modem->buffer[modem->buffer_index] = '\0';
}
else
{
modem->status = MODEM_STATE_ERROR;
return 0;
}
if(strstr(modem->buffer, "\r\nERROR\r\n") ||
strstr(modem->buffer, "+CME ERROR") ||
strstr(modem->buffer, "+CMS ERROR"))
{
modem->status = MODEM_STATE_ERROR;
return 0;
}
if(strstr(modem->buffer, expected))
{
if(strcmp(expected, ">") == 0)
{
modem->status = MODEM_STATE_OK;
return 1;
}
if(strstr(modem->buffer, "\r\nOK\r\n"))
{
modem->status = MODEM_STATE_OK;
return 1;
}
}
}
}
modem->status = MODEM_STATE_TIMEOUT;
return 0;
}
/*
* Wysyla pojedyncza komende AT do modemu i czeka na okreslona odpowiedz.
* Przed wyslaniem czysci bufor, aby nie mieszac z poprzednia odpowiedzia.
* W przypadku bledu wypisuje diagnostyke (co realnie przyszlo od modemu).
* cmd-> tekst komendy AT (z zakonczeniem \r\n)
* expected-> oczekiwany fragment odpowiedzi
* timeout_ms-> maksymalny czas oczekiwania
* return-> 1 sukces, 0 blad/timeout
*/
uint8_t Modem_Send_Command(sim7000e_t *modem, const char *cmd, const char *expected, uint32_t timeout_ms)
{
Clear_Modem_Buffer(modem);
Debug_Print("\r\n>> ");
Debug_Print(cmd);
Modem_Send_String(modem, cmd);
if(!Modem_Wait_For_Response(modem, expected, timeout_ms))
{
Debug_Print("\r\nTIMEOUT/BLAD\r\n");
Debug_Print(modem->buffer);
Debug_Print("\r\n");
return 0;
}
Debug_Print("\r\n<< ");
Debug_Print(modem->buffer);
Debug_Print("\r\n");
return 1;
}
/*
* Wielokrotnie probuje podstawowej komendy "AT", nie ruszajac PWRKEY.
* Sluzy do sprawdzenia, czy modem juz dziala (np. wystartowal sam
* wraz z podaniem zasilania) - unika zbednego/szkodliwego impulsu
* PWRKEY, ktory moglby wylaczyc juz aktywny modul.
* attempts->liczba prob
* return 1->jesli modem odpowiedzial w ktorejkolwiek probie, 0 jesli zaden sukces
*/
uint8_t Modem_Try_AT(sim7000e_t *modem, uint8_t attempts)
{
while(attempts--)
{
if(Modem_Send_Command(modem, "AT\r\n", "OK", 1500))
{
return 1;
}
delay(1000);
}
return 0;
}
W przypadku ESP32 zastosowaliśmy także ten sam sposób oddzielenia obsługi GNSS do osobnego modułu.
/*
* Modem_GPS.h
*
* Funkcje odpowiedzialne za obsluge GNSS w module SIM7000E:
* aktywacja silnika GNSS, odpytywanie o biezaca pozycje oraz
* dostep do ostatnio odczytanych wspolrzednych.
* Korzysta z typu sim7000e_t i funkcji bazowych z Modem_Core.h.
*/
#ifndef MODEM_GPS_H
#define MODEM_GPS_H
/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"
/* Exported types ---------------------------------------------------------------*/
/* Exported function prototypes --------------------------------------------------*/
uint8_t Modem_GPS_Activate(sim7000e_t *sim); // AT+CGNSPWR=1, aktywacja silnika GNSS
uint8_t Modem_GPS_Read(sim7000e_t *sim); // odczyt lokalizacji (AT+CGNSINF) - zwraca 0 jezeli brak fixa lub 1 po zlapaniu fixa
double Modem_GPS_Get_Lat(sim7000e_t *sim); // odczyt szerokosci geograficznej
double Modem_GPS_Get_Lon(sim7000e_t *sim); // odczyt dlugosci geograficznej
#endif /* MODEM_GPS_H */Kod pliku źródłowego Modem_GPS.cpp pokazano poniżej.
/*
* Modem_GPS.cpp
*
* Implementacja funkcji GNSS - patrz Modem_GPS.h.
*/
/* Includes ------------------------------------------------------------------*/
#include "Modem_GPS.h"
#include <stdlib.h> // strtod
#include <string.h> // strstr, strlen
/* Private user code -----------------------------------------------------------*/
/*
* Aktywuje silnik GNSS w module (AT+CGNSPWR=1). Bez tego AT+CGNSINF
* w Modem_GPS_Read bedzie zwracac puste dane (run=0).
*/
uint8_t Modem_GPS_Activate(sim7000e_t *sim)
{
return Modem_Send_Command(sim, "AT+CGNSPWR=1\r\n", "OK", 3000);
}
// Zwraca wskaźnik na początek N-tego pola w ciągu oddzielonym przecinkami (indeks od 0)
static const char* get_field_ptr(const char* str, uint8_t target_field) {
uint8_t current_field = 0;
if (target_field == 0) return str;
while (*str != '\0') {
if (*str == ',') {
current_field++;
if (current_field == target_field) {
return str + 1; // Początek szukanego pola za przecinkiem
}
}
str++;
}
return NULL;
}
/*
* Odpytuje modul o biezaca pozycje komenda AT+CGNSINF i parsuje
* odpowiedz, wypelniajac pola sim->lat, sim->lon, sim->fix.
*
* Format odpowiedzi:
* +CGNSINF: <run>,<fix>,<data/czas UTC>,<lat>,<lon>,<wys>,...
*
* W kazdym przypadku bledu (brak odpowiedzi, brak fixa, blad parsowania)
* pola struktury sa jawnie nadpisywane wartosciami sygnalizujacymi
* brak danych (fix=0, lat=9999.9f, lon=9999.9f), aby program glowny
* nigdy nie odczytal "starych" wspolrzednych z poprzedniego, udanego
* odczytu.
*
* return -> 1 jesli uzyskano wazny fix, 0 w przeciwnym razie
*/
uint8_t Modem_GPS_Read(sim7000e_t *sim) {
// 1. Wysyłamy komendę AT+CGNSINF
if (!Modem_Send_Command(sim, "AT+CGNSINF\r\n", "+CGNSINF:", 3000)) {
Debug_Print("Brak odpowiedzi na AT+CGNSINF\r\n");
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// 2. Szukamy ciągu "+CGNSINF:" w buforze
const char *p = strstr(sim->buffer, "+CGNSINF:");
if (p == NULL) {
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// Pomijamy nagłówek "+CGNSINF:" i ewentualne spacje
p += strlen("+CGNSINF:");
while (*p == ' ') p++;
// 3. Sprawdzamy status FIX (Pole o indeksie 1)
const char *fix_ptr = get_field_ptr(p, 1);
if (fix_ptr == NULL || *fix_ptr != '1') {
// Jeśli pole nie istnieje lub pierwszy znak to nie '1' -> brak fixa
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// 4. Pobieramy Latitude (Pole 3) i Longitude (Pole 4)
const char *lat_ptr = get_field_ptr(p, 3);
const char *lon_ptr = get_field_ptr(p, 4);
if (lat_ptr != NULL && lon_ptr != NULL && *lat_ptr != ',' && *lon_ptr != ',') {
// Funkcja strtod bezbłędnie konwertuje ciąg znaków na double aż do pierwszego przecinka
sim->lat = strtod(lat_ptr, NULL);
sim->lon = strtod(lon_ptr, NULL);
sim->fix = 1;
return 1; // Zwracamy 1 -> FIX JEST!
}
sim->fix = 0;
sim->lat = 9999.9f;
sim->lon = 9999.9f;
return 0;
}
// Odczyt szerokosci geograficznej z ostatniego udanego pomiaru
double Modem_GPS_Get_Lat(sim7000e_t *sim)
{
return sim->lat;
}
// Odczyt dlugosci geograficznej z ostatniego udanego pomiaru
double Modem_GPS_Get_Lon(sim7000e_t *sim)
{
return sim->lon;
}
Główny plik szkicu integruje wszystkie funkcjonalności modułu w jeden program. Funkcja setup() pozostaje analogiczna do procedury opisanej w części poświęconej wysyłce SMS. Natomiast funkcja loop() cyklicznie, co 10 sekund, odpytuje o pozycję geograficzną, wypisując w terminalu surową odpowiedź modułu na komendę AT+CGNSINF wraz z wyznaczonymi wartościami szerokości i długości geograficznej.
/*
* sketch_gps.ino
*
* Punkt wejscia programu: inicjalizacja sprzetu (piny, UART),
* sekwencja startowa modemu (AT -> ewentualny PWRKEY -> GNSS)
* i petla glowna odpytujaca o pozycje oraz wypisujaca sparsowane
* wspolrzedne na kanal diagnostyczny.
*/
/* Includes ------------------------------------------------------------------*/
#include "Modem_Core.h"
#include "Modem_GPS.h"
/* Private variables ------------------------------------------------------------*/
sim7000e_t modem; // jedyna globalna instancja - reprezentuje fizyczny modul podpiety do ESP32
/* Private user code -----------------------------------------------------------*/
void setup()
{
modem.serial = &Serial2;
modem.pwr_pin = 4; // GPIO4 - PWRKEY
pinMode(modem.pwr_pin, OUTPUT);
digitalWrite(modem.pwr_pin, LOW);
Serial.begin(115200);
Serial2.begin(115200, SERIAL_8N1, 16, 17); // RX=16, TX=17
while(Serial2.available() > 0)
{
Serial2.read();
}
modem.status = MODEM_STATE_CMD;
Modem_Clear_UART_Errors(&modem);
Clear_Modem_Buffer(&modem);
Debug_Print("\r\n");
Debug_Print("=================================\r\n");
Debug_Print(" START PROCEDURY ODPYTYWANIA O LOKALIZACJE GPS\r\n");
Debug_Print("=================================\r\n");
delay(3000);
Debug_Print("Proba komunikacji AT...\r\n");
uint8_t modem_ready = Modem_Try_AT(&modem, 15);
if(!modem_ready)
{
Debug_Print("Brak odpowiedzi.\r\n");
Debug_Print("Uruchamiam PWRKEY...\r\n");
digitalWrite(modem.pwr_pin, HIGH);
delay(1500);
digitalWrite(modem.pwr_pin, LOW);
delay(12000);
modem_ready = Modem_Try_AT(&modem, 10);
if(!modem_ready)
{
Fatal_Error(&modem, "Modem nie odpowiada po impulsie PWRKEY");
}
}
Debug_Print("AT OK\r\n");
if(!Modem_GPS_Activate(&modem))
{
Fatal_Error(&modem, "Brak aktywacji GNSS.");
}
Debug_Print("GNSS OK\r\n");
}
void loop()
{
if (Modem_GPS_Read(&modem)) {
Debug_Print("Fix OK -> ");
Debug_Print("Lat: ");
// Serial.print z drugim argumentem 6 wymusza wyświetlenie 6 miejsc po przecinku
Serial.print(Modem_GPS_Get_Lat(&modem), 6);
Debug_Print(" | Lon: ");
Serial.print(Modem_GPS_Get_Lon(&modem), 6);
Debug_Print("\r\n");
} else {
Debug_Print("Brak fixu GPS.\r\n");
}
delay(10000); // przerwa miedzy kolejnymi odczytami - bez niej modul jest odpytywany praktycznie bez przerwy
}
Obecna implementacja kodu pozwala zweryfikować poprawność komunikacji z silnikiem GNSS oraz zaobserwować moment uzyskania namiaru satelitarnego bezpośrednio w surowej odpowiedzi modułu. Program informuje nas również o szerokości i długości geograficznej, wyznaczonych na podstawie parsowania surowego ciągu znaków otrzymanego z SIM7000E.

Prezentacja otrzymanych wyników w Serial Monitor
Do tej pory wciąż nie wykorzystaliśmy jeszcze pełnego potencjału modułu. W kolejnych częściach artykułu, do lektury których już teraz serdecznie zapraszamy, zajmiemy się transmisją danych za pośrednictwem sieci komórkowej.
Archiwa ZIP do pobrania – gotowe projekty na STM32 i ESP32

