Moduł SIM7000E z STM32 i ESP32 (1). Wysyłka SMS

Udostępnij

Decyzja o wyborze kluczowych komponentów, możliwie najlepiej dopasowanych do wymagań projektu, zapada zwykle już na wczesnym etapie opracowywania koncepcji urządzenia. Gdy system ma łączyć pozycjonowanie satelitarne z transmisją danych przez sieć komórkową, jednym z dostępnych rozwiązań jest moduł Waveshare LTE GPS HAT oparty na układzie SIM7000E. Moduł obsługuje technologie LTE Cat-M, NB-IoT i EDGE/GPRS, a także systemy nawigacyjne GPS, BeiDou, GLONASS oraz Galileo.

Podstawowe informacje o module 

Producent modułu pierwotnie zaplanował go jako nakładkę do minikomputerów z rodziny Raspberry Pi, ale my skupimy się na zastosowaniu go z innymi układami nadrzędnymi, komunikującymi się z transceiverem za pośrednictwem magistrali UART. Sam moduł jest wyposażony w dwie zewnętrzne anteny: jedną przeznaczoną do przesyłu danych oraz drugą GNSS do odbioru sygnałów nawigacyjnych. Na spodzie płytki znajduje się miejsce na kartę SIM, konieczną do nawiązania łączności GSM.

Komunikacja z modułem SIM7000E odbywa się za pomocą komend AT. Spis wszystkich poleceń można znaleźć w dokumentacji, ale te niezbędne do uruchomienia zostaną opisane w dalszej części artykułu. Interfejsem pozwalającym na komunikację modułu z komputerem za pośrednictwem terminala jest port USB – przy pierwszym uruchomieniu warto sprawdzić poprawność funkcjonowania nakładki właśnie w ten sposób. Moduł jest wyposażony w dwie diody LED: pierwsza informuje o obecności napięcia zasilania, zaś druga wskazuje status modułu (dioda NET). Ta ostatnia może znajdować się w trzech stanach:

  • Cykliczne mignięcia (64 ms ON / 800 ms OFF) – informacja na temat braku rejestracji modułu w sieci.
  • Powolne mignięcia (64 ms ON / 3000 ms OFF) – poprawna rejestracja w domenie pakietowej.
  • Szybkie błyski (64 ms ON / 300 ms OFF) – aktywna transmisja danych.
  • Zgaszona (OFF) – moduł nie ma zasilania lub został programowo wprowadzony w stan głębokiego uśpienia.

Moduł domyślnie ma uruchomioną funkcję automatycznego wykrywania prędkości transmisji danych (auto baud rate), ale w razie potrzeby możemy to zmienić za pomocą dedykowanej komendy.

Moduł LTE GPS HAT – NB-IoT/LTE/GPRS/GPS SIM7000E firmy Waveshare (fot. HardwareDesigner.pl)

  

Spis podstawowych komend

  • AT – test komunikacji; najprostszy test sprawdzający, czy prędkości transmisji (baudrate) UART-u mikrokontrolera i modemu są ze sobą zgodne oraz czy warstwa fizyczna (linie TX/RX) działa poprawnie. Prawidłowa odpowiedź zwracana przez moduł to OK
  • ATI komenda do identyfikacji modułu; zwracana jest pełna nazwa producenta i dokładny model transceivera.
  • AT+CPIN? – polecenie sprawdzające, czy karta SIM została fizycznie wykryta w slocie i czy nie wymaga podania kodu PIN.
  • AT+CSQ – raport dotyczący jakości sygnału radiowego (signal quality). Zwraca dwie wartości: RSSI oraz bitową stopę błędów. Pierwszy parametr (o zakresie 0...31) informuje o mocy sygnału; dodatkowo wartość 99 jest definiowana przez dokumentację jako nieznana lub niemożliwa do wykrycia.
  • AT+CREG? – zapytanie dotyczące statusu rejestracji urządzenia w sieci komórkowej GSM. Komenda ta zwraca status zalogowania do stacji bazowej (BTS) dla podstawowych usług, takich jak klasyczna telefonia głosowa oraz wiadomości SMS. Pożądanymi odpowiedziami potwierdzającymi pomyślną autoryzację są statusy 0,1 lub 1,1 (rejestracja w sieci macierzystej) oraz 0,5 lub 1,5 (rejestracja w roamingu). Bez uzyskania tego statusu każda próba nadania wiadomości SMS zakończy się niepowodzeniem. Opcjonalnie, jeśli w projekcie ma być zastosowana obsługa połączeń internetowych, np. TCP/IP, poprzez sieć GPRS/LTE (GPRS), należy zastosować polecenie AT+CGREG? będące odpowiednikiem komendy AT+CREG? dla usług transmisji pakietowej (ang. Packet Switched).

Podłączenie modułu do mikrokontrolera

W przypadku wykorzystania innego urządzenia nadrzędnego (hosta) niż wspomniany wcześniej minikomputer Raspberry Pi, możemy skorzystać z zaimplementowanego w module interfejsu UART, wyprowadzonego na dodatkowe pady lutownicze. Znajdują się one na krótszym brzegu PCB. Schemat połączenia jest typowy dla tego interfejsu komunikacyjnego, natomiast trzeba pamiętać o podłączeniu pinu o nazwie PWR nakładki z dowolną dostępną linią GPIO używanego mikrokontrolera. Przed podłączeniem linii GPIO zalecamy sprawdzić rewizję płytki oraz konfigurację zworki PWR, jeżeli znajduje się ona w pozycji A to uruchamianie i włączanie następuję przez linię sterującą, w przypadku pozycji B następuję automatyczne uruchamianie po podaniu zasilania. Przy implementacji programu sterującego modułem będziemy podawać na tę linię impuls sterujący (stan wysoki trwający minimum 1,2 s), co zainicjuje procedurę uruchomienia transceivera. Napięcie zasilania warto podać przez wbudowane gniazdo microUSB ze źródła napięcia 5 V o odpowiedniej wydajności prądowej (szczytowy pobór prądu podczas transmisji może dochodzić nawet do 2 A). Dobrym wyborem będzie powerbank lub ładowarka USB. W trakcie testowania opisanych w artykule algorytmów, moduł został podłączony do ładowarki sieciowej, co zapewniło stabilne zasilanie, nawet w przypadku dużych wartości szczytowych prądu.

