środa, 9 września 2020

LPC1769 - LAN8720 LWIP TcpSerwer oraz HTTP

W tym poście opiszę szybką implementację serwera TCP oraz HTTP na układzie LPC1769 oraz bibliotekach LWIP. W oparciu o przykłady udostępnione przez producenta.

Image of LPCXpresso1769/CD


Do testów wykorzystałem płytę udostępnioną przez producenta jako demo board  która zawiera już zamontowany układ LAN8720 (https://www.embeddedartists.com/products/lpcxpresso1769/). Wymagane jest niestety dołożenie do niej gniazdka ETH. Można to wykonać bezpośrednio na kablach lub wykorzystać gotowy moduł np. taki https://www.sparkfun.com/products/13021.
Dodatkowe testy wykonałem na własnej płytce zawierającej wspomniany mikrokontroler wraz z układem LAN8720.

Program:


Do stworzenia programu wykorzystałem przykłady udostępnione przez firmę NXP.

Jedną z rzeczy o jakiej należy pamiętać jest rozmieszczenie danych w pamięci:


W celu poprawnego działania przykładu należy dodatkowo utrzymać uruchomione biblioteki do układu:

CMSISv2p00_LCP17xx
lpc_board_nxp_lpcepresso_1769
lpc_chip_176x_6x

Dodatkowo w projekcie głównym znajdują się biblioteki LWIP.

Opis połączeń układu jest zapisany w pliku board_sysinit.c.

Pętla główna programu wygląda następująco:

  1. int main(void)
  2. {
  3.     uint32_t physts;
  4.     ip_addr_t ipaddr, netmask, gw;
  5.     static int prt_ip = 0;
  6.  
  7.     prvSetupHardware();
  8.  
  9.     DEBUGSTR("Hardware Init...\r\n");
  10.  
  11.     lwip_init();
  12.  
  13.     DEBUGSTR("Starting LWIP TCP echo server...\r\n");
  14.  
  15.     /* Static IP assignment */
  16. #if LWIP_DHCP
  17.     IP4_ADDR(&gw, 0, 0, 0, 0);
  18.     IP4_ADDR(&ipaddr, 0, 0, 0, 0);
  19.     IP4_ADDR(&netmask, 0, 0, 0, 0);
  20. #else
  21.     IP4_ADDR(&ipaddr, 169, 254, 24, 157);
  22.     IP4_ADDR(&netmask, 255, 255, 255, 0);
  23.     IP4_ADDR(&gw, 169, 254, 24, 1);
  24. #endif
  25.     DEBUGSTR("Netif add....\r\n");
  26.  
  27.     /* Add netif interface for lpc17xx_8x */
  28.     netif_add(&lpc_netif, &ipaddr, &netmask, &gw, NULL, lpc_enetif_init, ethernet_input);
  29.     netif_set_default(&lpc_netif);
  30.     netif_set_up(&lpc_netif);
  31.  
  32. #if LWIP_DHCP
  33.     err_t response = dhcp_start(&lpc_netif);
  34.     if(response == ERR_OK) {
  35.         DEBUGSTR("DHCP start OK....\r\n");
  36.     }
  37. #endif
  38.  
  39.     DEBUGSTR("Before Echo Init....\r\n");
  40.     /* Initialize and start application */
  41.     echo_init();
  42.     httpd_init();
  43.     /* This could be done in the sysTick ISR, but may stay in IRQ context
  44.        too long, so do this stuff with a background loop. */
  45.     while (1) {
  46.         /* Handle packets as part of this loop, not in the IRQ handler */
  47.         lpc_enetif_input(&lpc_netif);
  48.  
  49.         /* lpc_rx_queue will re-qeueu receive buffers. This normally occurs
  50.            automatically, but in systems were memory is constrained, pbufs
  51.            may not always be able to get allocated, so this function can be
  52.            optionally enabled to re-queue receive buffers. */
  53. #if 0
  54.         while (lpc_rx_queue(&lpc_netif)) {}
  55. #endif
  56.  
  57.         /* Free TX buffers that are done sending */
  58.         lpc_tx_reclaim(&lpc_netif);
  59.  
  60.         /* LWIP timers - ARP, DHCP, TCP, etc. */
  61.         sys_check_timeouts();
  62.  
  63.         /* Call the PHY status update state machine once in a while
  64.            to keep the link status up-to-date */
  65.         physts = lpcPHYStsPoll();
  66.  
  67.         /* Only check for connection state when the PHY status has changed */
  68.         if (physts & PHY_LINK_CHANGED)
  69.         {
  70.             if (physts & PHY_LINK_CONNECTED)
  71.             {
  72.                 //Board_LED_Set(0, true);
  73.                 prt_ip = 0;
  74.  
  75.                 /* Set interface speed and duplex */
  76.                 if (physts & PHY_LINK_SPEED100) {
  77.                     Chip_ENET_Set100Mbps(LPC_ETHERNET);
  78.                     NETIF_INIT_SNMP(&lpc_netif, snmp_ifType_ethernet_csmacd, 100000000);
  79.                     DEBUGOUT("Chip_ENET_Set100Mbps\r\n");
  80.                 }
  81.                 else {
  82.                     Chip_ENET_Set10Mbps(LPC_ETHERNET);
  83.                     NETIF_INIT_SNMP(&lpc_netif, snmp_ifType_ethernet_csmacd, 10000000);
  84.                     DEBUGOUT("Chip_ENET_Set10Mbps\r\n");
  85.                 }
  86.                 if (physts & PHY_LINK_FULLDUPLX) {
  87.                     Chip_ENET_SetFullDuplex(LPC_ETHERNET);
  88.                     DEBUGOUT("Chip_ENET_SetFullDuplex\r\n");
  89.                 }
  90.                 else {
  91.                     Chip_ENET_SetHalfDuplex(LPC_ETHERNET);
  92.                     DEBUGOUT("Chip_ENET_SetHalfDuplex\r\n");
  93.                 }
  94.  
  95.                 netif_set_link_up(&lpc_netif);
  96.                 DEBUGOUT("netif_set_link_up\r\n");
  97.             }
  98.             else {
  99.                 //Board_LED_Set(0, false);
  100.                 netif_set_link_down(&lpc_netif);
  101.                 DEBUGOUT("netif_set_link_down\r\n");
  102.             }
  103.  
  104.             DEBUGOUT("Link connect status: %d\r\n", ((physts & PHY_LINK_CONNECTED) != 0));
  105.         }
  106.  
  107.         /* Print IP address info */
  108.         if (!prt_ip)
  109.         {
  110.             if (lpc_netif.ip_addr.addr)
  111.             {
  112.                 static char tmp_buff[16];
  113.                 printf("\r\nIP_ADDR    : %s\r\n", ipaddr_ntoa_r((const ip_addr_t *) &lpc_netif.ip_addr, tmp_buff, 16));
  114.                 printf("NET_MASK   : %s\r\n", ipaddr_ntoa_r((const ip_addr_t *) &lpc_netif.netmask, tmp_buff, 16));
  115.                 printf("GATEWAY_IP : %s\r\n", ipaddr_ntoa_r((const ip_addr_t *) &lpc_netif.gw, tmp_buff, 16));
  116.                 prt_ip = 1;
  117.             }
  118.         }
  119.     }
  120.  
  121.     /* Never returns, for warning only */
  122.     return 0;
  123. }

Ustawienie DHCP oraz uruchomienie wiadomości DEBUG może zostać włączone w pliku lwipopts.h. W przypadku uruchomionej opcji DEBUG należy pamiętać, że zwiększą one znacząco czas na odpowiedź od urządzenia. Zwłaszcza jak będą obsługiwały wszystkie wiadomości. 

Przesyłanie danych od klienta do serwera wykonywane jest w funkcji echo_send:

  1. void echo_send(struct tcp_pcb *tpcb, struct echo_state *es) {
  2.   struct pbuf *ptr;
  3.   err_t wr_err = ERR_OK;
  4.  
  5.   while ((wr_err == ERR_OK) && (es->p != NULL) && (es->p->len <= tcp_sndbuf(tpcb)))
  6.   {
  7.       ptr = es->p;
  8.  
  9.       wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);
  10.  
  11.       if (wr_err == ERR_OK)
  12.       {
  13.           u16_t plen;
  14.           u8_t freed;
  15.  
  16.           plen = ptr->len;
  17.  
  18.           /* continue with next pbuf in chain (if any) */
  19.           es->p = ptr->next;
  20.           if(es->p != NULL)
  21.           {
  22.               /* new reference! */
  23.               pbuf_ref(es->p);
  24.           }
  25.           /* chop first pbuf from chain */
  26.           do
  27.           {
  28.               /* try hard to free pbuf */
  29.               freed = pbuf_free(ptr);
  30.           }
  31.           while(freed == 0);
  32.           /* we can read more data now */
  33.           tcp_recved(tpcb, plen);
  34.       }
  35.       else if(wr_err == ERR_MEM)
  36.       {
  37.           /* we are low on memory, try later / harder, defer to poll */
  38.           es->p = ptr;
  39.       }
  40.       else
  41.       { }
  42.    }
  43. }

