Все, что вы хотели узнать о том, как за 5 минут запустить простой веб-сервер на чипе W5100, но стеснялись спросить.

image

В статье будет просто, подробно и ясно описано, как запустить, например, веб-сервер, на замечательной и недорогой микросхеме W5100 компании Wiznet.

Чем же она замечательна?
Во-вторых – недорогая.
И во-первых – всю работу она делает за Вас. Вам же остается лишь лениво слать-принимать ТЕКСТОВЫЕ (точнее — HTML) данные.

Дисклаймер1: Инженеграм-электронщикам с… дцатилетним стажем статья может показаться поверхностной и упрощенной, поскольку они и так (возможно) в теме. Данная статья рассчитана на тех, кто имеет некоторый опыт в электронике, или, по крайней мере, в программировании микроконтроллеров, и просто, черт подери, хочет, наконец (слитно), запустить эту… (зачеркнуто) (ладно, пусть будет «замечательную») микросхему W5100.

Дисклаймер 2: Я не ставил целью всеобщее обучение, полный перевод даташит и растолковывание всех его пунктов. Моей задачей является прояснить запутанные моменты и показать как пользоваться чипом W5100 и как запустить на нем аппликацию (например – веб-сервер).

На основе моих разъяснений и примера аппликации, каждый, кто имеет опыт в микроконтроллерах и хотя бы основные понятия в сетях – сможет разобраться во всем остальном самостоятельно.

Дисклаймер3: Пожалуйста, не проводите этот эксперимент дома. И желательно, чтобы рядом находился кто-нибудь из взрослых.


SCOPE


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

Однако, представленный в конце пример позволит Вам разобраться, как использовать интернет (точнее – Ethernet и TCP/IP ) в своих аппликациях на микроконтроллерах, и, несмотря на простоту, он может быть вполне применен практически. Вы сможете его усложнять, поскольку поймете, как работать со связкой клиент-сервер, и, наконец, имея под рукой что-то работающее и теперь уже много лучше понимая тему – Вам будет намного легче углублять свои познания, не засыпая над десятком страниц с описанием назначения регистров, принципов настройки NAT, или правильным указанием смещения фрагментов в пакетах.

("… фрагменты в пакетах.." Что то такое вроде у Вуди Аллена было?)

По своему опыту: обычно, человеку, который пишет статью, сабжект ясен и понятен, поэтому он пишет коротко и «ясно».
Однако, это часто не так для читателя, который может быть не в теме, и часто вынужден гадать, что же именно автор имел в виду, и следует ли понимать некое высказывание вот так, или совершенно наоборот? Поэтому, там, где можно, я не стану рассусоливать, а в тех местах, где нужно разжевать до полной ясности и однозначности понимания – не стану экономить слов.

При этом, помните, что никто не идеален (даже я), так что простите, если кто ушел неудовлетворенным.

Для дочитавщих до конца — бонус: в конце статьи будет приведен РАБОТАЮЩИЙ код на простом «Си» с кучей комментов, чтоб даже пятикласснику было понятно, что происходит в программе. Код написан для обычного PICa (не все ж еще вокруг AVR-щики и Arduin-щики?).

Ингредиенты


Если у вас пока нет особого желания разводить плату и паять на нее микросхему в корпусе TQFP80, рекомендую взять обычный ардуиновский Ethernet Shield на базе W5100. Также потребуется простой и недорогой PIC, макетка для PICa и немного соединительных проводов, Ethernet патч-корд и, по вкусу, сериальный кабель RS232, на случай, если вам вдруг интересно, что происходит внутри сервера во время работы (в код вставлены строки для вывода попутной информации – на терминал).

Лирика (можно пропустить)


Всем, имеющим некоторый опыт в разработке, эта ситуация знакома (или нет? Или это только я такой лузер?)

Вы берет некий чип, на котором необходимо запустить аппликацию. Для простоты допустим, что вам также знакома тема/область, в которой даннаая аппликация будет использоваться (например Ethernet, TCP/IP, HTTP, CGI etc).

Вначале Вы берете даташит на чип, в 100500 страниц,
не пропускать лирику

и внимательно его читаете. Затем вы перечитываете его еще несколько раз и начинаете, наконец, понимать, как с данным чипом работать. Затем вы берете errata и повторяете те же итерации.

После прочтения оных вы, наконец, понимаете, как чип работает, как его правильно программировать (100500 регистров -1), но пока, все же, остается не совсем ясным, как ПРАВИЛЬНО его надо запускать. (Например, имеет ли значение порядок обращение к регистрам, и если да – то какой порядок правильный, а если не имеет значения – то точно ли он не имеет значения?).

Разумеется, много полезной информации м��жно найти в инете и на форумах, но из всех найденных там примеров не совсем ясно, какие НА САМОМ ДЕЛЕ работают, какие работают несмотря на обилие ужасающих ошибок (о которых Вы пока и не подозреваете), а какие «примеры» – просто плод фантазии аффтара, который написал, да не проверил, надеясь, что проканает.

Ок, вы прошли все эти этапы, ТЕПЕРЬ Вам уж точно все понятно.

Вы правильно подключаете все пины чипа к чему следует, правильно подключаете чип к процессору, правильно устанавливаете все регистры и что там у него еще внутри, и – о чудо! – наконец-то…
Да-да, все в порядке – ничего не заработало.

Это самый противный этап – когда все сделано правильно и строго по даташит, но все равно не работает. Вы долго и муторно разбираетесь, в чем же проблема, и наконец н��ходите 1...5...17 багов. Ну, конечно! Именно об этом и было сказано в даташит/эррате. Ну, теперь-то понятно ЧТО ИМЕННО они там имели в виду (а о чем вы сами могли бы догадаться).

Все.
Аппликация заработала.
Теперь Вы – спец по данному чипу.
Фанфары. Премия. Пиво. Толпы поклонниц. Слава и следующий дидлайн.


Данная статья призвана помочь читателям пропустить нудные этапы и сразу перейти к стадии «все заработало» и далее — по тексту.

Лирика — 2 (можно пропустить)


Много лет назад ( даже чуть больше), еще в до- Windows98 времена, мой товарищ поварчивал, что вот мол, развелось этих лже-юзеров, DOSовских комманд ничерта не знают, думают, что раз есть Windows 3.11, то в config.sys лезть не обязательно, а туда же, — хотят компьютер иметь.
пропустим по Лирике 2 ?
Я всегда считал, что кто-то любит ковыряться во внутрях, а кто-то просто хочет пользоваться и не париться. И оба вида имеют право на существование. (Гай Кавасаки: «… Как микроволновка может выдавать сообщение об ошибке? Микроволновка должна просто разогревать! Никто не должен проходить специальные курсы программирования микроволновок!»).

Сегодня часто слышны сетования, что, вот, мол, эти Ардуины портят народ и плодят неучей – нет чтоб отучиться лет n и набраться опыта еще лет n+1 — все бросились клепать свои проекты, не пытаясь получить глубокие знания в программировании и электронике.
Ужас, ужас.
И что?
Я считаю, что это нормально — человек с творческой жилкой и креативом придумал задумку (или наоборот) и хочет ее реализовать. Не имея глубочайших познаний. И это нормально.

Также, на заре интернета, была эдакая мода гордо указывать внизу странички: «сайт создан полностью вручную в notepad.exe. Никаких HTML-редакторов !». Ничего не напоминает?

При этом я, например, начинал не с Си и даже не с ассемблера — первый свой комп, собранный с нуля и абсолютно пустой, чтобы залить в него для начала хотя бы загрузчик, я программировал в двоичном коде 16-тью тумблерами шины адреса и 8-ю тумблерами шины данных.
Мне долго еще во сне приходили длинные ряды большик красных нулей и единичек, а когда мама послала купить хлеба, первой мыслью было:
«пойти за хлебом… это какой КОД?».

И даже я, баггер со стажем, поддерживаю прогресс, увеличившиеся возможности для творчества новичков, и разнообразие видов юзеров.

Client – Server. В общих чертах


Вероятно, есть смысл начать с конца и двигаться наверх.

Вы запускаете программу браузера, вводите в ней название сайта – и получаете на экране содержимое веб-странички.
Разумеется браузер получает не красивую страничку с картинками, а кучу текста, HTML-тэгов, линки на картинки/видео и т.п., собирает все это вместе, и выдает вам в виде красивой (иногда) веб-странички.

Все это передается между сервером и браузером по протоколу HTTP, который также управляет и установлением соединения между ними и их поведением.

Поскольку мы имеем дело с сетью/интернетом, то, для того, чтобы быть переданными по правилам, принятым в сети/интернете, все эти данные (HTML, вокруг которого «обернут» HTTP) заворачиваются в новый «слой» — TCP/IP.

TCP (Transmission Control Protocol) используется для того, чтобы обеспечить гарантированное установление соединений и доставку пакетов данных. Веб браузеры (как и почтовые клиенты) используют именно TCP.

Вероятно, я должен упомянуть о том, что помимо ТСР существует и UDP (User Datagram Protocol) который вообще никак не озадачивается такой мелочью, как установление соединения и гарантированная доставка сообщений, а раз время на это не тратит, то и строчит пакетами как из пулемета, но использоваться может только если соединение (канал) качественный и надежный, либо если не предъявляются жесткие требования к потере (точнее НЕ потере) пакетов (господи, да кто их считает!).

В данной статье UDP рассматриваться не будет, но W5100 умеет и его.

Поскольку наш веб-сервер будет соединен с компьютером через Ethernet-соединение, все это «оборачивается» в еще один слой – Ethernet frames.

На самом деле, как вы знаете, мы могли бы передавать TCP/IР даже и по обычному RS-232 каналу, безо всякого Ethernet.

Итак, мы имеем матрешку: Ethernet фрейм, состоящий из служебных байтов («заголовков») и байтов с собственно данными (Payload).

Эти данные содержат в себе TCP/IP пакеты, которые, в свою очередь, также состоят из байтов заголовков и собственно данных.

А эти последние данные, в свою очередь, состоят из HTTP-заголовков и давно уже ожидающих и успевших чуть остыть HTML-форматированного текста и/или картинок.

Сказ про Etherтet, ТСР и IP — мы опустим, потому что все эти сложности W5100 берет на себя, снимая с нас головную боль (вот в чем ее прелесть!) (я имею в виду прелесть W5100, не головной боли).

А вот на HTTP остановимся капельку подробнее, потому что именно нам придется самим забирать его у W5100 и обрабатывать (когда веб-браузер будет делать запрос к нашему серверу) и наоборот — формировать его и отдавать W5100, когда мы (сервер) будем отвечать веб-браузеру.

