poniedziałek, 31 sierpnia 2020

[43] STM32F4 - HAL - LAN8720 LWIP TCP Server

 W tym poście chciałbym opisać sposób implementacji TCP Serwer na STM32F4 z użyciem bibliotek LWIP oraz układem LAN8720.

 


CubeMx:


Na samym początku należy utworzyć nowy projekt. Oprócz standardowej konfiguracji należy 

W powyższych ustawieniach należy wybrać RMII pozostałe dane zostaną ustawione automatycznie.

Kolejnym elementem jest uruchomienie biblioteki LWIP, wyłączenie DHCP oraz ustawienie statycznego adresu IP:


Następnie w zakładce Key Options zwiększam wartość MEM_NUM_TCP_SEG, która odpowiada za ilość segmentów TCP które mogą zostać umieszczone w kolejce.


Kolejnym elementem jest dopasowanie rozmiarów okna do standardowych wartości 2048 bajtów, maksymalnego rozmiaru segmentu oraz miejsca w buforze.


Po tej konfiguracji można wygenerować projekt:


Podłączenie:

  • VCC - 3V3
  • GND - GND
  • MDIO - PA2
  • MDC - PC1
  • REFCLK - PA1
  • CRS - PA7
  • RX0 - PC4
  • RX1 - PC5
  • TX_EN - PB11
  • TX0 - PB12
  • TX1 - PB13


Program:


Po wygenerowaniu projektu należy wprowadzić wartości IP do tablic wygenerowanych przez CubeMx:

  1. uint8_t IP_ADDRESS[4];
  2. uint8_t NETMASK_ADDRESS[4];
  3. uint8_t GATEWAY_ADDRESS[4];
  4. /* USER CODE BEGIN 2 */
  5.  
  6. /* USER CODE END 2 */
  7.  
  8. /**
  9.   * LwIP initialization function
  10.   */
  11. void MX_LWIP_Init(void)
  12. {
  13.   /* IP addresses initialization */
  14.  /* WPROWADZENIE DANYCH Z IP, MASKA oraz BRAMA*/
  15.   IP_ADDRESS[0] = 169;
  16.   IP_ADDRESS[1] = 254;
  17.   IP_ADDRESS[2] = 24;
  18.   IP_ADDRESS[3] = 157;
  19.   NETMASK_ADDRESS[0] = 255;
  20.   NETMASK_ADDRESS[1] = 255;
  21.   NETMASK_ADDRESS[2] = 255;
  22.   NETMASK_ADDRESS[3] = 0;
  23.   GATEWAY_ADDRESS[0] = 169;
  24.   GATEWAY_ADDRESS[1] = 254;
  25.   GATEWAY_ADDRESS[2] = 24;
  26.   GATEWAY_ADDRESS[3] = 1;
  27.   /* Initilialize the LwIP stack without RTOS */
  28.   lwip_init();
  29.  
  30.   /* IP addresses initialization without DHCP (IPv4) */
  31.   IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
  32.   IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
  33.   IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);
  34.  
  35.   /* add the network interface (IPv4/IPv6) without RTOS */
  36.   netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);
  37.  
  38.   /* Registers the default network interface */
  39.   netif_set_default(&gnetif);
  40.  
  41.   if (netif_is_link_up(&gnetif))
  42.   {
  43.     /* When the netif is fully configured this function must be called */
  44.     netif_set_up(&gnetif);
  45.   }
  46.   else
  47.   {
  48.     /* When the netif link is down this function must be called */
  49.     netif_set_down(&gnetif);
  50.   }
  51.  
  52. /* USER CODE BEGIN 3 */
  53.  
  54. /* USER CODE END 3 */
  55. }

Do głównej pętli należy dołożyć jeszcze funkcję MX_LWIP_Process(). Wykonuje ona odczytanie odebranych danych oraz dodaje ją do stosu LWIP w celu obsłużenia.

Następnie można sprawdzić czy wszystko zostało poprawnie wykonane przez użycie procedury ping z konsoli systemu.


Funkcje do obsługi TCP Serwera zostaną dodane te same co te zastosowane w innym poście dla układu STM32F7.

W celu uruchomienia funkcji TCP należy dodatkowo dołożyć funkcję tcp_echoserver_init() przed wywołaniem pętli while().


  1. void tcp_echoserver_init(void)
  2. {
  3.   /* Create new connection control block */
  4.   tcp_echoserver_pcb = tcp_new();
  5.  
  6.   if (tcp_echoserver_pcb != NULL)
  7.   {
  8.     if (tcp_bind(tcp_echoserver_pcb, IP_ADDR_ANY, 1001) == ERR_OK)
  9.     {
  10.       tcp_echoserver_pcb = tcp_listen(tcp_echoserver_pcb);
  11.       tcp_accept(tcp_echoserver_pcb, tcp_echoserver_accept);
  12.     }
  13.     else /* tcp_bind return with ERR_USE, port is already used */
  14.     {
  15.       /* Set selected element free, clear all settings for it */
  16.       memp_free(MEMP_TCP_PCB, tcp_echoserver_pcb);
  17.     }
  18.   }
  19. }


Teraz działanie programu można przetestować za pomocą programu Hercules:




Kolor fioletowy odpowiada za dane przesłane od klienta do serwera. Dane w kolorze czarnym odpowiadają danym odesłanym do klienta.

DHCP


W celu uruchomienia DHCP należy w programie CubeMx zaznaczyć odpowiednią opcję:


Po uruchomieniu opcji należy ponownie wygenerować projekt. 

Uruchomienie DHCP przez CubeMx powoduje ustawienie odpowiedniej flagi w bibliotekach LWIP oraz modyfikazję funkcji inizjalizującej:

  1. /*----- Value in opt.h for LWIP_DHCP: 0 -----*/
  2. #define LWIP_DHCP 1
  3. /*----- Value in opt.h for NO_SYS: 0 -----*/

  1. void MX_LWIP_Init(void)
  2. {
  3.   /* Initilialize the LwIP stack without RTOS */
  4.   lwip_init();
  5.  
  6.   /* IP addresses initialization with DHCP (IPv4) */
  7.   ipaddr.addr = 0;
  8.   netmask.addr = 0;
  9.   gw.addr = 0;
  10.  
  11.   /* add the network interface (IPv4/IPv6) without RTOS */
  12.   netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);
  13.  
  14.   /* Registers the default network interface */
  15.   netif_set_default(&gnetif);
  16.  
  17.   if (netif_is_link_up(&gnetif))
  18.   {
  19.     /* When the netif is fully configured this function must be called */
  20.     netif_set_up(&gnetif);
  21.   }
  22.   else
  23.   {
  24.     /* When the netif link is down this function must be called */
  25.     netif_set_down(&gnetif);
  26.   }
  27.  
  28.   /* Start DHCP negotiation for a network interface (IPv4) */
  29.   dhcp_start(&gnetif);
  30.  
  31. /* USER CODE BEGIN 3 */
  32.  
  33. /* USER CODE END 3 */
  34. }

Teraz uruchomienie interfejsu następuje z użyciem DHCP więc jako argumenty podawana jest wartość 0 dla IP, maski oraz bramy.

Obie funkcje można oczywiście połączyć w jednym projekcie tak aby możliwe było wybieranie odpowiedniej w danym czasie konfiguracji:

  1. uint8_t enable_DHCP = 0;
  2. void MX_LWIP_Init(void)
  3. {
  4.   /* Initilialize the LwIP stack without RTOS */
  5.   /* IP addresses initialization with DHCP (IPv4) */
  6.   if(enable_DHCP == 1){
  7.       ipaddr.addr = 0;
  8.       netmask.addr = 0;
  9.       gw.addr = 0;
  10.   }
  11.   else {
  12.       IP_ADDRESS[0] = 169;
  13.       IP_ADDRESS[1] = 254;
  14.       IP_ADDRESS[2] = 24;
  15.       IP_ADDRESS[3] = 157;
  16.       NETMASK_ADDRESS[0] = 255;
  17.       NETMASK_ADDRESS[1] = 255;
  18.       NETMASK_ADDRESS[2] = 255;
  19.       NETMASK_ADDRESS[3] = 0;
  20.       GATEWAY_ADDRESS[0] = 169;
  21.       GATEWAY_ADDRESS[1] = 254;
  22.       GATEWAY_ADDRESS[2] = 24;
  23.       GATEWAY_ADDRESS[3] = 1;
  24.       /* IP addresses initialization without DHCP (IPv4) */
  25.       IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
  26.       IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
  27.       IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);
  28.   }
  29.  
  30.   lwip_init();
  31.  
  32.   /* add the network interface (IPv4/IPv6) without RTOS */
  33.   netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);
  34.  
  35.   /* Registers the default network interface */
  36.   netif_set_default(&gnetif);
  37.  
  38.   if (netif_is_link_up(&gnetif))
  39.   {
  40.     /* When the netif is fully configured this function must be called */
  41.     netif_set_up(&gnetif);
  42.   }
  43.   else
  44.   {
  45.     /* When the netif link is down this function must be called */
  46.     netif_set_down(&gnetif);
  47.   }
  48.  
  49.   if(enable_DHCP == 1){
  50.       /* Start DHCP negotiation for a network interface (IPv4) */
  51.       dhcp_start(&gnetif);
  52.   }
  53.  
  54. /* USER CODE BEGIN 3 */
  55.  
  56. /* USER CODE END 3 */
  57. }

Wartość zmiennej enable_DHCP pozwala na wybór sposobu uruchomienia ETH. 

Należy pamiętać, że w przypadku zastosowaniu takiego lub podobnego rozwiązania po ponownym wygenerowaniu projektu przez CubeMx dane wprowadzone do funkcji mogą zostać utracone.

Podczas korzystania z DHCP urządzenie możemy zlokalizować np. w trybie DEBUG gdzie będzie można odczytać przydzielony adres IP, wypisanie przydzielonego adresu np. wykorzystując UART.
Adres IP zapisany jest w strukturze statycznej w pliku lwip.c gnetif.


Dane należy jeszcze przetworzyć na format pozwalający na łatwiejszy odczyt.Dodatkowo należy pamietać, żeby odczytać dane nie zaraz po uruchomieniu DHCP ponieważ dane nie będą jeszcze ustawione. Należy to wzrobić np. w pętli while gdy w strukturze pojawi się już poszukiwana informacja.

  1. uint8_t checkIp(void){
  2.     uint8_t ipArray[4] = {0x00, 0x00, 0x00, 0x00};
  3.  
  4.     if(gnetif.ip_addr.addr > 0)
  5.     {
  6.         ipArray[3] = (uint8_t)(gnetif.ip_addr.addr  >> 24);
  7.         ipArray[2] = (uint8_t)(gnetif.ip_addr.addr  >> 16);
  8.         ipArray[1] = (uint8_t)(gnetif.ip_addr.addr  >> 8);
  9.         ipArray[0] = (uint8_t)(gnetif.ip_addr.addr );
  10.     }
  11.  
  12.     if(ipArray[0] != 0 || ipArray[1] != 0 || ipArray[2] != 0 || ipArray[3] != 0) {
  13.         return 1;
  14.     }
  15.     return 0;
  16. }

Innym sposobem jest użycie programu wyszukującego urządzenie w sieci. W tym przypadku znając adres MAC wygenerowany przez CubeMX można odszukać adres IP np. za pomocą programu Advanced IP Scanner:



Biblioteka opisana w tym poście można pobrać z dysku Google pod tym linkiem.

Dokumentacja:

LAN8720 Datasheet