AVR Урок 52. LAN. ENC28J60. TCP WEB Server. Подключаем карту SD. Часть 1

 

 

 

 

Урок 52

Часть 1

 

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

 

 

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

Ножки мы назначим следующие

Chip Select — D4

SCK — D5

MISO — D7

MOSI — D6

Кроме этих ножек для карты потребуется питание, которое мы подадим с нашей платы Arduino UNO с контакта 3V3 и GND на соответствующие ножки.

Модуль будем использовать вот такой самодельный

 

image00

 

Флеш-карту используем типа Micro-SD, распиновка следующая

 

Контакт Micro SD

Назначение вывода

1

Not Connected

2

Chip Select

3

MOSI

4

+3,3 V (VDD)

5

SCK

6

GND (VSS)

7

MISO

8

Not Connected

 

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

 

image01

 

Запишем на карту SD несколько файлов со страницами разного размера с именами index,htm, index1.htm, index2.htm и т.д. Также в корневую директорию карты запишем файл с иконкой сайта favicon.ico. Чтобы создавать иконки существует ряд приложений и онлайн-сервисов. Так что это занятие затруднений не вызвало.

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

Создадим проект с именем ENC28J60_HTTPS_SD и добавим в него все файлы с прошлого занятия из проекта ENC28J60_HTTPS_LARGE. Также помимо тех файлов добавим к проекту ещё файлы из проекта MYSD_SPI_NEW из урока 33 с именами pff.c, mmc.c, diskio.h, pff.h и integer.h.

Зайдём в файл mmc.c и уберём из него строку с подключением дисплея

 

#include "lcd.h"

 

Аналогичную строку также удалим из файла pff.c.

Вернёмся в файл mmc.c и настроим его теперь на наши ножки портов.

Сначала номера ножек

 

#define SD_DI 6 //MOSI

#define SD_DO 7 //MISO

#define SD_CLK 5

#define SD_CS 4

 

А затем и сами порты. Заменим вхождения строк следующим образом

 

PORTB -> PORTD

DDRB -> DDRD

PINB -> PIND

 

В файле main.h подключим файлы библиотеки Petit FatFs

 

#include "enc28j60.h"

#include "pff.h"

#include "diskio.h"

#include "integer.h"

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

 

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

 

/*

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

ip_pkt->ipaddr_src & 0x000000FF,(ip_pkt->ipaddr_src>>8) & 0x000000FF,

(ip_pkt->ipaddr_src>>16) & 0x000000FF, ip_pkt->ipaddr_src>>24,

ip_pkt->ipaddr_dst & 0x000000FF,(ip_pkt->ipaddr_dst>>8) & 0x000000FF,

(ip_pkt->ipaddr_dst>>16) & 0x000000FF, ip_pkt->ipaddr_dst>>24, len_data);

USART_TX((uint8_t*)str1,strlen(str1));

*/

 

//USART_TX((uint8_t*)"ACK\r\n",5);

 

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

 

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 last_data_part_size;//размер последней части данных для передачи

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;

if(tcpprop.cnt_rem_data_part>1)

 

Теперь перейдём в функцию передачи средней части страницы и там, где мы передаём флаг 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;

if(tcpprop.cnt_rem_data_part>1)

 

 

Перейдём в заголовочный файл 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, чтобы при сборке кода не было ошибок.

Также добавим три глобальные переменные для работы с файловой системой

 

extern uint8_t macaddr[6];

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

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

WORD s1;

 

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

 

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

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

const PROGMEM 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;

}

USART_TX((uint8_t*)tcpprop.fname,strlen(tcpprop.fname));

USART_TX((uint8_t*)"\r\n",2);

result=pf_mount(&fs);

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

USART_TX((uint8_t*)str1,strlen(str1));

result=pf_open(tcpprop.fname); //Попытка открыть файл

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

USART_TX((uint8_t*)str1,strlen(str1));

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

USART_TX((uint8_t*)str1,strlen(str1));

 

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

 

USART_TX((uint8_t*)str1,strlen(str1));

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_P(jpg_header);

  }

  else

  {

    tcpprop.http_doc = EXISTING_HTML;

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

    tcpprop.data_size = strlen_P(http_header);

  }

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

  tcpprop.data_size += fs.fsize;

}

else

{

  tcpprop.http_doc = E404_HTML;

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

  tcpprop.data_size = strlen_P(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=pf_mount(&fs);

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

  USART_TX((uint8_t*)str1,strlen(str1));

  result=pf_open(tcpprop.fname); //Попытка открыть файл

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

  USART_TX((uint8_t*)str1,strlen(str1));

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

  USART_TX((uint8_t*)str1,strlen(str1));

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

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

  USART_TX((uint8_t*)str1,strlen(str1));

  if (tcpprop.http_doc==EXISTING_HTML)

  {

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

    result=pf_read((void*)tcp_pkt->data+strlen_P(http_header),(uint16_t)fs.fsize,&s1);

  }

  else

  {

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

    result=pf_read((void*)tcp_pkt->data+strlen_P(jpg_header),(uint16_t)fs.fsize,&s1);

  }

}

else

 

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

 

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

 

Предыдущий урок Программирование МК AVR Следующая часть

 

 

Приобрести плату Arduino Nano V3.0 оригинальный FT232RL можно здесь.

Программатор (продавец надёжный) USBASP USBISP 2.0

Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.

 

 

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

 

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

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

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

*