środa, 9 sierpnia 2017

[8.1] STM32F7 - Ethernet, bez RTOS

Ten przykład chciałbym poświęcić na konfigurację STM32, w wersji bez RTOS, jako serwer ethernet.

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

Programowanie[link]:


Tutaj chciałbym opisać dokładniej sposoby inicjalizacji biblioteki LWIP w wersji RAW, czyli bez systemu. Program przygotowałem w oparciu o pliki udostępnione przez ST. Które opierają się na przykładzie przygotowanym przez autora LWIP.

Na samym początku należy uruchomić bibliotekę wygenerowaną przez Cube lub skopiowaną w odpowiedniej wersji. Wykonuje się to poleceniem:

MX_LWIP_Init();

W pętli głównej lub w przerwaniach od timera należy wywoływać funkcje resetującą wszystkie zmienne zawierające informacje o czasie trwania operacji.

sys_check_timeouts();

Proces stawiania połączenia należy rozpocząć od wywołania funkcji tcp_new aby stworzyło nowy blok z danymi dla TCP.

Aby stworzyć nowe połączenie należy wywołać funkcje:

  1. struct tcp_pcb * tcp_new(void)
  2. tcp new();

Domyślnie będzie ona zawierała wartość NULL, i taką samą utrzyma jeśli proces tworzenia się nie powiedzie.

Następnie należy wywołać funkcje przypisującą adress IP oraz Port:

  1. err_t tcp_bind(struct tcp_pcb * pcb, struct ip_addr * ipaddr, u16_t port)
  2. tcp_bind(tcp_echoserver_pcb, IP_ADDR_ANY, 1001);

Jeśli port zostanie podany jako 0 to przypisanie nastąpi do wolnego portu. Aby ją wywołać to połączenie musi być ciągle zamknięte. Ja wykorzystuje zwykły port, można równie dobrze wykorzystać port 7 który jest przypisany jako echo. Funkcja zwróci wartość ERR_OK jeśli proces przejdzie prawidłowo lub ERR_USE, gdy port jest już wykorzystywany.

Poniżej funkcja inicjalizująca echoserver.

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

Po wywołaniu funkcji tcp_bind należy wywołać funkcje tcp_listen, która czeka na akceptacje połączenia. Zwraca ona nowy blok kontrolny dla zainicjalizowanego połączenia. Porzuca ona wcześniejszy i uruchamia nowy, który potrzebuje mniejszej ilości pamięci. Po jej wywołaniu należy wywołać tcp_accept. W przeciwnym wypadku stworzenie połączenia zostanie przerwane. Jeśli połączenia nie uda się nawiązać, to następuje wyczyszczenie przydzielonej pamięci.

  1. static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
  2. {
  3.   err_t ret_err;
  4.   struct tcp_echoserver_struct *es;
  5.   /* Unused arguments to prevent warnings */
  6.   (void)arg;
  7.   (void)err;
  8.   /* Set priority for new connection */
  9.   tcp_setprio(newpcb, TCP_PRIO_MIN);
  10.   /* allocate structure with info about tcp connection */
  11.   es = (struct tcp_echoserver_struct *)mem_malloc(sizeof(struct tcp_echoserver_struct));
  12.   if (es != NULL)
  13.   {
  14.     es->state = ES_ACCEPTED;
  15.     es->pcb = newpcb;
  16.     es->retries = 0;
  17.     es->= NULL;
  18.     /* Pass structure data do new connection */
  19.     tcp_arg(newpcb, es);
  20.     /* prepare to receive data */
  21.     tcp_recv(newpcb, tcp_echoserver_recv);
  22.     /* if error ocure then tha t callback will be */
  23.     tcp_err(newpcb, tcp_echoserver_error);
  24.     /* waits for connection */
  25.     tcp_poll(newpcb, tcp_echoserver_poll, 0);
  26.     ret_err = ERR_OK;
  27.   }
  28.   else
  29.   {
  30.     /* Close connection */
  31.     tcp_echoserver_connection_close(newpcb, es);
  32.     /* return error */
  33.     ret_err = ERR_MEM;
  34.   }
  35.   return ret_err;
  36. }

