STM Урок 68. LAN. ENC28J60. Часть 8



 

Урок 68

 

Часть 8

 

LAN. ENC28J60

В предыдущей части нашего урока мы написали несколько функций для отправки определённых пакетов и проверили ответ на запросы ARP в программе Wireshark.

 

ARP-ответ мы отправили, хотелось бы ещё отвечать на пинги, но это не так просто. Нужно научиться работать с протоколом ICMP (Internet Control Message Protocol — протокол межсетевых управляющих сообщений), находящимся между сетевым уровнем и транспортным. А чтобы работать с протоколом ICMP, необходимо разобраться ещё с протоколом IP. Вот с него мы пока и начнём.

Протокол IP является более тяжёлым, нежели протокол ARP, так как перед ним стоит много задач. Одна из них — найти маршрут, по которому нужно отправить пакет, также пакет может состоять из нескольких фрагментов, находящихся в различных фреймах. Но ничего, потихоньку разберёмся.

Выше функции eth_read добавим ещё функцию чтения пакета IP

 

//--------------------------------------------------

uint8_t ip_read(enc28j60_frame_ptr *frame, uint16_t len)

{

}

//--------------------------------------------------

 

Вот формат заголовка пакета IP

 

image37

 

Первое поле — номер версии, в нашем случае 4, так как версия протокола у нас IPv4, в которой IP-адрес состоит из 4 байтов.

Дальше идет длина заголовка, измеряемая в 4-байтных величинах или в двойных словах (DWORD), поэтому заголовок IP должен быть кратным 4 байтам. В нашем случае без параметров и доп. опций здесь будет цифра 5.

Тип обслуживания — в данный момент практически не используется.

Общая длина — это суммарная длина пакета вместе с данными. Нам она важна, так как мы из-за выравнивания пакетов её не знаем. Измеряется в байтах. Максимальное значение — 65535 байт. Но на практике не превышает 1500 из-за максимальной длины кадра Ethernet.

Идентификатор пакета, флаги и смещение фрагмента используются для фрагментации, мы её пока не рассматриваем.

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

 

 

Тип протокола — пример — TCP — 6, UDP — 17, ICMP — 1.

Контрольная сумма заголовка — это хитро рассчитанная величина, которая служит для проверки дохождения пакета до получателя. Расчитывается и на приёмнике и на источнике и сравнивается. Также контрольная сумма меняется после прохождения через маршрутизатор, так как там время жизни уменьшается на 1 и поле меняется.

А дальше понятно — IP-адреса отправителя и получателя.

Добавим структуру для пакте IP в файл net.h

 

} arp_msg_ptr;

//--------------------------------------------------

typedef struct ip_pkt{

uint8_t verlen;//версия протокола и длина заголовка

uint8_t ts;//тип севриса

uint16_t len;//длина

uint16_t id;//идентификатор пакета

uint16_t fl_frg_of;//флаги и смещение фрагмента

uint8_t ttl;//время жизни

uint8_t prt;//тип протокола

uint16_t cs;//контрольная сумма заголовка

uint8_t ipaddr_src[4];//IP-адрес отправителя

uint8_t ipaddr_dst[4];//IP-адрес получателя

uint8_t data[];//данные

} ip_pkt_ptr;

//--------------------------------------------------

 

Ещё напишем макросы для типов протоколов

 

//--------------------------------------------------

#define IP_ICMP 1

#define IP_TCP 6

#define IP_UDP 17

//--------------------------------------------------

 

 

Давайте теперь разберёмся немного с расчётом контрольной суммы заголовков. Она расчитывается практически одинаково для всех типов заголовков и пакетов, поэтому функция будет универсальная.

Общий принцип расчёта следующий. Заголовок разбивается на 16-битные величины. Они все складываются. Если сумма не умещается в 16 битную величину, что чаще и бывает, оно разбивается на два 16-битных числа — старшее полуслово и младшее, и они затем также складываются. Если опять не умещается, повторяем данную операцию, пока не влезет в 16 бит. Полученную величину сначала конвертируем в big endian, затем инвертируем, то есть вместо всех единиц записываем нули, а вместо нулей — единицы. Есть небольшой нюанс. Вдруг у нас длина заголовка будет состоять из нечётного числа байтов. Тогда последний байт выравниваем нулём, то есть существующий один байт становится старшим, а в младший пишем все нули. Как-то так. Можно заранее дополнить нулями до чётной длины весь заголовок, это кому как нравится. Также не забываем, что при расчёте контрольной суммы само поле с контрольной суммой также считается, поэтому его нужно сначала обнулить, а затем уже после расчёта заполнить значением контрольной суммы.