Полностью раскрыть все нюансы HTTP протокола в данной статье не представляется возможным (отсылаю любопытных к HTTP/1.1 RFC 2616), здесь же мы рассмотрим самое основное и то, что поможет нам поднять простой (но вполне юзабельный !) веб-сервер.

Когда мы открываем браузером веб-сайт (например: opticaldt.us — не реклама!) браузер отсылает на веб-сервер сайта запрос:

GET / HTTP/1.1
Host: opticaldt.us
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6


Обратите внимание на эту строку: GET…

После “… /” и до “HTTP/1.1” — пусто.
Это значит, что, по умолчанию, запрашивается файл index.html (или index.htm).
Если в данной субдиректории на сервере действительно имеется файл index.html, то браузер успешно его откроет. Если же этого файла нет (либо вообще, вы сделали запрос н отсутствующий файл с любым именем и расширением), то сервер ответит вам страничкой «error 404» (которая также возникает не извоздуха — отдать ее в ответ на неверный запрос – задача сервера. То есть вот прям лично вы должны создать такую страницу).

Как видно из заголовка, клиент (браузер) сообщает о себе много любопытного и полезного.
Поскольку наш сервер будет достаточно простым, и ничего такого особенного мы не станем посылать клиенту (например, не станем вытягивать из заголовка все интимные подробности и огорашивать юзера чем-то вроде: «Здравствуйте, вы пришли с такого-то ip, живете по такому-то адресу на втором этаже, хотя квартира вообще то не ваша, счет провайдеру закончится через 3 дня, не забудьте пополнить, вашего кота Федора пора бы покормить, с такой-то резолюцией экрана и WindowsXP негоже заходить на наш восхитительный сайт, который много лучше смотрелся бы, будь у вас не Mozilla, a IE 11, ах да, у вас ведь украинский ip, поэтому «здоровэньки булы», хотя нет, это же Евпатория, поэтому «и снова здравствуйте! И т.д. и т.п.), нас, при запросе от браузера, будет интересовать только первая строка – нам нужно будет ее, как говорят в народе «пропарсить», чтобы выяснить, какой именно файл клиент (браузер) желает получить от нашего сервера.

В простейшем варианте, просто чтоб протестировать, что сервер работает, можно вообще на любой запрос (то есть запрос любого файла) отсылать одну и ту же HTML страницу.
(Забегая вперед, в нашем примере мы все же будем отдавать «файл» index.html если запросят именно его, и страницу с ошибкой, если запрашивать будут что-либо иное).

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

HTTP/1.1 200 OK
Date: Sun, 13 Jul 2014 21:32:49 GMT
Server: Apache
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 185
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html


<html><head><meta http-equiv="Content-Type" content="text/html; charset=Windows-1251"><title>W5100 WebServer</title></head><body><center>ну шо,<br><b> хелло World ?!</b></center></html>


В первой строке («… 200 ОК..») сервер сообщает, что запрос верный (то есть такой файл имеется).
В строке «Content-Length: 185» сервер предупреждает клиента (то есть браузер) о длине передаваемого файла (точнее — именно и конкретно о длине веб страницы, которая идет сразу после HTTP заголовков).

Для того, чтобы обеспечить более менее корректную работу с браузером, нам (серверу) надо будет отдавать в ответ (помимо HTML файла) по крайней мере три параметра в заголовке HTTP:

HTTP/1.1 200 OK
Content-Type: text/html ( если мы посылаем именно это, а не, например, картинку).
Content-Length: (посчитаем потом)

На самом деле, современные браузеры весьма либеральны, поэтому можно отбросить и третью строку заголовка. Тем более, что в нашем примере, после отдачи клиенту «веб-страницы», мы разрываем соединение, поэтому клиент ( браузер) и так догадается, что он получил все.

Но… Если Ваша страница будет длинной, или, например, вы собираетесь посылать картинки и т.п. — лучше все-таки указывать длину посылаемого блока данных (Content-Length).

Совсем чуть чуть о TCP/IP


Когда я писал выше, что W5100 делает за нас всю работу с Ethernet и TCP/IP, я чуть приукрасил действительность. Кое-что (совсем капельку) нам все-таки придется делать самим. А именно: управлять процессом установления соединения и его завершением.

Коротко говоря, сервер и клиент общаются пересылкой друг-другу IP пакетов, которые могут содержать «полезные» данные (например – HTML страничка), а могут быть чисто служебными, без «полезных» данных.

Откроем даташит микросхемы W5100 (да-да, теперь уже пора) и рассмотрим, как общаются клиент (браузер) и W5100 в режиме сервера:

image

Вначале W5100 в режиме OPEN открывает/создает сокет (сетевое соединение) и переходит в режим «слушания» (LISTEN), дожидаясь запроса от клиента (т.е. браузера).

Когда запрос от клиента приходит, и между сервером и клиентом устанавливается соединение, сервер (W5100) переходит в режим ESTABLISHED и между сторонами происходит обмен данными.

На заметку: по уставу, максимальный размер данных в IP пакете – 1500 байт. Если ваши данные не укладываются в один пакет (большой размер HTML кода, или нужно передать картинку итп) – придется разбить данные на порции менее 1500 байт и передавать их одну за другой.

Далее, когда стороны высказали друг-другу все, что в душе нагорело, кто-то из них (как и в жизни) первым «бросает трубку», посылая запрос на разьединение (Disconnect Request).

Примечание: не всегда разьединение наступает именно после (полной) передачи данных. Например, у вас медленный канал, или пошли помехи, и браузер устал ждать завершения и прекратил соединение по своему тайм-ауту. Либо что-то не заладилось у веб-сервера и он решил прервать соединение в самом интересном месте. Ну, в общем, всякое ведь в жизни бывает.

После того, как связь между ними разорвана, сервер переходит в режим закрытия соединения (сокета) — CLOSED (не, ну все, что нужно – было сказано/отослано, че еще ждать то?).

Все.

Если вы сторонник мимолетных связей и/или одного раза Вам хватило с лихвой – на этом можно остановиться.
Если же вы оптимист по жизни ( или просто настойчивый) и полагаете, что к вашему серверу, вполне возможно, будут еще обращаться, и не раз — вам просто следует вернуться к шагу “OPEN” и далее — по картинке. (Порядочные сервера обычно так и поступают). Хотя… может быть в будущем появятся дешевые пластиковые одноразовые сервера? Типа использовал 1 раз – и выбросил?

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

Возьмем самый обычный IP стек:

Пусть не испугает никого красивое слово «стек», колдуны и маги от IBM-360 и AS-400 специально выдумывают заумные термины, чтобы подавить у простых смертных всякое желание потрогать немытыми руками их Храм. На самом деле это просто протокол, формат передачи данных, в котором расписано какой длины и в каком следуют порядке служебные поля и собственно данные. (Жванецкий: «… и даже болгары не могут понять нашего выражения, что «несмотря на погодные условия» — означает просто дождь!»)

image

Нас интересуют три старших бита в седьмом байте: ФЛАГИ.

Комбинациями этих трех битов формируют следующие флаги: FIN, SYN, RST, PSH, ACK, URG.

FIN — указывает на окончание сессии;
SYN — инициализация соединения;
RST — прерывание соединения (обычно – в ответ на ошибку);
ACK — подтверждение получения ( пакета);
PSH — упрощенно — требование немедленной высылки подтверждения;
URG — указание на архисрочноть данного пакета.

Если вам любопытно (что похвально и полезно), как же в реальности происходит обмен пакетами и какие флаги при этом выставляются и в каких случаях, а ко всему еще — для чего предназначены остальные поля заголовка (хорошо, хорошо, — «стека») – вам придется найти это самостоятельно. В данной статье мы рассматривать это не будем, поскольку W5100 берет всю заботу о пакетах и флагах на себя, мы же имеем дело с уже свершившимся фактом – статусом соединения на данный момент (OPEN, LISTEN, ESTABLISHED, CLOSED).

Краткое, насколько это возможно, описание W5100


Что ж, далее оттягивать этот момент не представляется возможным, пора рассмотреть W5100 с железной и программной сторон.

image

W5100 с одной стороны подключается к Ethernet коннектору (через трансформатор и согласующие цепи), с другой – с микроконтроллеру.
К микроконтроллеру ее можно подключать (по вкусу и ситуации) либо с использованием шин адреса и данных, либо через SPI интерфейс.
Как именно подключать чип – я расписывать не стану, готовых схем с описанием полно в интернете. Тем более (ладно, раскрою секрет), что мы будем в нашем примере использовать готовую плату с этим чипом.

Чип содержит в себе Ethernet PHY и МАС, реализованные в железе обработчики Ethernet фреймов и нескольких протоколов группы IP, позволяет создать до 4 сокетов (сетевых соединений) и содержит RX и ТХ буферы по 8Кбайт памяти каждый, которые можно распределять между каждым сокетом по-братски или по справеделивости (или поровну), выделяя 1,2,4 или 8 КБ на сокет (но не забывая, что размер всего буфера – 8КБ на прием и 8КБ на передачу).

Распределение памяти:

image

Как видно из рисунка:

с адреса 0х0000 по адрес 0х002F расположены общие для всех регистры,
с адреса 0х0400 по адрес 0х07FF – 4 одинаковые группы регистров – по одной на каждый сокет,
с адреса 0х4000 по адрес 0х5FFF — буфер передачи ( 8КБ), который вы можете распределить между сокетами по вашему усмотрению,
с адреса 0х6000 по адрес 0х7FFF — буфер приема ( 8КБ), который вы также можете распределить между сокетами по вашему усмотрению.

Все регистры – 8 битные. Большинство можно и читать и писать.

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

Важный момент: в W5100 используется порядок байт, который в народе называется «биг эндиэн» или «сетевой порядок». Глубокий же научный смысл сводится к тому, что сначала идет (передается) старший бит старшего байта и последним – младший бит младшего байта, и, главное, — старшие байты располагаются в младших адресах памяти. Тоесть, например, для записи 4 байт ай-пи адреса Вашего сервера (скажем 192.168.0.155) в предназначенные для этого регистры Source IP Address 0x000F … 0x0012 (SIPR0 … SIPR3), Вам следует записать «192» в регистр SIPR0, «168» — в SIPR1, «0» — в SIPR2 и «155» — в SIPR3.

Общие регистры


(Общие – значит влияют на работу всего чипа и всех сокетов, тоесть не относятся к конкретному сокету).

