STM Урок 118. LAN8720. LWIP. RAW. UDP Client



Возвращаемся к теме передачи данных по проводным каналам связи и также возвращаемся к модулю передачи данных по LAN — LAN8720.

И, прежде чем перейти к интерфейсу NETCONN API, давайте ещё раз поработаем с интерфейсом RAW API и напишем простенький UDP Client.

Во-первых, это вызвано огромным количеством просьб по данному протоколу, а во-вторых, мы, скорее всего, сначала в NETCONN API сначала будем работать с протоколом UDP и напишем сервер, и для этого нам обязательно потребуется и клиент. Вот мы всё это потом и соединим.

Плата для нашего урока у нас будет та же самая — STM32F4-Discovery, а микросхему LAN8720 мы будем использовать расположенную на плате DIS-BB. Вы, конечно же, можете использовать и отдельный модуль от WaveShare, только не забудьте в настройках в Cube включить единичку в поле «PHY Addres» в настройках ETH.

Так как мы уже давно прекрасно знаем, что из себя представляет протокол транспортного уровня UDP, а также неплохо себя чувствуем в программировании API RAW стека протоколов LWIP, то можем, в принципе, сразу перейти к проекту.

Чтобы нам не мучиться с настройками проекта, проект мы сделаем на основе проекта урока 96 LAN8720_TCP_CLIENT и назовём его LAN8720_UDP_CLIENT_RAW.

Откроем наш проект в Cube MX, перейдём в Configuration в настройки ETH, убедимся, что там стоит 0 в физическом адресе, а также немного изменим MAC-адрес

 

 

Затем перейдём настройки таймера и настроим там период приблизительно в 1 секунду

 

 

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

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

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

Если проект нормально собрался, то начнём с ним работать.

Перейдём в файл net.c и удалим там весь код кроме подключения заголовочного файла. Останется только это

 

#include "net.h"

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

 

Добавим обработчик от таймера, в котором пока только изменим уровень ножки синего светодиода

 

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

void TIM1_Callback(void)

{

  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_15);

}

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

 

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

 

#include "net.h"

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

struct udp_pcb *upcb;

char str1[30];

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

void udp_client_connect(void)

{

  ip_addr_t DestIPaddr;

  err_t err;

  upcb = udp_new();

}

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

 

Перейдём в заголовочный файл net.h, уберём там ненужные прототипы функций и добавим прототипы добавленных нами функций

 

void net_ini(void);

void UART6_RxCpltCallback(void);

void udp_client_connect(void);

void TIM1_Callback(void);

 

Структуру, объявленную ниже также удалим.

 

 

Также поменяем подключаемую библиотеку tcp на udp

 

#include "lwip/udp.h"

 

Идём теперь в файл main.c, из функции main() уберём вызов функции инициализации и вызов функции приёма байта из USART, а вызовем вместо всего этого функцию организации соединения UDP

 

net_ini();

HAL_UART_Receive_IT(&huart6,(uint8_t*)str,1);

udp_client_connect();

 

Из функции-обработчика прерываний от USART удалим вызов нашей функции-обработчика

 

if(huart==&huart6)

{

  UART6_RxCpltCallback();

}

 

После данной функции добавим ещё функцию-обработчик прерываний от таймера, в которой вызовем нашу самодельную функцию-обработчик

 

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

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

    TIM1_Callback();

  }

}

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

 

На данном этапе давайте соберём проект, прошьём контроллер и попробуем попинговать нашу плату с целью проверки её доступности

 

 

Всё нормально! Модуль и плата доступны.

Вернёмся в файл net.c и добавим в ней прототип функции-обработчика пришедших пакетов по UDP

 

char str1[30];

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

void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);

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

 

Продолжаем писать функцию udp_client_connect.

Добавим условие проверки создания структуры

 

upcb = udp_new();

if (upcb!=NULL)

{

}

 

В теле данного условия инициализируем переменную IP сервера, которому мы будем отправлять пакеты, и от которого будем также их принимать. В качестве сервера у нас будет ПК, поэтому адрес будем использовать именно его

 

if (upcb!=NULL)

{

  IP4_ADDR( &DestIPaddr, 192, 168, 1, 87);

}

 

 

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

 

