środa, 20 grudnia 2017

[33.3] STM32F4 - ENC28J60 TCP Serwer

Ten post chciałbym poświęcić na przygotowanie serwera TCP w oparciu o układ ENC28J60.

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

Segment TCP: [1]


Protokół TCP jest zorientowany połączeniowo. Oznacza to, umożliwia on zestawienie połączenia z którym nastąpi wymiana danych. Dużym plusem tej metody jest duża efektywność oraz niezawodność w przesyłaniu informacji.

W tym połączeniu stosuje się sterowanie przepływem informacji, potwierdzanie odbioru, kontroli błędów czy wykonanie retransmisji.

Na samym początku opiszę jak wygląda segment w protokole TCP. W jego skład wchodzi:

Port źródłowy - 16 bitów (2 bajty) zawiera numer portu źródłowego dla procesu aplikacyjnego, który korzysta z usługi TCP.

Port docelowy - 16 bitów (2 bajty) zawiera numer portu docelowego. Połączenie portu docelowego oraz portu źródłowego wraz z adresem IP pozwala na określenie pary gniazd połączeniowych dla protokołu TCP.

Numer sekwencyjny - 32 bity (4 bajty) zawiera on numer sekwencyjny dla pierwszego bajtu danych w segmencie. W czasie ustanawiania połączenia w polu wprowadzany jest inicjujący numer sekwencyjny. Od niego rozpoczyna się numerację bajtów. Dodatkowo bit SYN w polu znaczniki musi być ustawiony na 1.

Numer potwierdzenia - 32 bitowy (4 bajty) jest to numer potwierdzający otrzymanie pakietu przez odbiorcę. Pozwala to na synchronizację nadawania i odbierania między klientem a serwerem.

Długość nagłówka - 4 bitowa liczba oznaczająca 32 bitowy wiersz nagłówka. Pozwala na określenie kiedy zaczynają się dane. Nagłówek może posiadać długość, która jest wielokrotnością 32 bitów.

Rezerwa - 3 bity zarezerwowane do przyszłego użycia. Muszą być wpisane jako 0.

Znaczniki - to pole składa się z sześciu bitów sterujących:
NS - pojedynczy byt określający sumę wartości flag ENC;
CWR -  potwierdza odebranie powiadomienia od nadawcy;
ECE - ustawiana przez odbiorcę pakietu w momencie otrzymania pakietu w którym flaga CE jest ustawiona;
UGR - pozwala na określenie ważności dla pola Prioryter
ACK - określa ważność dla pola Numer potwierdzenia
PSH - wymuszenie przesłania pakietu
RST - resetuje połączenie
SYN - synchronizacja kolejnych numerów sekwencyjnych
FIN - zakończenie przesyłania danych

Szerokość okna - 16 bitów informacja o tym ile danych może aktualnie przyjąć odbiorca.

Suma kontrolna - 16 bitowa liczba, będzie wynikiem działań na całym pakiecie. Obliczany jest ona z całego nagłówka TCP. Pola sumy kontrolnego są wyzerowane. Ostatnie osiem pól nagłówka IP stanowią adres nadawcy oraz odbiorcy pakietu.

Wskaźnik pilności - w przypadku gdy flaga URG jest włączona, informuje o ważności pakietu.

Opcje - dodatkowe informacje oraz polecenia. Wartość 0 oznacza koniec listy opcji. 1 braz działania natomiast 2 ustawia maksymalną długość segmentu.

Można wyróżnić kilka rodzajów połączeń TCP:

LISTEN - serwer jest gotowy do przyjęcia połączenie na podanym porcie
SYN-SENT - początek nawiązywania połączenia. Klient wysyła do serwera pakiet SYN. Po czym przechodzi on do oczekiwania na ramkę SYN wraz z ACK.
SYN-RECEIVED - Serwer otrzymał pakiet SYN. Wysyła SYN+ACK. Serwer oczekuje teraz na ramkę ACK od klienta. Połączenie jest w połowie otwarte.
ESTABLISHED - połączenie zostało nawiązane pomiędzy klientem a serwerem
FIN-WAIT-1 - przesłano pakiet FIN kończący transmisję
FIN-WAIT-2 - oczekiwania na wysłanie FIN do serwera
CLOSE-WAIT - oczekiwanie na przesłanie własnego pakietu FIN.
CLOSING - zamykanie połączenia
LAST-ACK - ramki FIN zostały przesłane. Oczekiwanie na odebranie ostatniego pakietu ACK
TIME-WAIT - oczekiwanie na otrzymanie potwierdzenia rozłączenia
CLOSED - połączenie zamknięte

