Top.Mail.Ru

STM32 и Ethernet. Часть 5. Транспортный уровень. Протокол UDP.

Нежданно-негаданно, благодаря теме на форуме, произошел возврат к одной из предыдущих тем, а именно к связке STM32 и ENC28J60 и Ethernet. И в этой, пятой, части обработаем принятый UDP-пакет и отправим на него ответ. Причем в отличие от предыдущих частей я не буду делать обширную врезку с теорией касаемо UDP, может быть потом, в отдельной статье, но вряд ли ) Просто и кратко дополним созданный ранее проект необходимым функционалом и посмотрим на результат.

Но для начала освежим в памяти предыдущие части:

Итак, протокол UDP относится к транспортному уровню, а значит мы снова движемся вверх по модели OSI:

UDP протокол

Отличительной особенностью UDP является то, что для обмена сообщениями-датаграммами устройствам не требуется никакой предварительной подготовки. То есть никаких установок соединений, рукопожатий, открытия каналов связи и тому подобного. Данные могут спокойно потеряться по пути, порядок следования датаграмм может быть нарушен, все это не контролируется. Возникает резонный вопрос - зачем тогда это нужно? А отчет достаточно прост - например, для очень чувствительных ко времени систем, систем реального времени. В таком случае в жертву приносятся дополнительные проверки и контроль, из-за чего и получаем выигрыш во времени. Если потери пакетов, ошибки, дублирования критичны, то тут уже вступает в дело, к примеру, TCP, но сегодня не об этом.

Для обмена данными по UDP необходимо знать ip-адрес и порт участников обмена. Порт представляет из себя целое число от 0 до 65535 (16-ти битное значение). Собственно, в практическом примере увидим этот механизм наглядно.

Каждая датаграмма включает в себя заголовок и непосредственно данные:

UDP frame

Пробежимся по полям заголовка:

  • Source port - порт отправителя
  • Destination port - порт получателя
  • Length - длина датаграммы, причем включает в себя и заголовок и данные(!)
  • Checksum - контрольная сумма

При этом для IPv4 контрольная сумма и порт отправителя являются необязательными полями. Все, что нам потребуется на практике, выяснили, добавляем в проект два новых файла:

  • udp.c
  • udp.h

Первым делом, определим структуру для UDP датаграмм в udp.h, выглядит она следующим образом:

typedef struct UDP_Frame
{
  uint16_t srcPort;
  uint16_t destPort;
  uint16_t len;
  uint16_t checkSum;
  uint8_t data[];
} UDP_Frame;

Прекрасно, здесь же зададим номер порта, который используем для тестирования:

#define UDP_DEMO_PORT                                                   33333

Для примера реализуем простой механизм: инкрементируем все байты в принятой датаграмме и отправим обратно уже измененными. Материализуется все в следующий код:

/*----------------------------------------------------------------------------*/
uint16_t UDP_Process(UDP_Frame* udpFrame, uint16_t frameLen)
{
  uint16_t newFrameLen = 0;
    
  uint16_t destPort = ntohs(udpFrame->destPort);
  uint16_t len = ntohs(udpFrame->len);
  uint16_t dataLen = len - sizeof(UDP_Frame);
  
  if (destPort == UDP_DEMO_PORT)
  {
    for(uint16_t i = 0 ; i < dataLen - 1; i++)
    {
      udpFrame->data[i]++;
    }
  }
  
  uint16_t swapPort = udpFrame->destPort;
  udpFrame->destPort = udpFrame->srcPort;
  udpFrame->srcPort = swapPort;
  
  udpFrame->checkSum = 0;
  newFrameLen = len;
  
  return newFrameLen;
}



/*----------------------------------------------------------------------------*/

Здесь мы предварительно проверяем, что это наш пакет, то есть Destination port в заголовке соответствует тому, который мы для себя выбрали - UDP_DEMO_PORT. Вызов же этой функции поместим в обработчик принятых пакетов на сетевом уровне, то есть в IP_Process():