Funkcja odbierająca dane:

  1. static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
  2. {
  3.   struct tcp_echoserver_struct *es;
  4.   err_t ret_err;
  5.   LWIP_ASSERT("arg != NULL",arg != NULL);
  6.   es = (struct tcp_echoserver_struct *)arg;
  7.   /* If buffer is empty */
  8.   if (== NULL)
  9.   {
  10.     /* Close connection */
  11.     es->state = ES_CLOSING;
  12.     if(es->== NULL)
  13.     {
  14.        /* Close connection */
  15.        tcp_echoserver_connection_close(tpcb, es);
  16.     }
  17.     else
  18.     {
  19.       /* Callback function used when data was received */
  20.       tcp_sent(tpcb, tcp_echoserver_sent);
  21.       /* Send data back to server */
  22.       tcp_echoserver_send(tpcb, es);
  23.     }
  24.     ret_err = ERR_OK;
  25.   }
  26.   else if(err != ERR_OK)
  27.   {
  28.     /* Error occure, clear buffer  */
  29.     if (!= NULL)
  30.     {
  31.       es->= NULL;
  32.       pbuf_free(p);
  33.     }
  34.     ret_err = err;
  35.   }
  36.   else if(es->state == ES_ACCEPTED)
  37.   {
  38.     /* connection accept, first data received, chunk in p->payload */
  39.     es->state = ES_RECEIVED;
  40.     /* write data to structuce*/
  41.     es->= p;
  42.     tcp_sent(tpcb, tcp_echoserver_sent);
  43.     /* Send data */
  44.     tcp_echoserver_send(tpcb, es);
  45.     ret_err = ERR_OK;
  46.   }
  47.   else if (es->state == ES_RECEIVED)
  48.   {
  49.     /* All data received */
  50.     if(es->== NULL)
  51.     {
  52.       es->= p;
  53.       tcp_echoserver_send(tpcb, es);
  54.     }
  55.     else
  56.     {
  57.       struct pbuf *ptr;
  58.       /* chain two to the end of what we recv'ed previously */
  59.       ptr = es->p;
  60.       pbuf_chain(ptr,p);
  61.     }
  62.     ret_err = ERR_OK;
  63.   }
  64.   else if(es->state == ES_CLOSING)
  65.   {
  66.     //odd case, remote side closing twice, free all trash data
  67.     tcp_recved(tpcb, p->tot_len);
  68.     es->= NULL;
  69.     pbuf_free(p);
  70.     ret_err = ERR_OK;
  71.   }
  72.   else
  73.   {
  74.     //unkown es->state, trash data
  75.     tcp_recved(tpcb, p->tot_len);
  76.     es->= NULL;
  77.     pbuf_free(p);
  78.     ret_err = ERR_OK;
  79.   }
  80.   return ret_err;
  81. }

Obsługa błędu odbywa się przy użyciu następującej funkcji:

  1. static void tcp_echoserver_error(void *arg, err_t err)
  2. {
  3.   struct tcp_echoserver_struct *es;
  4.   (void)err;
  5.   es = (struct tcp_echoserver_struct *)arg;
  6.   if (es != NULL)
  7.   {
  8.     /* free structure */
  9.     mem_free(es);
  10.   }
  11. }

Funkcja odpowiedzialna za oczekiwanie na przesłanie danych:

  1. static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb)
  2. {
  3.   err_t ret_err;
  4.   struct tcp_echoserver_struct *es;
  5.   es = (struct tcp_echoserver_struct *)arg;
  6.   if (es != NULL)
  7.   {
  8.     if (es->!= NULL)
  9.     {
  10.       tcp_sent(tpcb, tcp_echoserver_sent);
  11.       /* There is data in pbuf, chain try to send it */
  12.       tcp_echoserver_send(tpcb, es);
  13.     }
  14.     else
  15.     {
  16.        /* No data in chain */
  17.       if(es->state == ES_CLOSING)
  18.       {
  19.         /* Close connection */
  20.         tcp_echoserver_connection_close(tpcb, es);
  21.       }
  22.     }
  23.     ret_err = ERR_OK;
  24.   }
  25.   else
  26.   {
  27.     /* Aborts the connection by sending a RST (reset) segment to the remote host */
  28.     tcp_abort(tpcb);
  29.     ret_err = ERR_ABRT;
  30.   }
  31.   return ret_err;
  32. }