Kod:


Tutaj w zadzie wykorzystałem prawie cały kod z poprzednich dwóch przykładów. Uległy tylko rodzaje ramek z UDP na TCP. Wobec tego poniżej przedstawię tylko zmienione funkcje.

Gdy przyjdą dane do układ to następuje wygenerowanie przerwania. W obsłudze przerwania dekodowany jest pakiet danych na jego podstawie sprawdzana jest ramka danych:

  1. void ethReadFrame(enc28j60_frame_t *frame, uint16_t length)
  2. {
  3.     uint8_t status=0;
  4.     char bufferString[50] = {0};
  5.     if(length > sizeof(enc28j60_frame_t))
  6.     {
  7.         if(frame->frameType==ETH_ARP)
  8.         {
  9.             #ifdef DEBUG_NET_FIL
  10.             sprintf(bufferString,"ETH_ARP:%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; arp\r\n",
  11.                 frame->sourceMacAddress[0],frame->sourceMacAddress[1],frame->sourceMacAddress[2],
  12.                 frame->sourceMacAddress[3],frame->sourceMacAddress[4],frame->sourceMacAddress[5],
  13.                 frame->destMacAddress[0],frame->destMacAddress[1],frame->destMacAddress[2],
  14.                 frame->destMacAddress[3],frame->destMacAddress[4],frame->destMacAddress[5],
  15.                 length);
  16.             HAL_UART_Transmit(&huart2,(uint8_t*)bufferString,strlen(bufferString),0x1000);
  17.             #endif
  18.             status = arpRead(frame, length-sizeof(enc28j60_frame_t));
  19.             if(status==1)
  20.             {
  21.                 arpSend(frame);
  22.             }
  23.             else if(status==2)
  24.             {
  25.                 arpTableFill(frame);
  26.             }
  27.         }
  28.         else if(frame->frameType==ETH_IP)
  29.         {
  30.             #ifdef DEBUG_NET_FIL
  31.             sprintf(bufferString,"ETH_IP_FUNCT:%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; ip\r\n",
  32.                 frame->sourceMacAddress[0],frame->sourceMacAddress[1],frame->sourceMacAddress[2],
  33.                 frame->sourceMacAddress[3],frame->sourceMacAddress[4],frame->sourceMacAddress[5],
  34.                 frame->destMacAddress[0],frame->destMacAddress[1],frame->destMacAddress[2],
  35.                 frame->destMacAddress[3],frame->destMacAddress[4],frame->destMacAddress[5],
  36.                 length);
  37.             HAL_UART_Transmit(&huart2,(uint8_t*)bufferString,strlen(bufferString),0x1000);
  38.             #endif
  39.             ipRead(frame,length-sizeof(ip_frame_t));
  40.         }
  41.         else
  42.         {
  43.             #ifdef DEBUG_NET_FIL
  44.             sprintf(bufferString,"ETH_ELSE:%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; %04X",
  45.             frame->sourceMacAddress[0],frame->sourceMacAddress[1],frame->sourceMacAddress[2],
  46.             frame->sourceMacAddress[3],frame->sourceMacAddress[4],frame->sourceMacAddress[5],
  47.             frame->destMacAddress[0],frame->destMacAddress[1],frame->destMacAddress[2],
  48.             frame->destMacAddress[3],frame->destMacAddress[4],frame->destMacAddress[5],
  49.             length, CONVERT_16BIT(frame->frameType));
  50.             HAL_UART_Transmit(&huart2,(uint8_t*)bufferString,strlen(bufferString),0x1000);
  51.             HAL_UART_Transmit(&huart2,(uint8_t*)"\r\n",2,0x1000);
  52.             #endif
  53.         }
  54.     }
  55. }

