STM Урок 120. LAN8742A. LWIP. NETCONN. UDP Server



Ну вот и настало время перейти нам на более совершенный API стека протоколов LWIP — NETCONN.

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

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

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

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

Работать мы будем с отладочной платой STM32F746-Discovery.

Чтобы нам не настраивать хотя бы операционную систему, проект мы сделаем из проекта урока 111 по работе с очередями TASKS_QUEUES и назовём его LAN8742_UDP_SERVER_NETCONN. Поэтому нам останется лишь только настроить стек протоколов LWIP и сетевой интерфейс ETH.

Поэтому запустим наш проект в Cube MX и первым делом включим ETH

 

 

Никакие ножки мы не переопределяем.

Также включим библиотеку стека протоколов LWIP

 

 

Перейдём в Configuration и начнём с настройки ETH. Хотя микросхема в нашей плате и LAN 8742A, но разницы в программировании никакой нет. Поэтому вспоминаем наши уроки по LAN8720 и настраиваем интерфейс

 

 

Далее настраиваем LWIP. Пока пользуемся статической адресацией

 

 

В разделе Key Options можно ничего не менять.

Сгенерируем наш проект и откроем его в System Workbench.

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

Если всё собралось, то начнём с ним работать.

Первым делом, конечно же шапка.

Открываем файл main.c и в функции main() подправим шапку, удалив всё лишнее

 

TFT_DisplayString(0, 10, (uint8_t *)"UDP Server", CENTER_MODE);

TFT_SetTextColor(LCD_COLOR_MAGENTA);

TFT_DisplayString(14, 60, (uint8_t *)"Task1:", LEFT_MODE);

TFT_DisplayString(14, 110, (uint8_t *)"Task2:", LEFT_MODE);

TFT_DisplayString(14, 160, (uint8_t *)"Task3:", LEFT_MODE);

 

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

 

 

Первым делом подключим несколько модулей, необходимых для работы со стеком протоколов LWIP

 

#include "fonts.h"

#include "lwip/opt.h"

#include "lwip/arch.h"

#include "lwip/api.h"

 

Три задачи, с которыми мы работали ранее, мы удалим. Они нам не потребуются.

Соответственно, сначала удалим объявления их идентификаторов

 

osThreadId Task01Handle,Task02Handle,Task03Handle,TaskStringOutHandle;

 

 

Затем удалим прототип функции данных задач

 

void Task01(void const * argument);

 

Удалим код их создания из функции main()

 

osThreadDef(tsk01, Task01, osPriorityIdle, 0, 128);

Task01Handle = osThreadCreate(osThread(tsk01), (void*)&arg01);

osThreadDef(tsk02, Task01, osPriorityIdle, 0, 128);

Task02Handle = osThreadCreate(osThread(tsk02), (void*)&arg02);

osThreadDef(tsk03, Task01, osPriorityLow, 0, 128);

Task03Handle = osThreadCreate(osThread(tsk03), (void*)&arg03);

 

Удалим функцию задач со всем её телом

 

void Task01(void const * argument)

{

  . . .

}

 

Из функции задачи по умолчанию StartDefaultTask удалим тело бесконечного цикла, оставив там только задержку

 

for(;;)

{

  osDelay(1);

}

 

Удалим вот такую глобальную структуру

 

typedef struct struct_arg_t {

  char str_name[10];

  uint16_t y_pos;

  uint32_t delay_per;

} struct_arg;

 

Вместо неё напишем структуру для передачи задачам параметров UDP — порта и вертикальной координаты вывода пришедшей строки на дисплее.

 

typedef struct struct_sock_t {

  uint16_t y_pos;

  uint16_t port;

} struct_sock;

 

Удалим объявление переменных структуры, а вместо них добавим новые

 

struct_arg arg01, arg02, arg03;

struct_sock sock01, sock02;

 

Немного добавим количество элементов очереди

 

#define MAIL_SIZE (uint32_t) 5

 

