В предыдущей части занятия мы написали код для вывода клиенту информации о каталоге.
Поищем причину создавшейся ситуации потери соединения с сервером после ответа на команду вывода информации о каталоге
Сервер шлёт нам какие-то неизвестные команды.
Команда PWD клиентом отправляется в том случае, если ему интересно знать рабочий (текущий) какталог.
Обработаем команды PWD и XPWD в функции ftp_cmd_parse в соотвтствующем месте
break;
case PWD_CMD:
case XPWD_CMD:
len = sprintf((char*)datasect->data, "257 \"%s\" is current directory.\r\n", ftpprop.work_dir);
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
break;
case QUIT_CMD :
Теперь после того, как мы соберём код и прошьём контроллер, мы можем убедиться, что мы спокойно заходим в каталоги и выходим из них.
Также мы видим, что ответ на команду от нас приходит клиенту
Следующая задача — создать новый каталог на флеш-карте со стороны клиента, Пока у нас это не обработаем. Тем не менее мы попробуем это сделать хотя бы для того, чтобы посмотреть, какую команду для этого передаст клиент серверу. Для этого соединимся с сервером, нажмём F7 или соответствующую кнопку снизу, придумаем имя каталога в открывшемся диалоге и нажмём ОК
Понятное дело, что мы получим ошибку
А в анализаторе трафика мы видим следующее
Клиент нам передал неизвестную команду MKD, а в качестве аргумента послал имя каталога, который мы по его просьбе должны создать в текущей директории. Конечно, по аббревиатуре любой команды можно спокойно понять, что она в себе несёт. MKD — Make Directory, CWD — Change Work Directory, PASV — passive и т.д.
Обработаем команду MKD в функции ftp_cmd_parse в соотвтствующем месте
break;
case MKD_CMD:
case XMKD_CMD:
len = strlen(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
sprintf(str1,"MKD_CMD\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
//дополним создаваемый каталог путём к текущему каталогу
strcpy(buf1,ftpprop.work_dir);
len = strlen(buf1);
if(buf1[len-1]!='/') strcat(buf1,"/");
strcat(buf1,arg);
if (f_mkdir(buf1) != 0)
{
len = sprintf((char*)datasect->data, "550 Can't create directory. \"%s\"\r\n", arg);
}
else
{
len = sprintf((char*)datasect->data, "257 MKD command successful. \"%s\"\r\n", arg);
}
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
break;
case QUIT_CMD :
Алгоритм обработчика данной команды и команды XMKD похож чем-то на алгоритм обработчика команды смены текущего каталога. Только мы используем функцию библиотеки FATFS f_mkdir для создания нового каталога.
Соберем код, прошьём контроллер и посмотрим результат, попробовав создать каталог в корневой папке и в какой-нибудь другой
Всё работает. Иногда подвисает, но я не думаю, что нам часто придётся создавать каталоги, тем не менее, я над этим вопросом подумаю. Может что с сетью, у меня здесь всё-таки очень загруженная сеть, в другом месте всё вроде работает нормально. А возможно, что когда напишем полностью весь проект, всё восстановится.
Далее нам надо научиться также удалять каталоги. Поступим таким же образом, вычислив команду попыткой удалить каталог. Для этого мы должны выбрать каталог, который мы желаем удалить, нажмём кнопку «Del» или «F8» и согласимся с диалогом. В результате этого мы сначала получим скорее всего диалог такого типа
Нажмём кнопку «Все» и получим вот это
То есть скорее всего клиент, получив от нас сообщение, что мы не знаем такую элементарную команду, обиделся как всегда и решир разорвать с нами соединение.
Убедимся в этом, посмотрев обмен в анализаторе трафика
Оказывается, наоборот. Обиделся сервер на то, что клиент просит такие вещи, которые не знает сервер.
Давайте как-то данную ситуацию исправлять. Клиент пытался использовать две команды:
1) RMD — команда удаления каталога,
2) DELE — команад удаления файла.
Начнём с первой.
Добавим обработчик в соответствующее место функции ftp_cmd_parse
break;
case XRMD_CMD:
case RMD_CMD:
len = strlen(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
sprintf(str1,"RMD_CMD\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if (f_unlink(arg) != 0)
{
len = sprintf((char*)datasect->data, "550 Could not delete. \"%s\"\r\n", arg);
}
else
{
len = sprintf((char*)datasect->data, "250 Deleted. \"%s\"\r\n", arg);
}
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
break;
case QUIT_CMD :
Обработчик от предыдущего отличается только тем, что функция из библиотеки FATFS вызывается f_unlink, а также сообщение тоже посылается соответствующее.
Ну, и давайте также обработаем и команду DELE
break;
case DELE_CMD:
len = strlen(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
sprintf(str1,"DELE_CMD\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if (f_unlink(arg) != 0)
{
len = sprintf((char*)datasect->data, "550 Could not delete. \"%s\"\r\n", arg);
}
else
{
len = sprintf((char*)datasect->data, "250 Deleted. \"%s\"\r\n", arg);
}
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
break;
case QUIT_CMD :
А этот обработчик вообще не отличается от предыдущего, разве только аргументом кейса.
Соберём код и прошьём контроллер. Попробуем заново соединиться с нашим FTP-сервером и удалить каталог в корневой директории, а также в какой-нибудь другой. Также попробуем удалить какие-нибудь файлы.
Файлы и каталоги удаляются. Правда каталоги удаляются только те, котрые мы создали сами с клиента. Видимо, не хватает прав.
Очередная задача — передавать файлы. Это самое интересное. Да и по большому счёту это то, зачем и существует протокол FTP. Он и расшифровывается file transfer protocol — протокол передачи файлов. Задача интересная, но очень непростая, поэтому нужно к этому отнестись предельно серьёзно, чтобы не наделать ошибок.
Начнём.
Думаю, сначала мы попробуем передавать файлы от сервера к клиенту. Оказалось, что передавать файлы на сервер труднее. Скорее всего, это из-за того, что мы с сервера файлы уже передавали по протоколу HTTP и общее представление, как это делать, имеем.
Сначала, как всегда, попробуем передать файл, не создавая никакого кода. И, понятное дело, выберем самый маленький файл по размеру. Для этого в другой панели файл-менеджера откроем любой каталог, а в панели, где открыта файловая система FTP-сервера выберем наш файл и нажмём «F5«. Согласимся с диалогом, на что получим вот такое сообщение
Посмотрим также обмен информации в WireShark
Мы видим. что клиент отправил команду RETR, которая служит для запроса файла с сервера, с аргументом имени файла, на что сервер ответил, что он такую команду не знает, и клиент решил разорвать соединение для передачи данных.
Поэтому начнём сочинять код, который мог бы обеспечить передачу файлов любого размера от сервера клиенту.
Сначала обработаем саму команду в соответствующем месте, чтобы ответить клиенту на неё сообщением по управляющему соединению. Надеюсь это место все уже запомнили, напоминать больше нет смысла
break;
case RETR_CMD:
len = strlen(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
sprintf(str1,"RETR_CMD\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(strlen(ftpprop.work_dir) == 1)
sprintf(ftpprop.filename, "/%s", arg);
else
sprintf(ftpprop.filename, "%s/%s", ftpprop.work_dir, arg);
len = sprintf((char*)datasect->data, "150 Opening data channel for file downloand from server of \"%s\"\r\n",
ftpprop.filename);
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
ftpprop.current_cmd = RETR_CMD;
break;
case QUIT_CMD :
Как всегда, стандартный обработчик, отвечающий сообщением клиенту в текстовом виде с кодом 150. Также мы заносим имя файла вместе с расширением в соответствующее поле структуры со свойствами для дальнейшей работы с ним по соединению для передачи данных. А также в другое поле структуры мы заносим индекс текущей команды.
Над функцией ftp_data_send создадим функцию для пердачи файла
//-----------------------------------------------
//функция чтения и передачи файла (части файла)
void ftp_send_file(uint8_t sn, char *buf)
{
uint32_t buf_ptr = 0;
buf_ptr+=3;//оставим место под ардес и опкод
uint32_t lensect = 0;
}
//-----------------------------------------------
Теперь перейдём в саму функцию ftp_data_send и добавим там ещё один вариант (case) в наш switch
break;
case RETR_CMD:
break;
default:
Теперь начнём потихоньку наполнять этот кейс полезным кодом.
Добавим туда условие с телом, что это у нас первый буфер (или единственный)
case RETR_CMD:
if(ftpprop.data_stat==DATA_FIRST)
{
res = f_open(&ftpprop.my_fil, (const char *)ftpprop.filename, FA_READ);
sprintf(str1,"f_open: %d\r\n",res);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
sprintf(str1,"f_size: %lu\r\n",ftpprop.my_fil.fsize);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(res == FR_OK)
{
ftpprop.rem_filesize = ftpprop.my_fil.fsize;
ftp_send_file(sn,buf_send);
}
}
break;
Здесь мы сначала попытаемся открыть файл, затем отчитаться в терминальную программу, что открытие произошло успешно, а также отправим туда размер файла, затем, если открытие произошло успешно, мы занесём в определённое поле структуры размер файла и вызовем функцию передачи файла, которую мы только что создали. Теперь обработаем противное условие, если у нас будет не первый буфер передачи файла, а средний (или последний)
ftp_send_file(sn,buf_send);
}
}
else if(ftpprop.data_stat==DATA_MIDDLE)
{
ftp_send_file(sn,buf_send);
}
break;
В этом случае мы просто вызываем функцию, так как подразумевается, что файл у нас уже открыт и существует.
Теперь начнём писать функцию передачи файла ftp_send_file
Добавим туда пока цикл
uint32_t lensect = 0;
while(1)
{
//f_lseek(&ftpprop.my_fil,ftpprop.my_fil.fsize-ftpprop.rem_filesize); //Установим курсор чтения в файле
lensect = 512;
//если осталось меньше, чем сектор
if(ftpprop.rem_filesize<=512)
{
lensect = ftpprop.rem_filesize;
}
}
}
В данном цикле мы инициализируем сектор, а затем в случае, если нам осталось передать информации меньше, чем размер сектора, то уменьшим размер сектора до данной величины.
Пишем дальше наш цикл
lensect = ftpprop.rem_filesize;
}
//ещё укоротим, если насчитали больше, чем осталось до переполнения буфера
if(lensect>(SIZE_WND+3-buf_ptr))
{
lensect = SIZE_WND+3-buf_ptr;
}
Собственно, то, что мы здесь делаем описано уже в комментарии.
Наполняем дальше кодом тело цикла
lensect = SIZE_WND+3-buf_ptr;
}
f_read(&ftpprop.my_fil,buf+buf_ptr,lensect,(UINT *)&bytesread);
ftpprop.rem_filesize -= lensect;
buf_ptr+=lensect;
Здесь мы считаем необходимое количество байтов из файла в буфер, учитывая смещение, затем скорректируем количество байтов файла, которое нам ещё осталось передать, а также скорректируем смещение в буфере.
Пишем дальше код тела цикла, ведь нам же ещё нужно когда-то из него выйти, не вечно же нам в нём вертеться
buf_ptr+=lensect;
if(ftpprop.rem_filesize<=0)
{
ftpprop.data_stat=DATA_END;
HAL_UART_Transmit(&huart2,(uint8_t*)"FILE Close\r\n",12,0x1000);
f_close(&ftpprop.my_fil);
break;
}
if((SIZE_WND+3-buf_ptr)<=0)
{
ftpprop.data_stat=DATA_MIDDLE;
break;
}
Вот тут мы и выходим из бесконечного цикла. Только, если нам передавать больше нечего (количество байтов, оставшихся для передачи меньше или равно нулю, то мы состояние передачи данных переводим в окончание и закрываем наш файл, а если ещё что-то осталось, но максимальный размер буфера мы уже начинаем превышать, то мы файл не закрываем и состояние передачи переводим в передачу средней части.
Выходим из цикла (не из условия) и отправляем наш набранный буфер клиенту
break;
}
}
tcp_send_ftp_one(sn, (uint8_t *)buf, buf_ptr-3);//отнимаем из общей длины служебные байты
}
Соберём код, прошьём контроллер и проверим, как происходит процесс копирования файлов с сервера клиенту
Процесс идёт нормально. Файлы любого размера передаются.
Также посмотрим процесс обмена в анализаторе трафика, вернее его окончание, весь процесс нам не нужен
Всё идёт нормально. Части файла передаются поровну, буфер наполняется полностью, отчёт о передаче с сервера пришел, правда вместо имени файла там имя текущего каталога, но ничего страшного, то что кроме кода сообщения как правило клиентами игнорируется. Иначе нам придётся ещё писать ветвление, где будем определять, файл мы передали, или каталог, или информацию о каталоге. Это ни к чему. Клиент не ругается, а это главное.
Теперь самая трудная задача. Передать информацию от клиента на сервер. Ну ничего, справимся.
Попытаемся до написания кода передать что то с клиента серверу. Данный процесс инициируется аналогично, только мы наоборот выбираем на второй панели в любой папке любой файл, а в первой панели у нас в это время открыт любой каталог на сервере. Встаем на файл, который хотим передать на сервер, и жмём «F5«. Получим вот такое вот сообщение об ошибке
Посмотрим процесс обмена
Мы видим, что клиент передал команду STOR, которая служит для того, чтобы сервер принял у клиента файл с именем, указанным в аргументе команды.
Ну что ж. Нам ничего не остаётся делать, как эту команду обработать. Сначала в функции разбора команд ответим на данную команду по управляющему соединению
break;
case STOR_CMD:
len = strlen(arg);
arg[len - 1] = 0x00;
arg[len - 2] = 0x00;
sprintf(str1,"STOR_CMD\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(strlen(ftpprop.work_dir) == 1)
sprintf(ftpprop.filename, "/%s", arg);
else
sprintf(ftpprop.filename, "%s/%s", ftpprop.work_dir, arg);
len = sprintf((char*)datasect->data, "150 Opening data channel for file upload to server of \"%s\"\r\n",
ftpprop.filename);
tcp_send_ftp_one(sn, (uint8_t *)buf_send, len);
ftpprop.current_cmd = STOR_CMD;
break;
case QUIT_CMD :
Здесь всё просто и стандартно. Все пляски с бубном будут в сокете для передачи данных.
Над функцией приёма и обработки пакетов FTP ftp_receive создадим функцию приёма данных от клиента
//-----------------------------------------------
void ftp_data_receive(uint8_t sn)
{
uint16_t len;
uint16_t point;
static char buf_recv[SIZE_WND+1];
uint16_t len_full=0;
FRESULT res; //результат выполнения
}
//-----------------------------------------------
В файле w5500.h добавим ещё один вариант передачи данных, изменив также индексы остальных вариантов
//--------------------------------------------------
//Статусы передачи данных
#define DATA_COMPLETED 0 //передача данных закончена
#define DATA_ONE 1 //передаём единственный пакет
#define DATA_FIRST 2 //передаём первый пакет
#define DATA_MIDDLE 3 //передаём средний пакет
#define DATA_STOR_MIDDLE 4 //передаём средний пакет файла FTP на сервер
#define DATA_LAST 5 //передаём последний пакет
#define DATA_END 6 //закрываем соединение после передачи данных
//--------------------------------------------------
Вернёмся в файл ftpd.c и в функции ftp_receive добавим ещё один кейс в последний switch (switch(ftpprop.current_cmd))
break;
case STOR_CMD:
if(ftpprop.data_stat==DATA_COMPLETED)
{
ftpprop.data_stat=DATA_FIRST;
}
else if(ftpprop.data_stat==DATA_FIRST)
{
if(len>0)
{
point = GetReadPointer(sn);
//Отобразим адрес данных
sprintf(str1,"S%d point RX:0x%04X\r\n",sn,point);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
ftp_data_receive(sn);
}
}
break;
default:
Сюда мы попадём, если у нас будет текущая команда STOR. Если у нас не осталось данных для приёма, то мы переходим в состояние приема первого пакета, если мы находимся уже в состоянии приема первого пакета, то изучим адрес данных, отобразим его в терминальной программе и вызовем нашу функцию приёма данных.
Перейдём в другой switch, который находится выше (switch(ftpprop.data_stat)) и добавим там ещё один вариант передачи данных
break;
case DATA_STOR_MIDDLE:
ftp_data_receive(sn);
break;
}
switch(ftpprop.current_cmd)
В этом случае мы просто вызываем функцию, которую мы только что создали.
Поэтому перейдём в данную функцию и начнём писать в неё код.
Попытаемся открыть файл для создания, ну а если есть, то открыть его для перезаписи
FRESULT res; //результат выполнения
if(ftpprop.data_stat==DATA_FIRST)
{
res = f_open(&ftpprop.my_fil, (const char *)ftpprop.filename, FA_CREATE_ALWAYS | FA_WRITE);
if(res != FR_OK) return;
}
Если файл открыть (создать) невозможно, то уходим из функции.
Создадим бесконечный цикл, выйдя из тела условия
if(res != FR_OK) return;
}
while(1)
{
}
Начнём писать тело цикла
while(1)
{
len = GetSizeRX(sn);
point = GetReadPointer(sn);
//Отобразим адрес данных
sprintf(str1,"S%d point RX:0x%04X\r\n",sn,point);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
//Отобразим размер принятых данных
sprintf(str1,"S%d len buf:0x%04X\r\n",sn,len);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(len<=0)
{
f_close(&ftpprop.my_fil);
break;
}
Мы узнали размер данных в буфере приёма, также определили адрес данных в данном буфере, отобразили это всё в терминальной программе, и, если длина данных в буфере равна нулю, то есть, мы, скорее всего, всё уже приняли, то закрываем файл и выходим из нашего бесконечного цикла (не из функции).
Теперь после функции tcp_send_ftp_one добавим функцию приёма данных
//-----------------------------------------------
void tcp_recv_ftp(uint8_t sn, uint8_t * buf, uint16_t len_buf)
{
uint16_t len;
uint16_t point;
len = GetSizeRX(sn);
point = GetReadPointer(sn);
//Отобразим адрес данных
sprintf(str1,"S%d point RX:0x%04X\r\n",sn,point);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
//Отобразим размер принятых данных
sprintf(str1,"S%d len buf:0x%04X\r\n",sn,len);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(len>len_buf) len=len_buf;
//примем данные
w5500_readSockBuf(sn, point, (uint8_t*) buf, len);
//Передвинем указатель
SetReadPointer(sn, point+len);
RecvSocket(sn);
}
//-----------------------------------------------
В данной функции мы читаем принятый буфер в буфер в оперативной памяти, адрес которого мы передём во входящем аргументе, также наряду с ним мы передаём ещё и длину принимаемых данных.
Теперь вернёмся в наш бесконечный цикл в функцию ftp_data_receive и продолжим писать его тело
break;
}
tcp_recv_ftp(sn,(uint8_t*)buf_recv,len);
f_write(&ftpprop.my_fil,buf_recv,len,(UINT *)&bytesread);
len_full+=len;
//Отобразим размер записанных данных
sprintf(str1,"S%d len_full buf:0x%04X\r\n",sn,len_full);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
point = GetReadPointer(sn);
//Отобразим адрес данных
sprintf(str1,"1 S%d point RX:0x%04X\r\n",sn,point);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
len = GetSizeRX(sn);
//Отобразим размер принятых данных
sprintf(str1,"1 S%d len buf:0x%04X\r\n",sn,len);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
Мы здесь забираем данные из буфера приёма в размере, который мы узнали выше, затем записываем их в файл. Кстати, оказалось, что указатель в файле передвигать не нужно по мере чтения и записи блоками, он передвигается сам, поэтому функцию f_lseek мы не используем. Затем мы отображаем в терминале размер записанных данных, узнаём адрес данных в буфере чтения, тоже отображаем его в терминальной программе, измеряем опять размер данных, так как пока мы читали буфер и писали его в файл, нам свободно могли прийти ещё данные с клиента. Конечно, по хорошему, мы не должны обрабатывать этот буфер сразу, мы должны пробежаться по другим сокетам и обработать там очередь буферов, иначе, если у нас будет передача огромного файла, то в это время не будет отвечать на запросы клиентов наш HTTP-сервер. Возможно, данную проблему поможет решить операционная система реального времени, но я с ней как следует ещё не разбирался, поэтому делал проект обычный. Ну ничего, мы пока не планируем одновременную работу обоих серверов, один бы как-нибудь поднять. Далее мы размер записанных данных отображаем также в терминальной программе.
Пишем тело цикла дальше
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
if(GetSocketStatus(sn)!=SOCK_ESTABLISHED)
{
//Если в буфере остались данные, то примем их и запишем в файл
tcp_recv_ftp(sn,(uint8_t*)buf_recv,len);
f_write(&ftpprop.my_fil,buf_recv,len,(UINT *)&bytesread);
f_close(&ftpprop.my_fil);
break;
}
Здесь мы обрабатываем ситуацию, когда клиент уже с нами пытается разорвать соединение, а мы ещё данные приняли не все. Как правило это бывает, когда клиент передал нам последнюю часть файла. В теле данного условия мы передаём оставшиеся данные, пишем их в файл и закрываем его.
Выходим из цикла совсем (а не из условия) и пишем код дальше
break;
}
}
ftpprop.current_cmd = NO_CMD;
ftpprop.data_stat=DATA_COMPLETED;
DisconnectSocket(FTP_SOCKET_DATA); //Разъединяемся
SocketClosedWait(FTP_SOCKET_DATA);
sprintf(str1,"S%d closed\r\n",FTP_SOCKET_DATA);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
OpenSocket(FTP_SOCKET_DATA,Mode_TCP);
//Ждём инициализации сокета (статус SOCK_INIT)
SocketInitWait(FTP_SOCKET_DATA);
//Продолжаем слушать сокет
ListenSocket(FTP_SOCKET_DATA);
SocketListenWait(FTP_SOCKET_DATA);
ftpprop.connect_stat_data = 0;
len = sprintf((char *)(sect+3), "226 Successfully transferred \"%s\"\r\n", ftpprop.work_dir);
tcp_send_ftp_one(FTP_SOCKET_CTRL, sect, len);
}
Здесь мы заносим необходимые значения в поля структуры, разъединяем с клиентом соединение для передачи данных, открываемего заново, начинаем слушать сокет и отправляем клиенту о том, что его данные передались успешно.
Собираем код, проверяем, прошив перед этим контроллер
Всё работает.
На этом, я думаю, мы можем закончить работу с сервером FTP. Если будут замечены какие-то ошибки, то пишите. Также пишите, если вам удалось с ними справиться и каким образом.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Ethernet LAN Сетевой Модуль можно купить здесь W5500 Ethernet LAN
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Материал, безусловно, познавательный. Но не знаю кому может понадобиться FTP сервер на МК. Ethernet реле на 100-рублевой плате с Али STM32F103 + плата W5500. Без всяких SD карточек. Простенькая HTML страничка прямо во flash. Вот это была бы бомба, а не материал! Модно сейчас управлять чем либо прям со смартфона (W5500 подключена прям в домашний роутер).
Добрый день.
Спасибо за ваши уроки!
Очень крутые!!!
Могли бы вы показать как сделать FTPS сервер?
Насчет кому нужно… Ну вот мне понадобилось. Устройство пишет данные во Flash и по ETH к нему можно подключиться и скачать их. Или удалить.