Komunikacja modułu SIM7000E z STM32

W tej części artykułu opisujemy krok po kroku proces konfiguracji mikrokontrolera w środowisku STM32CubeMX. Czytelnicy zaznajomieni z układami STM32 i ekosystemem STM32CubeMX/STM32CubeIDE mogą pominąć niniejszy rozdział i przejść od razu do opisu kodu programu.

Prawidłowe działanie modułu z mikrokontrolerem z rodziny STM32 wymaga poprawnej konfiguracji wybranego modelu płytki. Możemy to zrobić za pomocą konfiguratora STM32CubeMX. Proces konfiguracji graficznej nie wymaga zastosowania żadnych skomplikowanych opcji i przebiega w sposób opisany poniżej.

W menu głównym programu STM32CubeMX wybieramy File-> New Project. Otworzy się okno z zakładką Board Selector, w której możemy wybrać używaną płytkę ewaluacyjną (w naszym przypadku jest to STM32 NUCLEO-L452RE). Po wybraniu zestawu ewaluacyjnego klikamy przycisk Start Project, co spowoduje otwarcie okna wizualnego konfiguratora peryferiów. Najpierw musimy odpowiednio ustawić parametry interfejsu UART. Z panelu po lewej stronie wybieramy Connectivity->USART1. Warto wspomnieć, że na używanej przez nas płytce interfejs USART2 jest domyślnie połączony z programatorem ST-Link w celu realizacji funkcji wirtualnego portu COM do połączenia z terminalem na komputerze. Z tego względu CubeMX generuje dla niego konfigurację automatycznie.

W otwartym menu, w zakładce Mode, wybieramy tryb Asynchronous oraz sprawdzamy, czy w zakładce Parameter Settings są ustawione następujące wartości: 

  • Baud Rate: 115200
  • Word Length: 8 Bits
  • Parity: None;
  • Stop Bits: 1

Dalej przechodzimy na prawą stronę okna, w którym widoczny jest podgląd układu wyprowadzeń naszego mikrokontrolera, pozwalający na skonfigurowanie pinów. Na początku z dostępnych GPIO wybieramy jedną wolną linię (zaznaczoną kolorem szarym – np. PB0 lub PA4), klikamy na nią lewym przyciskiem myszy, a następnie wybieramy opcję GPIO_Output. Następnie klikamy jeszcze raz prawym przyciskiem myszy, wybieramy Enter User Label i nazywamy wybrany pin jako MODEM_PWR, co ułatwi nam pisanie kodu. Musimy oczywiście przypisać także linie interfejsu UART – i tak, pin PB6 będzie pełnił rolę linii USART1_TX, natomiast pin PB7 rezerwujemy dla USART1_RX.

Po zakończeniu konfiguracji graficznej zapisujemy zmiany – można w tym celu użyć skrótu Ctrl+S. Plik konfiguracji o rozszerzeniu .ioc możemy zapisać w dowolnej wybranej lokalizacji, po czym klikamy przycisk GENERATE CODE. Środowisko wyświetli zapytanie o automatyczne wygenerowanie kodu inicjalizacyjnego struktur HAL, co należy zaakceptować. 

Prezentacja konfiguracji interfejsu UART w programie STM32Cube IDE (rys. HardwareDesigner.pl)

Następnie przechodzimy do środowiska STM32CubeIDE i importujemy wygenerowany projekt. Z menu po prawej stronie wybieramy folder Core->Src, a z niego otwieramy plik main.c, gdy to w nim będziemy wklejać nasz kod.

Implementacja obsługi komend AT

W celu realizacji procedury bezprzewodowej wysyłki komunikatów SMS za pośrednictwem modułu SIM7000E, zaimplementowano kod zawierający wszystkie niezbędne funkcje i komendy dla poprawnej realizacji założonej funkcjonalności. Ważnym założeniem projektowym było rozdzielenie kanału komunikacyjnego od diagnostycznego. Skorzystaliśmy w tym celu z dwóch niezależnych interfejsów szeregowych:

  • USART 2 – traktowany jako kanał diagnostyczny wyprowadzony bezpośrednio przez wbudowany debugger ST-LINK do komputera PC.
  • USART 1 – służy do dwukierunkowej transmisji danych z układem SIM7000E.

Samo wysyłanie wiadomości SMS z poziomu komend AT i przy użyciu samego terminala (jak wspomnieliśmy wyżej) wydaje się proste i pozornie wymaga jedynie podania numeru odbiorcy oraz treści. W rzeczywistości moduł SIM7000E wymaga zachowania odpowiedniej kolejności dodatkowych operacji i sprawdzania szeregu warunków niezbędnych do poprawnej inicjalizacji. Z tego powodu strukturę programu oparliśmy o rozbudowaną, hierarchiczną architekturę funkcji pomocniczych, które realizuje sekwencyjna procedura inicjalizacji. Zamiast sekwencyjnego przesyłania komend „w ciemno”, każda operacja jest ściśle nadzorowana przez warstwę abstrakcji sprzętowej, w tym kluczową funkcję Modem_Wait_For_Response(). Odpowiada ona za blokujący odbiór znak po znaku, i odpytywanie interfejsu UART względem komend AT, kontrolę czasu bezczynności (ang. timeout) oraz natychmiastowe przechwytywanie i dekodowanie sprzętowych błędów modemu (np. odpowiedzi ERROR, +CME ERROR czy +CMS ERROR), co zapobiega wprowadzeniu modułu w stany nieustalone.