0х0000 MR — Mode – тут все понятно – через них мы задаем режим работы;
0х0001 ..0x0004 GAR0..3 — Gateway Address (напр. 192.168.0.254 );
0х0005… 0х0008 SUBR0..3 — Subnet mask Address (напр. 255.255.255.0);
0x0009..0x000E SHAR0..5 — Source Hardware Address
( проще говоря – МАС адрес, например 0A 1B 2C 3D 4E 5F).
(если Вы не собираетесь наводнить рынок Вашими серверами, то можете указать любой МАС, главное чтобы в вашей сети не было устройств с таким же МАС адресом – нам ведь не нужны конфликты, верно?)

0x000F… 0x0012 SIPR0..3 – Source IP Address – адрес нашего веб-сервера, например: 192.168.0.155;
0х001А — RMSR – RX Memory Size — здесь Вы указываете сколько памяти буфера приема придется на каждый сокет;
0х001В — ТMSR – ТX Memory Size — здесь Вы указываете сколько памяти буфера передачи придется на каждый сокет;

Регистры сокетов


В нашем примере мы будем работать только с одним сокетом и только с теми его регистрами, которые нужны для TCP/IP сервера, поэтому рассмотрим лишь эти регистры:

0х0400 S0_MR — Socket0 Mode Register — устанавливаем режим работы данного конкретного сокета;
0х0401 S0_CR — Socket0 Command Register– здесь мы будет выставлять команды для управления состоянием соединения;
0х0403 S0_SR – Socket0 Status Register — отсюда мы будем читать данные о текущем статусе происходящего;
0х0404, 0х0405 S0_PORT0, S0_PORT1 — Socket0 Source Port — порт, по которому доступен наш сервер (если корректнее – порт по которому доступен открытый нами сокет).

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

Например, если нам нужен файл index.html, находящийся на сервере с адресом 192.168.0.155 с доступом к сокету через порт 123, то в строке URL браузера (то есть в адресной строке) нам надо будет написать:

192.168.0.155:123/index.html (подождите, пока не нажимайте Enter).

Но… Для соединений вида «http://… » (то есть для протокола HTTP) принято выделять порт 80.
Поэтому, если мы назначим для Socket0 Source Port порт номер 80, то можем не вводить его в адресной строке браузера, поскольку, если порт не указан, браузер по определению будет обращаться именно к этому порту, в результате URL вида 192.168.0.155/index.html вполне будет работать без ошибок. (Говоря более обобщенно — это справедливо для всех случаев, для которых имеются дефолтные порта
– если вы их не указываете при доступе к сервису, клиент идет по дефолтному порту: 80 для HTTP, 23 для Telnet и так далее).

Также, как мы разбирали ранее, если не указывать название запрашиваемого файла, то обеими сторонами принято подразумевать, что речь идет о файле index.html (или index.htm) (или index.php). Поэтому, указав в адресной строке браузера: 192.168.0.155, мы получим верный отклик сервера (то есть, если клиент на запрашивает у нас конкретное имя файла, то мы понимаем, что он ожидает именно файл index ) (при этом помним, что если, например, клиент запросил директорию, содержимое которой мы стесняемся показывать всем и каждому (например – картинки с котиками, или скрипты и т.п.) то вполне комильфо будет выдать ему страничку «ошибка 404»).

Ну и чтоб совсем уж сэкономить время на ввод адреса — если не указывать тип протокола, то браузер автоматически будет считать, что речь идет о HTTP (если, конечно, браузер – не IE который любит повы… Ок, не важно, забыли).

Таким образом, просто написав в строке адреса 192.168.0.155 — все стороны поймут, что речь идет о 192.168.0.155:80/index.html

Значит, решено, да? Зададим в нашем сервере адрес порта сокета – 80.
При этом не забываем про «биг – эндиэн», поэтому 80 (точнее – 0080) запишется так: S0_PORT0 = 00, S0_PORT1 = 80 (да, и не забудьте – это десятичные «80», а не хекса).

0х0420, 0х0421 S0_TX_FSR0, S0_TX_FSR1 — Socket0 TX Free Size – размер свободного места в буфере передачи Сокета 0;
0х0422, 0х0423 S0_TX_RD0, S0_TX_RD1 — Socket0 TX Read Pointer (* только чтение) – указывает в буфере передачи на начало блока данных, которые (данные) мы собираемся послать клиенту.

Тут есть хитрая фишка ( Wiznet решила подгорчить канфэтку): если, например, вы прочли Socket0 TX Read Pointer, и он указывает, например, на адрес 0х4200, то… правильно — это совершенно не означает, что вы должны свои данные на передачу писать в буфер начиная именно с этого места. Ага, ага… («если женщина за рулем показывает левый поворот – это вовсе не означает, что она собирается ехать прямо!» (с)). Подробнее об этом мы поговорим чуть позже.

0х0424, 0х0425 S0_TX_WR0, S0_TX_WR1 — Socket0 TX Write Pointer – указывает в буфере передачи на конец блока данных, которые (данные) мы собираемся послать клиенту.

Тут необходимо (снова) сделать важное пояснение.
Два последних регистра не определяют физические адреса, по которым мы должны записать в буфере блок данных на передачу. Эти адреса мы должны вычислить сами.

Еще одно примечание: допустим, Вы выделили на буфер передачи 2КБ. Вы прочли Socket0 TX Read Pointer и оказалось, что он указывает на последние 10 байт буфера. А Вы вообще то намеревались передать (т.е. записать в буфер) 1000 байт. Что делать?
Все просто: вы пишете свои данные на передачу в эти 10 байт, затем переходите в самое начало буфера и продолжаете писать оставшиеся данные дальше (тоесть из 1000 байт Ваших данных, 10 байт разместятся в конце буфера, а остальная порция в 990 байт – в начале буфера).
W5100 знает об этом безобразии ( собственно говоря, он их и намутил) и потому, дойдя до конца – сам перескочит в начало буфера и терпеливо дочитает оставшуюся порцию.

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

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

На случай, если кто-то вздумает читать даташит, я сохраняю мнемоники констант и переменных максимально близко к даташиту, чтобы вы не путались с моим примером и с описанием в даташит. (Названия регистров я указал в точности, как в даташит; адреса, разумеется, тоже).

Итак, введем для удобства переменные:

S0_TX_BASE — базовый адрес буфера передачи Сокета0.

Если вы посмотрите на картинку распределения памяти, то он вроде бы равен 0х4000.
Ну а вдруг у вас чип W5100 сидит не на нулевом адресе? То есть, например, чиповский «нулевой» адрес смещен в адресном пространстве вашей системы, и в вашей системе ( например) нулевой адрес чипа равен 0х1984.
Далее: если с Сокетом0 все ясно, то базовый адрес Сокета1 (помимо потенциального смещения адреса чипа) зависит еще и от того, сколько Вы выделили памяти в буфере на каждый сокет.
Тоесть можно опять же жестко забить, а можно «по-правильному» прочесть TMSR регистр (в случае буфера передачи), узнать оттуда сколько буферной памяти распределено по каждому сокету, и таким образом «вычислить» где начинается область буферной памяти для каждого сокета.

В общем, я Вас предупредил, а в своем примере я его, для простоты, жестко забил:
S0_TX_BASE = 0х4000.

S0_TX_MASK — маска, понадобится нам далее в «расчетах».

Точно также, маску, по-хорошему, надо вычислять для каждого сокета исходя из того, что записано (вами же, кстати) в регистре распределения памяти буфера между сокетами (TMSR).

Я ничего не вычисляю (раньше я думал, что я – лентяй, но, став постоянным читателем Хабра узнал, что все в порядке – у меня просто прокрастинация), на каждый сокет я выделил по 2КБ (которые, как вы помните, равны не 2000 грам, но 2048 байт), соответственно маска — это 2048 -1 = 2047 ( 0x07FF ).

Итого, у нас в примере будет: S0_TX_MASK = 0x07FF.

Теперь добавим путаницы, а затем с честью ее распутаем:

Как я писал выше, регистры (read only) S0_TX_RD0, S0_TX_RD1– это указатель на начало блока Ваших данных на передачу, расположенных в буфере передачи сокета.
Если говорить более корректно – это указатель на тот конкретный байт данных, который будет передаваться наружу, в сеть, в данный момент (ну, тоесть не прям щаз пока вы читаете, а когда дело дойдет до передач).
И данные будут передаваться до тех пор, пока текущий адрес (в буфере) передаваемого байта не дойдет до конца блока ваших данных, который (указатель на конец блока), записан в паре регистров S0_TX_WR0, S0_TX_WR1.

Тоесть, по факту, W5100 вытаскивает из буфера передачи байт ваших данных, на который указывает пара регистров S0_TX_RD0, S0_TX_RD1, выплевывает его наружу в сеть, затем инкрементирует регистр и переходит к следующему байту, на который этот регистр указывает, и так этот регистр (пара) и инкрементируется, пока не сравняются по значению с парой регистров S0_TX_WR0, S0_TX_WR1, и в этот момент передача данных закончится. (Соответственно, передав все ваши данные, содержимое S0_TX_RD0, S0_TX_RD1 станет равно содержимому S0_TX_WR0, S0_TX_WR1).

Джентльмены, я прощу прощения за всю эту нудятину, очень не хотелось никого грузить, но без этого – никак (умом Визнету не объять). Потерпите еще чуть чуть, это скоро закончится, coffee break совсем близко.

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

Наплодим несколько переменных:

Смещение: S0_TX_OFFSET = make16 ( S0_TX_RD0, S0_TX_RD1 )

// создаем 16-разрядную переменную «смещение» из двух 8-разрядных регистров-пойнтеров
// не забываем, что поскольку «биг-эндиэн», старший байт находится в регистре S0_TX_RD0,
// а младший — в S0_TX_RD1

По жизни, этот пойнтер может указывать вообще за пределы адресного пространства чипа (когда почитаете эти регистры в живой аппликации – убедитесь сами), поэтому надо отрезать лишнее, чтобы ячейка памяти, на которую указывает пойнтер, лежала внутри отведенной для данного Сокета зоны (то есть в физическом диапазоне адресов).
Придадим значению регистров более близкую к реальности величину (значение):

S0_TX_OFFSET = ( S0_TX_OFFSET & S0_TX_MASK )

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

S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE

Ну что ж, теперь у нас есть пойнтер, который указывает чипу с какого места начинать считывать байты на передачу, мы рассчитали физический адрес в буфере передачи нашего сокета (Сокет0), и (допустим) мы уже залили наши данные на передачу — в буфер передачи чипа, начиная с рассчитанного нами физического адреса.

