poniedziałek, 20 listopada 2017

[8.3] STM32F7 - UDP Serwer

W tym przedstawię sposób konfiguracji STM32F7 oraz bibliotek LWIP tak aby uzyskać serwer UDP.

[Źródło: http://www.st.com/en/evaluation-tools/32f746gdiscovery.html]

UDP:

UDP czyli User Datagram Protocol. Jest on stosowany w warstwie transportowej. Jako protokół bezpołączeniowy nie nawiązuje on połączenia ani nie śledzi sesji jak w przypadku TCP. Za to ruch generowany w sieci jest znacznie mniejszy niż w TCP. Dzięki temu, że proces transmisji niepotrzebnych danych jest zminimalizowany, osiąga się dużo szybsze prędkości niż w TCP. 

W związku z tym że usługa nie gwarantuje poprawności danych należałoby zastosować jakieś mechanizmy zapobiegania błędom. Tutaj z pomocą może przyjść odpowiednio zdefiniowany sposób komunikacji tzn. określone ramki wiadomości. Dodatkowo dobrym pomysłem jest zastosowanie CRC.

Program:


Podobnie jak w części dotyczącej Http należy do projektu dołączyć bibliotekę LWIP oraz ją uruchomić wraz z całą warstwą sprzętową oraz programową.

Poniżej funkcja odpowiedzialna za ustawienie podstawowej konfiguracji:

  1. uint8_t serverUDPInit(void)
  2. {
  3.   /* Set operation status for default */
  4.   uint8_t status = 0;
  5.   /* check if server isn't initialized yet */
  6.   if(UDP_CONN_INF.status == UDP_ST_NOINIT)
  7.   {
  8.     /* Set link status */
  9.     EthLinkStatus = 0;
  10.     /* Enable Ethernet and LWIP Stack */
  11.     MX_LWIP_Init();
  12.     /* Init NVIC */
  13.     HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  14.     HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  15.     /* Set notification for user */
  16.     udpUserNotification(&gnetif);
  17.     /* Set status for initialization */
  18.     UDP_CONN_INF.status = UDP_ST_NOCONNECT;
  19.   }
  20.   else if(UDP_CONN_INF.status == UDP_ST_NOCONNECT)
  21.   {
  22.       /* udp connection already initialized */
  23.       status = 1;
  24.   }
  25.   return status;
  26. }

Pierwsza z przedstawionych funkcji ma za zadanie przygotowanie połączenia. Oznacza to uruchomienie biblioteki LWIP, ustawienie przerwań, wywołanie informacji dla użytkownika oraz zmianę informacji o wykonanym połączeniu.

Ustanowienie połączenia:

  1. UDP_SERVER_CONNECT_t serverUDPStart(void)
  2. {
  3.   UDP_SERVER_CONNECT_t status = UDP_SER_CON_STA_ERROR_2;
  4.   err_t err;
  5.   if(UDP_CONN_INF.status == UDP_ST_NOINIT)
  6.   {
  7.       return UDP_SER_CON_STA_INIT_ERR;
  8.   }
  9.   if(UDP_CONN_INF.status == UDP_ST_RUNNING)
  10.   {
  11.       return UDP_SER_CON_STA_RUNNING;
  12.   }
  13.   if(EthLinkStatus > 0)
  14.   {
  15.       return UDP_SER_CON_STA_NO_LINK;
  16.   }
  17.   /* Create new UDP pcb */
  18.   upcb_server = udp_new();
  19.   /* return pointer */
  20.   if (upcb_server == NULL)
  21.   {
  22.       return UDP_SER_CON_STA_ERROR_1;
  23.   }
  24. #ifdef HOST_IP_0
  25.   IP4_ADDR(&HostIPaddr, HOST_IP_0, HOST_IP_1, HOST_IP_2, HOST_IP_3 );
  26. #endif
  27.   IP4_ADDR(&OwnIPaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3 );
  28.   /* Bind PCB to local IP address and port*/
  29.   err = udp_bind(upcb_server, &OwnIPaddr, SERVER_UDP_PORT);
  30.   if (err != ERR_OK)
  31.   {
  32.       return UDP_SER_CON_STA_ERROR_2;
  33.   }
  34.   /* Set callback for incoming UDP data */
  35.   udp_recv(upcb_server, udpReceiveCallback, NULL);
  36.   /* change status */
  37.   status = UDP_SER_CON_STA_CONNECT_OK;
  38.   UDP_CONN_INF.status = UDP_ST_RUNNING;
  39.   return status;
  40. }

Ta funkcja odpowiada za rozpoczęcie połączenia po UDP. Najpierw sprawdzany jest status. Jeśli połączenie nie zostało jeszcze wykonane to zostanie stworzony nowy obiekt pcb UDP, zawierający informacje o połączeniu. Następnie sprawdzana jest poprawność utworzonych danych. Gdy jest wporządku to następuje wpisanie adresów IP do dwóch struktur. Jedna przechowuje informacje z danymi dla klienta (czyli np. PC) a druga informacje o serwerze (czyli STM). Dalej przypisany jest adres IP oraz Port do struktury upcb_server. Funkcja udp_recv przypisuje funkcje udpReceiveCallback do odbierania danych od UDP w obsłudze zdarzenia.

Rozłączenie serwera:

  1. void serverUDPDisconnect(void)
  2. {
  3.   if(UDP_CONN_INF.status==UDP_ST_RUNNING)
  4.   {
  5.     udp_remove(upcb_server);
  6.     UDP_CONN_INF.status=UDP_ST_NOCONNECT;
  7.   }
  8. }

W tym przypadku sprawa jest prosta jeśli serwer jest połączony to następuje jego rozłączenie przez usunięcie struktury z informacją o połączeniu. Dzięki temu można dynamicznie wykorzystywać kilku klientów.

Wysłanie wiadomości:

  1. uint8_t serverUDPSendString(uint8_t *ptr)
  2. {
  3.   uint8_t status = ERROR;
  4.   struct pbuf *p;
  5.   err_t err;
  6.   /* if server is enable */
  7.   if(UDP_CONN_INF.status == UDP_ST_RUNNING)
  8.   {
  9.     /* allocate buffer */
  10.     p = pbuf_alloc(PBUF_TRANSPORT, strlen((uint8_t*)ptr), PBUF_POOL);
  11.     /* if buffer is allocated */
  12.     if (== NULL)
  13.     {
  14.         status = 0;
  15.         return status;
  16.     }
  17.     /* copy data */
  18.     pbuf_take(p, (char*)ptr, strlen((char*)ptr));
  19.     /* Send data to a specified address using UDP */
  20.     err = udp_sendto(upcb_server, p, &HostIPaddr, CLIENT_USP_PORT);
  21.     /* clear sending buffer */
  22.     for(uint8_t loop = 0; loop < UDP_RECEIVE_MSG_SIZE; loop++)
  23.     {
  24.         UDP_CONN_INF.recBufer[loop] = 0;
  25.     }
  26.     /* free buffer */
  27.     pbuf_free(p);
  28.     if(err == ERR_OK)
  29.     {
  30.       status = SUCCESS;
  31.     }
  32.     else
  33.     {
  34.       /* remove a UDP pcb */
  35.       udp_remove(upcb_server);
  36.       UDP_CONN_INF.status=UDP_ST_NOCONNECT;
  37.     }
  38.   }
  39.   return status;
  40. }

Najpierw przeprowadzane są operacje na buforze.  Po ich wykonaniu wysyłany jest podany bufor za pomocą funkcji udp_sendto.

Sprawdzanie odpowiedzi od serwera:

  1. UDP_RECEIVE_t serverUDPWorks(uint8_t *ptr)
  2. {
  3.   UDP_RECEIVE_t status = UDP_REVEICE_BUF_EMPTY;
  4.   uint32_t i;
  5.   uint8_t value;
  6.   if(UDP_CONN_INF.status != UDP_ST_NOINIT)
  7.   {
  8.       /* Use when packet is ready to read from the interface */
  9.       ethernetif_input(&gnetif);
  10.       /* Handle timeout */
  11.       sys_check_timeouts();
  12.     /* check linki status */
  13.     if(EthLinkStatus > 0)
  14.     {
  15.       if(UDP_CONN_INF.status == UDP_ST_RUNNING)
  16.       {
  17.           /* remove a UDP pcb */
  18.           udp_remove(upcb_server);
  19.           UDP_CONN_INF.status = UDP_ST_NOCONNECT;
  20.       }
  21.     }
  22.   }
  23.   if(UDP_CONN_INF.status != UDP_ST_RUNNING)
  24.   {
  25.       return UDP_SERVER_BUF_OFFLINE;
  26.   }
  27.   if(UDP_CONN_INF.recLength > 0)
  28.   {
  29.       status = UDP_RECEIVE_BUF_READY;
  30.       /* copy buffer */
  31.       i=0;
  32.       value = UDP_CONN_INF.recBufer[i];
  33.       while((value != 0) && (< UDP_RECEIVE_MSG_SIZE-1))
  34.       {
  35.           value=UDP_CONN_INF.recBufer[i];
  36.           ptr[i]=value;
  37.           i++;
  38.       }
  39.       /* clear last character */
  40.       ptr[i]=0x00;
  41.       UDP_CONN_INF.recLength=0;
  42.   }
  43.   return status;
  44. }

Ta funkcja wywoływana jest w pętli. Sprawdza ona czy zostały odebrane jakieś dane. Dodatkowo obsługuje time outy od usługi sieciowej.

Dane do serwera przychodzą w przerwaniu.Jego obsługa wygląda następująco:

  1. static void udpReceiveCallback(void *arg, struct udp_pcb *upcb, struct pbuf *p, struct ip_addr *addr, u16_t port)
  2. {
  3.   uint16_t i;
  4.   uint8_t value;
  5.   uint8_t *ptr;
  6.   ptr=(uint8_t*)p->payload;
  7.   i=0;
  8.   do
  9.   {
  10.     value = *ptr;
  11.     UDP_CONN_INF.recBufer[i] = value;
  12.     ptr++;
  13.     i++;
  14.   }while((value != 0) && (< UDP_RECEIVE_MSG_SIZE-1));
  15.   UDP_CONN_INF.recBufer[i]=0x00;
  16.   UDP_CONN_INF.recLength = i;
  17.   pbuf_free(p);
  18. }

W tej funkcji dane kopiowane są z bufora z danymi odebranymi do bufora głównego.

Aby określić maksymalną wielkość znaków obsługiwanych przez lwip dla transmisji należy zmienić parametry w pliku pbuf.h:

  1. /** Currently, the pbuf_custom code is only needed for one specific configuration
  2.  * of IP_FRAG */
  3. #define LWIP_SUPPORT_CUSTOM_PBUF (IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF)
  4. #define PBUF_TRANSPORT_HLEN 150
  5. #define PBUF_IP_HLEN        150

To ustawienie pozwala na przygotowanie odpowiedniej ilości miejsca na przychodzącą ramkę. W przypadku przesłania za dużej ramki, dane będą zapisywane poza zakresem.

Pętla główna odbierająca dane z przesyłająca je ponownie po UDP oraz po UARCie wygląda następująco

  1.   uint8_t           buffer[UDP_RECEIVE_MSG_SIZE] = {0};
  2.   UDP_RECEIVE_t     rx_check = UDP_REVEICE_BUF_EMPTY;
  3.   uint32_t          aliveNotify = 0;
  4.   /* ...
  5.      ...
  6.      ...
  7.   */
  8.   EnableUartUsart();
  9.   Usart_Uart_SendString(USART1,"Enable USART", LF_CR);
  10.   //------------------------------
  11.   /* Enable UDP  */
  12.   serverUDPInit();
  13.   serverUDPStart();
  14.   Usart_Uart_SendString(USART1,"Server Connect", LF_CR);
  15.   /* USER CODE END 2 */
  16.   /* Infinite loop */
  17.   /* USER CODE BEGIN WHILE */
  18.   while (1)
  19.   {
  20.   /* USER CODE END WHILE */
  21.   /* USER CODE BEGIN 3 */
  22.       for(uint8_t i=0; i<UDP_RECEIVE_MSG_SIZE; i++)
  23.       {
  24.           buffer[i] = 0;
  25.       }
  26.       rx_check = serverUDPWorks(buffer);
  27.       if(rx_check == UDP_RECEIVE_BUF_READY)
  28.       {
  29.           serverUDPSendString(buffer);
  30.           Usart_Uart_SendString(USART1, (uint8_t *)buffer, LF_CR);
  31.           aliveNotify=0;
  32.       }
  33.       if(aliveNotify>1000000)
  34.       {
  35.           aliveNotify=0;
  36.           /* send default msg */
  37.           serverUDPSendString("UDP_alive_notify\r\n");
  38.           Usart_Uart_SendString(USART1, "UDP_alive_notify\r\n", LF_CR);
  39.       }
  40.       else
  41.       {
  42.           aliveNotify++;
  43.       }
  44.   }

Bibliotekę można pobrać pod tym linkiem. W folderze STM32 a następnie STM32F7.