Przy odebraniu ramki danych ETH_IP funkcja wywołuje ipRead która odpowiada za określenie typu ramki danych:

  1. static void ipRead(enc28j60_frame_t *frame, uint16_t length)
  2. {
  3.     ip_frame_t *ip_frame = (void*)(frame->dataBuffer);
  4.     if((ip_frame->protVerion==0x45) && (!memcmp(ip_frame->destinIpAdress,ipaddr,4)))
  5.     {
  6.         /* Data Length */
  7.         length = CONVERT_16BIT(ip_frame->packetLength) - sizeof(ip_frame_t);
  8.         if(ip_frame->protocolType==IP_ICMP)
  9.         {
  10.             ICMPRead(frame, length);
  11.         }
  12.         else if(ip_frame->protocolType==IP_TCP)
  13.         {
  14.             tcpRead(frame, length);
  15.         }
  16.     }
  17.     free(ip_frame);
  18. }

Gdy przyjdzie ramka TCP to wchodzimy do funkcji określającej dokładne parametry ramki. I odsyłające odpowiedź.

  1. void tcpRead(enc28j60_frame_t *frame, uint16_t len)
  2. {
  3.     ip_frame_t *ipFrame = (void*)(frame->dataBuffer);
  4.     tcp_frame_t *tcpFrame = (void*)(ipFrame->dataBuffer);
  5.     uint16_t dataLength = CONVERT_16BIT(ipFrame->packetLength) - 20 - (tcpFrame->dataOffset>>2);
  6.     char uartBuffer[50] = {0};
  7.  
  8.     #ifdef DEBUG_TCP_FIL
  9.     sprintf(uartBuffer,"Data receive from: %d.%d.%d.%d Data length: %d\r\n",
  10.             ipFrame->destinIpAdress[0],ipFrame->destinIpAdress[1],
  11.             ipFrame->destinIpAdress[2],ipFrame->destinIpAdress[3], dataLength);
  12.     HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  13.     #endif
  14.  
  15.     /* If there was send some data */
  16.     if (CHECK_TCP_DATA_RECEIVE(dataLength))
  17.     {
  18.         #ifdef DEBUG_TCP_FIL
  19.         sprintf(uartBuffer,"DataReceive %s\r\n", tcpFrame->dataBuffer);
  20.         HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  21.         #endif
  22.  
  23.         tcpSendData(ipFrame->sourceIpAdress, CONVERT_16BIT(tcpFrame->sourcePort));
  24.     }
  25.  
  26.     if (CHECK_TCP_SYN)
  27.     {
  28.         tcpSynAck(ipFrame->sourceIpAdress, CONVERT_16BIT(tcpFrame->sourcePort));
  29.     }
  30.     else if (CHECK_TCP_FIN)
  31.     {
  32.         tcpFin(ipFrame->sourceIpAdress, CONVERT_16BIT(tcpFrame->sourcePort));
  33.     }
  34.     else if (CHECK_TCP_ACK)
  35.     {
  36.         HAL_UART_Transmit(&huart2,(uint8_t*)"Transmit Finish\r\n",17,0x1000);
  37.     }
  38.  
  39.     free(ipFrame);
  40.     free(tcpFrame);
  41. }

Nawiązanie połączenia odbywa się poprzez procedurę three-way handshake. Gdy klient chce nawiązać połączenie z serwerem postawionym na STM32 przesyła on segment SYN. Po jej odebraniu następuje przesłanie ramki SYN od serwera oraz ACK. Po odebraniu obu ramek w odpowiedniej konfiguracji klient przechodzi w stan ESTABLISHED i odsyła ramkę ACK. Po jej odebraniu serwer przechodzi w stan ESTABLISHED.