Как чип узнает, до каких пор ему считывать данные из буфера передачи?
На это ему укажет упомянутый ранее регистр (пара) – пойнтер Socket0 TX Write Pointer.

Пока что пойнтер чтения Socket0 TX Read Pointer и пойнтер записи Socket0 TX Write Pointer содержат одинаковые значения.
Потому что (см. выше) после передачи блока данных их содержимое становится одинаковым, и подозреваю (в даташит об этом ни слова), после ресет чипа – тоже.

Итак, последний штрих – нам необходимо указать на конец блока данных в буфере передачи.
Внимание! — в отличие от физического адреса, который нам был нужен чтобы загрузить данные в буфер передачи, здесь мы будем оперировать именно с внутренним чиповским представлением самого себя – в памяти.
Тоесть мы просто увеличим содержимое пойнтера записи на длину передаваемого блока данных:

Socket0 TX Write Pointer = Socket0 TX Write Pointer + Длина_Блока_Данных.

Предполагая, что у Вас уже голова кругом идет, напомню, что под «Socket0 TX Write Pointer» (16 бит) мы подразумеваем содержимое пары 8-битных регистров S0_TX_WR0 (старший байт) и S0_TX_WR1 (младший байт).

(И — да, — если устали – можете попить кофе и перекурить, я подожду...)

Стойте! Перед перекуром… Даже не знаю, как вам сказать… Мы сейчас рассмотрели работу с регистрами при передаче данных. Понимаете, дело в том, что с регистрами приема данных – примерно такая же байда – преобразование пойнтеров – в физические адреса, длина блока.
Давайте вот как сделаем: принцип то Вы наверное поняли? В примере программы на Си я все этапы буду подробно комментировать, так что не запутаетесь. А если все же что-то останется непонятным – я в комментах отвечу. Deal?


Алгоритм


Если у меня и была надежда получить инвайт за статью, то она тает с каждым новым абзацем.

Да — наверняка думают читатели — вот это купились так купились на »… сервер за 5 минут"! Подождите — это вы еще код не видели! Нет, то есть он, конечно, не короткий, а немного наоборот, но это все изза обилия комментов и вставок для отладки. Если лишнее поубирать, чтобы остался только полезный код — его вообще можно записать на салфетке! И потом, лучше потратить 10 минут на чтение тут, чем 2 дня на чтение даташит в 100 страниц. Ну и опять же — «тяжело в ученье — 5 минут в бою» — никто ж не отменял.


Да, так вот, — алгоритм.

Я, как правило, люблю понимать, что делается в программе, и предпочитаю иметь фло-чарт. Точно так же, несмотря на непреодолимое желание сразу писать код, обычно мне удается себя одернуть и начать с рисования фло-чарт, чтобы вначале отладить логику работы на нем, а уж потом переходить к коду. Так что, давайте разберемся как будет работать наш веб-сервер (см. картинку):

image

Если принцип «лучше 100 раз увидеть — чем 1 раз услышать» еще работает, то, наверное, нет смысла описывать то, что написано на картинке?

Всего два слова:

Управление процессом будет происходить через S0_CR (Socket0 Command Register) (0х0401).

Проверка текущих состояний ( соединения и прочих) – через S0_SR (Socket0 Status Register) (0х0403).

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

Подключение Ethernet Shield — к PIC контроллеру


image

Здесь опять же нет смысла рассусоливать — на картинке все обозначено.

Совет: 2х3 хидер на плате (он под платой, на рисунке не виден) — довольно хлипкий и глючный. Рекомендую не пользоваться им, а подпаять провода прямо на плату, со стороны компонентов.

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

Программа (проверено – работает!)


Как настоящий ученый (?), прежде, чем проверять что-то на людях (или лабораторных животных) – я вначале проверил ее работу на себе, поэтому можете ей доверять, если вы еще не утратили эту способность.

Программа написана на CCS компайлере. Ее без проблем легко переделать (портировать – слишком громко будет сказано) на любой удобный IDE.

В качестве контроллера использовался PIC16F77 (просто оказался под рукой на готовой демо-платке). Точно также вы легко можете подправить ее (практически – одной строчкой в определении процессора) под любой PIC. Ресурсов она жрет мало, а если повыкидывать все функции print (полезные при отладке но никак не влияющие на программу), то код уменьшится еще раза в два.

Как я говорил ранее, пример напичкан «принтами» для вывода происходящего через RS232 — на экран терминала, для облегчения понимания процесса. Вы можете их спокойно поудалять – на работу сервера это никак не повлияет.

В программе сервера мы будет просто (простейшим способом) анализировать заголовок HTTP от клиента, чтобы уз��ать, какой файл он запрашивает. На самом деле мы будем искать запрос файла index.html «по умолчанию». То есть, когда название файла не прописывается – подразумевается именно индекс файл.

Запрос этот выглядит так:

GET / HTTP/1.1

Обратите внимание на пробел после первого "/" и до «HTTP» – в этом месте могла бы быть ваша реклама могло быть имя запрашиваемого файла.

Раз там пусто ( пробел) – значит запрашивается именно index.html. (Ок, объяснение не совсем корректно. Обычно, заходя на сайт, вы просто указываете имя сайта, безо всяких там слэш и имени файла. Пацаны с обеих сторон проплатили кому надо, договорились, что, если имя файла не указано, то будем выдавать в ответ индекс файл).

Например ввод в адресной строке браузера такого адреса:

192.168.0.155/kotiki.jpg

Отошлет на сервер запрос, который выглядит так:

GET /kotiki.jpg HTTP/1.1

Я к чему это так долго размусоливаю — мы будем просто проверять есть ли в HTTP заголовке пробел после слэш и если есть – отсылать клиенту «страничку» index.html (ну ооочень простую). А если там хоть что-то другое — то страничку с ошибкой_404.

То есть все будет очень простенько, потому что у нас и пример ведь тоже простой, причем пример запуска веб-сервера а не файловой системы и генерации HTML кода.

Но… (ааааа! афтар достал уже!) ага, опять но. Тут вот какая фишка: стандартный HTTP заголовок от сервера, в случае «страница не найдена», начинается так:

HTTP/1.1 404 Page not found

(В отличие от ситуаций когда все ок, и сервер отсылает HTTP/1.1 200 OK)

Если мы отправим браузеру нашу HTML страничку с каким угодно красивым сообщением об ошибке, и таким вот заголовком, то большинство из них красиво отобразят нашу красивую страничку. Кроме Internet Explorer. Он, получив такую строку в заголовке, просто похерит нашу страничку и выдаст свой ответ Керзону в стиле а-ля Internet Explorer.

Также, как бы мне ни не хотелось Вас утомлять, надо указать еще и вот на что: каждая строка в HTTP заголовке должна заканчиваться символами «возврат каретки» и «перевод строки» — \r \n ( или в кодах ASCII: 0D 0A ). И желательно именно в таком порядке, иначе некоторые браузеры ( не буду снова упоминать его всуе) могут Вас не понять. Далее, между последней строкой HTTP заголовка и началом HTML тэгов обязательно должна быть пара этих сочетаний (то есть \r\n\r\n или 0D0A0D0A в кодах ASCII) иначе не только сами_знаете_какой браузер но и другие тоже Вас не поймут.

А, ну и еще ( все-таки ценный опыт и Вам стоит о нем знать). Еще одна ложка дегтя в бочку чересчур щепетильного браузера (ну, вы поняли): я как-то ошибся и в HTTP заголовке, в указании длины последующего далее HTML кода (Content-Lenght: ...) указал буквально на 1 символ больше, чем на самом деле (ну, то есть символов, скажем, 665, а я указал, что их будет 666).
Так вот, все браузеры проглотили страничку глазом не моргнув, а IE честно до посинения дожидался, когда придет 666-й символ и пока он не прийдет – отказывался вообще показывать страничку.

Я обычно пишу программы составляя их из логических блоков-функций, а сами функции придумываю-расписываю потом. Поэтому у меня в примере вначале идет объявление функций, потом main(), и в самом конце – раписаны подробно функции. Кроме того, мне больше нравится, когда вначале ( и недалеко от констант и переменных) идет main() и можно быстро его просмотреть и разобраться в происходящем, а не продираться через тонны функций, прежде чем добраться до основного блюда.

