środa, 14 grudnia 2022

STM32H753ZI - UDP Client, Server

W tym poście chciałbym opisać sposób wykonania serwera i klienta UDP na układzie STM32H753ZI.

[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]

Program Client:


Poniżej podłączenie klienta dla serwera:

  1. void udp_client_connect(void)
  2. {
  3.   ip_addr_t DestIPaddr;
  4.   err_t err;
  5.  
  6.   upcb = udp_new();
  7.  
  8.   if (upcb!=NULL)
  9.   {
  10.     ip_addr_t myIPaddr;
  11.     IP_ADDR4(&myIPaddr, IP_CLIENT_ADDR0, IP_CLIENT_ADDR1, IP_CLIENT_ADDR2, IP_CLIENT_ADDR3);
  12.     udp_bind(upcb, &myIPaddr, MY_PORT);
  13.  
  14.     IP4_ADDR( &DestIPaddr, DEST_IP_ADDR0, DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3 );
  15.  
  16.     err= udp_connect(upcb, &DestIPaddr, UDP_SERVER_PORT);
  17.  
  18.     if (err == ERR_OK)
  19.     {
  20.       udp_recv(upcb, udp_receive_callback, NULL);
  21.     }
  22.   }
  23. }

Na samym początku tworzone jest nowy identyfikator połączenia. Do niego musimy wprowadzić dane dotyczące adresu IP wykorzystywanego na naszym mikrokontrolerze oraz port na którym będą otrzymywane wiadomości od serwera. Następnie wykonywane jest połączenie z serwerem, do którego należy wprowadzić jego adres IP oraz port na który będą wysyłane dane od mikrokontrolera do serwera. Gdy nie wystąpią żadne błędy to przypisujemy funkcje do wywołanie zwrotnego (callback), która będzie się zajmowała obrabianiem otrzymanych wiadomości.

Funkcja przesyłająca dane:

  1. void udp_client_send_string(char *dataToSend)
  2. {
  3.     struct pbuf *p;
  4.  
  5.     sprintf((char*)data, dataToSend);
  6.  
  7.     p = pbuf_alloc(PBUF_TRANSPORT,strlen((char*)data), PBUF_RAM);
  8.  
  9.     if (p != NULL)
  10.     {
  11.         pbuf_take(p, (char*)data, strlen((char*)data));
  12.         udp_send(upcb, p);
  13.  
  14.         pbuf_free(p);
  15.     }
  16. }

Callback odbierający dane i przesyłający otrzymaną wiadomość:

  1. void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
  2. {
  3.   message_count++;
  4.   udp_client_send_string(p->payload);
  5.   pbuf_free(p);
  6. }

W przypadku gdy chcemy coś zrobić z tą wiadomością innego niż odesłać z powrotem, to warto ją przekopiować do bufora i np. ustawić flagę o odebraniu wiadomości:

  1. strncpy (buffer, (char *)p->payload, p->len);

Dla cyklicznego przesyłania danych, można wykorzystać licznik w przerwaniu np tak:

  1. void SysTick_Handler(void)
  2. {
  3.   /* USER CODE BEGIN SysTick_IRQn 0 */
  4.  
  5.   /* USER CODE END SysTick_IRQn 0 */
  6.   HAL_IncTick();
  7.   if(timerValue < 1000)
  8.   {
  9.       timerValue++;
  10.   }
  11.   else if(timerValue >= 1000)
  12.   {
  13.       timerValue = 0;
  14.       udp_client_send();
  15.   }
  16.  
  17.   /* USER CODE BEGIN SysTick_IRQn 1 */
  18.   /* USER CODE END SysTick_IRQn 1 */
  19. }

lub tak:

  1. void SysTick_Handler(void)
  2. {
  3.   /* USER CODE BEGIN SysTick_IRQn 0 */
  4.  
  5.   /* USER CODE END SysTick_IRQn 0 */
  6.   HAL_IncTick();
  7.   if(timerValue < 1000)
  8.   {
  9.       timerValue++;
  10.   }
  11.   /* USER CODE BEGIN SysTick_IRQn 1 */
  12.   /* USER CODE END SysTick_IRQn 1 */
  13. }
  14.  
  15. int main(){
  16. //...
  17. //...
  18.     while(1)
  19.     {
  20.         if(timerValue >= 1000)
  21.         {
  22.             timerValue = 0;
  23.             udp_client_send();
  24.         }
  25.     }
  26. return 0;
  27. }

W drugim przypadku licznik jest aktualizowany w przerwaniu, po przekroczeniu czasu w pętli głównej następuje przesłanie wiadomości do serwera.

Program Serwer:


Inicjalizacja serwera wygląda następująco:

  1. void udp_server_init(void)
  2. {
  3.    err_t err;
  4.    upcb = udp_new();
  5.    
  6.    if (NULL != upcb)
  7.    {
  8.       err = udp_bind(upcb, IP_ADDR_ANY, SERVER_UDP_SERVER_PORT);
  9.  
  10.       if(ERR_OK == err)
  11.       {
  12.         udp_recv(upcb, udp_server_receive_callback, NULL);
  13.       }
  14.       else
  15.       {
  16.         udp_remove(upcb);
  17.       }
  18.    }
  19. }

Na samym początku tworzony jest nowy blok UDP. Następnie przypisujemy do niego połączenia z wszystkimi adresami IP . Jeśli wszystko przeszło poprawnie to zostanie przypisany callback, który będzie odbierał dane i (w tym przypadku) wysyłał odebrane dane ponownie do serwera. 

Przesłanie danych od serwera do klienta:

  1. void udp_server_send_message(void)
  2. {
  3.     struct pbuf *p;
  4.  
  5.     sprintf((char*)data, "test");
  6.  
  7.     /* allocate pbuf from pool*/
  8.     p = pbuf_alloc(PBUF_TRANSPORT,strlen((char*)data), PBUF_RAM);
  9.  
  10.     if (p != NULL)
  11.     {
  12.       ip_addr_t myIPADDR;
  13.       IP_ADDR4(&myIPADDR, UDP_CLIENT_ADDR0, UDP_CLIENT_ADDR1, UDP_CLIENT_ADDR2, UDP_CLIENT_ADDR3);
  14.       pbuf_take(p, (char*)data, strlen((char*)data));
  15.       udp_sendto(upcb, p, &myIPADDR, SERVER_UDP_CLIENT_PORT);
  16.       pbuf_free(p);
  17.     }
  18. }

Echo callback:

  1. void udp_server_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
  2. {
  3.   struct pbuf *p_tx;
  4.  
  5.   /* allocate pbuf from RAM*/
  6.   p_tx = pbuf_alloc(PBUF_TRANSPORT,p->len, PBUF_RAM);
  7.  
  8.   if(p_tx != NULL)
  9.   {
  10.     pbuf_take(p_tx, (char*)p->payload, p->len);
  11.     udp_connect(upcb, addr, SERVER_UDP_CLIENT_PORT);
  12.     udp_send(upcb, p_tx);
  13.     udp_disconnect(upcb);
  14.     pbuf_free(p_tx);
  15.     pbuf_free(p);
  16.   }
  17. }

Rozwiązania opisane powyżej testowałem w programie Hercules.



Pliki do projektu można pobrać z dysku Google pod tym linkiem.

Dokumenty: