STM Урок 98. LAN8720. LWIP. TCP Server. Часть 2

 

 

 

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

 

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

 

static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)

{

  struct server_struct *es;

 

Присвоим ей аргументы

 

err_t ret_err;

LWIP_ASSERT("arg != NULL",arg != NULL);

es = (struct server_struct *)arg;

 

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

 

es = (struct server_struct *)arg;

if (p == NULL)

{

}

else if(err != ERR_OK)

{

}

else if(es->state == ES_ACCEPTED)

{

}

else if (es->state == ES_RECEIVED)

{

}

else if(es->state == ES_CLOSING)

{

}

else

{

}

return ret_err;

 

И начнём теперь эти условия обрабатывать.

Сначала тело первого условия

 

if (p == NULL)

{

  es->state = ES_CLOSING;

  if(es->p == NULL)

  {

    tcp_recved(tpcb, p->tot_len);

  }

  else

  {

    //acknowledge received packet

    tcp_sent(tpcb, tcp_server_sent);

    //send remaining data

    tcp_server_send(tpcb, es);

  }

  ret_err = ERR_OK;

}

 

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

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

 

 

Вроде бы одно починили, другое сломали. Но так может показаться только на первый взгляд. Теперь разъединение будет происходить более осознанно, так что починим и то, что «сломали».

Теперь следующее условие

 

else if(err != ERR_OK)

{

  if (p != NULL)

  {

    es->p = NULL;

    pbuf_free(p);

  }

  ret_err = err;

}

 

Это случай, если пришел статус ошибки, мы его передаём в возврат функции и освобождаем буферы.

Следующее условие

 

else if(es->state == ES_ACCEPTED)

{

  tcp_recved(tpcb, p->tot_len);

  strncpy(str1,p->payload,p->len);

  str1[p->len] = '\0';

  HAL_UART_Transmit(&huart6, (uint8_t*)str1,strlen(str1),0x1000);

  ret_err = ERR_OK;

}

 

Это случай, если пришел нормальный пакет с. Мы проверяем статус, и после этого попадаем в тело условия, принимаем данные из буфера контроллера в буфер структуры, затем копируем его в строковый массив, завершаем нулём и отображаем в терминальной программе.

Можно опять собрать код и прошить контроллер. Также запустим терминальную программу с теми же настройками как и в уроке по клиенту TCP, соединимся с сервером и попытаемся ему передать строку из командной строки Putty

 

 

Посмотрим, пришла ли строка в терминальную программу

 

 

Строку мы получили. Отлично. Разъединяемся с сервером и пишем дальше наш код.

Напишем тело следующего условия

 

else if (es->state == ES_RECEIVED)

{

  if(es->p == NULL)

  {

    ret_err = ERR_OK;

  }

  else

  {

    struct pbuf *ptr;

    ptr = es->p;

    pbuf_chain(ptr,p);

  }

  ret_err = ERR_OK;

}

 

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

Следующее условие

 

else if(es->state == ES_CLOSING)

{

  tcp_recved(tpcb, p->tot_len);

  es->p = NULL;

  pbuf_free(p);

  ret_err = ERR_OK;

}

 

Это условие, когда нужно обработать статус разъединения. Мы отправляем подтверждение, и обнуляем все соответствующие указатели, а также освобождаем память буфера.

 

 

Ну и последнее условие — это когда не сходится с остальными, какой-то особый случай

 

else

{

  tcp_recved(tpcb, p->tot_len);

  es->p = NULL;

  pbuf_free(p);

  ret_err = ERR_OK;

}

 

В этом случае мы делаем то же самое.

Теперь напишем тело следующего обработчика, вызываемого в случае передачи пакета — tcp_server_sent

 

static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)

{

  struct server_struct *es;

  LWIP_UNUSED_ARG(len);

  es = (struct server_struct *)arg;

  es->retries = 0;

  if(es->p != NULL)

  {

    tcp_server_send(tpcb, es);

  }

  else

  {

    if(es->state == ES_CLOSING)

      tcp_server_connection_close(tpcb, es);

  }

  return ERR_OK;

}

 

То же самое — указатель на структуру, присваиваем аргументы, если буфер не пустой, то передаём, если пусто да и плюс если статус на разъединение, то разъединяемся.

Пишем тело следующей функции — tcp_server_poll. Это обработчик, который вызывается через определённое время для мониторига сети, не важно, есть ли пакеты, передаваемые в данный момент в ту или иную сторону или нет

 

err_t ret_err;

struct server_struct *es;

es = (struct server_struct *)arg;

HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);

 

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

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

Нет, он конечно перестанет, но не скоро, когда клиенту надоест ждать команды на разъединения и он передаст серверу флаг RST.

Напишем остальной код нашей функции

 

HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);

if (es != NULL)

{

  if (es->p != NULL)

  {

  }

  else

  {

    if(es->state == ES_CLOSING)

    {

      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);

      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);

      tcp_server_connection_close(tpcb, es);

    }

    if(usartprop.is_text==1)

    {

      usartprop.is_text=0;

    }

  }

  ret_err = ERR_OK;

}

else

{

  tcp_abort(tpcb);

  ret_err = ERR_ABRT;

}

return ret_err;

 

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

Если мы теперь соберём код, прошьём контроллер, соединимся с сервером, а затем разъединимся, то мы увидим, что процесс разъединения происходит корректно

 

 

Осталось нам лишь только научить сервер в любой момент передавать строки клиенту.

Для этого напишем тело функции передачи строки

 

void sendstring(char* buf_str)

{

  usartprop.is_text=1;

  tcp_sent(ss->pcb, tcp_server_sent);

  tcp_write(ss->pcb, (void*)buf_str, strlen(buf_str), 1);

  tcp_recved(ss->pcb, strlen(buf_str));

  usartprop.usart_cnt=0;

}

 

Мы сначала устанавливаем статус передачи текста, затем показываем нашей структуре «дорого» к функции-обработчику передачи данных, далее мы записываем нашу строку в буфер структуры TCP, затем производим передачу. Ну и по окончании сбрасываем счётчик USART.

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

Для этого мы введём строку в терминальной программе и передадим её в USART. Контроллер обработает данную строку и сначала он её эхом отправит обратно в USART

 

 

А потом отправим серверу, что мы и видим в окне Putty

 

 

По идее, для полного порядка мы при разъединении с клиентом должны освобождать и обнулять нашу глобальную структуру, но так как мы пока работаем с одним соединением, то это делать необязательно, думаю, ничего с памятью не случится, так как по большому счёту — наша структура лишь указатель, она сама по себе память не занимает. Поэтому цель урока считаю достигнутой, с чем вас и поздравляю! Мы создали простой сервер, который способен реагировать на желание клиента создать TCP-соединение с ним, также разъединяться, и также, что самое главное — сервер способен принимать и передавать пакеты с данными по протоколу TCP.

Всем спасибо за внимание!

 

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

 

Исходный код

 

 

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

Модуль LAN можно приобрести здесь: LAN8720

Плату расширения можно приобрести здесь: STM32F4DIS-BB

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

 

 

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

 

STM LAN8720. LWIP. TCP Server

2 комментария на “STM Урок 98. LAN8720. LWIP. TCP Server. Часть 2
  1. Roman:

    Здравствуйте!
    Использую похожий модуль dp83848. Есть ли у данных модулей возможность отслеживания приема данных по прерываниям а не в цикле? У меня через приложение с компьютера отправляются данные через lan на контроллер. И в цикле выполняется алгоритм. Важно быстродействие, поэтому можно как-то убрать проверку приема данных из while? Наподобие как с модулями wiznet, enc28j60.

    • Mikhail:

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

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

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

*