Также, стоит отметить, что программа поддерживает ping запросы ( то есть Вы можете послать ping на адрес нашего сервера ( 192.168.0.155) — и получить ответ.
На самом деле отклик на ping — это не очевидность.
Бывают случаи/варианты, когда сервис есть (и вполне себе рабочий), но на ping не отвечает.
Дело в том, что утилита ping посылает запросы через один из подвидов IP проткола, а именно ICMP (Internet Control Message Protocol), который и поддерживает диагностические функции, сообщая об ошибках в случаях неудачной доставки IP пакетов ( подробнее — в RFC 792 ), поэтому, Вы должны обеспечить поддержку в том числе и этого протокола, дабы ping-и проходили успешно.
В чипе W5100 эта поддержка уже реализована.

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

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

И, вы не поверите, но мы дошли до конца.
Вот прям в двух дюймах ниже — та самая обещанная программа «за 5 минут».

Ну-с, понеслась!

Исходный код (пароль - обычный - 6 звездочек)
// =====  Простой веб-сервер на базе W5100 от  Wiznet  ======
//   * Для удобства использован Ардуиновский Ethernet Shield  
//   на базе W5100
//===========================================================


#include    <16F77.H>
 

#fuses HS                  // внешний кварц, высокочастотный

#use   delay(clock=16M) )  //   используется кварц на 16 МГц
                           //  если у Вас другой – укажите здесь

#use   rs232(baud=19200, xmit=PIN_A2, rcv=PIN_A3)
// определяем пины для RS232
// *  в CCS компайлере функция « printf» - встроенная, 
//  и можно использовать  вообще ЛЮБЫЕ пины ввода-вывода
// ** не реклама
 


// определяем пины PICa  для подключение к Ethernet  Shield

#define RESET   pin_B2       // *active LOW
#define MISO    pin_B3       //  input
#define MOSI    pin_B5       //  output
#define SS      pin_B0       // output.  SlaveSelect,  Active LOW
#define SCK     pin_B1       // output.  

  

//********** прописываем регистры   W5100  *********************
//**************************************************************

#define MR    0x0000  // MODE register

#define GAR0  0x0001  // GATEWAY Addr register0   (MSB)
#define GAR1  0x0002  // GATEWAY Addr register1
#define GAR2  0x0003  // GATEWAY Addr register2
#define GAR3  0x0004  // GATEWAY Addr register3   (LSB)

#define SUBR0 0x0005  // SUBNET MASK Addr register0  (MSB)
#define SUBR1 0x0006  // SUBNET MASK Addr register1
#define SUBR2 0x0007  // SUBNET MASK Addr register2
#define SUBR3 0x0008  // SUBNET MASK Addr register3   (LSB)

// MAC адрес сервера
#define SHAR0 0x0009  // SOURCE HARDWARE  Addr register0 (MSB)
#define SHAR1 0x000A  // SOURCE HARDWARE  Addr register1
#define SHAR2 0x000B  // SOURCE HARDWARE  Addr register2
#define SHAR3 0x000C  // SOURCE HARDWARE  Addr register3
#define SHAR4 0x000D  // SOURCE HARDWARE  Addr register4
#define SHAR5 0x000E  // SOURCE HARDWARE  Addr register5  (LSB)

#define SIPR0 0x000F  // Source IP  Addr register0  (MSB)
#define SIPR1 0x0010  // Source IP  Addr register1
#define SIPR2 0x0011  // Source IP  Addr register2
#define SIPR3 0x0012  // Source IP  Addr register3  (LSB)



#define RMSR  0x001A  // RX memory size  (1K,2K,4K or 8K per socket, from total 8K)
#define TMSR  0x001B  // TX memory size  (1K,2K,4K or 8K per socket, from total 8K)



//-- Регистры  Socket0 (* в примере используется только этот Сокет )

#define S0_MR   0x0400   // Socket0 MODE register
#define S0_CR   0x0401   // Socket0 COMMAND register

 
#define S0_SR   0x0403   // Socket0 STATUS register

#define S0_PORT0   0x0404   // Socket0 SOURCE Port register0 (H byte)
#define S0_PORT1   0x0405   // Socket0 SOURCE Port register1 (L byte)

 

#define S0_TX_FSR0    0x0420   // Socket0 TX Free SIZE register0
#define S0_TX_FSR1    0x0421   // Socket0 TX Free SIZE register1

#define S0_TX_RD0    0x0422   // Socket0 TX Read POINTER register0
#define S0_TX_RD1    0x0423   // Socket0 TX Read POINTER register1

#define S0_TX_WR0    0x0424   // Socket0 TX Write POINTER register0
#define S0_TX_WR1    0x0425   // Socket0 TX Write POINTER register1


#define S0_RX_RSR0  0x0426   // Socket0 RX Received SIZE register0 (H byte)
#define S0_RX_RSR1  0x0427   // Socket0 RX Received SIZE register1 ( L byte)

#define S0_RX_RD0  0x0428   // Socket0 RX Read POINTER0  (H byte)
#define S0_RX_RD1  0x0429   // Socket0 RX Read POINTER1  (L byte)



// ----- Коды команд ( используются в  Регистре Команд Сокета0 ) -----
#define OPEN     0x01
#define LISTEN   0x02
#define CONNECT  0x04
#define DISCON   0x08
#define CLOSE    0x10
#define SEND     0x20
#define SEND_MAC   0x21
#define SEND_KEEP   0x02
#define RECV     0x40


// ----- Коды состояний ( используются в Регистре STATUS  сокета0 ) ---
#define SOCK_CLOSED     0x00
#define SOCK_INIT       0x13
#define SOCK_LISTEN     0x14
#define SOCK_ESTABLISHED   0x17
#define SOCK_CLOSE_WAIT    0x1C
 

// ---------- определяем «свои» переменные  -----------
#define SERVER_IP0   192   //  Наш сервер будет  192.168.0.155
#define SERVER_IP1   168
#define SERVER_IP2    0    
#define SERVER_IP3   155    

#define SERVER_PORT0   0   //  Наш порт будет       :80
#define SERVER_PORT1   80
 
#define GATEWAY_IP0   192  // Гэйтвэй адрес. Если у Вас другой - измените
#define GATEWAY_IP1   168
#define GATEWAY_IP2   0
#define GATEWAY_IP3   254

#define SUBNET_MASK0   255  // Маска подсети  ( Типовая)
#define SUBNET_MASK1   255
#define SUBNET_MASK2   255
#define SUBNET_MASK3   0

#define MAC0   0x00  // МАС адрес любой, главное, чтобы в Вашей сети
#define MAC1   0x1A  // не было устройств  с таким же  МАС
#define MAC2   0x2B
#define MAC3   0x3C
#define MAC4   0x4D
#define MAC5   0x5E

 
 
 
//----------- объявляем собственные функции  ---------------

void SSPI_write(int Data);      // запись  1 байта в W5100   через SPI
int  SSPI_read ( void);         // чтение 1 байта из W5100 через SPI 

void SetW5100register (int16 regaddr, int data);  
                // установка(запись) грегистров W5100  через SPI

int  GetW5100register (int16 regaddr);               
                // чтение байта из регистров W5100 через SPI


void Init (void);          // инициализация W5100  и Системы

int Open_Socket0(void);    // открывает сокет и возвращает статус (удачно/неудачно)

int Listen_Socket0(void);   // слушает сокет и возвращает статус (удачно/неудачно)

int Socket0_Connection_Established (void);   
     // проверка или соединение установлено и возвращает статус  (да/нет)

int Socket0_Received_Data_Size (void);       
      //  возвращает размер блока ДАННЫХ, принятых от Клиента ( 0 если данных нет) 

            void Socket0_Received_Data_Reading (void);
           // выводит на терминал принятые данные
           // после отладки ф-ция может быть удалена
 
int  Socket0_FIN_Received(void);      
  // проверяет  пришел ли от Клиента флаг FIN  и возвращает да/нет

void Socket0_Disconnect(void);  
  // пишет в регистр W5100  команду на разрыв соединения

int  Socket0_Closed(void);    // поверяет или Сокет ЗАКРЫТ и возвращает да/нет          

int  Socket0_Connection_Timeout(void);  
   // проверяет не произошел ли тайм-аут соединения  и возвращает да/нет

void Socket0_Closing(void);           // закрывает Сокет

int  Socket0_Received_Request_is_index_html(void);  
  // проверяет или  в данных принятых от Клиента запрашивается файл Index.html
  //  и возвращает да/нет


void Socket0_Send_index_html (void);  // отсылает Клиенту  «страничку»  index.html

  
void Socket0_Send_404_error (void); // Отсылает Клиенту  «страничку»  «ошибка 404»


//------------  собственные переменные  -----------------------------------

int16 S0_RX_BASE;      //  начальный адрес памяти, выделенной в RX буфере для Сокета0
int16 S0_RX_MASK;      // РАЗМЕР RX  буфера Сокета0
int16 S0_RX_OFFSET;    // указатель на начало принятого блока данных в RX буфере 
int16 S0_RX_Start_Addr; // ФИЗИЧЕСКИЙ  адрес начала принтого блока данных в RX буфере 
int16 S0_RX_RSR ;       //  РАЗМЕР принятого от Клиента блока данных

int16 S0_TX_BASE;    // начальный адрес памяти, выделенной в ТX буфере для Сокета0
int16 S0_TX_MASK;     // РАЗМЕР ТX  буфера Сокета0
int16 S0_TX_OFFSET ;  
   // указатель на начальный  адрес в памяти  ТХ буфера, куда следует записать блок 
   // данных для передачи Клиенту  

int16 S0_TX_Start_Addr;  
       // ФИЗИЧЕСКИЙ  Начальный адрес в памяти ТХ буфера, куда следует записать 
       // блок  данных для передачи Клиенту  

int16 S0_TX_End_Addr; 
   // указатель на Конечный адрес в памяти ТХ буфера, до которого W5100
   //  должен дойти при считывании данных отсылаемых Клиенту


// --- строки символов для использования как HTTP  заголовки и «веб-странички»
// * размер  массива [222] взят с потолка и с запасом. Потом программа  расчитает  реальную длину данных
// ** CONST – чтобы  расположить строку в ПРОГРАММНОЙ памяти ( где места полно)

CONST  char INDEX[222]  = { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Lenght: 43\r\n\r\n<HTML><CENTER>HELLO WORLD !</CENTER></HTML>"};
// символы для  создания  странички «index.html”

CONST  char ERROR404[222] = { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Lenght: 39\r\n\r\n<HTML><CENTER>ERROR 404</CENTER></HTML>"};
// символы для  создания  странички «ошибка 404”

 
// закомментил чтоб была на примете,
//  Можно  использовать и такой вариант для «ошибка 404», код сам по себе верный, но...
//CONST  char ERROR404[222] = { "HTTP/1.1 404 Page not found\r\nContent-Type: text/html\r\nContent-Lenght: 39\r\n\r\n<HTML><CENTER>ERROR 404</CENTER></HTML>"};
// .. но IExplorer  получив в HTTP заголовке "HTTP/1.1 404 Page not found" 
// отобразит СОБСТВЕННУЮ страницу ошибки, а НЕ ту, что мы ему посылаем






         int try=0;  //  временно, для отладки. Номер обращения браузера –к серверу


//===================== MAIN ============================================
//=======================================================================
void main()
{

   
Init();   // инициализация  W5100  и системы ( сервера)

 

OpenSocket0:     

//ooooooooooooooooooo ОТКРЫВАЕМ СОКЕТ 0 oooooooooooooooooooooooo

 if ( ! Open_Socket0() ) goto OpenSocket0; // цикл пока Сокет не откроется


 
//ooooooooooooooooo  СЛУШАЕМ СОКЕТ  ooooooooooooooooooooooooo

 if ( Listen_Socket0() == FALSE ) goto OpenSocket0;

            // если  сокет не «прослушивается» - уходим заново на открытие сокета

 
//oooooooooooooo Соединение УСТАНОВЛЕНО ? oooooooooooooo

CheckConnection:     //метка

  if (Socket0_Connection_Established() == FALSE ) goto CheckConnection;
// цикл пока соединение не установится ( тоесть пока не придет запрос от браузера)
    
                   printf("> Connection Established... \r\n");
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
                   
                   
                   
//ooooooo( соединение уже установлено)  в принятых пакетах -  ДАННЫЕ? oooooo               
                   
  if  ( Socket0_Received_Data_Size() == 0 )  
  
  {
      printf("\r\n> (Zero) Received Data size is: %Lu (bytes) \r\n", S0_RX_RSR);
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
  
  goto CheckFIN;   // раз ДАННЫХ нет о и передавать в отет нечего
                            // поэтому сразу идем на проверку флагаFIN
  }
  // раз данных нет ( размер=0) то уходим на этап проверки закрытия соединения



  
   else    
   {
   printf("\r\n> (NonZero) Received Data size is: %Lu (bytes) \r\n", S0_RX_RSR);
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
   }
  
 
// oooooooooo ( Данные в пакете есть) Процесс обработки*   ooooooooooooooo 

//на самом деле просто  выводит на терминал принятые данные
// после отладки этот кусок можно удалить


                 Socket0_Received_Data_Reading();   
                   // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
        


    
// oooooooooooooo  Процесс ПЕРЕДАЧИ данных  ooooooooooooooooooooooooooooooo
// тоесть отсылаем браузеру HTTP заголовок и HTML страничку

 
if ( Socket0_Received_Request_is_index_html() ==  TRUE)   Socket0_Send_index_html ();
   // если  клиент запрашивает "index.html"  то остылаем ему  index.html

else  Socket0_Send_404_error (); 
   //  если запрос любого другого файла  то  отсылаем  «страничку»  «ошибка 404»
   
  


//ooooooooooo    Получен флаг  FIN ? ooooooooooooooooooooooooooo

//  тоесть проверка требует ли  Клиент разрыва соединения

CheckFIN:

if ( Socket0_FIN_Received() == TRUE) goto  CloseConnection;

     //  если FIN  пришел  - то  уходим закрывать СОКЕТ0





//ooooooooooo    Разрыв соединения   ooooooooooooooooooooooo 


  Socket0_Disconnect();    //  разрыв СОЕДИНЕНИЯ Сокета0





//ooooooooooo   Сокет ЗАКРЫТ ?    ooooooooooooooooooooooooooooooo

if (Socket0_Closed() == TRUE )  goto CloseConnection;
    // если Сокет закрыт – уходим закрывать Соединение




//ooooooooooo   Не наступил ли тайм-аут по соединению  ?  ooooooooooooooooooo


if ( Socket0_Connection_Timeout() == TRUE)  goto CloseConnection;
  // сокет не закрыт, но наступил тайм-аут, – уходим закрывать сокет




CloseConnection:

//ooooooooooo   Закрываем Сокет    ooooooooooooooooooooooooooooooooooo
 
  Socket0_Closing(); 
 

 
 
 
//oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo






goto OpenSocket0;   

// соединение с Клиентом отработано, цикл завершился.
//  Уходим в начало и запускаем все по новой ( ждем новых запросов «веб-сайта» )
 
}


//===================  END of MAIN ===========================
//============================================================





//---------------------------------------------------------------
//--------------------------INIT -------------------------------
void Init (void)
{


output_low(RESET); //  генерируем «Сброс» для Ардуиновского Ethernet  шилда 
delay_ms(1);
output_high(RESET);

output_low(SCK);     // готовим пины (уровни) для  SPI
output_high(SS) ;

//программный сброс чипа W5100.
SetW5100register(MR, 0x80);   // пишем код  RST  в W5100 Mode Register



//------------ Настраиваем  память (W5100) для Сокета 0 -------------
 
 

SetW5100register(RMSR, 0x55);   // настраиваем RX буфер: по 2КБ под каждый сокет 


S0_RX_BASE = 0x6000;     //  базовый адрес RX  буфера    для Сокета0

S0_RX_MASK = 0x07FF ;   // (2048 -1 )= 0x07FF, RX Маска ( = длина_буфера – 1 )


SetW5100register(TMSR, 0x55);   // настраиваем ТX буфер: по 2КБ под каждый сокет
 
S0_TX_BASE = 0x4000;  // базовый адрес TX буфера  для Сокета0
 
S0_TX_MASK = 0x07FF;  // (2048 -1 )= 0x07FF, ТХ Маска ( = длина_буфера – 1 )

 

//------------ прописываем свой МАС адрес   --------------
//  т.е.  просто закидываем в МАС регистры W5100 
//    – свои переменные  с определенными ранее величинами


 SetW5100register(SHAR0, MAC0);  
 SetW5100register(SHAR1, MAC1); 
 SetW5100register(SHAR2, MAC2);
 SetW5100register(SHAR3, MAC3);
 SetW5100register(SHAR4, MAC4);
 SetW5100register(SHAR5, MAC5);
  
 
//------------  прописываем свой  IP  --------------
// так же -  раскидываем в регистры W5100 – свои переменные

SetW5100register(SIPR0, SERVER_IP0);
SetW5100register(SIPR1, SERVER_IP1);
SetW5100register(SIPR2, SERVER_IP2);
SetW5100register(SIPR3, SERVER_IP3);

 

//------------  прописываем свой PORT  --------------
// так же – свои константы – в регистры

SetW5100register(S0_PORT0, SERVER_PORT0);
SetW5100register(S0_PORT1, SERVER_PORT1); 

//------------ прописываем  Gateway addr  --------------
// так же – свои константы – в регистры

SetW5100register(GAR0, GATEWAY_IP0); 
SetW5100register(GAR1, GATEWAY_IP1); 
SetW5100register(GAR2, GATEWAY_IP2); 
SetW5100register(GAR3, GATEWAY_IP3); 

//------------ set Subnet Mask  --------------

SetW5100register(SUBR0, SUBNET_MASK0); 
SetW5100register(SUBR1, SUBNET_MASK1); 
SetW5100register(SUBR2, SUBNET_MASK2); 
SetW5100register(SUBR3, SUBNET_MASK3); 
 
      printf(" \r\n\r\n > =============== W5100 ВЕБ-СЕРВЕР ==========\r\n\r\n");
      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
}





//--------------------------------------------------------------
//-------------  ОТКРЫТИЕ СОКЕТА 0  ----------------------------

int Open_Socket0(void)
{

                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

                 printf(" \r\n =========== TRY # %u   ====================\r\n", try);
                 if (try ==255) try =0;
                 else try++;
              
              
                 printf("\r\n>  Start Open Socket... \r\n");
          
              


// устанавливаем Сокет – в режим ТСР, остальные опции отключаем 
SetW5100register(S0_MR, 0x01);   

// засылаем в регистр команд – команду ОТКРЫТЬ ( сокет)
SetW5100register(S0_CR, OPEN);  

// проверяем или сокет открылся успешно
// * в режиме ТСР , по  «SOCK_INIT»  можно проверить или сокет открылся
if (GetW5100register(S0_SR) != SOCK_INIT)  // проверяем STATUS регистр
   {
    SetW5100register(S0_CR, CLOSE); // если не открылся то закрываем
    return FALSE ;                //  и выходим с кодом 0
   }

 return TRUE;     //открылся успешно.  Выходим с кодом 1
}




//--------------------------------------------------------------
//-------------  Переводим Сокет0 в режим  СЛУШАТЬ  ------------

int Listen_Socket0 (void)
{
 
                 printf("> Sock opened. Go Listen... \r\n");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


// засылаем в регистр команд команду перевода сокета в режим СЛУШАТЬ
// *СЛУШАТЬ – т.к. мы – Сервер, а инициатива  исходит от Клиента

SetW5100register(S0_CR, LISTEN);  


if ( GetW5100register(S0_SR) != SOCK_LISTEN)  //  проверяем  Регистр Состояния
 {
  SetW5100register(S0_CR, CLOSE); //  если сокет не вошел в режим СЛУШАТь
  return FALSE;                   // закрываем его и выходим с кодом 0   
 } 
 
return TRUE;               //  сокет в режиме СЛУШАТЬ,  выходим с  кодом 1
 
}


//--------------------------------------------------------------------------
//-------------   проверка Установлено  ли  соединение с  Сокетом0 ? --------
// * в смысле от клиента ( браузера)

int Socket0_Connection_Established(void)
{

// если W5100  получает от клиента запрос на соединение, он отсылает ему пакет 
// с  установленным флагом ACK и  изменяет свой статуст на SOCK_ESTABLISHED
// («соединение с сокетом установлено)
// Проверить это можно либо  проверкой бита в регистре прерываний, 
// либо как здесь -  проверкой  регистра состояния


if  ( GetW5100register(S0_SR) == SOCK_ESTABLISHED)  return TRUE ;

// читаем Регистр Состояния.  
// Если  комбинация битов (код) = «SOCK_ESTABLISHED» то выходим с 1

else return FALSE;     // если нет – выходим с кодом 0


                   printf("> Connection Established... \r\n");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


// *после того как соединение установлено,  сокет готов к приему/передаче данных

}



//----    проверка были ли приняты ДАННЫЕ -------------
//-----------------------------------------------------

// * «служебные» пакеты между сторонами  соединения могут и пересылаться
// но нас интересует были ли в них именно ДАННЫЕ
//  (например, запрос  страницы/файла  с веб-сервера)


int Socket0_Received_Data_Size (void)  

// на самом деле  интересует   размер принятых данных = НОЛЬ или нет

{

S0_RX_RSR = make16 (GetW5100register(S0_RX_RSR0), GetW5100register(S0_RX_RSR1) );  
// читаем два  8-битных регистра размера принятых данных 
// RSR0 – старш байт, RSR1 – младш байт
// и «собираем» 16-битное слово -  размер принятых данных


                printf("> Received Data size is: %Lu (bytes) \r\n", S0_RX_RSR);
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

if (S0_RX_RSR == 0 ) return FALSE;   // нет ДАННЫХ ( т.е. размер=0 )
                                     // выходим с кодом 0

else  return TRUE;    // размер данных не нулевой ( т.е. данные  -есть)
                      // выходим с кодом 1



}

//---------- вывод на терминал принятых ДАННЫХ ---------------
//------------------------------------------------------------
// * функция  полезна только при отладке.  Ее МОЖНО УДАЛИТЬ

void Socket0_Received_Data_Reading (void) 
{
 int16 n; 
 int RXbyte;


  S0_RX_OFFSET = make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) );  
// из двух  8-разрядных регистров  склеиваем  16 разрядную переменную -
// УКАЗАТЕЛЬ на  начало  принятых данных в RX буфере  сокета0


                 printf("> S0_RX_RD (RX mem read pointer) = %LX \r\n", make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) )  );
       

  S0_RX_OFFSET = (S0_RX_OFFSET & S0_RX_MASK ) ;
 // отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера

                 printf("> S0_RX_Offset = S0_RX_RD & S0_RX_MASK = %LX \r\n\r\n",S0_RX_OFFSET );
    

  S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE ;

