STM32 Ethernet – UDP Server 

W ostatnim czasie miałem okazję pracować nad pewnym projektem wykorzystującym mikrokontroler STM32 oraz interfejs Ethernet. Zapewne podobnie jak inne osoby podejmujące się tego tematu natknąłem się na pewien problem – brak „prostych” poradników. We większości dostępnych źródeł łączenie interfejsu Ethernet i układów STM32 opisane jest dość zagmatwanie i według mnie brakuje instrukcji opisujących jak skonfigurować tę funkcjonalność w najprostszy możliwy sposób, bez zbędnego zgłębiania aspektów sieciowych. Dlatego postanowiłem przygotować ten oraz powiązane z nim materiały poruszające temat Ethernetu na STM32. W tym artykule pokaże wam podstawową funkcjonalność serwera UDP.

W projektach korzystam z płytki Nucleo F767ZI, kody dostępne są do pobrania na moim profilu GitHub.

Ostrzeżenie o nowej wersji firmware

Przygotowując kody do tego poradnika, korzystałem z CubeMX V6.12.1. Jeśli ktoś będzie chciał skorzystać z gotowego projektu, który udostępniam na moim profilu GitHub, prawdopodobnie spotka się z ostrzeżeniem o nowej wersji firmwaru. W takim przypadku zalecam kontynuowanie i korzystanie z wersji 6.12.1.

Konfiguracja projektu

Przygotowując projekt serwera UDP należy skorzystać z konfiguracji opisanej w pierwszym artykule. Jest ona identyczna i polega na wybraniu opcji RMII dla interfejsu Ethernet oraz odpowiedniego drivera i adresu IP w sekcji LWIP.

Przygotowanie biblioteki UDP Server

udp_server.h

#ifndef INC_UDP_SERVER_H_
#define INC_UDP_SERVER_H_

void udpServer_init(void);

#endif

Konstrukcja pliku nagłówkowego udp_server.h jest niezwykle prosta i zawiera tylko prototyp funkcji inicjalizującej.

udp_server.c

#include "lwip/pbuf.h"  // Provides structures and functions for packet buffers.
#include "lwip/udp.h"   // Includes UDP-specific functionality.
#include "lwip/tcp.h"   // Used for TCP/IP stack functionality (though not required for UDP operations).

#include "stdio.h"      // Standard input/output library.
#include "udp_server.h"  // Custom header file for this server (likely contains function prototypes).

// Callback function declaration.
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);

Na początku pliku udp_server.c dołączamy wbudowane w bibliotekę lwip pliki nagłówkowe związane z funkcjonownaiem interfejsu Ethernet. Dodatkowo zadeklarowana została standardowa biblioteka stdio.h oraz plik nagłówkowy utworzony wcześniej.

W tej sekcji umieszcozny jest też prototyp funkcji udp_revieve_callback, która uruchamiana będzie, gdy nawiązane zostanie połączenie z modułem STM32.

void udpServer_init(void)
{
    // UDP Control Block structure
    struct udp_pcb *upcb;
    err_t err;

    /* 1. Create a new UDP control block */
    upcb = udp_new();

    /* 2. Bind the upcb to the local port */
    ip_addr_t myIPADDR; // Define the IP address for the server.
    IP_ADDR4(&myIPADDR, 192, 168, 8, 200); // Set IP to 192.168.8.200

    // Bind the UDP control block to the IP and port 1100.
    err = udp_bind(upcb, &myIPADDR, 1100);

    /* 3. Set a receive callback for the upcb */
    if (err == ERR_OK)
    {
        // Register the callback function for receiving data.
        udp_recv(upcb, udp_receive_callback, NULL);
    }
    else
    {
        // If binding fails, remove the UDP control block.
        udp_remove(upcb);
    }
}

Funkcja inicjalizująca tworzy strukturę udp_pcb dla serwera UDP. W kodzie umieścić należy adres IP, który przypisany został do modułu STM32 oraz port, którym wymieniane będą dane. Tutaj też znajduje się odwołanie do udp_recieve_callback, czyli funkcji, której zadaniem będzie obsługa danych przychodzących.