Poniżej przedstawimy pełną, zweryfikowaną doświadczalnie implementację algorytmu zawierającego wieloetapowy proces weryfikacji gotowości modułu radiowego: począwszy od synchronizacji szybkości transmisji szeregowej (Modem_Try_AT), poprzez sprzętową kontrolę stanu linii PWRKEY (impulsy są generowane wyłącznie przy braku wcześniejszej aktywności modemu), aż po rygorystyczne sprawdzanie statusu rejestracji karty SIM (AT+CPIN?) oraz wielokrotne odpytywanie o dostępność sieci operatora komórkowego (AT+CREG?). Jeżeli wszystkie kroki zostaną wykonane bez przeszkód, system przejdzie do nadania komunikatu tekstowego, z wysokim prawdopodobieństwem jego prawidłowego wysłania w warunkach rzeczywistych.

Najważniejsze funkcje, w tym ciało głównej funkcji main(), pokazano na listingu poniżej.


// 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 USART1.
void Modem_Send_String(const char *str)
{
    HAL_UART_Transmit(&MODEM_UART, (uint8_t*)str, strlen(str), 1000);
}
//Zeruje globalny bufor odpowiedzi modemu przed kolejna komenda AT, aby pozostalosci z poprzedniej odpowiedzi nie zafalszowaly parsowania.
void Clear_Modem_Buffer(void)
{
    memset(modem_buffer,0,sizeof(modem_buffer));
    buffer_index=0;
}

/*
 * Czysci flagi bledow sprzetowych UART1 (Overrun, Noise, Framing).
 * Moze to byc przydatne po intensywnej transmisji (np. tresc SMS + Ctrl+Z),
 * 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(void)
{
    __HAL_UART_CLEAR_OREFLAG(&MODEM_UART);
    __HAL_UART_CLEAR_NEFLAG(&MODEM_UART);
    __HAL_UART_CLEAR_FEFLAG(&MODEM_UART);
    __HAL_UART_CLEAR_PEFLAG(&MODEM_UART);
}

/*
 * Zatrzymuje program w przypadku bledu krytycznego, z ktorego dalsza
 * czesc procedury nie moze sensownie kontynuowac (np. brak modemu,
 * brak karty SIM, brak sieci, brak potwierdzenia AT+CMGS).
 * Wypisuje komunikat diagnostyczny na kanal debug i zatrzymuje program
 * w petli nieskonczonej.
 */
void Fatal_Error(const char *msg)
{
    Debug_Print("\r\n### BLAD KRYTYCZNY: ");
    Debug_Print(msg);
    Debug_Print(" ###\r\n");
    Debug_Print(modem_buffer);

    while(1)
    {
        // Celowo pusta petla - program zatrzymuje sie tutaj,
        // dalsza czesc procedury nie zostanie wykonana.
    }
}

