В предыдущей части нашего урока мы работали над проектом для клиентского МК и проверили данный проект, соединившись с сервером, роль которого пока выполнял компьютер.
Переходим теперь к серверу.
Проект для сервера мы создадим из проекта урока 98 с именем LAN8720_TCP_SERVER и назовём его LAN8720_TCP_SERVER_LCD.
Откроем данный проект в Cube MX, включим I2C
Затем перейдём в Configuration сначала в ETH. Здесь нам надо теперь не только выставить адрес PHY 0, так как у нас DIS-BB, но и ещё немного изменить MAC-адрес, а то они у нас с клиентом одинаковые, сеть такого не простит
То же самое и с адресом сетевым — IP. Он тоже должен быть у всех участников сетевых соединений разный, но в одной сетевой маске. То есть если у нас маска 255.255.255.0, то получается, что первые 24 бита у них должны быть одинаковые, а последний байт — разный. Поэтому изменим данный адрес, зайдя в настройки LWIP
Также зайдём в NVIC и поставим прерываниям от таймера приоритет поменьше (цифра наоборот должна быть больше)
Сгенерируем проект, откроем его в System Workbench, настроим аналогичным образом, как и клиент и попробуем также его собрать.
Пока также воткнём в сервер сетевой кабель для связи с ПК, чтобы его отладить. У кого-то этот кабель идёт напрямую из ПК, у кого-то хабы, маршрутизаторы, не важно, главное чтобы ПК нашу плату с сервером видел.
Теперь начнём работать с самим проектом.
Сначала скопируем файлы lcd.h и lcd.c из проекта по HC-05 урока 99 с именем HC_05_MASTER в соответствующие каталоги.
Перейдём в файл lcd.h и исправим там подключение библиотеки HAL
#include "stm32f4xx_hal.h"
Перейдём в файл main.c и подключим нашу библиотеку дисплея
#include "net.h"
#include "lcd.h"
/* USER CODE END Includes */
Инициализируем наш дисплей в функции main()
HAL_TIM_Base_Start_IT(&htim2);
LCD_ini();
LCD_Clear();
net_ini();
Старт таймера перенесём ниже
tcp_server_init();
HAL_TIM_Base_Start_IT(&htim2);
Чтобы проверить работу дисплея добавим следующие строки в код
net_ini();
LCD_StrBottom("String 1");
LCD_StrBottom("String 2");
LCD_StrBottom("String 3");
LCD_StrBottom("String 4");
Соберём код, прошьём контроллер и проверим работоспособность нашего дисплея
Дисплей испытан, теперь можно удалить данные строки.
Также как и в случае с клиентом в бесконечном цикле мы вызов двух функций объединим в вызов одной функции
/* USER CODE BEGIN 3 */
MX_LWIP_Process();
}
В net.h также подключим библиотеку работы с дисплеем
#include "lwip/tcp.h"
#include "lcd.h"
Перейдём в файл net.c и добавим парочку глобальных переменных
__IO uint32_t message_count=0, timeout_count=0;
__IO uint8_t net_stat=0;
Одна переменная для счёта от таймера, а вторая — для отслеживания статуса нашего соединения с клиентом.
Добавим также в перечислении ещё один вид состояния соединения
enum server_states
{
ES_NONE = 0,
ES_ACCEPTED,
ES_RECEIVED,
ES_CLOSING,
ES_CLOSED
};
Теперь в функции tcp_server_accept отобразим адреса клиента и сервера, также проинициализируем переменную соединения и погасим все светодиоды
tcp_poll(newpcb, tcp_server_poll, 0);
sprintf(str1,"LOC %d.%d.%d.%d", (uint8_t) newpcb->local_ip.addr, (uint8_t) (newpcb->local_ip.addr>>8),
(uint8_t) (newpcb->local_ip.addr>>16), (uint8_t) (newpcb->local_ip.addr>>24));
//IP_ADDR_ANY[0],IP_ADDR_ANY[1],IP_ADDR_ANY[2],IP_ADDR_ANY[3]);
LCD_StrBottom(str1);
sprintf(str1,"REM %d.%d.%d.%d", (uint8_t) newpcb->remote_ip.addr, (uint8_t) (newpcb->remote_ip.addr>>8),
(uint8_t) (newpcb->remote_ip.addr>>16), (uint8_t) (newpcb->remote_ip.addr>>24));
//IP_ADDR_ANY[0],IP_ADDR_ANY[1],IP_ADDR_ANY[2],IP_ADDR_ANY[3]);
LCD_StrBottom(str1);
LCD_StrBottom(" ");
LCD_StrBottom(" ");
net_stat=ES_ACCEPTED;
ret_err = ERR_OK;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
Соберём код, прошьём контроллер и попробуем соединиться с нашим сервером при помощи программы Putty, не забыв изменить адрес IP. Если соединение успешно установится, то на дисплее отобразятся IP-адреса наших участников соединения
Отлично!
Следующая задача — отобразить на дисплее приходящие от клиента строки.
Только строки мы будем отображать в нижней части дисплея без скроллинга изображения, чтобы показания температуры у нас просто изменялись при их реально изменении, а не бежали по экрану.
Сначала мы в функции разрыва соединения tcp_server_connection_close тажке присвоим правильный статус нашей переменной
tcp_close(tpcb);
net_stat=ES_CLOSED;
Также заполним тело функции tcp_server_error, так как мы про неё совсем забыли на уроке по серверу
static void tcp_server_error(void *arg, err_t err)
{
struct server_struct *es;
LWIP_UNUSED_ARG(err);
es = (struct server_struct *)arg;
if (es != NULL)
{
/* free es structure */
mem_free(es);
}
}
В основном мы в этом теле освобождаем память структуры.
Теперь поработаем с функцией tcp_server_recv. Поначалу изменим состояние светодиода
es = (struct server_struct *)arg;
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_15);
Теперь в теле условия, в котором мы отправляем принятую строку в USART, нарастим счётчик принятых пакетов, так как переменная для этого у нас есть, а мы ей не пользуемся
tcp_recved(tpcb, p->tot_len);
message_count++;
Очистим буфер, так как без этого у меня принималось только ровно 16 пакетов и сервер больше не принимал
str1[p->len] = '\0';
pbuf_free(p);
И после передачи в USART отобразим принятую строку ещё и на дисплее, также заодно отобразим номер пакета, чтобы было видно, что они нам приходят, также обнулим счётчик тиков
HAL_UART_Transmit(&huart6, (uint8_t*)str1,strlen(str1),0x1000);
str1[strlen(str1)-2] = '\0';
sprintf(str1,"%s %lu ",str1, message_count);
LCD_SetPos(0,3);
LCD_String(str1);
timeout_count = 0;
ret_err = ERR_OK;
Соберём наш код и прошьём контроллер.
Теперь давайте проверим, как приходят строки на сервер и отображаются на дисплее. И, спешу вас обрадовать, проверим мы это уже, соединив наш сервер с настоящим клиентом — со второй платой Discovery. Серверную плату мы оставим питаться от USB-порта компьютера, так как мы ещё будем продолжать писать код и нам надо будет его прошивать, а вот клиентскую запитаем от зарядного устройства.
Перезагрузим сначала сервер, а потом клиент и, если всё нормально, то мы должны будем увидеть вот такую картину на дисплее
Отлично! Температура к нам приходит. Я пробовал нагревать датчик прикосновением, всё отображается на сервере.
Можно было бы конечно на этом и закончить, но давайте опять и на сервере реализуем какую-нибудь жизненную затею.
У нас вполне может произойти ситуация, что у нас клиент будет питаться от батарейки и она сядет, ну, вообщем, у нас клиент может пропасть и от него не будут приходить пакеты. А затем когда-то мы заменим батарейку или найдём ещё какую-нибудь неисправность на клиенте и он вдруг появится в сети. При этом сервер прийдётся перезагружать, а затем перезагружать и клиент. Чтобы этого не происходило, на сервере нужно будет поймать момент, когда перестанут приходить пакеты с клиента и разорвать с ним соединение, а затем продолжать слушать порт. И, как только клиент появится и запросит соединение, то мы с ним тогда и соединимся.
Давайте этим и займёмся. Мы могли бы задействовать таймер, но думаю он нам когда-то пригодится для более серьёзных вопросов. У нас и так есть прекрасный таймер — это функция-обработчик tcp_server_poll, которая исправно нам отсчитывает половинки секунд.
Перейдём в эту функцию и удалим для начала вот эту работу со светодиодами
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
tcp_server_connection_close(tpcb, es);
Проинкрементируем счётчик тиков
usartprop.is_text=0;
}
timeout_count++;
Затем, если, например данный счётчик достигнет отметки 16, то закроем наше соединение, а заодно и обнулим все наши счёчики
timeout_count++;
if(timeout_count>16)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
tcp_server_connection_close(tpcb, es);
timeout_count=0;
message_count=0;
}
В данное тело условия мы попадаем тогда, когда буфер будет пустым. И вот как только он будет пустым долго, значит нам ничего не поступает.
Соберём код, прошьём наш контроллер. Перезагрузим клиент, через некоторое время он начнёт передавать температуру.
Затем отключим клиент от питание и через некоторое время сервер разорвёт соединение, а порт он в принципе всегда слушает. У нас же допустимо до 5 соединений TCP. А при разрыве соединения, я думаю, счётчик открытых соединений опять увеличится. Я конечно это не проверял, но думаю, что это в LWIP реализовано. Если мы затем когда-то подадим питание клиенту, то он запросит соединение и сервер в этом ему не откажет и показания температуры опять будут поступать на сервер.
Вот, в принципе и всё.
Таким образом, мы сегодня реализовали очень интересную и, я думаю, долгожданную идею. Мы соединили наконец-то два контроллера посредством сети LAN.
Всех поздравляю с данным событием!
Недаром и урок у нас с таким юбилейным номером.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F4-DISCOVERY
Модуль LAN можно приобрести здесь: LAN8720
Плату расширения можно приобрести здесь: STM32F4DIS-BB
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК (нажмите на картинку)
Респект автору! Ваши уроки — лучшее на данный момент что можно найти в интернете по STM32. Вопрос — где и как определяется скорость принятых пакетов в сервере? Я переделал проект на передачу телеметрии, увеличил скорость на клиенте. Сервер часть пакетов пропускает. Спасибо.