Dane zapisane są w wskaźniku payload. Ich długość określona jest przez zmienną len. W celu skopiowania otrzymanych danych do bufora najłatwiej posłużyć się funkcją memcpy:

  1. memcpy(odebraneDane, ptr->payload, ptr->len);

Po uruchomieniu przykładu najlepiej przetestować poprawność podłączenia urządzenia przez zastosowanie komendy ping:


Jeśli podczas uruchamiania tego przykładu nie udało się uzyskać odpowiedzi na komendę ping to najprawdopodobniej występują problemy sprzętowe np. niepoprawne podłączenie układu lub niedopasowanie linii PHY. Drugi przypadek można rozwiązać przez zastosowanie rezystorów szeregowych i/lub dodatkowych kondensatorów filtrujących (15-22p na liniach CTS, RXD1 czy RXD0).

Do testów połączenia z serwerem można wykorzystać program hercules.exe


Strona internetowa jest zapisane w pliku lwip_fs.c:

  1. const static char http_index_html[] =
  2.     "<!doctype html>"
  3.     "<html>"
  4.     "<head>"
  5.         "<meta charset=\"UTF-8\" />"
  6.         "<title>"
  7.             "Test"
  8.         "</title>"
  9.         "</head>"
  10.     "<body style=\"background-color: yellow; color: black\">"
  11.         "<h1>"
  12.             "Działa"
  13.         "</h1>"
  14.         "<p>strona</p>"
  15.         "</body>"
  16.     "</html>";

Widok uruchomionej strony wygląda następująco:


Projekt wykorzystywany w środowisku MXUExpresso IDE można znaleźć na dysku Google pod tym linkiem.