В функции main() удалим инициализацию параметров

 

strcpy(arg01.str_name,"task1");

strcpy(arg02.str_name,"task2");

strcpy(arg03.str_name,"task3");

arg01.y_pos = 60;

arg02.y_pos = 110;

arg03.y_pos = 160;

arg01.delay_per = 1000;

arg02.delay_per = 677;

arg03.delay_per = 439;

 

Немного изменим функцию вывода строк на дисплей TaskStringOut.

Мы уберём вывод на дисплей количества системных квантов. Сегодня они нам не нужны

 

sprintf(str1,"%s %lu", qstruct->str, qstruct->tick_count);

 

Также немного сдвинем строку влево, а то вдруг наша информация не уместится

 

TFT_DisplayString(50, qstruct->y_pos, (uint8_t *)str1, LEFT_MODE);

 

 

После функции TaskStringOut добавим функцию для задач соединений UDP. Я думаю, все уже догадались, что у нас максимально будет 2 соединения, так как мы добавили 2 переменных структуры

 

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

static void udp_thread(void *arg)

{

 

  for(;;)

  {

    osDelay(1);

  }

}

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

 

В теле функции задачи по умолчанию StartDefaultTask удалим объявление переменной

 

uint32_t syscnt;

 

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

 

sock01.port = 7;

sock01.y_pos = 60;

sock02.port = 8;

sock02.y_pos = 180;

 

Создадим задачи

 

sock02.y_pos = 180;