void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
    struct pbuf *txBuf; // Packet buffer for transmitting data.

    /* Get the IP of the Client */
    char *remoteIP = ipaddr_ntoa(addr); // Convert client IP address to a readable string.

    char response[100];

    // Determine the response based on the received payload
    if (strncmp((char *)p->payload, "UDP00", 5) == 0)
    {
        sprintf(response, "Hello World\n");
    }
    else if (strncmp((char *)p->payload, "UDP01", 5) == 0)
    {
        sprintf(response, "RafalBartoszak\n");
    }
    else
    {
        sprintf(response, "ERR\n");
    }

    int len = strlen(response);

    /* Allocate a pbuf for the outgoing message from RAM */
    txBuf = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);

    /* Copy the response message into the buffer */
    pbuf_take(txBuf, response, len);

    /* Connect to the remote client */
    udp_connect(upcb, addr, port);

    /* Send a reply to the client */
    udp_send(upcb, txBuf);

    /* Disconnect the UDP connection to allow new clients */
    udp_disconnect(upcb);

    /* Free the transmit buffer */
    pbuf_free(txBuf);

    /* Free the receive buffer */
    pbuf_free(p);
}

udp_recieve_callback jest funkcją zwrotną (callbackiem), wywoływaną automatycznie przez stos lwIP w momencie, gdy serwer UDP odbierze pakiet danych od klienta. Oczekuje on przekazania kilku argumentów: arg – dodatkowy argument przekazywany przez udp_recv() – tutaj nieużywany, upcb – wskaźnik do kontrolki UDP – reprezentuje połączenie serwera, p – bufor zawierający dane odebrane od klienta, addr – adres IP klienta, który wysłał dane oraz port – numer portu, z którego klient wysłał dane.

W pierwszym kroku tworzone są odpowiednie zmienne i struktury wykorzystywane w ciele funkcji. Następnie dzięki funkcjom warunkowym możemy zareagować na otrzymane dane i przygotować zmienną response przechowującą tekst do odesłania. W kodzie znajdziecie prostą obsługę dwóch komend UDP00 – odpowiedź Hello World, UDP01 – odpowiedź RafalBartoszak. W przypadku każdych innych danych serwer odeśle kod ERR.

Ostatnim krokiem jest odpowiednia alokacja zmiennej response i uruchamianie tymczasowego połączenia, w którego czasie dane zostaną wysłane do klienta. Na końcu umieszczone zostało dodatkowe zwolnienie pamięci.

Główny kod programu

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern struct netif gnetif;
/* USER CODE END 0 */

W pliku main.c, który zawiera główny, wykonywany przez STM32 program umieszczamy deklarację struktury netif o nazwie gnetif. Jest to podstawowa struktura zdefiniowana we wbudowanej bibliotece LwIP (Lightweight IP) obsługującej stos sieciowy.

/* USER CODE BEGIN 2 */
  udpServer_init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  ethernetif_input(&gnetif);
	  sys_check_timeouts();
  }
  /* USER CODE END 3 */

W głównej pętli while umieszczamy instrukcje ethernetif_input(&gnetif); oraz sys_check_timeouts();. Ich zadaniem jest kolejno odebrać wszystkie dane z portu Ethernet i przypisać je do stosu sieciowego, innymi słowy przekazać je do struktury gnetif, do której wskaźnik umieszczony jest w argumencie. Drugie polecenie sprawdza i obsługuje wszystkie timeouty związane ze stosem LwIP. Dodatkowo powyżej pętli while znalazła się funkcja inicjalizująca serwer UDP.

Tak przygotowany program należy zapisać w pamięci mikrokontrolera, pamiętając, aby wcześniej podłączyć do płytki przewód Ethernet. Choć robiąc to później, nie stanie się nic złego.

Uruchomienie

Do sprawdzenia działania kodu wykorzystałem program Hercules, dostępny za darmo w internecie. Z jego poziomu, a dokładnie zakładki UDP możemy połączyć się z dowolnym tego typu serwerem. Wpisując odpowiednie IP i Port, takie jak ustawiliśmy w kodzie dla STM32 możemy nawiązać połączenie z mikrokontrolerem. W dolnej części okna widoczne są trzy miejsca na dane do wysłania. Jak widać wysłałem kolejno UDP00, UDP01 oraz test. Odpowiedzi mikrokontrolera widoczne są wyżej i są zgodne z opisanym kodem.

Jak widać implementacja serwera UDP w STM32 jest dość prosta. W następnym poradniku odwrócimy role i to z poziomu mikrokontrolera będziemy łączyć się z innym serwerem UDP.

Chcesz być na bieżąco?
Dołącz do newslettera

Otrzymywać będziesz powiadomienia o nowych artykułach oraz informacje o projektach, nad którymi pracuję.

Leave a Comment

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

Przewiń do góry