STM Урок 135. LAN8742A. LWIP. SOCKET. TCP. Соединяем три контролера



Продолжаем работать с протоколом TCP и сегодня мы попытаемся уже созданные нами на прошлых занятиях сервер и клиент соединить между собой и научить их обмениваться данными. Думаю, такой урок может пригодиться в будущем, так как зачастую приходится соединять контроллеры между собой, не прибегая вообще к участию в сети компьютеров. Тем более, мы такими вещами уже неоднократно занимались, соединяя между собой платы, используя прежде изученные интерфейсы библиотеки стека протоколов LWIP. Только попробуем мы соединить не два, а три контроллера. Этим мы также занимались, правда использовали мы при этом интерфейс NETCONN и протокол UDP в уроке 123.

Начнём мы по традиции с сервера.

Плата для сервера будет использоваться STM32F746G-DISCOVERY, а проект за основу возьмём из урока 133 с именем LAN8742_TCP_SERVER_SOCKET. Имя проекту мы присвоим LAN8742_TCP_SERVER_SOCKET1, чтобы хоть немного были изменения и впоследствии данные проекты из разных занятий не перепутать.

Запустим наш проект в Cube MX и, ничего в нём не меняя, запустим генерацию проекта для System Workbench, в котором затем удалим при наличии отладочную конфигурацию, а также включим уровень оптимизации традиционно в 1. В файле main.c мы сначала уберём так же, как и в прошлом занятии, массивы на SDRAM.

Удалим здесь

 

unsigned char *out_buffer;

char *str_usart;

char *str_out;

unsigned char *send_ws_buf;

char* str_buf1;

char* str_buf2;

char* str_buf3;

 

Вместо этого добавим глобальные массивы в обычной ОЗУ

 

 

Также в функции main() удалим инициализацию массивов в памяти SDRAM

 

//str_usart 512 bytes

str_usart = (char *) 0xC0400000;

//str_out 32 bytes

str_out = (char *) 0xC0400200;

//out_buffer 192 bytes

out_buffer = (uint8_t *) 0xC0400220;

memset(out_buffer,0,192);

//send_ws_buf 1312 bytes

send_ws_buf = (uint8_t *)0xC04002E0;

//str_buf1 256 bytes

str_buf1 = (char *) 0xC0400800;

//str_buf2 256 bytes

str_buf2 = (char *) 0xC0400900;

//str_buf2 1024 bytes

str_buf3 = (char *) 0xC0400A00;

 

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

 

 

Для чего мы это делаем, я объяснял.

Из тела функции tcp_thread удалим вывод в USART информации

 

sprintf(str_usart,» socket: %d\r\n», accept_sock);

HAL_UART_Transmit(&huart1,(uint8_t*)str_usart,strlen(str_usart),0x1000);

 

В функции client_socket_thread добавим переменную для хранения количества системных квантов, которые нам передадут наши клиенты, и добавим указатель на структуру для очереди

 

 

Далее в этой же функции изменим имя массива для сохранения данных из приёмного буфера

 

ret = recvfrom(accept_sock, str_buf1, buflen, 0, (struct sockaddr *)&remotehost, &sockaddrsize);

 

Удалим полностью весь код из тела этого условия

 

 

Заберём число из буфера

 

 

Мы будем просить с клиентов закрывать соединение другим способом. Когда это потребуется, мы передадим ноль в качестве числа.

Поэтому на сервере обработаем такой случай, закрыв при этом соединение и уничтожив текущую задачу

 

 

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

 

 

Передадим в качестве ответа серверу число, в 1000 раз меньше, чем пришло от него, то есть преобразуем его в секунды

 

 

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

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

 

 

Проект для клиента мы возьмём из прошлого занятия с именем LAN8742_TCP_CLIENT_SOCKET и присвоим ему имя LAN8742_TCP_CLIENT_SOCKET1.

Запустим наш проект в Cube MX и, ничего в нём не меняя, запустим генерацию проекта для System Workbench, в котором затем удалим при наличии отладочную конфигурацию, а также включим уровень оптимизации в 1.

В файле main.c в функции send_thread удалим буфер

 

char buf[15] = {};

 

Также изменим порт сервера

 

remotehost.sin_port = htons(7);

 

Изменим также и сетевой адрес

 

ip4addr_aton("192.168.1.191",(ip4_addr_t*)&remotehost.sin_addr);

 

Удалим вывод информации в USART

 

sprintf(str_usart,«Connected\r\n»);

HAL_UART_Transmit(&huart1,(uint8_t*)str_usart,strlen(str_usart),0x1000);

 

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

 

 

Также исправим координату Y для вывода информации о разъединении

 

qstruct->y_pos = arg_sock->y_pos + 80;

 

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

