Возвращаемся к теме передачи данных по проводным каналам связи и также возвращаемся к модулю передачи данных по 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
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий