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 с адаптером можно здесь USBASP USBISP 3.3 с адаптером

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

 

 

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

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

 

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

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

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

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

*