Funkcja odpowiedzialna za przesłanie wiadomości SYN + ACK:

  1. static void tcpSynAck(uint8_t *ip_addr, uint16_t port)
  2. {
  3.     uint16_t bufferLength       = 0;
  4.     enc28j60_frame_t *encFrame  = (void*)net_buf;
  5.     ip_frame_t *ipFrame         = (void*)(encFrame->dataBuffer);
  6.     tcp_frame_t *tcpFrame       = (void*)(ipFrame->dataBuffer);
  7.     char uartBuffer[50]         = {0};
  8.  
  9.     tcpFrame->destPort = CONVERT_16BIT(port);               /* Prepare dest port        */
  10.     tcpFrame->sourcePort = CONVERT_16BIT(PORT_TCP);         /* Prepare source port      */
  11.  
  12.     /* Ack number need to add 1 for computing ACK flag  */
  13.     tcpFrame->ackNumber     = CONVERT_32BIT(CONVERT_32BIT(tcpFrame->sequNumber) + 1);
  14.     tcpFrame->sequNumber    = 3472621397U;             /* Set sequence number      */
  15.     tcpFrame->flags         = TCP_SYN | TCP_ACK;       /* Set SYN and ACK Flags    */
  16.     tcpFrame->windowSize    = WINDOW_SIZE;             /* Set window size          */
  17.     tcpFrame->urgentPointer = 0;                       /* URG is not set so data to 0 */
  18.     bufferLength            = sizeof(tcp_frame_t)+4;
  19.  
  20.     tcpFrame->dataOffset = bufferLength << 2;
  21.  
  22.     /* Add MMS option */
  23.     tcpFrame->dataBuffer[0] = 2;
  24.     tcpFrame->dataBuffer[1] = 4;
  25.     tcpFrame->dataBuffer[2] = 0x05;
  26.     tcpFrame->dataBuffer[3] = 0x80;
  27.  
  28.     /* Clear checksum */
  29.     tcpFrame->checkSum = 0;
  30.     /* Calculate checksum */
  31.     tcpFrame->checkSum=checksum((uint8_t*)tcpFrame-8, bufferLength+8, Prot_TCP);
  32.     bufferLength += sizeof(ip_frame_t);
  33.  
  34.     ipFrame->packetLength   = CONVERT_16BIT(bufferLength);
  35.     ipFrame->packetId       = 0;
  36.     ipFrame->serviceType    = 0;
  37.     ipFrame->protVerion     = 0x45;                        
  38.     ipFrame->flags          = 0;
  39.     ipFrame->timeToLive     = 64;                  
  40.     ipFrame->controlType    = 0;
  41.     ipFrame->protocolType   = IP_TCP;       /* Protocol number */
  42.    
  43.     memcpy(ipFrame->destinIpAdress,ip_addr,4);
  44.     memcpy(ipFrame->sourceIpAdress,ipaddr,4);
  45.    
  46.     ipFrame->controlType = checksum((void*)ipFrame,sizeof(ip_frame_t),0);
  47.    
  48.     memcpy(encFrame->destMacAddress,    encFrame->sourceMacAddress, 6);
  49.     memcpy(encFrame->sourceMacAddress,  macaddr,                    6);
  50.    
  51.     encFrame->frameType = ETH_IP;
  52.  
  53.     bufferLength += sizeof(enc28j60_frame_t);
  54.     enc28j60PacketSend((void*)encFrame,bufferLength);
  55.  
  56.     #ifdef DEBUG_TCP_FIL
  57.  
  58.     sprintf(uartBuffer,"Syn Ack Buffer Length:%d\r\n", bufferLength);
  59.     HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  60.  
  61.     #endif
  62. }