//вычисляем ФИЗИЧЕСКИЙ адрес начала  области памяти хранящей принятые данные


                 printf("> S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE = %LX (physical)\r\n\r\n", S0_RX_Start_Addr );
 

                  printf(">  Going  to print-out Received Data... \r\n\r\n");
                  printf("ooooooooooooooooooooooooooooooooooooooooooo\r\n");


for (n=0; n < S0_RX_RSR ; n++)   
 {
  
   if ( S0_RX_Start_Addr > (S0_RX_BASE + S0_RX_MASK)  )  S0_RX_Start_Addr = S0_RX_BASE;

   RXbyte = GetW5100register(S0_RX_Start_Addr);
  
  
                   printf("%c", RXbyte);

   S0_RX_Start_Addr++;
 
 }

                   printf("\r\noooooooooooooooo END of  received data oooooooooooooo\r\n\r\n");
                   
 
}


// --- проверяем или от Клиента пришел запрос на INDEX.HTML  файл ------
//----------------------------------------------------------------------

int Socket0_Received_Request_is_index_html(void)  
{
int RXbyte=0;

 S0_RX_OFFSET = make16 ( GetW5100register(S0_RX_RD0), GetW5100register(S0_RX_RD1) );  
// из двух  8-разрядных регистров  склеиваем  16 разрядную переменную -
// УКАЗАТЕЛЬ на  начало  принятых данных в RX буфере  сокета0


 

 S0_RX_OFFSET = (S0_RX_OFFSET & S0_RX_MASK ) ;
// отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера

 
 S0_RX_Start_Addr = S0_RX_OFFSET + S0_RX_BASE ;  
//вычисляем ФИЗИЧЕСКИЙ адрес начала  области памяти хранящей принятые данные

  
 
 
                printf("\r\n>----------- parsing HTTP header-------------\r\n");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

 
 while (RXbyte != 0x2F)   // ищем первый  "/"  в HTTP  заголовке
 {
  
    if ( S0_RX_Start_Addr > (S0_RX_BASE + S0_RX_MASK)  )  S0_RX_Start_Addr = S0_RX_BASE;
// начало блока данных не обязательно м располагаться в начале  буфера
// проверяем, не дошли  ли до КОНЦА  буфера
// если да – идем в самое НАЧАЛО буфера – данный продолжаются именно с того места

   RXbyte = GetW5100register(S0_RX_Start_Addr);  
// читаем из буфера RX байт,  на который  указывает СтартовыйАДрес
  
  
                      printf("%c", RXbyte);
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


   S0_RX_Start_Addr++;  // инкрементируем Адрес – теперь он указывает на следующ юайт
 
 }
 
// раз мы здесь значит уже дошли («отловили») до первого в HTTP заголовке символа “/”
// и сейчас Адрес  указывает на следующий за "/"  символ.  
// Ради него и затевался весь сыр-бор

    RXbyte = GetW5100register(S0_RX_Start_Addr); //считываем этот  символ
 
 
 
          printf("\r\n> -------- END of parsing HTTP header -------\r\n");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ


 
     if (RXbyte == 0x20)  return TRUE;  
// если это «пробел»  - значит клиент запрашивает файл без названия, тоесть index.html
//  выходим с кодом подтверждения 1

     else return FALSE;                
// если это был не пробел а любой другой символ, значит  запрашивается НЕ index.html
// выходим с кодом ошибки - 0

}





