STM Урок 89. LAN. ENC28J60. TCP WEB Server. Подключаем карту SD



 

Урок 89

 

LAN. ENC28J60. TCP WEB Server. Подключаем карту SD

 

Продолжаем подключать модуль LAN ENC28J60 к микроконтроллеру STM32F103, расположенному на одноимённой недорогой отладочной плате. Мы смогли ответить клиенту на запрос веб-страницы, но только передавать мы большие документы и изображения все равно не сможем ввиду того, что подошли мы к такому моменту, что на это у нас никакой памяти уже не хватит, ни оперативной, ни энергонезависимой. На помощь нам придёт старая добрая карта SD, которую мы подключим по интерфейсу SPI. Подключать карту по интерфейсу SPI с использованием библиотеки FATFS мы научились на предыдущем занятии, поэтому сегодняшняя задача — соединить наш проект последнего урока по LAN с проектом прошлого занятия.

Схема наша остаётся прежняя. Подключим обратно модуль LAN к контроллеру, так как когда мы занимались с картой SD, мы его отключали, а карту SD оставим там, где она и была. Теперь наша схема получится вот такого вида (нажмите на картинку для увеличения изображения)

 

image00_0500

 

Теперь проект. Проект сделан из проекта урока 87 с именем ENC28J60_HTTPS_LARGE и назван был ENC28J60_HTTPS_SD. Только единственное отличие — положил я его во вложенную папку WB, так как проект у нас разрабатываться будет уже в среде System Workbench для STM32, так как после подключения библиотеки FATFS и написания нескольких строк коду проект превысил заветные 32 килобайта кода, чего бесплатная лицензия Keil делать категорически запрещает. И, как вы знаете, права мы ничьи не нарушаем, поэтому прибегнуть пришлось к бесплатной среде разработки. Я пробовал CooCox, но это отдельная история, и я забросил данное занятие. Да и плюс ко всему, мне всё же удалось победить недостаточный размер кучи в System Workbench и я наконец-то возобновил работу внешнего сборщика. Если кому интересно, как я это сделал, пишите в комментариях. Вообще, выбор компилятора — это дело ваше, так что можете пользоваться любым. Разница между ними по настройке и по работе не столь велика. Открываем проект в Cube MX, включаем SPI2 и FATFS

 

index07   index19

 

Также включим на выход ножку PA3, так как это у нас Chip Select для карты SD

 

index12

 

Переходим на вкладку с настройками интерфейсов (Configuration) и поначалу настроим SPI (делитель можно включить теперь 2 и скорость у нас возрастёт до 10 мбпс, так как карта на такой скорости работает отлично и нам не нужно теперь ничего анализировать логическим анализатором), А такие настройки у шины SPI Cube устанавливает автоматически, поэтому ничего не трогаем и уходим из данного диалога

 

Image01

 

Настроим также FATFS, добавив поддержку длинных имён, вдруг пригодится, а также увеличив максимальный размер сектора

 

Image02

 

Также не забываем включить среднюю скорость для ножки PA3 в GPIO

 

Image03

 

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

 

Image04

 

Сохраним настройки, сгенерируем проект, перейдём в System Workbench, подключим наш свежесгенерированный проект и в свойствах убедимся, что у нас нет готоых настроек дебагеров, а если есть, то удалим, как всегда. Сброщик на внутренний мы теперь не меняем. Открываем main.c и собираем наш проект. Если всё нормально собралось, то можно попробовать его запустить и проверить что у нас всё по прежнему работает, например, пропинговав наш модуль.

В папки Inc и Src проекта соответственно скопируем файлы нашей библиотеки для SD-карты, которые мы создали на прошлом занятии из проекта SD_FATFS sd.c и sd.h. Обновим дерево проектов в среде разработки (Refresh). Перейдём в файл sd.c и глобальной строковой переменной добавим extern и уберём ее инициализацию

 

extern char str1[60]={0};

 

Также нам необходимо будет здесь внести ещё некоторые исправления. Некоторые функции, связанные с интерфейсом SPI, вернее их имена уже использовались в нашем проекте для LAN, поэтому давайте их немного пеерименуем, например добавив префикс SD_

 

uint8_t SD_SPIx_WriteRead(uint8_t Byte)