/*
 * 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 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(const char *expected, uint32_t timeout_ms)
{
    uint8_t rx;
    uint32_t start = HAL_GetTick();

    Modem_Clear_UART_Errors();

    while((HAL_GetTick() - start) < timeout_ms)
    {
        if(HAL_UART_Receive(&MODEM_UART, &rx, 1, 10) == HAL_OK)
        {
            // Filtr znakow: akceptujemy tylko znaki drukowalne ASCII oraz CR/LF.
            // Odrzuca przypadkowe smieci elektryczne na linii RX, ktore mogly
            // pojawic sie tuz po intensywnej transmisji (tresc SMS + Ctrl+Z).
            if(!((rx >= 32 && rx <= 126) || rx == '\r' || rx == '\n'))
            {
                continue; // pomijamy ten bajt, nie dodajemy go do bufora
            }
            if(buffer_index < MODEM_BUFFER_SIZE - 1)
            {
                modem_buffer[buffer_index++] = rx;
                modem_buffer[buffer_index] = '\0';
            }
            else
            {
                // Bufor pelny - odpowiedz modemu jest dluzsza niz zakladano,
                // dalsze bajty i tak by zostaly odrzucone - to traktujemy jako blad.
                return 0;
            }

            /* błędy modemu */
            if(strstr(modem_buffer, "\r\nERROR\r\n"))
                return 0;

            if(strstr(modem_buffer, "+CME ERROR"))
                return 0;

            if(strstr(modem_buffer, "+CMS ERROR"))
                return 0;

            /* czekamy na oczekiwany tekst */
            if(strstr(modem_buffer, expected))
            {
                /*
                 * Jeżeli oczekujemy tylko znaku '>' (AT+CMGS),
                 * możemy od razu zakończyć.
                 */
                if(strcmp(expected, ">") == 0)
                    return 1;

                /*
                 * Dla wszystkich pozostałych komend
                 * czekamy jeszcze na końcowe OK.
                 */
                if(strstr(modem_buffer, "\r\nOK\r\n"))
                    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(&MODEM_UART, UART_FLAG_ORE))
            {
                Modem_Clear_UART_Errors();
            }
        }
    }

    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(const char *cmd, const char *expected, uint32_t timeout_ms)
{
    Clear_Modem_Buffer();

    Debug_Print("\r\n>> ");
    Debug_Print(cmd);

    Modem_Send_String(cmd);

    if(!Modem_Wait_For_Response(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 brak odpowiedzi
 */
uint8_t Modem_Try_AT(uint8_t attempts)
{
    while(attempts--)
    {
        if(Modem_Send_Command("AT\r\n", "OK", 1500))
        {
            return 1;
        }

        HAL_Delay(1000);
    }

    return 0;
}
/*
 * Sprawdza gotowosc karty SIM komenda AT+CPIN?.
 * Oczekuje frazy "READY", co oznacza brak blokady kodem PIN.
 */
uint8_t Modem_Check_SIM(void)
{
    return Modem_Send_Command( "AT+CPIN?\r\n", "READY", 5000);
}
/*
 * Odczytuje poziom sygnalu radiowego komenda AT+CSQ.
 * Wynik (RSSI, BER) trafia do modem_buffer i moze byc
 * wypisany do celow diagnostycznych.
 */
uint8_t Modem_Check_Signal(void)
{
    return Modem_Send_Command("AT+CSQ\r\n", "+CSQ:", 3000);
}
/*
 * Sprawdza status rejestracji w sieci komorkowej komenda AT+CREG?.
 * Wysyla zapytanie JEDEN raz, a nastepnie parsuje odpowiedz w formacie
 * "+CREG: <n>,<stat>", wyciagajac wartosc <stat>. Akceptowane wartosci:
 * 1 - zarejestrowany w sieci macierzystej
 * 5 - zarejestrowany w roamingu
 */
uint8_t Modem_Check_Network(void)
{
    if(!Modem_Send_Command("AT+CREG?\r\n", "+CREG:", 3000))
    {
        return 0;
    }

    char *p = strstr(modem_buffer, "+CREG:");
    if(p == NULL)
    {
        return 0;
    }

    int n = 0;
    int stat = 0;
    if(sscanf(p, "+CREG: %d,%d", &n, &stat) == 2)
    {
        if(stat == 1 || stat == 5)
        {
            return 1;
        }
    }

    return 0;
}




int main(void)
{
 
  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();

  /*
   * ETAP WSTEPNY: czyszczenie bufora sprzetowego UART1 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_UART, &flush, 1, 10)==HAL_OK);

  Modem_Clear_UART_Errors();
  Clear_Modem_Buffer();

  Debug_Print("\r\n");
  Debug_Print("=================================\r\n");
  Debug_Print(" START PROCEDURY WYSYLANIA SMS\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).
   * Sprawdzamy to najpierw, aby uniknac zbednego impulsu,
   * ktory moglby wylaczyc juz dzialajacy modul.
   */

  Debug_Print("Proba komunikacji AT...\r\n");

  uint8_t modem_ready=Modem_Try_AT(15);

  /*
   * JEŻELI MODEM NIE ODPOWIADA
   * DOPIERO TERAZ PWRKEY
   * Impuls PWRKEY jest wysylany tylko raz i tylko jako
   * ostatecznosc - po nim nastepuje dlugie oczekiwanie
   * na pelny rozruch modemu (12s) i kolejna proba AT.
   * Jesli i to zawiedzie - dalsza czesc procedury nie ma
   * sensu bez dzialajacego modemu, wiec program sie zatrzymuje.
   */

  if(!modem_ready)
  {
      Debug_Print("Brak odpowiedzi.\r\n");
      Debug_Print("Uruchamiam PWRKEY...\r\n");

      HAL_GPIO_WritePin(MODEM_PWR_GPIO_Port, MODEM_PWR_Pin,  GPIO_PIN_SET);

      HAL_Delay(1500);

      HAL_GPIO_WritePin(MODEM_PWR_GPIO_Port, MODEM_PWR_Pin, GPIO_PIN_RESET);

      HAL_Delay(12000);

      modem_ready=Modem_Try_AT(10);

      if(!modem_ready)
      {
          Fatal_Error("Modem nie odpowiada po impulsie PWRKEY");
      }
  }

  Debug_Print("AT OK\r\n");

  /*
   * WYLACZENIE ECHA
   * ATE0 wylacza odsylanie przez modem wyslanych do niego
   * komend - upraszcza to parsowanie kolejnych odpowiedzi.
   */

  if(!Modem_Send_Command("ATE0\r\n", "OK", 3000))
  {
	  Debug_Print("ERROR.\r\n");
  }

  /*
   * SPRAWDZENIE KARTY SIM
   * AT+CPIN? musi zwrocic "READY" - w przeciwnym razie
   * karta jest zablokowana kodem PIN lub nie jest wykryta.
   * Bez gotowej karty SIM dalsza procedura nie ma sensu.
   */

  Debug_Print("Sprawdzanie SIM...\r\n");

  if(!Modem_Check_SIM())
  {
      Fatal_Error("Karta SIM nie jest gotowa (brak READY)");
  }

  Debug_Print("SIM READY\r\n");

  /*
   * REJESTRACJA W SIECI
   * Petla probuje AT+CREG? do 30 razy co 1s (do ok. 30-60s
   * lacznie, z uwzglednieniem timeoutow komend), az modul
   * zarejestruje sie w sieci operatora. Brak rejestracji
   * po tym czasie jest bledem krytycznym.
   */

  Debug_Print("Rejestracja sieci...\r\n");

  uint8_t registered=0;

  for(uint8_t i=0;i<30;i++)
  {
      if(Modem_Check_Network())
      {
          registered=1;
          break;
      }

      HAL_Delay(1000);
  }

  if(!registered)
  {

      Fatal_Error("Brak rejestracji w sieci");
  }

  Debug_Print("Siec OK\r\n");

  /*
   * JAKOSC SYGNALU (niekrytyczne)
   * AT+CSQ zwraca wskaznik sily sygnalu (RSSI) i wskaznik
   * jakosci (BER) - wynik trafia do modem_buffer i jest
   * wypisywany do celow diagnostycznych, bez wplywu na
   * dalszy przebieg procedury.
   */

  if(!Modem_Check_Signal())
  {
      Debug_Print("CSQ ERROR\r\n");
  }

  Debug_Print(modem_buffer);
  Debug_Print("\r\n");

  /*
   * TRYB TEKSTOWY SMS
   * AT+CMGF=1 przelacza modem z domyslnego trybu PDU
   * na tryb tekstowy, w ktorym tresc SMS mozna podac
   * jako zwykly tekst ASCII.
   */

  if(!Modem_Send_Command("AT+CMGF=1\r\n", "OK", 3000))
  {
	 Debug_Print("CMGF ERROR\r\n");
  }

  /*
   * PODANIE NUMERU
   * AT+CMGS="numer" rozpoczyna procedure wysylania SMS.
   * Modem odpowiada znakiem zachety '>', po ktorym
   * nalezy wpisac tresc wiadomosci. Brak tego znaku
   * oznacza, ze modem nie jest gotowy na przyjecie tresci -
   * wysylanie dalszych danych (tresci, Ctrl+Z) bez niego
   * jest bledne, wiec program sie zatrzymuje.
   */
  Clear_Modem_Buffer();

  Debug_Print("AT+CMGS...\r\n");

  Modem_Send_String("AT+CMGS=\"" NUMER_TELEFONU "\"\r\n");

  if(!Modem_Wait_For_Response(">", 5000))
  {
      Fatal_Error("Brak znaku zachety '>' po AT+CMGS");
  }

  /*
   * TRESC SMS
   * Tekst wiadomosci wysylany jest jako zwykly string,
   * bez znaku konca linii - koniec tresci sygnalizuje
   * dopiero znak Ctrl+Z (ponizej).
   */

  Modem_Send_String("Wiadomosc testowa z STM32 dziala!");

  HAL_Delay(300);

  /*
   * CTRL+Z
   * Znak ASCII 26 (Ctrl+Z) konczy wprowadzanie tresci SMS
   * i uruchamia jego faktyczna wysylke przez siec.
   */

  Clear_Modem_Buffer();

  uint8_t ctrlZ = 26;

  HAL_UART_Transmit(&MODEM_UART, &ctrlZ, 1, 1000);

  Debug_Print("Oczekiwanie na +CMGS...\r\n");

  /*
   * CZEKAMY NA +CMGS
   * Po wyslaniu Ctrl+Z modem potrzebuje realnego czasu na
   * przekazanie wiadomosci do sieci. Aktywnie czytamy UART
   * az do pojawienia sie "+CMGS:" (numer wiadomosci nadany
   * przez siec) - dopiero to potwierdza faktyczne wyslanie.
   * Modem_Wait_For_Response czysci flagi bledow UART na starcie
   * i w trakcie oczekiwania (patrz Modem_Clear_UART_Errors), co
   * zapobiega "milczeniu" spowodowanemu Overrun Error po
   * intensywnej transmisji (tresc + Ctrl+Z) tuz wczesniej -
   * dzieki temu odbior dziala tak samo szybko, jak przy
   * recznym wpisywaniu komend w terminalu.
   */

  if(!Modem_Wait_For_Response("+CMGS:", 30000))
  {
      Debug_Print("Blad: brak potwierdzenia wyslania SMS.\r\n");
      Debug_Print(modem_buffer);
      Debug_Print("\r\n");

  }else{
	  /*
	    * SUKCES
	    * To miejsce jest osiagane wylacznie, gdy wszystkie
	    * powyzsze etapy krytyczne zakonczyly sie powodzeniem -
	    * kazdy blad krytyczny zatrzymuje procedure wczesniej
	    * (Fatal_Error) i to miejsce nigdy nie zostanie wykonane
	    * w przypadku bledu.
	    */

	   Debug_Print("\r\n");
	   Debug_Print("=========================\r\n");
	   Debug_Print(" SMS WYSLANY POPRAWNIE\r\n");
	   Debug_Print("=========================\r\n");

  }


  while (1)
  {

  }

}

Po wgraniu programu musimy chwilę poczekać, ponieważ następuje wtedy detekcja stanu modułu – mikrokontroler musi zweryfikować, czy był on już wcześniej włączony. Jeżeli nie, wysyłamy impuls na pin MODEM_PWR. Zauważymy szybkie miganie diody NET, które następnie przejdzie w kolejny stan opisany we wcześniejszej części artykułu.

Widok terminala szeregowego podczas uruchamiania modułu z poziomu STM32 (rys. HardwareDesigner.pl)

Jeżeli odpowiednio wykonaliśmy fizyczne połączenie modułu z mikrokontrolerem oraz program nie zgłosił błędów w kodzie, poprawne zakończenie procedury zostanie potwierdzone w terminalu. 

Prezentacja otrzymanej wiadomości SMS z modułu SIM7000E pod kontrolą STM32 (rys. HardwareDesigner.pl)

Komunikacja modułu SIM7000E z ESP32

Mając na płytce modułu do dyspozycji interfejs UART, możemy skorzystać z niemal dowolnego mikrokontrolera w roli hosta. W tej części artykułu skupimy się na implementacji analogicznego programu dla mikrokontrolerów z rodziny ESP32. Poniższy kod powstał w środowisku Arduino IDE.

#include <string.h> // funkcje strstr/strcmp/memset do parsowania odpowiedzi modemu
#include <stdio.h>  // sscanf - parsowanie wartosci <stat> z odpowiedzi AT+CREG?

/* Private define ------------------------------------------------------------*/
#define MODEM_RX 16   // GPIO16 - pin RX dla Serial2 (USART1 ze STM32)
#define MODEM_TX 17   // GPIO17 - pin TX dla Serial2 (USART1 ze STM32)
#define MODEM_PWR 4   // GPIO4 - pin sterowania zasilaniem PWRKEY

#define NUMER_TELEFONU "+48xxxxxxxxx" // numer odbiorcy SMS w formacie miedzynarodowym

#define MODEM_BUFFER_SIZE 256

// Bufor globalny gromadzacy surowa odpowiedz modemu miedzy kolejnymi komendami AT
char modem_buffer[MODEM_BUFFER_SIZE];
uint16_t buffer_index = 0;

/* Private function prototypes -----------------------------------------------*/
void Debug_Print(const char *text); // wypisanie tekstu na kanal diagnostyczny
void Modem_Send_String(const char *str); // wyslanie surowego tekstu do modemu
void Clear_Modem_Buffer(void); // zerowanie bufora odpowiedzi modemu
void Modem_Clear_UART_Errors(void); // czyszczenie flag/buforow UART (zapobieganie blokowaniu)

uint8_t Modem_Wait_For_Response(const char *expected, uint32_t timeout_ms); // oczekiwanie na okreslony fragment tekstu w odpowiedzi modemu, z limitem czasu

uint8_t Modem_Send_Command(const char *cmd, const char *expected, uint32_t timeout_ms); // wyslanie komendy AT i oczekiwanie na okreslona odpowiedz

uint8_t Modem_Try_AT(uint8_t attempts); // wielokrotna proba komunikacji AT bez ingerencji w PWRKEY (sprawdzenie czy modem juz "zyje")

uint8_t Modem_Check_SIM(void); // weryfikacja gotowosci karty SIM (AT+CPIN?)

uint8_t Modem_Check_Network(void); // weryfikacja rejestracji w sieci komorkowej (AT+CREG?)

uint8_t Modem_Check_Signal(void); // odczyt poziomu sygnalu (AT+CSQ)

void Fatal_Error(const char *msg); // zatrzymanie programu w przypadku bledu krytycznego

/* 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 Serial2.
void Modem_Send_String(const char *str)
{
    Serial2.print(str);
}

//Zeruje globalny bufor odpowiedzi modemu przed kolejna komenda AT, aby pozostalosci z poprzedniej odpowiedzi nie zafalszowaly parsowania.
void Clear_Modem_Buffer(void)
{
    memset(modem_buffer, 0, sizeof(modem_buffer));
    buffer_index = 0;
}

/*
 * Czysci programowy bufor odbiorczy UART2 (Serial2).
 * 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(void)
{
    while(Serial2.available() > 0)
    {
        Serial2.read();
    }
}

/*
 * Zatrzymuje program w przypadku bledu krytycznego, z ktorego dalsza
 * czesc procedury nie moze sensownie kontynuowac (np. brak modemu,
 * brak karty SIM, brak sieci, brak potwierdzenia AT+CMGS).
 * Wypisuje komunikat diagnostyczny na kanal debug i zatrzymuje program
 * w petli nieskonczonej.
 */
void Fatal_Error(const char *msg)
{
    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(const char *expected, uint32_t timeout_ms)
{
    uint8_t rx;
    uint32_t start = millis();

    Modem_Clear_UART_Errors();

    while((millis() - start) < timeout_ms)
    {
        // Odczyt UART z timeoutem 10 ms (odpowiednik HAL_UART_Receive)
        unsigned long read_start = millis();
        bool byte_received = false;
        while(millis() - read_start < 10)
        {
            if(Serial2.available() > 0)
            {
                rx = Serial2.read();
                byte_received = true;
                break;
            }
        }

        if(byte_received)
        {
            // Filtr znakow: akceptujemy tylko znaki drukowalne ASCII oraz CR/LF.
            // Odrzuca przypadkowe zaburzenia elektryczne na linii RX, ktore mogly
            // pojawic sie tuz po intensywnej transmisji (tresc SMS + Ctrl+Z).
            if(!((rx >= 32 && rx <= 126) || rx == '\r' || rx == '\n'))
            {
                continue; // pomijamy ten bajt, nie dodajemy go do bufora
            }

            if(buffer_index < MODEM_BUFFER_SIZE - 1)
            {
                modem_buffer[buffer_index++] = rx;
                modem_buffer[buffer_index] = '\0';
            }
            else
            {
                // Bufor pelny - odpowiedz modemu jest dluzsza niz zakladano,
                // dalsze bajty i tak by zostaly odrzucone - to traktujemy jako blad.
                return 0;
            }

            /* błędy modemu */
            if(strstr(modem_buffer, "\r\nERROR\r\n"))
                return 0;

            if(strstr(modem_buffer, "+CME ERROR"))
                return 0;

            if(strstr(modem_buffer, "+CMS ERROR"))
                return 0;

            /* czekamy na oczekiwany tekst */
            if(strstr(modem_buffer, expected))
            {
                /*
                 * Jeżeli oczekujemy tylko znaku '>' (AT+CMGS),
                 * możemy od razu zakończyć.
                 */
                if(strcmp(expected, ">") == 0)
                    return 1;

                /*
                 * Dla wszystkich pozostałych komend
                 * czekamy jeszcze na końcowe OK.
                 */
                if(strstr(modem_buffer, "\r\nOK\r\n"))
                    return 1;
            }
        }
    }

    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(const char *cmd, const char *expected, uint32_t timeout_ms)
{
    Clear_Modem_Buffer();

    Debug_Print("\r\n>> ");
    Debug_Print(cmd);

    Modem_Send_String(cmd);

    if(!Modem_Wait_For_Response(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(uint8_t attempts)
{
    while(attempts--)
    {
        if(Modem_Send_Command("AT\r\n", "OK", 1500))
        {
            return 1;
        }

        delay(1000);
    }

    return 0;
}

/*
 * Sprawdza gotowosc karty SIM komenda AT+CPIN?.
 * Oczekuje frazy "READY", co oznacza brak blokady kodem PIN.
 */
uint8_t Modem_Check_SIM(void)
{
    return Modem_Send_Command("AT+CPIN?\r\n", "READY", 5000);
}

/*
 * Odczytuje poziom sygnalu radiowego komenda AT+CSQ.
 * Wynik (RSSI, BER) trafia do modem_buffer i moze byc
 * wypisany do celow diagnostycznych.
 */
uint8_t Modem_Check_Signal(void)
{
    return Modem_Send_Command("AT+CSQ\r\n", "+CSQ:", 3000);
}

/*
 * Sprawdza status rejestracji w sieci komorkowej komenda AT+CREG?.
 * Wysyla zapytanie JEDEN raz, a nastepnie parsuje odpowiedz w formacie
 * "+CREG: <n>,<stat>", wyciagajac wartosc <stat>. Akceptowane wartosci:
 * 1 - zarejestrowany w sieci macierzystej
 * 5 - zarejestrowany w roamingu
 */
uint8_t Modem_Check_Network(void)
{
    if(!Modem_Send_Command("AT+CREG?\r\n", "+CREG:", 3000))
    {
        return 0;
    }

    char *p = strstr(modem_buffer, "+CREG:");
    if(p == NULL)
    {
        return 0;
    }

    int n, stat;
    if(sscanf(p, "+CREG: %d,%d", &n, &stat) == 2)
    {
        if(stat == 1 || stat == 5)
        {
            return 1;
        }
    }

    return 0;
}

void setup()
{
  // Inicjalizacja GPIO dla ESP32
  pinMode(MODEM_PWR, OUTPUT);
  digitalWrite(MODEM_PWR, LOW);
  

  // Inicjalizacja podstawowego portu UART do PC (USB debug)
  Serial.begin(115200);

  // Inicjalizacja sprzetowego drugiego portu UART (Serial2) dla ESP32
  Serial2.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);

  /* wyczyszczenie bufora UART */
  /*
   * ETAP WSTEPNY: czyszczenie bufora sprzetowego UART z ewentualnego
   * szumu/przypadkowych bajtow, ktore mogly pojawic sie na linii RX
   * podczas resetu/programowania ESP32, zanim wyslano jakakolwiek komende.
  */
  while(Serial2.available() > 0)
  {
      Serial2.read();
  }

  Modem_Clear_UART_Errors();
  Clear_Modem_Buffer();

  Debug_Print("\r\n");
  Debug_Print("=================================\r\n");
  Debug_Print(" START PROCEDURY WYSYLANIA SMS\r\n");
  Debug_Print("=================================\r\n");

  /*
  * STABILIZACJA ZASILANIA
  * Czas potrzebny na ustabilizowanie napiecia zasilajacego
  * modul modemu po wlaczeniu calego ukladu.
  */
  delay(3000);

  /*
   * PROBA AUTOBAUD
   * Modul SIM7000E moze wystartowac samodzielnie wraz z
   * podaniem zasilania (bez potrzeby impulsu PWRKEY).
   * Sprawdzamy to najpierw, aby uniknac zbednego impulsu,
   * ktory moglby wylaczyc juz dzialajacy modul.
   */
  Debug_Print("Proba komunikacji AT...\r\n");

  uint8_t modem_ready = Modem_Try_AT(15);

  /*
   * JEŻELI MODEM NIE ODPOWIADA
   * DOPIERO TERAZ PWRKEY
   * Impuls PWRKEY jest wysylany tylko raz i tylko jako
   * ostatecznosc - po nim nastepuje dlugie oczekiwanie
   * na pelny rozruch modemu (12s) i kolejna proba AT.
   * Jesli i to zawiedzie - dalsza czesc procedury nie ma
   * sensu bez dzialajacego modemu, wiec program sie zatrzymuje.
   */
  if(!modem_ready)
  {
      Debug_Print("Brak odpowiedzi.\r\n");
      Debug_Print("Uruchamiam PWRKEY...\r\n");

      digitalWrite(MODEM_PWR, HIGH);
      delay(1500);
      digitalWrite(MODEM_PWR, LOW);

      delay(12000);

      modem_ready = Modem_Try_AT(10);

      if(!modem_ready)
      {
          Fatal_Error("Modem nie odpowiada po impulsie PWRKEY");
      }
  }

  Debug_Print("AT OK\r\n");

  /*
   * WYLACZENIE ECHA
   * ATE0 wylacza odsylanie przez modem wyslanych do niego
   * komend - upraszcza to parsowanie kolejnych odpowiedzi.
   */
  if(!Modem_Send_Command("ATE0\r\n", "OK", 3000))
  {
      Debug_Print("ERROR.\r\n");
  }

  /*
   * SPRAWDZENIE KARTY SIM
   * AT+CPIN? musi zwrocic "READY" - w przeciwnym razie
   * karta jest zablokowana kodem PIN lub nie jest wykryta.
   * Bez gotowej karty SIM dalsza procedura nie ma sensu.
   */
  Debug_Print("Sprawdzanie SIM...\r\n");

  if(!Modem_Check_SIM())
  {
      Fatal_Error("Karta SIM nie jest gotowa (brak READY)");
  }

  Debug_Print("SIM READY\r\n");

  /*
   * REJESTRACJA W SIECI
   * Petla probuje AT+CREG? do 30 razy co 1s (do ok. 30-60s
   * lacznie, z uwzglednieniem timeoutow komend), az modul
   * zarejestruje sie w sieci operatora. Brak rejestracji
   * po tym czasie jest bledem krytycznym.
   */
  Debug_Print("Rejestracja sieci...\r\n");

  uint8_t registered = 0;

  for(uint8_t i = 0; i < 30; i++)
  {
      if(Modem_Check_Network())
      {
          registered = 1;
          break;
      }

      delay(1000);
  }

  if(!registered)
  {
      Fatal_Error("Brak rejestracji w sieci");
  }

  Debug_Print("Siec OK\r\n");

  /*
   * JAKOSC SYGNALU (niekrytyczne)
   * AT+CSQ zwraca wskaznik sily sygnalu (RSSI) i wskaznik
   * jakosci (BER) - wynik trafia do modem_buffer i jest
   * wypisywany do celow diagnostycznych, bez wplywu na
   * dalszy przebieg procedury.
   */
  if(!Modem_Check_Signal())
  {
      Debug_Print("CSQ ERROR\r\n");
  }

  Debug_Print(modem_buffer);
  Debug_Print("\r\n");

  /*
   * TRYB TEKSTOWY SMS
   * AT+CMGF=1 przelacza modem z domyslnego trybu PDU
   * na tryb tekstowy, w ktorym tresc SMS mozna podac
   * jako zwykly tekst ASCII.
   */
  if(!Modem_Send_Command("AT+CMGF=1\r\n", "OK", 3000))
  {
     Debug_Print("CMGF ERROR\r\n");
  }

  /*
   * PODANIE NUMERU
   * AT+CMGS="numer" rozpoczyna procedure wysylania SMS.
   * Modem odpowiada znakiem zachety '>', po ktorym
   * nalezy wpisac tresc wiadomosci. Brak tego znaku
   * oznacza, ze modem nie jest gotowy na przyjecie tresci -
   * wysylanie dalszych danych (tresci, Ctrl+Z) bez niego
   * jest bledne, wiec program sie zatrzymuje.
   */
  Clear_Modem_Buffer();

  Debug_Print("AT+CMGS...\r\n");

  Modem_Send_String("AT+CMGS=\"" NUMER_TELEFONU "\"\r\n");

  if(!Modem_Wait_For_Response(">", 5000))
  {
      Fatal_Error("Brak znaku zachety '>' po AT+CMGS");
  }

  /*
   * TRESC SMS
   * Tekst wiadomosci wysylany jest jako zwykly string,
   * bez znaku konca linii - koniec tresci sygnalizuje
   * dopiero znak Ctrl+Z (ponizej).
   */
  Modem_Send_String("Wiadomosc testowa z ESP-WROOM-32 dziala!");

  delay(300);

  /*
   * CTRL+Z
   * Znak ASCII 26 (Ctrl+Z) konczy wprowadzanie tresci SMS
   * i uruchamia jego faktyczna wysylke przez siec.
   */
  Clear_Modem_Buffer();

  uint8_t ctrlZ = 26;
  Serial2.write(ctrlZ);

  Debug_Print("Oczekiwanie na +CMGS...\r\n");

  /*
   * CZEKAMY NA +CMGS
   * Po wyslaniu Ctrl+Z modem potrzebuje realnego czasu na
   * przekazanie wiadomosci do sieci. Aktywnie czytamy UART
   * az do pojawienia sie "+CMGS:" (numer wiadomosci nadany
   * przez siec) - dopiero to potwierdza faktyczne wyslanie.
   * Modem_Wait_For_Response czysci bufor wejsciowy na starcie
   * (patrz Modem_Clear_UART_Errors), co zapobiega "milczeniu"
   * spowodowanemu przepełnieniem buforów po intensywnej transmisji
   * (tresc + Ctrl+Z) tuz wczesniej - dzieki temu odbior dziala
   * wysoce wydajnie.
   */
  if(!Modem_Wait_For_Response("+CMGS:", 30000))
  {
      Debug_Print("Blad: brak potwierdzenia wyslania SMS.\r\n");
      Debug_Print(modem_buffer);
      Debug_Print("\r\n");
  }
  else
  {
      /*
       * SUKCES
       * To miejsce jest osiagane wylacznie, gdy wszystkie
       * powyzsze etapy krytyczne zakonczyly sie powodzeniem -
       * kazdy blad krytyczny zatrzymuje procedure wczesniej
       * (Fatal_Error) i to miejsce nigdy nie zostanie wykonane
       * w przypadku bledu.
       */
      Debug_Print("\r\n");
      Debug_Print("=========================\r\n");
      Debug_Print(" SMS WYSLANY POPRAWNIE\r\n");
      Debug_Print("=========================\r\n");
  }
}

void loop()
{
  // Pętla pozostaje pusta – cała procedura wykonuje się jednorazowo w sekcji setup()
}

W przypadku implementacji kodu dla układu ESP32, cała logika programu również została umieszczona w sekcji poprzedzającej główną pętlę (czyli w funkcji setup()), dzięki czemu procedura wysyłki wykona się tylko raz po uruchomieniu mikrokontrolera.

Warto zauważyć, że w programie konfigurujemy sprzętowy port UART Serial2, wykorzystując piny GPIO16 (RX) oraz GPIO17 (TX). Wybór ten jest celowy – dzięki temu port UART0 (podpięty pod fabryczne złącze USB płytki deweloperskiej) pozostaje dostępny jako kanał diagnostyczny. Pozwala to na jednoczesne przesyłanie logów diagnostycznych do komputera przez funkcję Serial.println() i niezależną komunikację z modemem.

Widok terminala szeregowego podczas uruchamiania modułu z poziomu ESP32

Poprawnie zmontowany i zaprogramowany system pozwoli nam na wysłanie wiadomości SMS na zdefiniowany przez nas numer telefonu.

Doszliśmy do etapu, w którym obsługa wysyłania wiadomości SMS z pomocą modułu jest dziecinnie prosta. Jak wspomniano na początku artykułu, kolejnym krokiem będzie omówienie funkcji lokalizacji GPS, którą dogłębnie przeanalizujemy w następnym artykule.

Prezentacja otrzymanej wiadomości SMS z modułu SIM7000E pod kontrolą ESP32

Pobierz kompletny projekt dla STM32 (archiwum ZIP, 8,7 MB)

Pobierz kompletny projekt dla ESP32 (archiwum ZIP, 4,8 kB)

Paulina
Paulina
Redaktorka techniczna portalu. W zespole HardwareDesigner.pl odpowiada za opracowywanie tutoriali dotyczących programowania ESP32 i STM32.

Czytaj więcej

Wybrane dla Ciebie