//------   отправка Клиенту страницы «ошибка 404» ------------
//-------------------------------------------------------------

// заполняем ТХ буфер сокета0  блоком данных из HTTP заголовка и   HTML  кода
//  страницы «ошибка 404»,  затем  показываем W5100  начало и конец 
// этого блока данных в буфере,  и даем команду  ОТОСЛАТЬ

void Socket0_Send_404_error (void) 
{   int16 n;
char TXbyte;
int16 datalength;



  S0_TX_OFFSET = make16 ( GetW5100register(S0_TX_RD0), GetW5100register(S0_TX_RD1) );  // из двух  8-разрядных регистров  склеиваем  16 разрядную переменную - УКАЗАТЕЛЬ 
// на  место, откуда можно начать размещать блок данных в ТX буфере  сокета0
  
  
  S0_TX_OFFSET = (S0_TX_OFFSET & S0_TX_MASK ) ;
// отсекаем лишнее  чтобы  укладывался в размеры выделенного под Сокет0 буфера
  
  
  S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE ;
//вычисляем ФИЗИЧЕСКИЙ начальный адрес для размещения блока данных в буфере ТХ
  

  //вычисляем ДЛИНУ строки  содержащей HTTP заголовки и HTML  коды
  datalength=0;
  while ( ERROR404[datalength] !=0) datalength++;  
// попросту  инкрементируем «datalength» пока не наткнемся на первый 0х00 в строке
//  (0х00 -  признак конца данных)
 
 


// Для того, чтобы W5100 передала блок Данных, его необходимо разместить 
// в ТХ буфере Сокета, начиная с адреса, который ммы вычислили ранее.
// После этого, W5100 необходимо указать   КОНЕЦ блока данных в буфере.

// Указание на конец нашего блока данных мы записываем в соотв регистр W5100:


// Вытаскиваем из пары регистров - указателей конца блока данных их текущее значение
// и склеиваем из них  2-байтный указатель
// *( в отличие от предыдущего  вычисления ФИЗИЧЕСКОГО адреса начала длока данных
// здесь мы имеем дело именно с «внутренним» указателем адреса)
  S0_TX_End_Addr = make16 ( GetW5100register(S0_TX_WR0), GetW5100register(S0_TX_WR1) );  

// добавляем к получившемуся -  вычисленную ранее длину нашего блока данных
      S0_TX_End_Addr += datalength ;      // increment to  fatalength
   // получившееся  значение ( указатель на конец наших данных) вновь «расклеииваем» 
   // на  2 байта и  записываем в  соотв  регистры   
      SetW5100register(S0_TX_WR0, make8( S0_TX_End_Addr ,1) );  // старший байт 
      SetW5100register(S0_TX_WR1, make8( S0_TX_End_Addr,0) );  // младший байт
 
      
                 printf("\r\n>Data length is: %Lu \r\n", datalength);
                 printf("\r\n>--- Filling TX buffer  w  data: -----------\r\n");
                 // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
      
      
 // теперь приступаем к собственно заполнению  буфера ТХ – нашими  данными
For (n=0; n < datalength; n++)  // цикл  на длину блока данных
  {
     TXbyte = ERROR404[n];  
// читаем текущий байт из строки,  в которой записан весь код «веб страницы»
// ( HTTP заголовки и  HTML коды)
 

// поскольку «выданный» нам от W5100  адрес начала блока данных в ТХ буфере  
// может не совпадать с  началом  буфера, а  находиться, например в 10 байтах
// от конца буфера, необходимо при записи каждого нового байта наших данных
// проверять, не вылезли  ли  мы за границы буфера
 
  if (S0_TX_Start_Addr  > (S0_TX_BASE + S0_TX_MASK)) S0_TX_Start_Addr = S0_TX_BASE;
 // .. и если  дошли до края буфера – то   продолжать следует с НАЧАЛА буфера

// * W5100  в курсе этих манипуляций (это  вообще была  ее  идея а не наша ))и когда //будет передавать данные  - также, в  случае, когда дойдет до конца буфера  
//( именно БУФЕРА,  а НЕ до указателя на КОНЕЦ Данных) – также будет продолжать
//  от начала буфера
 

 // с ТЕКУЩИМ адресом  записи текущего байта мы разобрались выше 
// и сейчас просто пишем текущий байт данных – в  соотв  ячейку памяти буфера ТХ 
  SetW5100register( S0_TX_Start_Addr, TXbyte ) ;   
  
               
                       putc(TXbyte);   
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

  S0_TX_Start_Addr++ ;
  // инкрементируем текущий адрес (для записи следующего байта данных)
  
  }     
      
                  printf("\r\n>--- end of  Filling  -----------\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

// все, все данные для передачи мы разместили в буфере, 
// указали, где наши данные заканчиваются, теперь можно их и передавать    
  
    //  засылаем в Регистр Команд сокета  - команду SEND  
    SetW5100register(S0_CR, SEND);       
      
                    printf("> Data was  sent \r\n\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}


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

//-- отправка Клиенту страницы «index.html» ---------------
//-------------------------------------------------------------------
void Socket0_Send_index_html (void) // send index.html "page"  index.html
{

// расписывать не стану – все абсолютно так же как при посылке страницы «ошибка 404»
// только  «строка»  содержащая  HTTP  заголовки и  HTML  коды – другая

int16 n;
char TXbyte;
int16 datalength;

                    printf("\r\n>......  going to send INDEX.HTML.....\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

  S0_TX_OFFSET = make16 ( GetW5100register(S0_TX_RD0), GetW5100register(S0_TX_RD1) );  // склеиваем  2-байтный указатель на началь адрес для размещения блока данных
  
  
  S0_TX_OFFSET = (S0_TX_OFFSET & S0_TX_MASK ) ;
  
  
  S0_TX_Start_Addr = S0_TX_OFFSET + S0_TX_BASE ;
  // вычисляем ФИЗИЧЕСКИЙ  начальный адрес  для размещения блока данных


                   printf("\r\n  INDEX[i], datalegth -------------\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
      
      
  //вычисляем ДЛИНУ блока данных
  datalength=0;

  while ( INDEX[datalength] !=0) datalength++; 
 // считаем длину данных – пока не дойдем до первого  0х00 – признака конца данных
  
                    printf("%c  %Lu ",INDEX[datalength], datalength );
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
               
      
       

// записываем в  W5100  УКАЗАТЕЛЬ  на КОНЕЦ блока данных на передачу
 S0_TX_End_Addr = make16 ( GetW5100register(S0_TX_WR0), GetW5100register(S0_TX_WR1) );  // «склеиваем» вместе 2 байта текущего  значения

      S0_TX_End_Addr += datalength ;      // increment to  fatalength
      // добавляем длину нашего блока данных

 // новое значение заново распихиваем по двум 1-байтным регистрам     
      SetW5100register(S0_TX_WR0, make8( S0_TX_End_Addr ,1) );  // старш байт
      SetW5100register(S0_TX_WR1, make8( S0_TX_End_Addr,0) );  // младш байт

                 
                  printf("\r\n>Data length is: %Lu \r\n", datalength);
                  printf("\r\n>--- Filling TX buffer  w  data: -----------\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
           
      
 // заполняем буфер – нашими данными на передачу
For (n=0; n < datalength; n++)
  {
   TXbyte= INDEX[n]; 
// читаем текущий байт из строки  содержащей «веб страницу»  index.html
// (т.е.   HTTP Header  + HTML code)
  
  if (S0_TX_Start_Addr  > (S0_TX_BASE + S0_TX_MASK)) S0_TX_Start_Addr = S0_TX_BASE;
  // проверяем не дошли ли до края буфера, и если да – перескакиваем на начало
  
  SetW5100register( S0_TX_Start_Addr, TXbyte ) ;  
   // зарисываем текущий байт блока данных ( «веб страницы») – в  буфер ТХ 
  

                putc(TXbyte);  // printout to Terminal ( for testing purpose)
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
   
  S0_TX_Start_Addr++ ;
  // переходим к следующему адресу ТХ буфера
  
  }     
      
                 printf("\r\n>--- end of  Filling  -----------\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
    
  // буфер заполнен нашим блоком данных, можно их передавать Клиенту

    //  пишем в Регистр Команд сокета - команду SEND  
    SetW5100register(S0_CR, SEND);       
      
                     printf("> Data was  sent \r\n\r\n");
                    // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}

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

 

//--- содержит ли пакет, пришедший от Клиента,  установленный флаг FIN ? ------
//-----------------------------------------------------------------------------

int Socket0_FIN_Received(void)
{ 
// проверяем  не пожелал ли  клиент разрыва отношений (тоесть соединения)
// послав нам  флаг FIN  -  запрос на разрыв соединения

// можно проверять через проверку бита в регистре прерываний
// либо как здесь – через проверку  Регистра Состояния Сокета0


// выходим с  1  если  FIN пришел  и  с 0  если  FINа не было
if ( GetW5100register(S0_SR) == SOCK_CLOSE_WAIT) return TRUE;
else return FALSE;

}


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


 
// ------ разрываем СОЕДИНЕНИЕ для  Socket0   -----------------
//-------------------------------------------------------------
void Socket0_Disconnect(void)
{


//  отключаем сокет от СОЕДИНЕНИЯ с клиентом
// заслав соотв команду в Регистр Команд  сокета0

SetW5100register(S0_CR, DISCON);

}





// ------  проверка или Сокет0 ЗАКРЫТ --------------------
//--------------------------------------------------------
int Socket0_Closed(void)
{
// Сокет мож быть закрыт после засылки нами в Регистр Команд Сокета
//  команды ЗАКРЫТь (CLOSE), или  после тайм-аута, или  при разрыве соединения

// проверка или  Сокет0 действительно закрыт

// * можно делать через проверку  бита в регистре прерываний,
// либо как здесь -  проверкой Регистра Состояния
 

If  ( GetW5100register(S0_SR) == SOCK_CLOSED) return TRUE;
else return FALSE;
// выходим с 1  если Сокет (или СОЕДИНЕНИЕ сокета0) закрыто
// либо с 0 если сокет  до сих пор не закрылся


}





 
// -----  нет ли ТАЙМ-АУТа по соединению Сокета0 ? ------------
//-------------------------------------------------------------

int Socket0_Connection_Timeout(void)
{
// если на линии ошибки, или  клиент хочет закрыть соединение, или  от клиента давно
// ничего не приходит   итп – проверяем соединение на тайм-аут
//  иначе будет некорректно,  если наш  сервер будет слать клиенту пакеты
// ( с  соответствующими TCP флагами ) в том порядке, который подразумевается 
// для нормального процесса обмена пакетами


// тайм-аут проверяется либо через биты в  регистре прерываний
// либо как у нас – через проверку Регистра Состояния сокета0

// ПРИЕЧАНИЕ: как видим, регистр проверяется на состояние «SOCK_CLOSED»
// - какая тут связь с тайм-аутом??
// Дело в том что в Регистре Состояний нет отдельного кода для тайм-аут,
// но в W5100  код «SOCK_CLOSED»  связан также и  с тайм-аутом
// поэтому  проверка на этот код – вполне легитимна

If  ( GetW5100register(S0_SR) == SOCK_CLOSED) return TRUE;
else return FALSE;

// выходим с 1  если   тайм-аут  наступил, либо с 0 если тайм-аута не было

}



// ------------  ЗАКРЫТИЕ Сокета0    ----------------------------
//----------------------------------------------------------------


void Socket0_Closing(void)
{
//  should be performed in case that connection is closed after data exchange,
// socket should be closed with Timeout occurrence, 
// or forcible disconnection is necessary due to abnormal operation etc.
  

                    printf(">going to Close Socket0 ..... \r\n");
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ
           
// засылаем в Регистр Команд сокета0  код на ЗАКРЫТИЕ сокета
SetW5100register(S0_CR, CLOSE);


                   printf(">  ------ Socket CLOSED    ----- \r\n\r\n");
                      // для отладки (вывод на терминал)  МОЖНО УДАЛИТЬ

}
 




// ---- ЗАПИСЬ (посылка) байта  через SPI   -------------
//--------------------------------------------------------


//* у меня название функции «SSPI» оттого, что в компайлере есть собственная 
// готовая йункция (SPI… )но она глючная. Поэтому я написал свой вариант 
// а чтоб не было конфликтов (названий) в компайлере – дал ей другое имя

// назначение функции – послать ОДИН БАЙТ через SPI   
//  более верхние уровни «протокола»  обмена (тоесть запись всех ТРЕХ байтов)  реализует 
//  другая функция  - функция для записи в РЕГИСТР

//* состояние линаа данных -  валидное для W5100 при  переходе клока из 0 в 1

void SSPI_write( int Data)
{
int i;
int mask=0x80; // ставим маску на СТАРШИЙ бит (тоесть начинаем со СТАРШЕГО бита)
                // так  как по  протоколу  SPI для W5100 данные следуют 
                // от СТАРШЕГО бита -  к МЛАДШЕМУ  ( MSB first)


output_low(SCK);   // просто чтоб быть уверенными что клок – в «исходном» положении

for (i=0; i<8;i++)    // цикл для считывания 8 бит
 { 
  output_low(MOSI);   // просто выставляем «данные» на линии -  в 0 

  if ( (mask & Data) != 0) output_high(MOSI);   
          // если  (маска & ДАННЫЕ)  =1  то  выставляем на линию 1
         // если нет – то на линии  так и остается 0, выставленный строчкой выше


  output_high(SCK);   
   // выдаем пульс клока ( из 0 в 1)  - именно по нему  происходит запись бита 
   // с линии  данных SPI – в   регистры W5100
 
  mask = mask>>1; ;  // сдвигаем маску  на 1 бит вправо
                    // *можно поставить  эту операцию и  после  цикла
                    // но в этом месте этим заодно  обеспечивается 
                    // некотор задержка между тактами (тоесть частота клока)

 
  output_low(SCK); // завершаем  клок, переводим его в «исходное» состояние ( 0 )
 }
 // «.. и так восэм  рас» (с)

}
//----------------------------------------------------------



//----------- ЧТЕНИЕ байта с линии   SPI  ------------------
//-----------------------------------------------------------


//* у меня название «SSPI» оттого, что в компайлере есть собственная 
// готовая йункция (SPI… )но она глючная. Поэтому я написал свой вариант 
// а чтоб не было конфликтов в компайлере – дал ей другое имя

// назначение функции – прочесть  ОДИН БАЙТ
//  более верхние уровни «протокола» реализует 
// собственно функция  чтения РЕГИСТРА

// *данные при чтении из регистров W5100 -  валидные при  переходе клока из 1 в 0

//* тоесть если при передаче  мы  сначала  выставляли данные на линию
//  и только после этого  подтверждали их валидность переводом клока из 0 в 1
// а  на прниеме  мы сначала  выставляем  клок в 1,  читаем данные
// и «защелкиваем» данные  переходом клока из 1 в 0


int SSPI_read ( void)
{
int Data=0;
int i;
int mask=0x80; // ставим маску на СТАРШИЙ бит (тоесть начинаем со старшего бита)
                // так  как по  протоколу  SPI для W5100 данные следуют 
                // от СТАРШЕГО бита -  к МЛАДШЕМУ  ( MSB first)


output_low(SCK);   // просто чтоб быть уверенными что клок – в «исходном» положении

for (i=0; i<8;i++)  // цикл для считывания 8 бит

 { 
  output_high(SCK);   // выдаем пульс клока ( из 0 в 1)      
    
  if ( input(MISO)!= 0) Data = Data | mask ; 
  // если на линии 1, то  делаем ИЛИ  маски – и текущего  значения «собираемого» байта
  
  mask = mask>>1; ;   // сдвигаем маску  на 1 бит вправо
                    // *можно поставить и  после  цикла
                    // но в этом месте этим заодно  обеспечивается 
                    // некотор задержка между тактами (тоесть частота клока)

  
  output_low(SCK); // завершаем  клок, переводим его в «исходное» состояние ( 0 )

 }

return Data;
}
//---------------------------------------------------------------



//-------  ЗАПИСЬ  ( установка)  регистров W5100 -------------
//--------------------------------------------------------------
void SetW5100register (int16 regaddr, int8 data)
// 2 аргумента: 16 бит адреса регистра и 8 бит данных для записи

{
output_low(SS); // выставляем Чип Селект ( ставим в 0)

SSPI_write (0xF0);  // сперва  посылаем в регистр команду ЗАПИСЬ

                                   //*  make8 – преобразует 16 бит - в  8 бит
SSPI_write (  make8(regaddr,1) );  // выделяем из адреса старший байт  (MSB)
                                   // и пишем  его на SPI     


SSPI_write (  make8(regaddr,0) );  // выделяем из адреса младший байт  (LSB)
                                   // и шлем  его на SPI  

SSPI_write (data);   // пишем на SPI ДАННЫЕ для записи в регистр 


output_high(SS);     //  снимаем ЧипСелект  ( ставим в 1)

}
//---------------------------------------------------------




//-----  ЧТЕНИЕ содержимого регистров W5100 ----------
//----------------------------------------------------
int  GetW5100register (int16 regaddr)
//аргумент – 2-байтовый адрес регистра
// возвращает: 1 байт считанных из регистра данных
{
int RegData;

output_low(SS); // выставляем Чип Селект ( ставим в 0)


SSPI_write (0x0F);  // сперва  засылаем команду ЧТЕНИЕ

                                   //*  make8 – преобразует 16 бит в  8 бит
SSPI_write (  make8(regaddr,1) );  //  выделяем из адреса старший байт  (MSB)
                                   // и выставляем его на SPI     

SSPI_write (  make8(regaddr,0) );  //  выделяем из адреса младший байт  (LSB)
                                   // и выставляем его на SPI  

RegData = SSPI_read ();    // теперь в ответ W5100  выдаст нам содержимое (8 бит) 
                           // регистра по засланному перед этим адресу


output_high(SS);    //  снимаем ЧипСелект  ( ставим в 1)

return RegData;	// выходим с  прочитанным байтом

}
 
 
  
//*************  END of PROGRAM  **************