Gdy zostaną przesłane dane od klienta to funkcja wchodzi do odebrania danych i przesłania odpowiedzi do klienta.

  1. static void tcpSendData(uint8_t *ip_addr, uint16_t port)
  2. {
  3.     uint16_t receiveDataLength  = 0;
  4.     uint16_t bufferLength       = 0;
  5.     uint32_t segmentNumber      = 0;
  6.     char uartBuffer[50]         = {0};
  7.  
  8.     enc28j60_frame_t *frame     = (void*)net_buf;
  9.     ip_frame_t       *ipFrame   = (void*)(frame->dataBuffer);
  10.     tcp_frame_t      *tcpFrame  = (void*)(ipFrame->dataBuffer);
  11.  
  12.     receiveDataLength       = CONVERT_16BIT(ipFrame->packetLength) - 20 - (tcpFrame->dataOffset>>2);
  13.     tcpFrame->destPort      = CONVERT_16BIT(port);
  14.     tcpFrame->sourcePort    = CONVERT_16BIT(PORT_TCP);
  15.     segmentNumber           = tcpFrame->ackNumber;
  16.     tcpFrame->ackNumber     = CONVERT_32BIT(CONVERT_32BIT(tcpFrame->sequNumber) + receiveDataLength);
  17.  
  18.     #ifdef DEBUG_TCP_FIL
  19.  
  20.     sprintf(uartBuffer,"receiveDataLength:%u\r\n", receiveDataLength);
  21.     HAL_UART_Transmit(&huart2,(uint8_t*)uartBuffer,strlen(uartBuffer),0x1000);
  22.  
  23.     #endif
  24.  
  25.     /* Prepare ACK frame */
  26.     tcpFrame->sequNumber    = segmentNumber;
  27.     tcpFrame->flags         = TCP_ACK;
  28.     tcpFrame->windowSize    = WINDOW_SIZE;
  29.     tcpFrame->urgentPointer = 0;
  30.  
  31.     bufferLength            = sizeof(tcp_frame_t);
  32.  
  33.     tcpFrame->dataOffset    = bufferLength << 2;
  34.     tcpFrame->checkSum      = 0;
  35.     tcpFrame->checkSum      = checksum((uint8_t*)tcpFrame-8, bufferLength+8, Prot_TCP);
  36.  
  37.     bufferLength+=sizeof(ip_frame_t);
  38.  
  39.     /* prepare IP*/
  40.     ipFrame->packetLength   = CONVERT_16BIT(bufferLength);
  41.     ipFrame->packetId       = 0;
  42.     ipFrame->serviceType    = 0;
  43.     ipFrame->protVerion     = 0x45;
  44.     ipFrame->flags          = 0;
  45.     ipFrame->timeToLive     = 64;
  46.     ipFrame->controlType    = 0;
  47.     ipFrame->protocolType   = IP_TCP;
  48.  
  49.     memcpy(ipFrame->destinIpAdress,ip_addr,4);
  50.     memcpy(ipFrame->sourceIpAdress,ipaddr,4);
  51.  
  52.     ipFrame->controlType    = checksum((void*)ipFrame,sizeof(ip_frame_t),0);
  53.  
  54.     memcpy(frame->destMacAddress,   frame->sourceMacAddress, 6);
  55.     memcpy(frame->sourceMacAddress, macaddr,                 6);
  56.  
  57.     frame->frameType        = ETH_IP;
  58.  
  59.     bufferLength += sizeof(enc28j60_frame_t);
  60.     enc28j60PacketSend((void*)frame,bufferLength);
  61.  
  62.     //--------------------------------------------------------------------------
  63.     /* Send data to client */
  64.     strcpy((char*)tcpFrame->dataBuffer,"Data Received\r\n");
  65.  
  66.     tcpFrame->flags      = TCP_ACK|TCP_PSH;
  67.     bufferLength         = sizeof(tcp_frame_t);
  68.  
  69.     tcpFrame->dataOffset = bufferLength << 2;
  70.     bufferLength += strlen((char*)tcpFrame->dataBuffer);
  71.  
  72.     /* Clear checksum */
  73.     tcpFrame->checkSum   = 0;
  74.     tcpFrame->checkSum   = checksum((uint8_t*)tcpFrame-8, bufferLength+8, Prot_TCP);
  75.  
  76.     bufferLength += sizeof(ip_frame_t);
  77.  
  78.     ipFrame->packetLength   = CONVERT_16BIT(bufferLength);
  79.     ipFrame->controlType    = 0;
  80.     ipFrame->controlType    = checksum((void*)ipFrame,sizeof(ip_frame_t),0);
  81.  
  82.     bufferLength+=sizeof(enc28j60_frame_t);
  83.  
  84.     enc28j60PacketSend((void*)frame,bufferLength);
  85. }

Dla weryfikacji nadawania oraz odbierania stosowane są sumy kontrolne oraz sekwencyjne numery pakietów. Odbiorca potwierdza otrzymanie pakietów przez ustawienia flagi ACK.

