Ну вот и настало время перейти нам на более совершенный 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
Смотреть ВИДЕОУРОК (нажмите на картинку)
Небольшое замечание и разъяснение по оператору дополнения строки «%-20s» нулями. числовой аргумент должен быть равным размеру буфера, если мы используем оператор переноса строки «\r\n\», то нужно уменьшить это значение на 2, т.к. к строке прибавляется два знака.
Здравствуйте! Уроки у вас вроде неплохо выглядят, но вот что страдает.
«Давайте начнём программировать этот урок! но для этого мы будем использовать проект из прошлого урока…»
я перехожу на другой урок, смотрю там:
«Давайте начнём программировать этот урок! но для этого мы будем использовать проект из прошлого урока…»
и так я перелопачиваю 4-5 уроков, не понимая, что там происходит, что там надо добавить/удалить.
Сделайте нормальные уроки, где стартуете с нуля. Какая связь между проектом по сетям и очередям?? Вы их никак не связываете, зачем тогда лопатить кучу кода?
А если постоянно делать с нуля, то будет три части в каждом уроке по настройке проекта. Из-за этого уроки будут выходить реже. Не каждый этого хотел бы.
Согласен с данным замечанием: очень много времени теряется на перепрыгивание с одного урока на другой (из предыдущих) и вспоминание того, что там делалось. Cub можно запускать с нуля не углубляясь в подробности. А далее указать: какие файлы (библиотеки) необходимо скопировать из предыдущих уроков.
За уроки огромное спасибо.
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.