{

 

void SD_SPI_SendByte(uint8_t bt)

{

 

uint8_t SD_SPI_ReceiveByte(void)

{

 

Соответственно, нам также необходимо будет такой же префикс добавить во всех местах вызова данных функций ниже.

Также удалим функцию SD_PowerOn и её прототип, так как нам не нужно будет ждать в инициализации карты 20 милисекунд, они и так у нас пройдут с лихвой, так как сначала будет инициализация модуля LAN, потом когда-то мы получим запрос документа от клиента, и вот тогда только будет происходить первичная инициализация карты.

Сответственно, тогда удалим и глобальную переменную для таймера

 

extern volatile uint16_t Timer1;

 

Теперь перейдём в файл user_diskio.c, удалим там полностью весь код и вставим его из одноименного файла проекта SD_FATFS. В теле функции USER_initialize удалим вызов функции SD_PowerOn.

 

SD_PowerOn();

if(sd_ini()==0) {Stat &= ~STA_NOINIT;} //сбросим статус STA_NOINIT

 

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

Теперь переходим в файл tcp.c и начнём работать над тем, как нам теперь запрошенные документы вызывать из файловой системы SD-карты.

В функции tcp_read закомментируем вывод информации в USART, чтобы не тормозило. Если что-то не пойдёт, то мы всегда можем это раскомментировать

 

/*

sprintf(str1,"%d.%d.%d.%d-%d.%d.%d.%d %d tcp\r\n",

ip_pkt->ipaddr_src[0],ip_pkt->ipaddr_src[1],ip_pkt->ipaddr_src[2],ip_pkt->ipaddr_src[3],

ip_pkt->ipaddr_dst[0],ip_pkt->ipaddr_dst[1],ip_pkt->ipaddr_dst[2],ip_pkt->ipaddr_dst[3], len_data);

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

*/

 

//HAL_UART_Transmit(&huart1,(uint8_t*)"ACK\r\n",5,0x1000);

 

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

 

volatile uint16_t tcp_mss = 458;

volatile uint16_t tcp_size_wnd = 8192;

 

В функции tcp_header_prepare изменим строку

 

tcp_pkt->size_wnd = be16toword(tcp_size_wnd);

 

И теперь, чтобы нам при приближении количества переданных данных к размеру окна передавать в пакете с данными флаг PCH, нам потребуется переменная в структуре tcp_prop. Для этого перейдём в файл tcp.h и добавим её

 

volatile uint16_t cnt_rem_data_part;//количество оставшихся частей данных для передачи

volatile uint16_t cnt_size_wnd;//количество переданных байтов окна

 

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

 

  char fname[20];//имя файла (документа)

} tcp_prop_ptr;

 

Вернёмся в файл tcp.c и проинициализируем поле с количеством переданных байтов окна в функции tcp_read в запросе документа клиентом

 

if (strncmp((char*)tcp_pkt->data,"GET /", 5) == 0)

{

  //инициализируем количество переданных байтов окна

  tcpprop.cnt_size_wnd = 0;

 

Теперь нам нужно найти место, где мы можем превысить размер окна и передать там соответствующий флаг.

 

 

Функция передачи страницы размером в один пакет нас вообще не интересует, там мы точно ничего не превысим.

Зайдём в функцию передачи первого пакета многопакетной страницы и нарастим там наш счётчик на размер сегмента

 

tcpprop.cnt_rem_data_part--;

//добавим переданные байты в окно

tcpprop.cnt_size_wnd += tcp_mss;

 

Теперь перейдём в функцию передачи средней части страницы и там, где мы передаём флаг ACK, в случае достижения максимального размера окна передадим ещё и флаг PCH

 

len=len_tcp + tcp_mss;

//Узнаем, не подолши ли мы к предельному размеру окна

if ((tcp_size_wnd - tcpprop.cnt_size_wnd) > tcp_mss)

{

  tcp_header_prepare(tcp_pkt, port, TCP_ACK, len_tcp, len);

}

else

{

  tcp_header_prepare(tcp_pkt, port, TCP_PSH|TCP_ACK, len_tcp, len);

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

  tcpprop.cnt_size_wnd = 0;

}

len+=sizeof(ip_pkt_ptr);

 

Продвинемся по функции немного ниже и также нарастим счётчик

 

tcpprop.cnt_rem_data_part--;

//добавим переданные байты в окно

tcpprop.cnt_size_wnd += tcp_mss;

 

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

 

//Варианты документов HTTP

#define EXISTING_HTML 0

#define E404_HTML 1

#define EXISTING_JPG 2

 

Теперь начнём работать непосредственно с SD-картой.

Вернёмся в файл tcp.c и в функции tcp_read добавим ещё три локальные переменные

 

uint16_t i=0;

char *ss1;

int ch1=' ';

int ch2='.';

 

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

 

if((char)tcp_pkt->data[5]==' ')

{

  strcpy(tcpprop.fname,"index.htm");

  tcpprop.http_doc = EXISTING_HTML;

}

else

{

  //скопируем 20 байтов из запроса после символа '/' в поле fname

  memcpy((void*)tcpprop.fname,(void*)(tcp_pkt->data+5),20);

  //найдём пробел и заменим его нулём

  ss1 = strchr(tcpprop.fname,ch1);

  ss1[0] = 0;

}

 

Дальше во всех местах файла, где встречался макрос INDEX_HTML, заменим его на новый макрос EXISTING_HTML, чтобы при сборке кода не было ошибок.

В файле tcp.h подключим библиотеку FATFS

 

#include "net.h"

#include "fatfs.h"

 

Вернёмся в файл tcp.c и добавим несколько глобальных переменные для работы с файловой системой

 

extern uint8_t ipaddr[4];

FATFS SDFatFs;//указатель на объект

extern char USER_Path[4]; /* logical drive path */

FIL MyFile;

FRESULT result; //результат выполнения

uint32_t bytesread;

 

Также добавим ещё один глобальный строковый массив для заголовка передачи картинки в формате jpeg, заодно в других массивах включим версию 1.0, и запретим не закрывать соединение, чтобы клиент не слишком многого хотел от нашего сервера

 

const char http_header[] = {"HTTP/1.0 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"};

const char jpg_header[] = {"HTTP/1.0 200 OK\r\nServer: nginx\r\nContent-Type: image/jpeg\r\nConnection: close\r\n\r\n"};

const char error_header[] = {"HTTP/1.0 404 File not found\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"};

 

Затем продолжим писать код функции tcp_read.

 

 

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

 

  ss1[0] = 0;

}

HAL_UART_Transmit(&huart1,(uint8_t*)tcpprop.fname,strlen(tcpprop.fname),0x1000);

HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000);

result=f_mount(&SDFatFs,(TCHAR const*)USER_Path,0);

sprintf(str1,"f_mount: %d\r\n",result);

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

result=f_open(&MyFile,tcpprop.fname,FA_READ); //Попытка открыть файл

sprintf(str1,"f_open: %d\r\n",result);

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

sprintf(str1,"f_size: %lu\r\n",MyFile.fsize);

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

tcpprop.cnt_rem_data_part = tcpprop.data_size / tcp_mss + 1;

 

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

 

C:\ISH\ARM\MYLESSON\CUBE\F103\WB\ENC28J60_HTTPS_SD\Debug/../Middlewares/Third_Party/FatFs/src/ff.c:1943: undefined reference to ff_convert'

C:\ISH\ARM\MYLESSON\CUBE\F103\WB\ENC28J60_HTTPS_SD\Debug/../Middlewares/Third_Party/FatFs/src/ff.c:1997: undefined reference to ff_convert'

Middlewares/Third_Party/FatFs/src/ff.o: In function cmp_lfn':

C:\ISH\ARM\MYLESSON\CUBE\F103\WB\ENC28J60_HTTPS_SD\Debug/../Middlewares/Third_Party/FatFs/src/ff.c:1361: undefined reference to ff_wtoupper'

C:\ISH\ARM\MYLESSON\CUBE\F103\WB\ENC28J60_HTTPS_SD\Debug/../Middlewares/Third_Party/FatFs/src/ff.c:1362: undefined reference to `ff_wtoupper'

 

Мы сталкивались в прошлом занятии с такой ошибкой и присоединяли файл ccsbcs.c, который у нас генерировался, но не был присоединён. В этот раз у меня почему-то данный файл даже не сгенерировался, поэтому возьмём его из предыдущего проекта и положим в папку с «Src«, затем сделаем refresh в проекте, откроем этот файл и уберём там точки из строки с подключением заголовочного файла

 

#include "ff.h"

 

После этого обновим дерево проекта и проект нормально соберётся.

То есть только сейчас мы начали пользоваться библиотекой FATFS, о чём свидетелсьвует резко возросший размер прошивки

 

Image05

 

Вот поэтому мы сейчас и работаем со средой программирования System WorkBench.

Затем случае положительного результата открытия файла (если файл существует на карте SD мы сначала изучим его расширение и уже основываясь на этом будем использовать соответствующий заголовок для пакета HTTP, а в противном случае мы передадим документ с ошибкой

 

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

if (result==FR_OK)

{

  //изучим расширение файла

  ss1 = strchr(tcpprop.fname,ch2);

  ss1++;

  if (strncmp(ss1,"jpg", 3) == 0)

  {

    tcpprop.http_doc = EXISTING_JPG;

    //сначала включаем в размер размер заголовка

    tcpprop.data_size = strlen(jpg_header);

  }

  else

  {

    tcpprop.http_doc = EXISTING_HTML;

    //сначала включаем в размер размер заголовка

    tcpprop.data_size = strlen(http_header);

  }

  //затем размер самого документа

  tcpprop.data_size += MyFile.fsize;

}

else

{

  tcpprop.http_doc = E404_HTML;

  //сначала включаем в размер размер заголовка

  tcpprop.data_size = strlen(error_header);

  //затем размер самого документа

  tcpprop.data_size += sizeof(e404_htm);

}

tcpprop.cnt_rem_data_part = tcpprop.data_size / tcp_mss + 1;

 

С данной функцие мы закончили, теперь переходим к функциям передачи. начнём с функции tcp_send_http_one.

В данной функции заменим код в том месте где мы наполняем поле структуры, предназначенное для данных

 

//Отправляем страницу

if ((tcpprop.http_doc==EXISTING_HTML)||(tcpprop.http_doc==EXISTING_JPG))

{

  result=f_mount(&SDFatFs,(TCHAR const*)USER_Path,0);

  sprintf(str1,"f_mount: %d\r\n",result);

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

  result=f_open(&MyFile,tcpprop.fname,FA_READ); //Попытка открыть файл

  sprintf(str1,"f_open: %d\r\n",result);

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

  sprintf(str1,"f_size: %lu\r\n",MyFile.fsize);

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

  result=f_lseek(&MyFile,0); //Установим курсор чтения на 0 в файле

  sprintf(str1,"f_lseek: %d\r\n",result);

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

  if (tcpprop.http_doc==EXISTING_HTML)

  {

    strcpy((char*)tcp_pkt->data,http_header);

    result=f_read(&MyFile,(void*)(tcp_pkt->data+strlen(http_header)),(uint16_t)MyFile.fsize,(UINT *)&bytesread);

  }

  else

  {

    strcpy((char*)tcp_pkt->data,jpg_header);

    result=f_read(&MyFile,(void*)(tcp_pkt->data+strlen(jpg_header)),(uint16_t)MyFile.fsize,(UINT *)&bytesread);

  }

}

else

 

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

Аналогичные действия проделаем в следующей функции — tcp_send_http_first

 

//Отправляем первую часть страницы

if ((tcpprop.http_doc==EXISTING_HTML)||(tcpprop.http_doc==EXISTING_JPG))

{

  strcpy((char*)tcp_pkt->data,http_header);

  result=f_mount(&SDFatFs,(TCHAR const*)USER_Path,0);

  result=f_open(&MyFile,tcpprop.fname,FA_READ); //Попытка открыть файл

  result=f_lseek(&MyFile,0); //Установим курсор чтения на 0 в файле

  if (tcpprop.http_doc==EXISTING_HTML)

  {

    strcpy((char*)tcp_pkt->data,http_header);

    result=f_read(&MyFile,(void*)(tcp_pkt->data+strlen(http_header)),tcp_mss-strlen(http_header),(UINT *)&bytesread);

  }

  else

  {

    strcpy((char*)tcp_pkt->data,jpg_header);

    result=f_read(&MyFile,(void*)(tcp_pkt->data+strlen(jpg_header)),tcp_mss-strlen(jpg_header),(UINT *)&bytesread);

  }

}

else

 

Следующая функция — tcp_send_http_middle

 

if ((tcpprop.http_doc==EXISTING_HTML)||(tcpprop.http_doc==EXISTING_JPG))

{

  if (tcpprop.http_doc==EXISTING_HTML)

  {

    result=f_lseek(&MyFile,((uint32_t)tcp_mss*(tcpprop.cnt_data_part-tcpprop.cnt_rem_data_part))-strlen(http_header)); //Установим курсор чтения в файле

  }

  else

  {

    result=f_lseek(&MyFile,((uint32_t)tcp_mss*(tcpprop.cnt_data_part-tcpprop.cnt_rem_data_part))-strlen(jpg_header)); //Установим курсор чтения в файле

  }

  result=f_read(&MyFile,(void*)tcp_pkt->data,tcp_mss,(UINT *)&bytesread);

}

else

 

Здесь практически то же самое, только указатель мы уже устанавливаем не на 0, а в соответствующее место в файле, а также мы перед этим не монтируем файловую систему и не открываем файл, так как это у нас уже сделано.

Ну и последняя функция — tcp_send_http_last

 

if ((tcpprop.http_doc==EXISTING_HTML)||(tcpprop.http_doc==EXISTING_JPG))

{

  if (tcpprop.http_doc==EXISTING_HTML)

  {

    result=f_lseek(&MyFile,(tcp_mss*(tcpprop.cnt_data_part-1))-strlen(http_header)); //Установим курсор чтения в файле

  }

  else

  {

    result=f_lseek(&MyFile,(tcp_mss*(tcpprop.cnt_data_part-1))-strlen(jpg_header)); //Установим курсор чтения в файле

  }

  sprintf(str1,"f_lseek: %d\r\n",result);

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

  result=f_read(&MyFile,(void*)tcp_pkt->data,tcpprop.last_data_part_size,(UINT *)&bytesread);

}

else

 

Массив с главной страницей теперь можно удалить

 

const uint8_t index_htm[] = {0x3c,

 

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

Запросим главную страницу (нажмите на картинку для увеличения изображения)

 

Image06_0500

 

Запросим другой файл, например index1.htm

 

Image07

 

Загрузим в папку IMG на карту SD несколько файлов в формате jpeg

 

Image08

 

Также откроем файл index.htm и добавим туда картинку из файла куда-нибудь в текст страницы

 

</ul></p>
<p>
  <img src="/IMG/img02.jpg" />
</p>

<p>Memories</p>

 

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

 

Image09_0500

 

Можно вставить картинку и поменьше и побольше но только одну, иначе браузер пытается открыть несколько соединений, а у нас такой поддержки нет. Я пытался добавить виртуальные сокеты, но видимо потому, что свободное количество памяти у нас уже стремится к нулю, номер сокета, а также некоторые переменные, хотя и с аттрибутом volatile начинают без моего ведома менять значение, поэтому я забросил это занятие. Здесь нужен уже контроллер помощнее. Но и так, я считаю неплохо. если же мы хотим всё равно добавить несколько рисунков, правда небольших, обойти этот нюанс можно с помощью внедрения данных картинок прямо в код html, то есть в саму страницу. Для этого существует несколько онлайн-сервисов, с помощью которых можно сделать эти преобразования. Код выглядит приблизительно так

 

<img width="102" height="58" title="" alt="" src="data:image/jpeg;base64,/9j/4RSXRXhpZgAASUkqA... и т.д.

 

Таким образом я добился вывода нескольких изображений на странице

 

Image10

 

Таким образом, с помощью сегодняшнего урока мы смогли улучшить наш HTTP-сервер, расширив его память для хранения файлов с помощью подключения карты SD и файловой системы. Хотя конечно скорость оставляет желать лучшего, так как карта у нас подключена по интерфейсу SPI, что не позволяет общаться с ней со скоростью превышающей 1 мегабит в секунду, тем не менее мы ещё раз проработали протокол HTTP, научившись также учитывать и размер окна, а также передавать другие документы кроме главной страницы, причём и других форматов.

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

 

 

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

 

Исходный код

 

 

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

Программатор недорогой можно купить здесь ST-Link V2

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

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

STM LAN. ENC28J60. TCP WEB Server. Подключаем карту SD

 

 

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

STM LAN. ENC28J60. TCP WEB Server. Подключаем карту SD

 

5 комментариев на “STM Урок 89. LAN. ENC28J60. TCP WEB Server. Подключаем карту SD
  1. SmNikolay:

    Добрый день. Я начинающий в программировании 🙂 В последних версиях куба в структуре FIl Fatfs отсутствует fsize. Как с этим бороться? Собираю по вашему уроку на контроллере f407 с SDIO SD.

  2. SmNikolay:

    Еще раз здравствуйте. Проблему с Fatfs решил. Осталась 1 проблема. Ели функция сети вызывается в прерывании то зависает на открытии файла с SD. Если в основном цикле то все прекрасно работает. Не подскажите в какую сторону копать?

  3. SaeedO:

    Hello sir,
    Thank you for your help. It is great.

    I want to use Ajax for transferring data from server continuously to client and plot it on the gragh.

    I want to do this with ENC28J60.

    Don't have a suggestion to do this?

  4. Семен:

    Здравствуйте.
    Я не могу понять откуда взялись команды «sd_read_block» и «sd_Write_block»

  5. Sajad:

    Привет
    Ваше обучение было очень хорошим.
    В продолжение этого проекта я хочу собрать локальный веб-сервер с ENC 28j 60 и отображать текущие значения нескольких датчиков на веб-странице в браузере.Значения датчиков меняются мгновенно и в режиме реального времени. Как мне это сделать? Пожалуйста, помогите мне.

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

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

*