Zakończenie transmisji odbywa się za pomocą następującej funkcji:

  1. static void tcpFin(uint8_t *ip_addr, uint16_t port)
  2. {
  3.     uint16_t bufferLength = 0;
  4.     uint32_t segmentNumber = 0;
  5.  
  6.     enc28j60_frame_t *frame = (void*)net_buf;
  7.     ip_frame_t       *ipFrame = (void*)(frame->dataBuffer);
  8.     tcp_frame_t      *tcpFrame = (void*)(ipFrame->dataBuffer);
  9.  
  10.     tcpFrame->destPort          = CONVERT_16BIT(port);
  11.     tcpFrame->sourcePort        = CONVERT_16BIT(PORT_TCP);
  12.     segmentNumber               = tcpFrame->ackNumber;
  13.     tcpFrame->ackNumber         = CONVERT_32BIT(CONVERT_32BIT(tcpFrame->sequNumber) + 1);
  14.  
  15.     tcpFrame->sequNumber        = segmentNumber;
  16.     tcpFrame->flags                 = TCP_ACK;              /* Set ACK flag */
  17.     tcpFrame->windowSize        = WINDOW_SIZE;
  18.     tcpFrame->urgentPointer         = 0;
  19.  
  20.     bufferLength                = sizeof(tcp_frame_t);
  21.  
  22.     tcpFrame->dataOffset        = bufferLength << 2;
  23.     tcpFrame->checkSum          = 0;
  24.     tcpFrame->checkSum          = checksum((uint8_t*)tcpFrame - 8, bufferLength+8, Prot_TCP);
  25.  
  26.     bufferLength += sizeof(ip_frame_t);
  27.  
  28.     /* IP part */
  29.     ipFrame->packetLength       = CONVERT_16BIT(bufferLength);
  30.     ipFrame->packetId           = 0;
  31.     ipFrame->serviceType        = 0;
  32.     ipFrame->protVerion             = 0x45;
  33.     ipFrame->flags              = 0;
  34.     ipFrame->timeToLive         = 64;
  35.     ipFrame->protocolType       = IP_TCP;
  36.  
  37.     memcpy(ipFrame->destinIpAdress, ip_addr, 4);
  38.     memcpy(ipFrame->sourceIpAdress, ipaddr,  4);
  39.  
  40.     ipFrame->controlType        = 0;
  41.     ipFrame->controlType        = checksum((void*)ipFrame,sizeof(ip_frame_t),0);
  42.  
  43.     memcpy(frame->destMacAddress,   frame->sourceMacAddress, 6);
  44.     memcpy(frame->sourceMacAddress, macaddr,                 6);
  45.  
  46.     frame->frameType            = ETH_IP;
  47.  
  48.     bufferLength+=sizeof(enc28j60_frame_t);
  49.     enc28j60PacketSend((void*)frame,bufferLength);
  50.  
  51.     tcpFrame->flags             = TCP_FIN|TCP_ACK;      /* Set ACK and FIN flag */
  52.     bufferLength = sizeof(tcp_frame_t);
  53.  
  54.     tcpFrame->checkSum = 0;
  55.     tcpFrame->checkSum = checksum((uint8_t*)tcpFrame-8, bufferLength+8, Prot_TCP);
  56.  
  57.     bufferLength += sizeof(ip_frame_t);
  58.     bufferLength += sizeof(enc28j60_frame_t);
  59.     enc28j60PacketSend((void*)frame,bufferLength);
  60.  
  61.     #ifdef DEBUG_TCP_FIL
  62.  
  63.     HAL_UART_Transmit(&huart2,(uint8_t*)"FIN Transmit End\r\n",5,0x1000);
  64.  
  65.     #endif
  66. }

Zakończenie połączenia może być wykonane przez klienta jak i przez serwer. Przesyłany pakiet ma ustawioną flagę FIN. Jego przesłanie wymaga odpowiedzi w postaci ramki ACK.

Działanie programu polega na przesłaniu zapytania przez klienta do serwera. On wtedy odpowiada przykładowymi danymi z odpowiedzią:


Cały projekt można pobrać z dysku Google pod tym linkiem w folderze STM32 a następnie STM32F4.