Давайте же напишем данную функцию где-нибудь повыше, чтобы была видна из других функций

 

//--------------------------------------------------

uint16_t checksum(uint8_t *ptr, uint16_t len)

{

}

//--------------------------------------------------

 

ptr — адерс заголвока, контрольную сумму которго мы рассчитываем,

len — длина заголовка.

Возвращаем значение рассчитанной контрольной суммы.

Добавим переменную для суммы

 

uint16_t checksum(uint8_t *ptr, uint16_t len)

{

  uint32_t sum=0;

 

Добавим условный цикл, отслеживающий окончание заголовка

 

uint32_t sum=0;

while(len>1)

{

}

 

В теле условного цикла посчитаем и прибавим к общей сумме очередную двухбайтовую величину

 

while(len>1)

{

  sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));

 

Как мы видим, для того чтобы получить big endian, необязательно пользоваться макросом и терять лишние машинные циклы, достаточно наоборот сдвинуть влево и сделать сразу старшим младший байт и прибавить к нему по «ИЛИ» старший байт, который автоматически станет младшим.

Далее здесь же в теле условного цикла сместим указатель дальше на 2 пункта и значение длины заголовка уменьшим на 2

 

  sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1));

  ptr+=2;

  len-=2;

}

 

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

 

  len-=2;

}

if(len) sum+=((uint32_t)*ptr)<<8;

 

Дальше нам по условию расчёта суммы необходимо узнать, поместилась ли наша сума в размер 16 бит. Для этого мы создадим ещё один условный цикл, в теле которого мы будем постоянно складывать между собой старшую и младшую части суммы до тех пор, пока сумма не уместится в 16-битный размер

 

if(len) sum+=((uint32_t)*ptr)<<8;

while (sum>>16) sum=(uint16_t)sum+(sum>>16);

 

И по окончанию функции нам осталось только лишь сконвертировать сумму в формат big endian, затем её проинвертировать и вернуть из функции

 

  while (sum>>16) sum=(uint16_t)sum+(sum>>16);

  return ~be16toword((uint16_t)sum);

}

 

Добавим в функцию чтения IP-пакета переменную для результата, а также установим указатель на пакет

 

uint8_t ip_read(enc28j60_frame_ptr *frame, uint16_t len)

{

  uint8_t res=0;

  ip_pkt_ptr *ip_pkt = (void*)(frame->data);

 

Но так как в данной функции писать ещё много исходного кода, то отложим это дело до следующей части нашего занятия.

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Техническая документация:

Документация на микросхему ENC28J60

Перечень ошибок ENC28J60 (Errata)

 

 

Отладочную плату можно приобрести здесь STM32F103C8T6

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN

Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN. ENC28J60

 

2 комментария на “STM Урок 68. LAN. ENC28J60. Часть 8
  1. Сергей:

    Приведенную здесь функцию подсчета контрольной checksum суммы можно упростить…
    sum += (uint16_t) ((uint32_t)*ptr|*(ptr+1) <<8); — меняем местами указатели
    Это нам позволит избавиться от макроса "реверсирования" в конце функции
    return ~((uint16_t)sum);

  2. Сергей:

    а есть и более удобный способ подсчета контрольной суммы, который оперирует сразу с шестнадцатиразрядными данными, а не с байтами. Нашел в одном источнике

    uint16_t ip_check_sum(uint16_t *buf, uint8_t len)
    {
    uint32_t sum = 0;
    // Рассчитываем сумму word'ов блока
    while (len>1)
    {
    sum += *buf;
    buf ++;
    len -= 2;
    }

    if (len) // если остался нечетный байт
    sum += (uint8_t)*buf;

    // Складываем старший и младший word суммы
    // пока не получим число, влезающее в word
    while (sum >> 16)
    sum = (sum & 0xffff) + (sum >> 16);

    //Берём дополнение до единицы
    return ~((uint16_t)sum);
    }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*