/*----------------------------------------------------------------------------*/
uint16_t IP_Process(IP_Frame* ipFrame, uint16_t frameLen)
{
  uint16_t newFrameLen = 0;

  if (memcmp(ipFrame->destIpAddr, ipAddr, IP_ADDRESS_BYTES_NUM) == 0)
  {             
    uint16_t rxCheckSum = ipFrame->checkSum;
    ipFrame->checkSum = 0;
    uint16_t calcCheckSum = IP_CalcCheckSum((uint8_t*)ipFrame, sizeof(IP_Frame));
    
    if (rxCheckSum == calcCheckSum)
    {
      uint16_t dataLen = frameLen - sizeof(IP_Frame);
      uint16_t newDataLen = 0;
        
      if (ipFrame->protocol == IP_FRAME_PROTOCOL_ICMP)
      {
        newDataLen = ICMP_Process((ICMP_EchoFrame*)ipFrame->data, dataLen);
      }
      
      if (ipFrame->protocol == IP_FRAME_PROTOCOL_UDP)
      {
        newDataLen = UDP_Process((UDP_Frame*)ipFrame->data, dataLen);
      }
      
      newFrameLen = newDataLen + sizeof(IP_Frame);
      ipFrame->len = htons(newFrameLen);
      
      ipFrame->fragId = 0;
      ipFrame->fragOffset = 0;
      
      memcpy(ipFrame->destIpAddr, ipFrame->srcIpAddr, IP_ADDRESS_BYTES_NUM);
      memcpy(ipFrame->srcIpAddr, ipAddr, IP_ADDRESS_BYTES_NUM);
      
      ipFrame->checkSum = IP_CalcCheckSum((uint8_t*)ipFrame, sizeof(IP_Frame));
    }
  }
  
  return newFrameLen;
}



/*----------------------------------------------------------------------------*/

IP_FRAME_PROTOCOL_UDP соответственно определяем в ip.h:

/* Declarations and definitions ----------------------------------------------*/

#define IP_FRAME_PROTOCOL_ICMP                                  1
#define IP_FRAME_PROTOCOL_UDP                                   17

В целом, все прекрасно вписалось в ту изначальную архитектуру, которую мы запланировали. Вроде бы я ничего не забыл, компилируем, прошиваем и можно переходить к проверке.

Для тестирования я использую утилиту ncat, в командной строке выполняем:

.\ncat.exe -u 169.254.191.22 33333

Ключ -u сигнализирует о том, что будет использован UDP, далее следует IP-адрес платы и номер порта получателя. Получателями же и являются в данном случае STM32 и ENC28J60. После ввода команды можем отправлять сами данные:

STM32 и UDP

В результате видим, что работает, как и задумано - отправленные данные инкрементированы и высланы обратно (две нижние строки, первая - отправленные, вторая - принятые). Вот на этом позитивном моменте сегодняшний пост и закончим, всех благодарю за внимание, оставайтесь на связи 🤝

/**
  ******************************************************************************
  * @file           : udp.c
  * @brief          : UDP driver
  * @author         : MicroTechnics (microtechnics.ru)
  ******************************************************************************
  */



/* Includes ------------------------------------------------------------------*/

#include "udp.h"
#include "ip.h"



/* Declarations and definitions ----------------------------------------------*/



/* Functions -----------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
uint16_t UDP_Process(UDP_Frame* udpFrame, uint16_t frameLen)
{
  uint16_t newFrameLen = 0;
    
  uint16_t destPort = ntohs(udpFrame->destPort);
  uint16_t len = ntohs(udpFrame->len);
  uint16_t dataLen = len - sizeof(UDP_Frame);
  
  if (destPort == UDP_DEMO_PORT)
  {
    for(uint16_t i = 0 ; i < dataLen - 1; i++)
    {
      udpFrame->data[i]++;
    }
  }
  
  uint16_t swapPort = udpFrame->destPort;
  udpFrame->destPort = udpFrame->srcPort;
  udpFrame->srcPort = swapPort;
  
  udpFrame->checkSum = 0;
  newFrameLen = len;
  
  return newFrameLen;
}



/*----------------------------------------------------------------------------*/
/**
  ******************************************************************************
  * @file           : udp.h
  * @brief          : UDP driver interface
  * @author         : MicroTechnics (microtechnics.ru)
  ******************************************************************************
  */

#ifndef UDP_H
#define UDP_H



/* Includes ------------------------------------------------------------------*/

#include "stm32f1xx_hal.h"
#include "common.h"



/* Declarations and definitions ----------------------------------------------*/

#define UDP_DEMO_PORT                                                   33333                                                

typedef struct UDP_Frame
{
  uint16_t srcPort;
  uint16_t destPort;
  uint16_t len;
  uint16_t checkSum;
  uint8_t data[];
} UDP_Frame;



/* Functions -----------------------------------------------------------------*/

extern uint16_t UDP_Process(UDP_Frame* udpFrame, uint16_t frameLen);



#endif // #ifndef UDP_H

Ссылка на проект - MT_ENC28J60_Part_5.

Подписаться
Уведомить о
guest

2 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Shen
Shen
1 месяц назад

Крутой гайд, спасибо. Было бы неплохо если бы вы выложили ни гитхаб пятую (финальную) версию проекта, а то там только четвёртая. Пришлось вручную поправить несколько несостыковок, но ничего страшного 🙂

2
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x