IP4_ADDR( &DestIPaddr, 192, 168, 1, 87);

upcb->local_port = 1555;

 

Вызовем функцию соединения, в которой в качестве одного из входных аргументов передадим также и порт сервера (в нашем случае программы на ПК)

 

upcb->local_port = 1555;

err= udp_connect(upcb, &DestIPaddr, 1556);

 

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

 

err= udp_connect(upcb, &DestIPaddr, 1556);

if (err == ERR_OK)

{

  udp_recv(upcb, udp_receive_callback, NULL);

}

 

После данной функции добавим функцию передачи пакета, в теле которой пока только объявим указатель на структуру буфера

 

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

void udp_client_send(void)

{

  struct pbuf *p;

}

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

 

После функции передачи пакета добавим функцию—обработчик пришедших пакетов по UDP

 

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

void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)

{

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

  str1[p->len]=0;

  pbuf_free(p);

  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);

}

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

 

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

Вызовем функцию передачи пакета из функции-обработчика прерываний таймера

 

void TIM1_Callback(void)

{

  udp_client_send();

 

Теперь вернёмся в функцию передачи пакета udp_client_send и что-нибудь попробуем там передать.

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

 

struct pbuf *p;

sprintf(str1,"%lurn",HAL_GetTick());

 

Выделим память под данные в буфере

 

sprintf(str1,"%lurn",HAL_GetTick());

p = pbuf_alloc(PBUF_TRANSPORT, strlen(str1), PBUF_POOL);

 

Если выделение памяти прошло успешно, то передадим нашу строку серверу и освободим буфер

 

p = pbuf_alloc(PBUF_TRANSPORT, strlen(str1), PBUF_POOL);

if (p != NULL)

{

  pbuf_take(p, (void *) str1, strlen(str1));

  udp_send(upcb, p);

  pbuf_free(p);

}

 

Соберём код, прошьём контроллер и перейдём к ПК.

Зайдём в каталог с программой netcat и запустим команду cmd.

затем в командной строке мы запустим следюущую команду

 

 

Значение после параметра -p — это номер порта программы netcat. Оказывается, его также можно назначать, я только недавно узнал, изучая встроенную справку. Больше я такую информацию нигде не видел, а затем уже идёт IP-адрес платы, а затем номер порта нашего соединения в плате.

И, как мы можем наблюдать, пакеты приходят нормально. Вследствие очень сильной загруженности сети, в которой я всё это проверял, некоторые пакеты теряются. Мы это отчётливо видим. Например, после пакета со строкой «20792» идёт сразу пакет со строкой «22792», а это значит, что пакет «21792» у нас потерялся. А вообще, благодаря использованию таймера, у нас пакеты отправляются в строго заданное время, о чём свидетельствуют неизменные последние три цифры. Следовательно, таймер мы настроили правильно.

Теперь давайте что-нибудь передадим из ПК в плату. Для этого введём в командной строке, не разрывая соединение какую-нибудь строку и нажмём клавишу «Enter»

 

 

После этого на плате должен засветиться зелёный светодиод

 

 

Если мы отправим ещё пакет, то светодиод должен будет потухнуть.

Давайте всё-таки прочитаем из нашего контроллера переданную в него из ПК строку. Для этого поставим точку останова в функции-обработчике приёма пакета вот в этом месте

 

 

Запустим отладку с помощью вот такой кнопочки

 

 

Затем запустим проект на выполнение в отладке

 

 

Проект начнёт работать, пакеты также будут уходить в ПК по сети.

Отправим из ПК такую же строку. После этого мы должны будем попасть в точку останова

 

 

Добавим наш строчный массив в Expression и посмотрим его содержимое

 

 

Всё соответствует.

Остановим отладку, вернёмся в обычное представление и запустим наш проект на выполнение. Теперь мы можем дальше наслаждаться приёмом отправленных строк в ПК.

Таким образом, сегодня мы написали несложный клиент для работы с протоколом UDP.

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

 

 

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

 

Исходный код

 

 

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

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

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

 

 

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

 

STM LAN8720. LWIP. RAW. UDP Client

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

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

*