Sprawdzanie dostępności danych:

  1. static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
  2. {
  3.   struct tcp_echoserver_struct *es;
  4.   (void)len;
  5.   es = (struct tcp_echoserver_struct *)arg;
  6.   es->retries = 0;
  7.   if(es->!= NULL)
  8.   {
  9.     /* data still in buffer */
  10.     tcp_sent(tpcb, tcp_echoserver_sent);
  11.     tcp_echoserver_send(tpcb, es);
  12.   }
  13.   else
  14.   {
  15.     /* No data to send close connection */
  16.     if(es->state == ES_CLOSING)
  17.     {
  18.       tcp_echoserver_connection_close(tpcb, es);
  19.     }
  20.   }
  21.   return ERR_OK;
  22. }

Przesłanie danych:

  1. #define USART_COPY 1
  2. static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)
  3. {
  4.   struct pbuf *ptr;
  5.  
  6. #if USART_COPY == 1
  7.   char dane[256] = {0};
  8.   char buffer[256] = {0};
  9. #endif
  10.  
  11.   err_t wr_err = ERR_OK;
  12.  
  13.   /* tcp_sndbuf - returns number of bytes in space that is avaliable in output queue */
  14.   while ((wr_err == ERR_OK) && (es->!= NULL) && (es->p->len <= tcp_sndbuf(tpcb)))
  15.   {
  16.     /* get pointer on pbuf from es structure */
  17.     ptr = es->p;
  18.  
  19. #if USART_COPY == 1
  20.     sprintf(dane, "%s",(char *)ptr->payload);
  21.     for(uint8_t i = 0; i<ptr->len; i++)
  22.     {
  23.         buffer[i] = dane[i];
  24.     }
  25.  
  26.     /* Close connection */
  27.     if(buffer[0] == 'E' && buffer[1] == 'N' && buffer[2] == 'D') {
  28.         tcp_echoserver_connection_close(tpcb, es);
  29.     }
  30.     else{
  31.         Usart_Uart_SendString(USART1, buffer, LF_CR);
  32.     }
  33. #endif
  34.  
  35.     //wr_err = tcp_write(tpcb, buffereth, strlen(buffereth), 1);
  36.     wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);
  37.  
  38.     /* Clear data */
  39.     memset(dane, 0x00, sizeof(dane));
  40.  
  41.     if (wr_err == ERR_OK)
  42.     {
  43.       u16_t plen;
  44.       u8_t freed;
  45.  
  46.       plen = ptr->len;
  47.  
  48.       /* continue with next pbuf in chain (if any) */
  49.       es->= ptr->next;
  50.  
  51.       if(es->!= NULL)
  52.       {
  53.         /* increment reference count for es->p */
  54.         pbuf_ref(es->p);
  55.       }
  56.  
  57.       /* chop first pbuf from chain */
  58.       do
  59.       {
  60.         /* try hard to free pbuf */
  61.         freed = pbuf_free(ptr);
  62.       }
  63.       while(freed == 0);
  64.      /* we can read more data now */
  65.      tcp_recved(tpcb, plen);
  66.    }
  67.    else if(wr_err == ERR_MEM)
  68.    {
  69.      /* we are low on memory, try later / harder, defer to poll */
  70.      es->= ptr;
  71.    }
  72.    else { }
  73.   }
  74. }

Czyszczenie zmiennych, zamknięcie połączenia:

  1. static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)
  2. {
  3.   //remove all callbacks
  4.   tcp_arg(tpcb, NULL);
  5.   tcp_sent(tpcb, NULL);
  6.   tcp_recv(tpcb, NULL);
  7.   tcp_err(tpcb, NULL);
  8.   tcp_poll(tpcb, NULL, 0);
  9.  
  10.   /* free structure */
  11.   if (es != NULL)
  12.   {
  13.     mem_free(es);
  14.   }
  15.  
  16.   /* Connection close */
  17.   tcp_close(tpcb);
  18. }

W głównej części programu należy wywołać tylko:

  1. //...
  2. //...
  3. //...
  4. MX_LWIP_Init();
  5. tcp_echoserver_init();
  6. while(1)
  7. {
  8.     ethernetif_input(&gnetif);
  9.     sys_check_timeouts();
  10. }

Funkcje inicjalizującą bibliotekę LWIP należy wywołać tylko jeden raz, ponowne wywołania zawiesi układ.

Pliki do projektu można pobrać pod tym linkiem. Po wygenerowaniu projektu przez Cube Mx należy podmienić biblioteki i dodać pliki których brakuje.