AVR Урок 49. LAN. ENC28J60. TCP Server. Передаём данные

 

 

 

 

Урок 49

 

LAN. ENC28J60. TCP Server. Передаём данные

 

 

На прошлом занятии мы плотно познакомились с протоколом передачи данных TCP и научились устанавливать и разрывать соединение, так как это является неотъемлемым условием данного протокола.

Теперь перед нами встала задача следующая — а как же нам что-то передать полезное с помощью данного протокола. Иначе зачем он тогда нужен.

Вот этим мы сегодня и займёмся.

Проект мы назовём теперь ENC28J60_TCPS_DATA, а файлы с исходным кодом будем использовать из проекта предыдущего занятия с именем ENC28J60_TCPS_CONNECT.

Скомпилируем наш проект, прошьём контроллер и пока, не добавляя никакого кода попробуем послать данные в модуль.

Для этого мы сегодня уже попробуем другую утилиту — Putty, которую можно всегда скачать с официального сайта, и которая является абсолютно бесплатной.

Сначала, как обычно, запустим терминальную программу, в которой соединимся с нашим контроллером, а также запустим утилиту анализа сетевого трафика WireShark, в которой также подключимся к нашему модулю. Затем запустим утилиту Putty, в которой заполним IP-адрес модуля и порт 80, выберем тип соединения RAW, а также запретим закрывать окно автоматически

 

image00

 

Мы можем данные настройки сохранить, добавить придуманное имя нашего соединения нажав кнопку Save, После чего появится строка с нашими сохранёнными настройками в списке соединений

 

image01

 

После этого при запуске программы мы можем выбирать данную строку и, нажимая кнопку Load, восстанавливать все наши настройки и не вводить из каждый раз заново.

Теперь мы нажимаем кнопку Open и у нас откроется окно с командной строкой

 

image02

 

Также в момент открытия нашего окна соединение также открывается, о чём свидетельствуют следующие строки в WireShark

 

image03

 

Попробуем ввести какую-нибудь строку и нажать кнопку Enter

 

image04

 

Мы получим следующие несколько строк в Wireshark (нажмите на картинку для увеличения изображения)

 

image05_0500

 

Всё дело в том, что клиент нам пытается отправить данные, но так как установлен флаг ACK, то это означает, что клиент также от нас ещё ждёт и подтверждения. Данные попытки не могут продолжаться бесконечно и, не дождавшись подтверждения, клиент рвёт с нами соединение. Так же об этом рапортует и Putty

 

image06

 

Но нам это не важно сейчас, всё это мы впоследствии обработаем. Зато мы сейчас можем проанализировать, что именно нам передал клиент и с какими флагами. Мы видим также, что помимо флага ACK в пакете также присутствует флаг PSH, означающий, что данные мы должны сразу обработать.

Поэтому закрываем утилиту Putty и идём в наш проект, а в нём сначала попытаемся отобразить в терминальной программе пришедшую строку в качестве данных в пакете.

Для этого мы в функции tcp_read в файле tcp.h сначала проведём некоторые корректировки.

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

 

uint8_t res=0;

uint16_t len_data=0;

uint16_t i=0;

 

Строки подключения к пакетам перенесём выше, так как они нам нужны будут уже сейчас

 

uint16_t i=0;

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

tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);

 

Посчитаем длину данных в пакете, вычтя из длины пакета IP, которая находится в соответствующем поле включая данные, длину заголовка IP (20) и длину заголовка TCP, в которую входят также опции, а данные не входят

 

tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);

len_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);

 

И теперь мы отобразим в терминальной программе всё, что собирались отобразить

 

len_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);

sprintf(str1,"%ld.%ld.%ld.%ld-%ld.%ld.%ld.%ld %d tcp\r\n",

  ip_pkt->ipaddr_src & 0x000000FF,(ip_pkt->ipaddr_src>>8) & 0x000000FF,

  (ip_pkt->ipaddr_src>>16) & 0x000000FF, ip_pkt->ipaddr_src>>24,

  ip_pkt->ipaddr_dst & 0x000000FF,(ip_pkt->ipaddr_dst>>8) & 0x000000FF,

  (ip_pkt->ipaddr_dst>>16) & 0x000000FF, ip_pkt->ipaddr_dst>>24, len_data);

USART_TX((uint8_t*)str1,strlen(str1));

 

 

Далее создадим условие, в которое мы зайдём, если в пакете будут данные, то есть результат будет ненулевой

 

USART_TX((uint8_t*)str1,strlen(str1));

//Если есть данные, то покажем их в терминальной программе

if (len_data)

{

}

 

Отобразим в терминальной программе данные в теле условия и перейдём на новую строку

 

if (len_data)

{

  for (i=0;i<len_data;i++)

  {

    USART_Transmit(tcp_pkt->data[i]);

  }

  USART_TX((uint8_t*)"\r\n",2);

}

 

Соберём в очередной раз код и прошьём контроллер, затем соединимся с сервером и пошлём ту же строку

 

image07

 

Мы видим, что данные приходят, причём почему-то сначала в данных находится только строка, а затем ещё и перевод строки с возвратом каретки.

