[Ź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:
- uint8_t serverUDPInit(void)
- {
- /* Set operation status for default */
- uint8_t status = 0;
- /* check if server isn't initialized yet */
- if(UDP_CONN_INF.status == UDP_ST_NOINIT)
- {
- /* Set link status */
- EthLinkStatus = 0;
- /* Enable Ethernet and LWIP Stack */
- MX_LWIP_Init();
- /* Init NVIC */
- HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
- /* Set notification for user */
- udpUserNotification(&gnetif);
- /* Set status for initialization */
- UDP_CONN_INF.status = UDP_ST_NOCONNECT;
- }
- else if(UDP_CONN_INF.status == UDP_ST_NOCONNECT)
- {
- /* udp connection already initialized */
- status = 1;
- }
- return status;
- }
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:
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:
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:
Najpierw przeprowadzane są operacje na buforze. Po ich wykonaniu wysyłany jest podany bufor za pomocą funkcji udp_sendto.
Sprawdzanie odpowiedzi od serwera:
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:
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:
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
Bibliotekę można pobrać pod tym linkiem. W folderze STM32 a następnie STM32F7.
Ustanowienie połączenia:
- UDP_SERVER_CONNECT_t serverUDPStart(void)
- {
- UDP_SERVER_CONNECT_t status = UDP_SER_CON_STA_ERROR_2;
- err_t err;
- if(UDP_CONN_INF.status == UDP_ST_NOINIT)
- {
- return UDP_SER_CON_STA_INIT_ERR;
- }
- if(UDP_CONN_INF.status == UDP_ST_RUNNING)
- {
- return UDP_SER_CON_STA_RUNNING;
- }
- if(EthLinkStatus > 0)
- {
- return UDP_SER_CON_STA_NO_LINK;
- }
- /* Create new UDP pcb */
- upcb_server = udp_new();
- /* return pointer */
- if (upcb_server == NULL)
- {
- return UDP_SER_CON_STA_ERROR_1;
- }
- #ifdef HOST_IP_0
- IP4_ADDR(&HostIPaddr, HOST_IP_0, HOST_IP_1, HOST_IP_2, HOST_IP_3 );
- #endif
- IP4_ADDR(&OwnIPaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3 );
- /* Bind PCB to local IP address and port*/
- err = udp_bind(upcb_server, &OwnIPaddr, SERVER_UDP_PORT);
- if (err != ERR_OK)
- {
- return UDP_SER_CON_STA_ERROR_2;
- }
- /* Set callback for incoming UDP data */
- udp_recv(upcb_server, udpReceiveCallback, NULL);
- /* change status */
- status = UDP_SER_CON_STA_CONNECT_OK;
- UDP_CONN_INF.status = UDP_ST_RUNNING;
- return status;
- }
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:
- void serverUDPDisconnect(void)
- {
- if(UDP_CONN_INF.status==UDP_ST_RUNNING)
- {
- udp_remove(upcb_server);
- UDP_CONN_INF.status=UDP_ST_NOCONNECT;
- }
- }
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:
- uint8_t serverUDPSendString(uint8_t *ptr)
- {
- uint8_t status = ERROR;
- struct pbuf *p;
- err_t err;
- /* if server is enable */
- if(UDP_CONN_INF.status == UDP_ST_RUNNING)
- {
- /* allocate buffer */
- p = pbuf_alloc(PBUF_TRANSPORT, strlen((uint8_t*)ptr), PBUF_POOL);
- /* if buffer is allocated */
- if (p == NULL)
- {
- status = 0;
- return status;
- }
- /* copy data */
- pbuf_take(p, (char*)ptr, strlen((char*)ptr));
- /* Send data to a specified address using UDP */
- err = udp_sendto(upcb_server, p, &HostIPaddr, CLIENT_USP_PORT);
- /* clear sending buffer */
- for(uint8_t loop = 0; loop < UDP_RECEIVE_MSG_SIZE; loop++)
- {
- UDP_CONN_INF.recBufer[loop] = 0;
- }
- /* free buffer */
- pbuf_free(p);
- if(err == ERR_OK)
- {
- status = SUCCESS;
- }
- else
- {
- /* remove a UDP pcb */
- udp_remove(upcb_server);
- UDP_CONN_INF.status=UDP_ST_NOCONNECT;
- }
- }
- return status;
- }
Najpierw przeprowadzane są operacje na buforze. Po ich wykonaniu wysyłany jest podany bufor za pomocą funkcji udp_sendto.
Sprawdzanie odpowiedzi od serwera:
- UDP_RECEIVE_t serverUDPWorks(uint8_t *ptr)
- {
- UDP_RECEIVE_t status = UDP_REVEICE_BUF_EMPTY;
- uint32_t i;
- uint8_t value;
- if(UDP_CONN_INF.status != UDP_ST_NOINIT)
- {
- /* Use when packet is ready to read from the interface */
- ethernetif_input(&gnetif);
- /* Handle timeout */
- sys_check_timeouts();
- /* check linki status */
- if(EthLinkStatus > 0)
- {
- if(UDP_CONN_INF.status == UDP_ST_RUNNING)
- {
- /* remove a UDP pcb */
- udp_remove(upcb_server);
- UDP_CONN_INF.status = UDP_ST_NOCONNECT;
- }
- }
- }
- if(UDP_CONN_INF.status != UDP_ST_RUNNING)
- {
- return UDP_SERVER_BUF_OFFLINE;
- }
- if(UDP_CONN_INF.recLength > 0)
- {
- status = UDP_RECEIVE_BUF_READY;
- /* copy buffer */
- i=0;
- value = UDP_CONN_INF.recBufer[i];
- while((value != 0) && (i < UDP_RECEIVE_MSG_SIZE-1))
- {
- value=UDP_CONN_INF.recBufer[i];
- ptr[i]=value;
- i++;
- }
- /* clear last character */
- ptr[i]=0x00;
- UDP_CONN_INF.recLength=0;
- }
- return status;
- }
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:
- static void udpReceiveCallback(void *arg, struct udp_pcb *upcb, struct pbuf *p, struct ip_addr *addr, u16_t port)
- {
- uint16_t i;
- uint8_t value;
- uint8_t *ptr;
- ptr=(uint8_t*)p->payload;
- i=0;
- do
- {
- value = *ptr;
- UDP_CONN_INF.recBufer[i] = value;
- ptr++;
- i++;
- }while((value != 0) && (i < UDP_RECEIVE_MSG_SIZE-1));
- UDP_CONN_INF.recBufer[i]=0x00;
- UDP_CONN_INF.recLength = i;
- pbuf_free(p);
- }
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:
- /** Currently, the pbuf_custom code is only needed for one specific configuration
- * of IP_FRAG */
- #define LWIP_SUPPORT_CUSTOM_PBUF (IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF)
- #define PBUF_TRANSPORT_HLEN 150
- #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
- uint8_t buffer[UDP_RECEIVE_MSG_SIZE] = {0};
- UDP_RECEIVE_t rx_check = UDP_REVEICE_BUF_EMPTY;
- uint32_t aliveNotify = 0;
- /* ...
- ...
- ...
- */
- EnableUartUsart();
- Usart_Uart_SendString(USART1,"Enable USART", LF_CR);
- //------------------------------
- /* Enable UDP */
- serverUDPInit();
- serverUDPStart();
- Usart_Uart_SendString(USART1,"Server Connect", LF_CR);
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- for(uint8_t i=0; i<UDP_RECEIVE_MSG_SIZE; i++)
- {
- buffer[i] = 0;
- }
- rx_check = serverUDPWorks(buffer);
- if(rx_check == UDP_RECEIVE_BUF_READY)
- {
- serverUDPSendString(buffer);
- Usart_Uart_SendString(USART1, (uint8_t *)buffer, LF_CR);
- aliveNotify=0;
- }
- if(aliveNotify>1000000)
- {
- aliveNotify=0;
- /* send default msg */
- serverUDPSendString("UDP_alive_notify\r\n");
- Usart_Uart_SendString(USART1, "UDP_alive_notify\r\n", LF_CR);
- }
- else
- {
- aliveNotify++;
- }
- }
Bibliotekę można pobrać pod tym linkiem. W folderze STM32 a następnie STM32F7.