А подготовку буфера старую и вывод в терминальную программу удалим

sprintf(buf,«%lu\r\n»,syscnt);

HAL_UART_Transmit(&huart1,(uint8_t*)buf,strlen(buf),0x1000);

А вместо этого теперь мы в нужную позицию выведем нашу информацию на дисплей

 

 

Отправку также немного подправим, так как мы уже отправляем не строку, а непосредственно число

 

write(sock,(void *) &syscnt, 4);

 

С отправкой закончили, поэтому переходим к приёму.

 

 

В теле функции recv_thread добавим локальную переменную

 

 

Удалим ноль

 

data_buffer[recv_data-1] = 0;

 

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

 

 

Отображение на дисплее также немного исправим, так как у нас была строка.

Во-первых, исправим строку

 

sprintf(qstruct->str,"%10lu", syscnt);

 

Во-вторых исправим вертикальную координату

 

qstruct->y_pos = arg_recv_socket->y_pos + 40;

 

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

 

 

По окончании передачи дисплей также нам отчитался

 

 

Теперь переходим ко второму клиенту.

В качестве второго клиента мы будем использовать плату STM32F4-Discovery вместе с платой расширения DIS-BB., а также по I2C подключим к ней дисплей LCD1602.

Плату первого клиента отключим от ПК, запитаем от независимого источника, а плату второго клиента теперь соединим по USB с ПК, а также по интерфейсу LAN с коммутатором

 

 

Проект для второго клиента возьмём из урока 123 с именем LAN8720_UDP_CLIENT_NETCONN и присвоим ему имя LAN8720_TCP_CLIENT_SOCKET2.

Откроем наш проект в Cube MX и в настройках FREERTOS немного исправим размер кучи (я так понял, в схеме распределения памяти heap_5 это размер одного региона), а также изменим схему распределения памяти

 

 

Сохраним настройки.

Зайдём теперь в настройки LWIP и кое-что там изменим в разделе Key Options, чтобы они были одинаковыми у всех участников нашей сети, так лучше.

Добавим размер кучи

 

 

В TCP установим следующие параметры

 

 

Немного вернёмся назад и количество одновременно открытых сегментов убавим до 16

 

 

Сгенерируем проект для System Workbench, откроем его там, затем удалим при наличии отладочную конфигурацию, а также включим уровень оптимизации в 1.

 

 

В файле main.c подключим библиотеку API SOCKET

 

 

Для пущего порядка поднимем макрос на самый верх секции

 

 

Добавим идентификатор задачи приёма пакетов

 

 

Старую структуру соединения удалим вместе с переменной её типа

 

typedef struct struct_conn_t {

uint32_t conn;

uint32_t buf;

} struct_conn;

struct_conn conn01;

 

Вместо этого добавим две новые структуры с переменными, как и в проекте для первого клиента

 

 

Мы в основном весь основной код теперь заимствуем из проекта первого клиента.

Объявим массивы регионов для кучи

 

 

Удалим вот этот прототип

void udp_receive_callback(struct netconn* conn, enum netconn_evt evt, u16_t len);

Также удалим и одноимённую функцию udp_receive_callback вместе с телом.

В функции вывода строки на дисплей TaskStringOut очистим память элемента очереди

 

 

В main() не забываем проинициализировать нашу кучу

 

 

Выше функции send_thread добавим функцию для задачи приёма пакетов от сервера сразу с кодом, так как он практически полностью взят из проекта первого клиента

 

 

В функции send_thread также заменим тело

 

 

Функцию udp_thread удалим вместе с телом.

В функции StartDefaultTask удалим создание задачи и добавим туда другой код

 

 

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

 

 

Также по окончанию передачи дисплей перед нами отчитается об этом

 

 

Перезагрузим оба клиента одновременно, передача прекрасно видна на сервере с обоих клиентов

 

 

Таким образом, мы сегодня создали сеть из 3 узлов на платах с контроллерами STM32 и убедились в её работоспособности.

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

 

 

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

 

Исходный код сервера

Исходный код первого клиента

Исходный код второго клиента

 

 

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

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

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

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

Дисплей LCD 16×2 можно приобрести тут: LCD 16×2

Переходник I2C to LCD1602 2004 можно приобрести здесь: I2C to LCD1602 2004

 

 

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

 

STM LAN8742A. LWIP. SOCKET. TCP. Соединяем три контролера

2 комментария на “STM Урок 135. LAN8742A. LWIP. SOCKET. TCP. Соединяем три контролера
  1. Yuriy_V:

    Очень информационно насыщенные и так необходимые многим уроки. Спасибо большое от читателей.
    А автор планирует рассказать о протоколе SNMP, которому незаслуженно мало уделяется внимания особенно на уровне «железа»…?

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

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

*