Вообщем, строка к нам пришла, осталось нам подтвердить этот пакет, причём если пришла именно такая строка, то мы ещё и ответим на неё другой строкой, тем самым испытаем передачу данных в другом направлении.

В файле tcp.h добавим ещё один код операции

 

#define TCP_OP_ACK_OF_RST 3

#define TCP_OP_ACK_OF_DATA 4

 

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

Не выходя из условия обнаружения данных в функции tcp_read, добавим код, согласно которому мы будем отвечать на пакет

 

USART_TX((uint8_t*)"\r\n",2);

//Если включен флаг подтверждения, то подтвердим приём данных

if (tcp_pkt->fl&TCP_ACK)

{

  tcp_send(ip_pkt->ipaddr_src, be16toword(tcp_pkt->port_src), TCP_OP_ACK_OF_DATA);

}

 

Перейдём теперь в функцию отправки tcp_send и добавим там сначала ещё одну локальную переменную для размера полезных данных пакета TCP

 

uint32_t num_seg=0;

uint16_t sz_data=0;

 

Добавим там новое условие реакции на наш четрвёртый код опции

 

else if (op==TCP_OP_ACK_OF_DATA)

{

}

return res;

 

В тело данного условия мы скопируем код из предыдущего условия до первой отправки данных, а затем будем просто изменять его в некоторых местах

Сначала в самом начале тела условия вставим строку для сохранения размера полезных данных в переменную

 

//Заполним заголовок пакета TCP

sz_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);

tcp_pkt->port_dst = be16toword(port);

 

И вот здесь вместо единички мы прибавим этот размер

 

tcp_pkt->num_ask = be32todword(be32todword(tcp_pkt->bt_num_seg) + sz_data);

 

Пока больше ничего не трогаем, попробуем собрать код и прошить контроллер и ввести ту же строку и посмотреть, прийдет ли наше подтверждение, так как если мы ошибёмся например в контрольной сумме, то клиенту наше подтверждение не понравится (нажмите на картинку для увеличения изображения)

 

image08_0500

 

Нам отправилось целых 2 пакета, и оба от нас подтверждены. Отлично!

Теперь давайте в ответ именно на эту строку ответим клиенту. Для этого продолжаем наше тело условия. А в нём создадим ещё одно условие

 

enc28j60_packetSend((void*)frame,len);

//Если пришло "Hello!!!", то отправим ответ

if (!strcmp((char*)tcp_pkt->data,"Hello!!!"))

{

}

 

А в тело уже этого добавленного условия мы добавим код, который занесёт в поле данных строку с нашим ответом и отправит пакет (флаги поставим такие же как ставил клиент при отправки нам строки)

 

if (!strcmp((char*)tcp_pkt->data,"Hello!!!"))

{

  strcpy((char*)tcp_pkt->data,"Hello to TCP Client!!!\r\n");

  tcp_pkt->fl = TCP_ACK|TCP_PSH;

  sprintf(str1,"hdr_len:%d\r\n",sizeof(tcp_pkt_ptr));

  USART_TX((uint8_t*)str1,strlen(str1));

  len = sizeof(tcp_pkt_ptr);

  tcp_pkt->len_hdr = len << 2;

  len+=strlen((char*)tcp_pkt->data);

  tcp_pkt->cs = 0;

  tcp_pkt->cs=checksum((uint8_t*)tcp_pkt-8, len+8, 2);

  //Заполним заголовок пакета IP

  len+=sizeof(ip_pkt_ptr);

  ip_pkt->len=be16toword(len);

  ip_pkt->cs = 0;

  ip_pkt->cs = checksum((void*)ip_pkt,sizeof(ip_pkt_ptr),0);

  len+=sizeof(enc28j60_frame_ptr);

  enc28j60_packetSend((void*)frame,len);

}

 

В очередной раз соберём код и прошьём контроллер и проверим наш "чат", в котором при попытке ввести строку "Hello!!!" нам сервер наш будет отвечать своей строкой, а при попытке ввода другой строки (в нашем случае почти такая же, но только с двумя восклицательными знаками, сервер нам уже строкой не отвечает

 

image09

 

  В терминальной программе мы также видим, что сервер нам уже строку несколько раз не шлёт

 

image10

 

Завершим соединение, закрыв Putty. Можно также посмотреть в WireShark, что оно завершилось корректно двумя двухкратными рукопожатиями.

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

 

else if (tcp_pkt->fl == (TCP_PSH|TCP_ACK))

{

  //Если данных нет

  if(!be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2))

  {

    tcp_send(ip_pkt->ipaddr_src, be16toword(tcp_pkt->port_src), TCP_OP_ACK_OF_FIN);

  }

}

else if (tcp_pkt->fl == TCP_ACK)

 

Соберём в очередной раз код и проверим его работоспособность.

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

 

Предыдущий урок Программирование МК AVR Следующий урок

 

Исходный код

 

 

Приобрести плату Arduino UNO R3 можно здесь.

Программатор (продавец надёжный) USBASP USBISP 2.0

Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.

 

 

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

 

AVR LAN. ENC28J60. TCP Server. Передаём данные

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

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

*