sys_thread_new("udp_thread1", udp_thread, (void*)&sock01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

sys_thread_new("udp_thread2", udp_thread, (void*)&sock02, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

 

Мы использовали несколько другой приём для создания задачи. Он несколько упрощён по сравнению с традиционным. Мы явно не создаём промежуточную структуру с помощью функции osThreadDef. Без неё проще.

Также при создании задач мы передаём параметры с помощью переменной структуры.

Теперь займёмся непосредственно функцией задач для соединений udp_thread

 

static void udp_thread(void *arg)

{

  struct_out *qstruct;

  err_t err, recv_err;

  struct netconn *conn;

  struct netbuf *buf;

  ip_addr_t *addr;

  unsigned short port;

  struct_sock *arg_sock;

  arg_sock = (struct_sock*) arg;

  TFT_SetTextColor(LCD_COLOR_BLUE);

  conn = netconn_new(NETCONN_UDP);

 

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

Продолжим дальше.

Если инициализация структуры прошла нормально, то свяжем её с локальным IP-адресом и портом

 

conn = netconn_new(NETCONN_UDP);

if (conn!= NULL)

{

  err = netconn_bind(conn, IP_ADDR_ANY, arg_sock->port);

}

 

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

 

err = netconn_bind(conn, IP_ADDR_ANY, arg_sock->port);

if (err == ERR_OK)

{

}

else

{

  netconn_delete(conn);

}

 

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

А при положительном исходе мы попадаем в другой бесконечный цикл

 

if (err == ERR_OK)

{

  for(;;)

  {
  }

}

 

Пишем тело данного бесконечного цикла.

Попытаемся принять пакет

 

for(;;)

{

  recv_err = netconn_recv(conn, &buf);

 

Здесь мы будем висеть до тех пор, пока не придет пакет с клиента. Как только пакет придет, то мы провалимся дальше.

Дальше проверим, что пакет принялся нормально, то есть без ошибок

 

recv_err = netconn_recv(conn, &buf);

if (recv_err == ERR_OK)

{

}

 

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

 

if (recv_err == ERR_OK)

{

  addr = netbuf_fromaddr(buf);

  port = netbuf_fromport(buf);

 

Затем создадим соединение с ними

 

port = netbuf_fromport(buf);

netconn_connect(conn, addr, port);

 

Вооще-то, соединение — это громко сказано. Просто имя функции такое. А на самом деле с помощью неё лишь только наши адреса присвоятся определённым полям структуры conn.

Далее отобразим порт клиента и также пришедшую строку от клиента, затем очистим память структуры очереди

 

netconn_connect(conn, addr, port);

qstruct = osMailAlloc(strout_Queue, osWaitForever);

qstruct->y_pos = arg_sock->y_pos;

sprintf(qstruct->str,"%5u %-20s",port, (char*) buf->p->payload);

//Пробел вместо переноса строки

qstruct->str[5 + strlen((char*) buf->p->payload)] = ' ';

osMailPut(strout_Queue, qstruct);

osMailFree(strout_Queue, qstruct);

 

Мы применили интересный формат %-20s, с помощью которого мы забиваем пробелами оставшиеся элементы строки. Знак минуса означает — выравнивание по левому краю. В случае его отсутствия строка выравнивается по правому краю и пробелами заполняются символы слева. Затем мы также заменили пробелом пришедший от клиента перенос строки.

И по окончании отправим строку назад клиенту в качестве эха и освободим память буфера

 

  osMailFree(strout_Queue, qstruct);

  netconn_send(conn,buf);

  netbuf_delete(buf);

}

 

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

 

 

Пошлём какую-нибудь строку серверу, стараясь не превышать 20 символов

 

 

Как мы смогли заметить, строка клиенту вернулась, значит сервер функционирует нормально.

Также посмотрим информацию на дисплее нашей платы

 

 

Всё пришло и отобразилось!

Запустим ещё одну сессию программы netcat, не разъединяясь с портом 7, и соединимся в ней с портом 8 сервера

 

 

Передадим и здесь строку серверу

 

 

Здесь мы также видим, что строка вернулась, значит второе соединение на сервере также отлично функционирует.

Посмотрим информацию на дисплее

 

 

Сюда также всё пришло!

Таким образом, в данном уроке мы сегодня создали простой, но вполне функционирующий сервер UDP с помощью интерфейса NETCON библиотеки стека протоколов LWIP. Оказалось, что создать данный сервер очень легко, намного легче, чем с применением интерфейса RAW.

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

 

 

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

 

Исходный код

 

 

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

 

 

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

 

STM LAN8720. LWIP. NETCONN. UDP Server

5 комментариев на “STM Урок 120. LAN8742A. LWIP. NETCONN. UDP Server
  1. Георгий:

    Небольшое замечание и разъяснение по оператору дополнения строки «%-20s» нулями. числовой аргумент должен быть равным размеру буфера, если мы используем оператор переноса строки «\r\n\», то нужно уменьшить это значение на 2, т.к. к строке прибавляется два знака.

  2. Иван:

    Здравствуйте! Уроки у вас вроде неплохо выглядят, но вот что страдает.
    «Давайте начнём программировать этот урок! но для этого мы будем использовать проект из прошлого урока…»

    я перехожу на другой урок, смотрю там:
    «Давайте начнём программировать этот урок! но для этого мы будем использовать проект из прошлого урока…»

    и так я перелопачиваю 4-5 уроков, не понимая, что там происходит, что там надо добавить/удалить.

    Сделайте нормальные уроки, где стартуете с нуля. Какая связь между проектом по сетям и очередям?? Вы их никак не связываете, зачем тогда лопатить кучу кода?

    • А если постоянно делать с нуля, то будет три части в каждом уроке по настройке проекта. Из-за этого уроки будут выходить реже. Не каждый этого хотел бы.

      • Ильяс:

        Согласен с данным замечанием: очень много времени теряется на перепрыгивание с одного урока на другой (из предыдущих) и вспоминание того, что там делалось. Cub можно запускать с нуля не углубляясь в подробности. А далее указать: какие файлы (библиотеки) необходимо скопировать из предыдущих уроков.
        За уроки огромное спасибо.

  3. hello,
    what can be maximum TOTAL HEAP SIZE in freertos system?When i increase the total heap size, the system is not work.How can i handle this problem.

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

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

*